@fragno-dev/db 0.1.12 → 0.1.13

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.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +27 -27
  2. package/CHANGELOG.md +6 -0
  3. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  4. package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
  5. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-query.js +4 -0
  7. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
  9. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
  11. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  12. package/dist/adapters/drizzle/generate.js +1 -1
  13. package/dist/adapters/kysely/kysely-adapter.d.ts +1 -1
  14. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  15. package/dist/adapters/kysely/kysely-query.js +29 -1
  16. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  17. package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
  18. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  19. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  20. package/dist/mod.d.ts +2 -1
  21. package/dist/mod.d.ts.map +1 -1
  22. package/dist/mod.js +2 -1
  23. package/dist/mod.js.map +1 -1
  24. package/dist/query/cursor.d.ts +67 -32
  25. package/dist/query/cursor.d.ts.map +1 -1
  26. package/dist/query/cursor.js +84 -31
  27. package/dist/query/cursor.js.map +1 -1
  28. package/dist/query/query.d.ts +5 -0
  29. package/dist/query/query.d.ts.map +1 -1
  30. package/dist/query/unit-of-work.d.ts +14 -4
  31. package/dist/query/unit-of-work.d.ts.map +1 -1
  32. package/dist/query/unit-of-work.js +52 -9
  33. package/dist/query/unit-of-work.js.map +1 -1
  34. package/package.json +3 -3
  35. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +72 -5
  36. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +6 -4
  37. package/src/adapters/drizzle/drizzle-query.ts +9 -0
  38. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +19 -3
  39. package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
  40. package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
  41. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +102 -4
  42. package/src/adapters/kysely/kysely-query.ts +50 -1
  43. package/src/adapters/kysely/kysely-uow-compiler.test.ts +19 -3
  44. package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
  45. package/src/mod.ts +6 -1
  46. package/src/query/cursor.test.ts +113 -68
  47. package/src/query/cursor.ts +127 -36
  48. package/src/query/query.ts +19 -0
  49. package/src/query/unit-of-work.ts +133 -15
@@ -4,6 +4,8 @@ import type { Condition, ConditionBuilder } from "./condition-builder";
4
4
  import type { SelectClause, TableToInsertValues, TableToUpdateValues, SelectResult } from "./query";
5
5
  import { buildCondition } from "./condition-builder";
6
6
  import type { CompiledJoin } from "./orm/orm";
7
+ import type { CursorResult } from "./cursor";
8
+ import { Cursor } from "./cursor";
7
9
 
8
10
  /**
9
11
  * Builder for updateMany operations that supports both whereIndex and set chaining
@@ -112,11 +114,11 @@ type FindOptions<
112
114
  /**
113
115
  * Cursor for pagination - continue after this cursor
114
116
  */
115
- after?: string;
117
+ after?: Cursor | string;
116
118
  /**
117
119
  * Cursor for pagination - continue before this cursor
118
120
  */
119
- before?: string;
121
+ before?: Cursor | string;
120
122
  /**
121
123
  * Number of results per page
122
124
  */
@@ -144,6 +146,7 @@ export type RetrievalOperation<
144
146
  table: TTable;
145
147
  indexName: string;
146
148
  options: FindOptions<TTable, SelectClause<TTable>>;
149
+ withCursor?: boolean;
147
150
  }
148
151
  | {
149
152
  type: "count";
@@ -262,12 +265,13 @@ export class FindBuilder<
262
265
  indexName: string;
263
266
  direction: "asc" | "desc";
264
267
  };
265
- #afterCursor?: string;
266
- #beforeCursor?: string;
268
+ #afterCursor?: Cursor | string;
269
+ #beforeCursor?: Cursor | string;
267
270
  #pageSizeValue?: number;
268
271
  #selectClause?: TSelect;
269
272
  #joinClause?: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TJoinOut>;
270
273
  #countMode = false;
274
+ #cursorMetadata?: Cursor;
271
275
 
