• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rogerpadilla / uql / 28907893047

08 Jul 2026 12:15AM UTC coverage: 94.906% (-0.002%) from 94.908%
28907893047

push

github

rogerpadilla
chore(package): update gitHead to latest commit hash

3228 of 3575 branches covered (90.29%)

Branch coverage included in aggregate %.

5603 of 5730 relevant lines covered (97.78%)

385.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.08
/packages/uql-orm/src/util/dialect.util.ts
1
import { getContext, UqlSecurityError } from '../context/context.js';
2
import {
3
  type CascadeType,
4
  type EntityMeta,
5
  type FieldKey,
6
  type FieldOptions,
7
  type FilterOnMissing,
8
  type IdValue,
9
  type MongoId,
10
  type OnFieldCallback,
11
  type QueryAggregateOp,
12
  type QueryExclude,
13
  type QueryGroupMap,
14
  type QueryOptions,
15
  QueryRaw,
16
  type QuerySelect,
17
  type QuerySortMap,
18
  type QueryVectorSearch,
19
  type QueryWhere,
20
  type QueryWhereMap,
21
  type RelationKey,
22
  type UqlContext,
23
} from '../type/index.js';
24
import { getFieldKeys, getKeys } from './object.util.js';
25

26
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
27

28
export function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E, callbackKey: CallbackKey): FieldKey<E>[] {
29
  return getKeys(payload as object).filter((key) => {
665✔
30
    const fieldOpts = meta.fields[key];
1,817✔
31
    return fieldOpts && !fieldOpts.virtual && (callbackKey !== 'onUpdate' || fieldOpts.updatable !== false);
1,817✔
32
  }) as FieldKey<E>[];
33
}
34

35
export function getFieldCallbackValue(val: OnFieldCallback) {
36
  return typeof val === 'function' ? val() : val;
813✔
37
}
38

39
/**
40
 * Resolves the value stamped on the soft-delete field when deleting a row.
41
 * `true` stamps the current timestamp (`new Date()`); any other marker is an {@link OnFieldCallback}.
42
 */
43
export function getSoftDeleteValue(field: FieldOptions) {
44
  return field.softDelete === true ? new Date() : getFieldCallbackValue(field.softDelete as OnFieldCallback);
180✔
45
}
46

47
export function fillOnFields<E>(meta: EntityMeta<E>, payload: E | E[], callbackKey: CallbackKey): E[] {
48
  const payloads = Array.isArray(payload) ? payload : [payload];
665✔
49
  const keys = getKeys(meta.fields).filter((key) => meta.fields[key]![callbackKey]!) as FieldKey<E>[];
5,240✔
50
  return payloads.map((it) => {
665✔
51
    for (const key of keys) {
869✔
52
      if (it[key] === undefined) {
917✔
53
        it[key] = getFieldCallbackValue(meta.fields[key]![callbackKey]!) as E[typeof key];
632✔
54
      }
55
    }
56
    return it;
869✔
57
  });
58
}
59

60
export function filterPersistableRelationKeys<E>(
61
  meta: EntityMeta<E>,
62
  payload: E,
63
  action: CascadeType,
64
): RelationKey<E>[] {
65
  const keys = getKeys(payload as object);
1,014✔
66
  return keys.filter((key) => {
1,014✔
67
    const relOpts = meta.relations[key];
3,503✔
68
    return relOpts && isCascadable(action, relOpts.cascade);
3,503✔
69
  }) as RelationKey<E>[];
70
}
71

72
export function isCascadable(action: CascadeType, configuration?: boolean | CascadeType): boolean {
73
  if (typeof configuration === 'boolean') {
1,176✔
74
    return configuration;
204✔
75
  }
76
  return configuration === action;
972✔
77
}
78

