@atscript/utils-db 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,53 @@
1
- import { TAtscriptAnnotatedType, TValidatorPlugin, TMetadataMap, TAtscriptDataType, FlatOf, Validator, TAtscriptTypeObject, TValidatorOptions, TAtscriptTypeArray } from '@atscript/typescript/utils';
2
- import { FilterExpr, Uniquery, UniqueryControls } from '@uniqu/core';
1
+ import { TAtscriptAnnotatedType, TValidatorPlugin, TMetadataMap, TAtscriptDataType, FlatOf, PrimaryKeyOf, Validator, TAtscriptTypeObject, TValidatorOptions, TAtscriptTypeArray } from '@atscript/typescript/utils';
2
+ import { UniqueryControls, FilterExpr, Uniquery } from '@uniqu/core';
3
3
  export { FieldOpsFor, FilterExpr, FilterVisitor, Uniquery, UniqueryControls, isPrimitive, walkFilter } from '@uniqu/core';
4
4
 
5
+ /**
6
+ * Wraps a raw `$select` value and provides lazy-cached conversions
7
+ * to the forms different adapters need.
8
+ *
9
+ * Only instantiated when `$select` is actually provided —
10
+ * `controls.$select` is `UniquSelect | undefined`.
11
+ *
12
+ * For exclusion → inclusion inversion, pass `allFields` (physical field names).
13
+ */
14
+ declare class UniquSelect {
15
+ private _raw;
16
+ private _allFields?;
17
+ private _arrayResolved;
18
+ private _array?;
19
+ private _projectionResolved;
20
+ private _projection?;
21
+ constructor(raw: UniqueryControls['$select'], allFields?: string[]);
22
+ /**
23
+ * Resolved inclusion array of field names.
24
+ * For exclusion form, inverts using `allFields` from constructor.
25
+ */
26
+ get asArray(): string[] | undefined;
27
+ /**
28
+ * Record projection preserving original semantics.
29
+ * Returns original object as-is if raw was object.
30
+ * Converts `string[]` to `{field: 1}` inclusion object.
31
+ */
32
+ get asProjection(): Record<string, 0 | 1> | undefined;
33
+ }
34
+
35
+ /** Controls with resolved projection. Used in the adapter interface. */
36
+ interface DbControls extends Omit<UniqueryControls, '$select'> {
37
+ $select?: UniquSelect;
38
+ }
39
+ /** Query object with resolved projection. Passed to adapter methods. */
40
+ interface DbQuery {
41
+ filter: FilterExpr;
42
+ controls: DbControls;
43
+ }
44
+ /** Describes an available search index exposed by a database adapter. */
45
+ interface TSearchIndexInfo {
46
+ /** Index name. Empty string or 'DEFAULT' for the default index. */
47
+ name: string;
48
+ /** Human-readable label for UI display. */
49
+ description?: string;
50
+ }
5
51
  interface TDbInsertResult {
6
52
  insertedId: unknown;
7
53
  }