272
276
  constructor(tableName: string, table: TTable) {
273
277
  this.#tableName = tableName;
@@ -357,17 +361,27 @@ export class FindBuilder<
357
361
 
358
362
  /**
359
363
  * Set cursor to continue pagination after this point (forward pagination)
364
+ * If a Cursor object is provided, its metadata will be used to set defaults for
365
+ * index, orderByIndex, and pageSize (if not explicitly set)
360
366
  */
361
- after(cursor: string): this {
367
+ after(cursor: Cursor | string): this {
362
368
  this.#afterCursor = cursor;
369
+ if (cursor instanceof Cursor) {
370
+ this.#cursorMetadata = cursor;
371
+ }
363
372
  return this;
364
373
  }
365
374
 
366
375
  /**
367
376
  * Set cursor to continue pagination before this point (backward pagination)
377
+ * If a Cursor object is provided, its metadata will be used to set defaults for
378
+ * index, orderByIndex, and pageSize (if not explicitly set)
368
379
  */
369
- before(cursor: string): this {
380
+ before(cursor: Cursor | string): this {
370
381
  this.#beforeCursor = cursor;
382
+ if (cursor instanceof Cursor) {
383
+ this.#cursorMetadata = cursor;
384
+ }
371
385
  return this;
372
386
  }
373
387
 
@@ -400,7 +414,47 @@ export class FindBuilder<
400
414
  indexName: string;
401
415
  options: Pick<FindOptions<TTable>, "where" | "useIndex">;
402
416
  } {
403
- if (!this.#indexName) {
417
+ // Apply cursor metadata as defaults if available and not explicitly set
418
+ let indexName = this.#indexName;
419
+ let orderByIndex = this.#orderByIndexClause;
420
+ let pageSize = this.#pageSizeValue;
421
+
422
+ if (this.#cursorMetadata) {
423
+ // Use cursor metadata as defaults
424
+ if (!indexName) {
425
+ indexName = this.#cursorMetadata.indexName;
426
+ }
427
+ if (!orderByIndex) {
428
+ orderByIndex = {
429
+ indexName: this.#cursorMetadata.indexName,
430
+ direction: this.#cursorMetadata.orderDirection,
431
+ };
432
+ }
433
+ if (pageSize === undefined) {
434
+ pageSize = this.#cursorMetadata.pageSize;
435
+ }
436
+
437
+ // Validate that explicit params match cursor params
438
+ if (indexName && indexName !== this.#cursorMetadata.indexName) {
439
+ throw new Error(
440
+ `Index mismatch: builder specifies "${indexName}" but cursor specifies "${this.#cursorMetadata.indexName}"`,
441
+ );
442
+ }
443
+ if (
444
+ orderByIndex &&
445
+ (orderByIndex.indexName !== this.#cursorMetadata.indexName ||
446
+ orderByIndex.direction !== this.#cursorMetadata.orderDirection)
447
+ ) {
448
+ throw new Error(`Order mismatch: builder and cursor specify different ordering`);
449
+ }
450
+ if (pageSize !== undefined && pageSize !== this.#cursorMetadata.pageSize) {
451
+ throw new Error(
452
+ `Page size mismatch: builder specifies ${pageSize} but cursor specifies ${this.#cursorMetadata.pageSize}`,
453
+ );
454
+ }
455
+ }
456
+
457
+ if (!indexName) {
404
458
  throw new Error(
405
459
  `Must specify an index using .whereIndex() before finalizing find operation on table "${this.#tableName}"`,
406
460
  );
@@ -410,9 +464,9 @@ export class FindBuilder<
410
464
  if (this.#countMode) {
411
465
  return {
412
466
  type: "count",
413
- indexName: this.#indexName,
467
+ indexName,
414
468
  options: {
415
- useIndex: this.#indexName,
469
+ useIndex: indexName,
416
470
  where: this.#whereClause,
417
471
  },
418
472
  };
@@ -424,18 +478,24 @@ export class FindBuilder<
424
478
  compiledJoins = buildJoinIndexed(this.#table, this.#joinClause);
425
479
  }
426
480
 
481
+ // Convert Cursor objects to strings for after/before
482
+ const afterCursor =
483
+ this.#afterCursor instanceof Cursor ? this.#afterCursor.encode() : this.#afterCursor;
484
+ const beforeCursor =
485
+ this.#beforeCursor instanceof Cursor ? this.#beforeCursor.encode() : this.#beforeCursor;
486
+
427
487
  const options: FindOptions<TTable, TSelect> = {
428
- useIndex: this.#indexName,
488
+ useIndex: indexName,
429
489
  select: this.#selectClause,
430
490
  where: this.#whereClause,
431
- orderByIndex: this.#orderByIndexClause,
432
- after: this.#afterCursor,
433
- before: this.#beforeCursor,
434
- pageSize: this.#pageSizeValue,
491
+ orderByIndex,
492
+ after: afterCursor,
493
+ before: beforeCursor,
494
+ pageSize,
435
495
  joins: compiledJoins,
436
496
  };
437
497
 
438
- return { type: "find", indexName: this.#indexName, options };
498
+ return { type: "find", indexName, options };
439
499
  }
440
500
  }
441
501
 
@@ -983,6 +1043,64 @@ export class UnitOfWork<
983
1043
  >;
984
1044
  }
985
1045
 
1046
+ /**
1047
+ * Add a find operation with cursor metadata (retrieval phase only)
1048
+ */
1049
+ findWithCursor<
1050
+ TTableName extends keyof TSchema["tables"] & string,
1051
+ TSelect extends SelectClause<TSchema["tables"][TTableName]> = true,
1052
+ TJoinOut = {},
1053
+ >(
1054
+ tableName: TTableName,
1055
+ builderFn: (
1056
+ // We omit "build" because we don't want to expose it to the user
1057
+ builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
1058
+ ) => Omit<FindBuilder<TSchema["tables"][TTableName], TSelect, TJoinOut>, "build"> | void,
1059
+ ): UnitOfWork<
1060
+ TSchema,
1061
+ [
1062
+ ...TRetrievalResults,
1063
+ CursorResult<SelectResult<TSchema["tables"][TTableName], TJoinOut, TSelect>>,
1064
+ ],
1065
+ TRawInput
1066
+ > {
1067
+ if (this.#state !== "building-retrieval") {
1068
+ throw new Error(
1069
+ `findWithCursor() can only be called during retrieval phase. Current state: ${this.#state}`,
1070
+ );
1071
+ }
1072
+
1073
+ const table = this.#schema.tables[tableName];
1074
+ if (!table) {
1075
+ throw new Error(`Table ${tableName} not found in schema`);
1076
+ }
1077
+
1078
+ // Create builder and pass to callback
1079
+ const builder = new FindBuilder(tableName, table as TSchema["tables"][TTableName]);
1080
+ builderFn(builder);
1081
+ const { indexName, options, type } = builder.build();
1082
+
1083
+ this.#retrievalOps.push({
1084
+ type,
1085
+ // Safe: we know the table is part of the schema from the findWithCursor() method
1086
+ table: table as TSchema["tables"][TTableName],
1087
+ indexName,
1088
+ // Safe: we're storing the options for later compilation
1089
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1090
+ options: options as any,
1091
+ withCursor: true,
1092
+ });
1093
+
1094
+ return this as unknown as UnitOfWork<
1095
+ TSchema,
1096
+ [
1097
+ ...TRetrievalResults,
1098
+ CursorResult<SelectResult<TSchema["tables"][TTableName], TJoinOut, TSelect>>,
1099
+ ],
1100
+ TRawInput
1101
+ >;
1102
+ }
1103
+
986
1104
  /**
987
1105
  * Add a create operation (mutation phase only)
988
1106
  * Returns a FragnoId with the external ID that can be used immediately in subsequent operations