79
export function normalizeScalarFieldSelection<E>(
80
  meta: EntityMeta<E>,
81
  select?: QuerySelect<E>,
82
  exclude?: QueryExclude<E>,
83
): FieldKey<E>[] {
84
  const positiveFields: FieldKey<E>[] = [];
4,221✔
85
  const excludedFields = new Set<FieldKey<E>>();
4,221✔
86

87
  for (const [key, value] of Object.entries(select ?? {})) {
4,221✔
88
    if (!(key in meta.fields)) continue;
4,240✔
89
    if (value) {
4,227✔
90
      positiveFields.push(key as FieldKey<E>);
4,205✔
91
    } else {
92
      excludedFields.add(key as FieldKey<E>);
22✔
93
    }
94
  }
95

96
  for (const [key, value] of Object.entries(exclude ?? {})) {
4,221✔
97
    if (value && key in meta.fields) {
9✔
98
      excludedFields.add(key as FieldKey<E>);
7✔
99
    }
100
  }
101

102
  if (positiveFields.length) {
4,221✔
103
    return positiveFields;
4,021✔
104
  }
105

106
  return getFieldKeys(meta.fields).filter((it) => !excludedFields.has(it)) as FieldKey<E>[];
1,501✔
107
}
108

109
export function buildSortMap<E>(sort: QuerySortMap<E> | undefined): QuerySortMap<E> {
110
  return (sort ?? {}) as QuerySortMap<E>;
5,958✔
111
}
112

113
/** Type guard: checks whether a sort value is a vector similarity search. */
114
export function isVectorSearch(value: unknown): value is QueryVectorSearch {
115
  return value !== null && typeof value === 'object' && '$vector' in (value as Record<string, unknown>);
562✔
116
}
117

118
export function augmentWhere<E>(
119
  meta: EntityMeta<E>,
120
  target: QueryWhere<E> = {},
127✔
121
  source: QueryWhere<E> = {},
127✔
122
): QueryWhere<E> {
123
  const targetComparison = buildQueryWhereAsMap(meta, target);
127✔
124
  const sourceComparison = buildQueryWhereAsMap(meta, source);
127✔
125
  return {
127✔
126
    ...targetComparison,
127
    ...sourceComparison,
128
  };
129
}
130

131
/**
132
 * Normalizes any `$where` shape (id, id[], raw, or map) to a `QueryWhereMap`. Read-only: for a map
133
 * input it returns that same object by reference (no copy), so callers must not mutate the result -
134
 * {@link applyFilters} and {@link augmentWhere} return new objects instead.
135
 */
136
export function buildQueryWhereAsMap<E>(meta: EntityMeta<E>, filter: QueryWhere<E> = {}): QueryWhereMap<E> {
14,898✔
137
  if (filter instanceof QueryRaw) {
14,898✔
138
    return { $and: [filter] } as QueryWhereMap<E>;
5✔
139
  }
140
  if (isIdValue(filter)) {
14,893✔
141
    return {
517✔
142
      [meta.id]: filter,
143
    } as QueryWhereMap<E>;
144
  }
145
  return filter as QueryWhereMap<E>;
14,376✔
146
}
147

148
/** Returns a `QueryOptions.filters` value with the built-in soft-delete filter disabled (used by hard delete). */
149
export function withoutSoftDeleteFilter(filters: QueryOptions['filters']): QueryOptions['filters'] {
150
  return filters === false ? false : { ...filters, softDelete: false };
1,944!
151
}
152

153
/**
154
 * Returns a new `$where` map with every active entity filter's condition merged in, resolving
155
 * parameterized conditions against the explicit or ambient {@link UqlContext}. Never mutates the input.
156
 *
157
 * Convenience filters are active by default (unless `opts.filters === false` or bypassed by name), and
158
 * their keys are applied only when absent from the map, so an explicit `$where` on that key opts out.
159
 *
160
 * `security` filters are always active (bypass is ignored) and AND-merged, so a client `$where` on the
161
 * same field can't override them. A security condition that returns `undefined` fails the query closed
162
 * (throws {@link UqlSecurityError}) unless its `onMissing` is `skip`.
163
 */