@@ -16,9 +62,11 @@ interface TDbUpdateResult {
16
62
  interface TDbDeleteResult {
17
63
  deletedCount: number;
18
64
  }
65
+ type TDbIndexType = 'plain' | 'unique' | 'fulltext';
19
66
  interface TDbIndexField {
20
67
  name: string;
21
68
  sort: 'asc' | 'desc';
69
+ weight?: number;
22
70
  }
23
71
  interface TDbIndex {
24
72
  /** Unique key used for identity/diffing (e.g., "atscript__plain__email") */
@@ -26,16 +74,17 @@ interface TDbIndex {
26
74
  /** Human-readable index name. */
27
75
  name: string;
28
76
  /** Index type. */
29
- type: 'plain' | 'unique' | 'fulltext';
77
+ type: TDbIndexType;
30
78
  /** Ordered list of fields in the index. */
31
79
  fields: TDbIndexField[];
32
80
  }
81
+ type TDbDefaultFn = 'increment' | 'uuid' | 'now';
33
82
  type TDbDefaultValue = {
34
83
  kind: 'value';
35
84
  value: string;
36
85
  } | {
37
86
  kind: 'fn';
38
- fn: 'increment' | 'uuid' | 'now';
87
+ fn: TDbDefaultFn;
39
88
  };
40
89
  interface TIdDescriptor {
41
90
  /** Field names that form the primary key. */
@@ -43,6 +92,7 @@ interface TIdDescriptor {
43
92
  /** Whether this is a composite key (multiple fields). */
44
93
  isComposite: boolean;
45
94
  }
95
+ type TDbStorageType = 'column' | 'flattened' | 'json';
46
96
  interface TDbFieldMeta {
47
97
  /** The dot-notation path to this field (logical name). */
48
98
  path: string;
@@ -66,7 +116,7 @@ interface TDbFieldMeta {
66
116
  * - 'flattened': a leaf scalar from a flattened nested object
67
117
  * - 'json': stored as a single JSON column (arrays, @db.json fields)
68
118
  */
69
- storage: 'column' | 'flattened' | 'json';
119
+ storage: TDbStorageType;
70
120
  /**
71
121
  * For flattened fields: the dot-notation path (same as `path`).
72
122
  * E.g., for physicalName 'contact__email', this is 'contact.email'.
@@ -143,6 +193,24 @@ declare abstract class BaseDbAdapter {
143
193
  * @returns Update result.
144
194
  */
145
195
  nativePatch(filter: FilterExpr, patch: unknown): Promise<TDbUpdateResult>;
196
+ /**
197
+ * Builds a custom insert validator for this adapter.
198
+ * When defined, {@link AtscriptDbTable} uses this instead of the default
199
+ * insert validator (which makes all primary keys optional).
200
+ *
201
+ * Example: MongoDB only makes ObjectId primary keys optional (auto-generated),
202
+ * but string/number IDs remain required.
203
+ */
204
+ buildInsertValidator?(table: AtscriptDbTable): any;
205
+ /**
206
+ * Builds a custom patch validator for this adapter.
207
+ * When defined, {@link AtscriptDbTable} uses this instead of the default
208
+ * partial validator for the `'patch'` purpose.
209
+ *
210
+ * Example: MongoDB wraps top-level array fields with `$replace`/`$insert`/…
211
+ * patch operators that the default validator doesn't understand.
212
+ */
213
+ buildPatchValidator?(table: AtscriptDbTable): any;
146
214
  /**
147
215
  * Called before field flattening begins.
148
216
  * Use to extract table-level adapter-specific annotations.
@@ -154,7 +222,7 @@ declare abstract class BaseDbAdapter {
154
222
  * Called for each field during flattening.
155
223
  * Use to extract field-level adapter-specific annotations.
156
224
  *
157
- * Example: MongoDB adapter extracts `@db.mongo.index.text`, `@db.mongo.search.vector`.
225
+ * Example: MongoDB adapter extracts `@db.mongo.search.vector`, `@db.mongo.search.text`.
158
226
  */
159
227
  onFieldScanned?(field: string, type: TAtscriptAnnotatedType, metadata: TMetadataMap<AtscriptMetadata>): void;
160
228
  /**
@@ -209,14 +277,51 @@ declare abstract class BaseDbAdapter {
209
277
  prefix?: string;
210
278
  shouldSkipType?(type: TDbIndex['type']): boolean;
211
279
  }): Promise<void>;
280
+ /**
281
+ * Returns available search indexes for this adapter.
282
+ * UI uses this to show index picker. Override in adapters that support search.
283
+ */
284
+ getSearchIndexes(): TSearchIndexInfo[];
285
+ /**
286
+ * Whether this adapter supports text search.
287
+ * Default: `true` when {@link getSearchIndexes} returns any entries.
288
+ */
289
+ isSearchable(): boolean;
290
+ /**
291
+ * Full-text search. Override in adapters that support search.
292
+ *
293
+ * @param text - Search text.
294
+ * @param query - Filter, sort, limit, etc.
295
+ * @param indexName - Optional search index to target.
296
+ */
297
+ search(text: string, query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
298
+ /**
299
+ * Full-text search with count (for paginated search results).
300
+ *
301
+ * @param text - Search text.
302
+ * @param query - Filter, sort, limit, etc.
303
+ * @param indexName - Optional search index to target.
304
+ */
305
+ searchWithCount(text: string, query: DbQuery, indexName?: string): Promise<{
306
+ data: Array<Record<string, unknown>>;
307
+ count: number;
308
+ }>;
309
+ /**
310
+ * Fetches records and total count in one call.
311
+ * Default: two parallel calls. Adapters may override for single-query optimization.
312
+ */
313
+ findManyWithCount(query: DbQuery): Promise<{
314
+ data: Array<Record<string, unknown>>;
315
+ count: number;
316
+ }>;
212
317
  abstract insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
213
318
  abstract insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
214
319
  abstract replaceOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
215
320
  abstract updateOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
216
321
  abstract deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
217
- abstract findOne(query: Uniquery): Promise<Record<string, unknown> | null>;
218
- abstract findMany(query: Uniquery): Promise<Array<Record<string, unknown>>>;
219
- abstract count(query: Uniquery): Promise<number>;
322
+ abstract findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
323
+ abstract findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
324
+ abstract count(query: DbQuery): Promise<number>;
220
325
  abstract updateMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
221
326
  abstract replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
222
327
  abstract deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
@@ -267,9 +372,9 @@ declare function resolveDesignType(fieldType: TAtscriptAnnotatedType): string;
267
372
  * @typeParam T - The Atscript annotated type for this table.
268
373
  * @typeParam DataType - The inferred data shape from the annotated type.
269
374
  */
270
- declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>, FlatType = FlatOf<T>> {
375
+ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>, FlatType = FlatOf<T>, A extends BaseDbAdapter = BaseDbAdapter, IdType = PrimaryKeyOf<T>> {
271
376
  protected readonly _type: T;
272
- protected readonly adapter: BaseDbAdapter;
377
+ protected readonly adapter: A;
273
378
  protected readonly logger: TGenericLogger;
274
379
  /** Resolved table/collection name. */
275
380
  readonly tableName: string;
@@ -293,10 +398,18 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
293
398
  protected _jsonFields: Set<string>;
294
399
  /** Intermediate paths → their leaf physical column names (for $select expansion in relational DBs). */
295
400
  protected _selectExpansion: Map<string, string[]>;
401
+ /** Physical column names of boolean fields (for storage coercion on read). */
402
+ protected _booleanFields: Set<string>;
296
403
  /** Fast-path flag: skip all mapping when no nested/json fields exist. */
297
404
  protected _requiresMappings: boolean;
405
+ /** All non-ignored physical field names (for UniquSelect exclusion inversion). */
406
+ protected _allPhysicalFields: string[];
407
+ /** Cached result of adapter.supportsNestedObjects(). */
408
+ protected readonly _nestedObjects: boolean;
298
409
  protected readonly validators: Map<string, Validator<T, DataType>>;
299
- constructor(_type: T, adapter: BaseDbAdapter, logger?: TGenericLogger);
410
+ constructor(_type: T, adapter: A, logger?: TGenericLogger);
411
+ /** Returns the underlying adapter with its concrete type preserved. */
412
+ getAdapter(): A;
300
413
  /** The raw annotated type. */
301
414
  get type(): TAtscriptAnnotatedType<TAtscriptTypeObject>;
302
415
  /** Lazily-built flat map of all fields (dot-notation paths → annotated types). */
@@ -305,6 +418,25 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
305
418
  get indexes(): Map<string, TDbIndex>;
306
419
  /** Primary key field names from `@meta.id`. */
307
420
  get primaryKeys(): readonly string[];
421
+ /**
422
+ * Registers an additional primary key field.
423
+ * Useful for adapters (e.g., MongoDB) where `_id` is always the primary key
424
+ * even without an explicit `@meta.id` annotation.
425
+ *
426
+ * Typically called from {@link BaseDbAdapter.onFieldScanned}.
427
+ */
428
+ addPrimaryKey(field: string): void;
429
+ /**
430
+ * Removes a field from the primary key list.
431
+ * Useful for adapters (e.g., MongoDB) where `@meta.id` fields should be
432
+ * unique indexes rather than part of the primary key.
433
+ */
434
+ removePrimaryKey(field: string): void;
435
+ /**
436
+ * Registers a field as having a unique constraint.
437
+ * Used by adapters to ensure `findById` falls back to this field.
438
+ */
439
+ addUniqueField(field: string): void;
308
440
  /** Logical → physical column name mapping from `@db.column`. */
309
441
  get columnMap(): ReadonlyMap<string, string>;
310
442
  /** Default values from `@db.default.*`. */
@@ -325,14 +457,6 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
325
457
  * encapsulating all the type introspection gotchas.
326
458
  */
327
459
  get fieldDescriptors(): readonly TDbFieldMeta[];
328
- /**
329
- * Resolves `$select` from {@link UniqueryControls} to a list of field names.
330
- * - `undefined` → `undefined` (all fields)
331
- * - `string[]` → pass through
332
- * - `Record<K, 1>` → extract included keys
333
- * - `Record<K, 0>` → invert using known field names
334
- */
335
- resolveProjection(select?: UniqueryControls['$select']): string[] | undefined;
336
460
  /**
337
461
  * Creates a new validator with custom options.
338
462
  * Adapter plugins are NOT automatically included — use {@link getValidator}
@@ -370,7 +494,7 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
370
494
  /**
371
495
  * Deletes a single record by primary key value.
372
496
  */
373
- deleteOne(id: unknown): Promise<TDbDeleteResult>;
497
+ deleteOne(id: IdType): Promise<TDbDeleteResult>;
374
498
  /**
375
499
  * Finds a single record matching the query.
376
500
  */
@@ -383,6 +507,47 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
383
507
  * Counts records matching the query.
384
508
  */
385
509
  count(query?: Uniquery<FlatType>): Promise<number>;
510
+ /**
511
+ * Finds records and total count in a single logical call.
512
+ * Adapters may optimize into a single query (e.g., MongoDB `$facet`).
513
+ */
514
+ findManyWithCount(query: Uniquery<FlatType>): Promise<{
515
+ data: DataType[];
516
+ count: number;
517
+ }>;
518
+ /** Whether the underlying adapter supports text search. */
519
+ isSearchable(): boolean;
520
+ /** Returns available search indexes from the adapter. */
521
+ getSearchIndexes(): TSearchIndexInfo[];
522
+ /**
523
+ * Full-text search with query translation and result reconstruction.
524
+ *
525
+ * @param text - Search text.
526
+ * @param query - Filter, sort, limit, etc.
527
+ * @param indexName - Optional search index to target.
528
+ */
529
+ search(text: string, query: Uniquery<FlatType>, indexName?: string): Promise<DataType[]>;
530
+ /**
531
+ * Full-text search with count for paginated search results.
532
+ *
533
+ * @param text - Search text.
534
+ * @param query - Filter, sort, limit, etc.
535
+ * @param indexName - Optional search index to target.
536
+ */
537
+ searchWithCount(text: string, query: Uniquery<FlatType>, indexName?: string): Promise<{
538
+ data: DataType[];
539
+ count: number;
540
+ }>;
541
+ /**
542
+ * Finds a single record by primary key or unique property.
543
+ *
544
+ * 1. Tries primary key lookup (single or composite).
545
+ * 2. Falls back to unique properties if PK validation fails.
546
+ *
547
+ * @param id - Primary key value (scalar for single PK, object for composite).
548
+ * @param controls - Optional query controls ($select, etc.).
549
+ */
550
+ findById(id: unknown, controls?: UniqueryControls<FlatType>): Promise<DataType | null>;
386
551
  updateMany(filter: FilterExpr<FlatType>, data: Partial<DataType> & Record<string, unknown>): Promise<TDbUpdateResult>;
387
552
  replaceMany(filter: FilterExpr<FlatType>, data: Record<string, unknown>): Promise<TDbUpdateResult>;
388
553
  deleteMany(filter: FilterExpr<FlatType>): Promise<TDbDeleteResult>;
@@ -400,7 +565,10 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
400
565
  * Scans `@db.*` and `@meta.id` annotations on a field during flattening.
401
566
  */
402
567
  private _scanGenericAnnotations;
403
- protected _addIndexField(type: TDbIndex['type'], name: string, field: string, sort?: 'asc' | 'desc'): void;
568
+ protected _addIndexField(type: TDbIndex['type'], name: string, field: string, opts?: {
569
+ sort?: 'asc' | 'desc';
570
+ weight?: number;
571
+ }): void;
404
572
  /**
405
573
  * Classifies each field as column, flattened, json, or parent-object.
406
574
  * Builds the bidirectional _pathToPhysical / _physicalToPath maps.
@@ -408,15 +576,10 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
408
576
  */
409
577
  private _classifyFields;
410
578
  /**
411
- * Finds the nearest ancestor that is a flattened parent object.
412
- * Returns the parent path, or undefined if no ancestor is being flattened.
413
- */
414
- private _findFlattenedParent;
415
- /**
416
- * Finds the nearest ancestor that is a @db.json field.
417
- * Children of JSON fields don't get their own columns.
579
+ * Finds the nearest ancestor of `path` that belongs to `set`.
580
+ * Used to locate flattened parents and @db.json ancestors.
418
581
  */
419
- private _findJsonParent;
582
+ private _findAncestorInSet;
420
583
  private _finalizeIndexes;
421
584
  /**
422
585
  * Applies default values for fields that are missing from the payload.
@@ -436,9 +599,10 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
436
599
  */
437
600
  private _flattenPayload;
438
601
  /**
439
- * Recursively flattens a nested object, writing physical keys to result.
602
+ * Classifies and writes a single field to the result object.
603
+ * Recurses into nested objects that should be flattened.
440
604
  */
441
- private _flattenObject;
605
+ private _writeFlattenedField;
442
606
  /**
443
607
  * When a parent object is null/undefined, set all its flattened children to null.
444
608
  */
@@ -448,6 +612,11 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
448
612
  * JSON fields are parsed from strings back to objects/arrays.
449
613
  */
450
614
  protected _reconstructFromRead(row: Record<string, unknown>): Record<string, unknown>;
615
+ /**
616
+ * Coerces boolean fields from storage representation (0/1) to JS booleans.
617
+ * Used on the fast-path when no column mapping is needed.
618
+ */
619
+ private _coerceBooleans;
451
620
  /**
452
621
  * Sets a value at a dot-notation path, creating intermediate objects as needed.
453
622
  */
@@ -459,6 +628,7 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
459
628
  /**
460
629
  * Translates a Uniquery's filter, sort, and projection from logical
461
630
  * dot-notation paths to physical column names.
631
+ * Always wraps `$select` in {@link UniquSelect}.
462
632
  */
463
633
  private _translateQuery;
464
634
  /**
@@ -467,6 +637,7 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
467
637
  private _translateFilter;
468
638
  /**
469
639
  * Translates field names in sort and projection controls.
640
+ * Wraps `$select` in {@link UniquSelect} after path translation.
470
641
  */
471
642
  private _translateControls;
472
643
  /**
@@ -527,5 +698,5 @@ type TDbPatch<T> = {
527
698
  */
528
699
  declare function getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
529
700
 
530
- export { AtscriptDbTable, BaseDbAdapter, NoopLogger, decomposePatch, getKeyProps, resolveDesignType };
531
- export type { TArrayPatch, TDbDefaultValue, TDbDeleteResult, TDbFieldMeta, TDbIndex, TDbIndexField, TDbInsertManyResult, TDbInsertResult, TDbPatch, TDbUpdateResult, TGenericLogger, TIdDescriptor };
701
+ export { AtscriptDbTable, BaseDbAdapter, NoopLogger, UniquSelect, decomposePatch, getKeyProps, resolveDesignType };
702
+ export type { DbControls, DbQuery, TArrayPatch, TDbDefaultFn, TDbDefaultValue, TDbDeleteResult, TDbFieldMeta, TDbIndex, TDbIndexField, TDbIndexType, TDbInsertManyResult, TDbInsertResult, TDbPatch, TDbStorageType, TDbUpdateResult, TGenericLogger, TIdDescriptor, TSearchIndexInfo };