@voltro/database 0.3.0 → 0.5.0

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
@@ -1251,7 +1251,15 @@ export declare type DatabaseHandle<Tables extends Record<string, Table<string, R
1251
1251
  export declare const databaseHandle: <Tables extends Record<string, Table<string, Record<string, ColumnDefinition<unknown>>, boolean, string>>>(tables: Tables) => DatabaseHandle<Tables>;
1252
1252
 
1253
1253
  export declare interface DataStore {
1254
- /** Execute a typed query descriptor and return matching rows. */
1254
+ /**
1255
+ * Execute a typed query descriptor and return matching rows.
1256
+ *
1257
+ * Deliberately NOT generic: `DataStore` is the driver SPI, implemented by
1258
+ * every dialect store and its transactional / namespace views. Threading a
1259
+ * phantom row type through all of them would be pure churn — they genuinely
1260
+ * do return untyped rows off the wire. The row type is re-applied one layer
1261
+ * up, on `FluentStore.query`, which is what handlers actually call.
1262
+ */
1255
1263
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
1256
1264
  /** Insert a row. Emits an insert ChangeEvent (deferred if inside `transactional()`). */
1257
1265
  insert(table: string, row: Row): Promise<Row>;
@@ -2829,6 +2837,29 @@ export declare interface OrPredicate {
2829
2837
  readonly or: ReadonlyArray<Predicate>;
2830
2838
  }
2831
2839
 
2840
+ /**
2841
+ * Keyset pagination on ANY orderable column — the general form of
2842
+ * `paginateById`.
2843
+ *
2844
+ * `paginateById` hardcodes `id`, which is correct for a stable typeid/uuidv7
2845
+ * key and useless for the case real feeds actually need: "the next page by
2846
+ * `createdAt`". Apps hit that immediately and fell back to hand-rolled
2847
+ * `limit + 1` / slice / `hasMore` triples — often on a timestamp cursor the
2848
+ * helper could not express at all.
2849
+ *
2850
+ * The column MUST be unique, or monotonic enough that ties do not straddle a
2851
+ * page boundary; otherwise a keyset cursor can skip or repeat rows at the seam.
2852
+ * For a non-unique column (a timestamp with collisions) pass a composite order
2853
+ * on the descriptor first — `.orderBy('createdAt').orderBy('id')` — and
2854
+ * paginate by the tie-breaker.
2855
+ *
2856
+ * `direction` controls both the comparison and the ORDER BY, so a `desc` feed
2857
+ * ("newest first") pages with `<` instead of `>`. Getting that pairing wrong is
2858
+ * the classic keyset bug: an ascending comparison under a descending sort
2859
+ * silently returns the same page forever.
2860
+ */
2861
+ export declare const paginateBy: (descriptor: QueryDescriptor, column: string, cursor: string | number | Date | undefined, limit: number, direction?: "asc" | "desc") => QueryDescriptor;
2862
+
2832
2863
  /**
2833
2864
  * Cursor pagination by primary key — `WHERE id > cursor ORDER BY id ASC LIMIT n`.
2834
2865
  *
@@ -2968,7 +2999,8 @@ export declare interface Query<RowOf, IxNames extends string = string,
2968
2999
  * `'alias.column'` → value type. Unjoined queries default to `{}`.
2969
3000
  */
2970
3001
  Joins extends JoinsMap = {}> {
2971
- readonly descriptor: QueryDescriptor;
3002
+ /** Carries `RowOf` through to `store.query()` — see `QueryDescriptor<R>`. */
3003
+ readonly descriptor: QueryDescriptor<RowOf>;
2972
3004
  /** Refine the predicate. Multiple `.where(...)` calls combine with AND. */
2973
3005
  where: (predicate: Predicate) => Query<RowOf, IxNames, Joins>;
2974
3006
  orderBy: <K extends keyof RowOf & string>(column: K, direction?: 'asc' | 'desc') => Query<RowOf, IxNames, Joins>;
@@ -3338,9 +3370,30 @@ Joins extends JoinsMap = {}> {
3338
3370
  }, IxNames, Joins>;
3339
3371
  }
3340
3372
 
3341
- export declare interface QueryDescriptor {
3373
+ /**
3374
+ * The runtime-facing materialized query — a plain bag of strings the store and
3375
+ * the matcher consume.
3376
+ *
3377
+ * `R` is a PHANTOM row type. It exists so the row shape the typed builder
3378
+ * already knows survives the trip through `.descriptor` into `store.query()`,
3379
+ * which is where it used to be thrown away: `store.query(database.notes...)`
3380
+ * returned `Row` (`Record<string, unknown>`), so every field read needed a
3381
+ * hand-written cast. One downstream app wrote 2,032 of them. The type was
3382
+ * always available — it was just dropped at this boundary.
3383
+ *
3384
+ * It is an OPTIONAL field, never set at runtime. That matters for two reasons:
3385
+ * a plain object literal still satisfies `QueryDescriptor` (dispatcher, matcher
3386
+ * and store code build and forward these structurally), and `R` stays
3387
+ * covariant, so a `QueryDescriptor<Note>` is still assignable to the bare
3388
+ * `QueryDescriptor` that generic infrastructure passes around. Defaulting to
3389
+ * `Row` keeps every existing annotation meaning exactly what it meant before.
3390
+ */
3391
+ export declare interface QueryDescriptor<R = Row> {
3342
3392
  readonly table: string;
3343
3393
  readonly predicate: Predicate | undefined;
3394
+ /** Phantom — carries the row type through to `store.query()`. Never set at
3395
+ * runtime; erased entirely by the compiler. */
3396
+ readonly __row?: R;
3344
3397
  /**
3345
3398
  * Opt OUT of the runtime's automatic `deletedAt IS NULL` scope on
3346
3399
  * `softDelete()` tables. Set by `.withDeleted()`. The query builder is