164
export function applyFilters<E>(
165
  meta: EntityMeta<E>,
166
  whereMap: QueryWhereMap<E>,
167
  opts?: QueryOptions,
168
): QueryWhereMap<E> {
169
  if (!meta.filters) {
6,890✔
170
    return whereMap;
6,073✔
171
  }
172
  const context = getContext();
817✔
173
  const result: Record<string, unknown> = { ...whereMap };
817✔
174
  const securityConditions: unknown[] = [];
817✔
175

176
  for (const name of getKeys(meta.filters)) {
817✔
177
    const filter = meta.filters[name];
836✔
178

179
    let active: boolean;
180
    if (filter.security) {
836✔
181
      active = true;
5✔
182
    } else if (opts?.filters === false) {
831✔
183
      active = false;
7✔
184
    } else {
185
      active = opts?.filters?.[name] ?? filter.default !== false;
824✔
186
    }
187
    if (!active) {
836✔
188
      continue;
55✔
189
    }
190

191
    const raw = filter.condition;
781✔
192
    const condition =
193
      typeof raw === 'function' ? (raw as (c: UqlContext | undefined) => QueryWhere<E> | undefined)(context) : raw;
781✔
194
    if (condition === undefined) {
836✔
195
      const onMissing: FilterOnMissing = filter.onMissing ?? (filter.security ? 'throw' : 'skip');
1!
196
      if (onMissing === 'throw') {
1!
197
        throw new UqlSecurityError(`filter '${name}' on '${meta.name ?? ''}' could not resolve (missing context)`);
1!
198
      }
199
      continue;
×
200
    }
201

202
    const conditionMap = buildQueryWhereAsMap(meta, condition) as Record<string, unknown>;
780✔
203
    if (filter.security) {
780✔
204
      securityConditions.push(conditionMap);
4✔
205
    } else {
206
      for (const key of getKeys(conditionMap)) {
776✔
207
        if (result[key] === undefined) {
776✔
208
          result[key] = conditionMap[key];
765✔
209
        }
210
      }
211
    }
212
  }
213

214
  if (securityConditions.length) {
816✔
215
    const existing = result['$and'] as unknown[] | undefined;
4✔
216
    result['$and'] = existing ? [...existing, ...securityConditions] : securityConditions;
4✔
217
  }
218

219
  return result as QueryWhereMap<E>;
816✔
220
}
221

222
function isIdValue<E>(filter: QueryWhere<E>): filter is IdValue<E> | IdValue<E>[] {
223
  const type = typeof filter;
14,893✔
224
  return (
14,893✔
225
    type === 'string' ||
73,786✔
226
    type === 'number' ||
227
    type === 'bigint' ||
228
    typeof (filter as MongoId).toHexString === 'function' ||
229
    Array.isArray(filter)
230
  );
231
}
232

233
/**
234
 * Parsed entry from a `$group` map - either a raw group key or an aggregate function call.
235
 */
236
export type ParsedGroupEntry =
237
  | { readonly kind: 'key'; readonly alias: string }
238
  | { readonly kind: 'fn'; readonly alias: string; readonly op: QueryAggregateOp; readonly fieldRef: string };
239

240
/**
241
 * Parse a `QueryGroupMap` into structured entries consumable by any dialect.
242
 * Eliminates the duplicated `value === true` / `typeof value === 'object'` pattern.
243
 */
244
export function parseGroupMap<E>(group: QueryGroupMap<E>): ParsedGroupEntry[] {
245
  const entries: ParsedGroupEntry[] = [];
71✔
246
  for (const alias of getKeys(group)) {
71✔
247
    const value = group[alias];
153✔
248
    if (value === true) {
153✔
249
      entries.push({ kind: 'key', alias });
55✔
250
    } else if (value && typeof value === 'object') {
98✔
251
      const fnEntry = value as Record<string, string>;
95✔
252
      const op = getKeys(fnEntry)[0] as QueryAggregateOp;
95✔
253
      entries.push({ kind: 'fn', alias, op, fieldRef: fnEntry[op] });
95✔
254
    }
255
  }
256
  return entries;
71✔
257
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc