@cfast/db 0.4.1 → 0.6.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 +47 -794
- package/dist/index.js +147 -65
- package/dist/seed.d.ts +258 -0
- package/dist/seed.js +378 -0
- package/dist/types-FUFR36h1.d.ts +221 -0
- package/llms.txt +212 -44
- package/package.json +11 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,696 +1,7 @@
|
|
|
1
|
-
import { Grant,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Drizzle tables expose `$inferSelect` (the row shape returned by `SELECT *`).
|
|
7
|
-
* `InferRow<typeof posts>` resolves to `{ id: string; title: string; ... }`.
|
|
8
|
-
*
|
|
9
|
-
* Falls back to `Record<string, unknown>` for opaque `DrizzleTable` references
|
|
10
|
-
* (e.g. when callers do not specify a concrete table type generic).
|
|
11
|
-
*
|
|
12
|
-
* @typeParam TTable - The Drizzle table type (e.g. `typeof posts`).
|
|
13
|
-
*/
|
|
14
|
-
type InferRow<TTable> = TTable extends {
|
|
15
|
-
$inferSelect: infer R;
|
|
16
|
-
} ? R : Record<string, unknown>;
|
|
17
|
-
/**
|
|
18
|
-
* A lazy, permission-aware database operation.
|
|
19
|
-
*
|
|
20
|
-
* Every method on {@link Db} returns an `Operation` instead of a promise. The operation
|
|
21
|
-
* exposes its permission requirements via `.permissions` for inspection and executes with
|
|
22
|
-
* full permission checking via `.run()`. This two-phase design enables UI adaptation,
|
|
23
|
-
* upfront composition via {@link compose}, and introspection before any SQL is executed.
|
|
24
|
-
*
|
|
25
|
-
* @typeParam TResult - The type of the result returned by `.run()`.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const op = db.query(posts).findMany();
|
|
30
|
-
*
|
|
31
|
-
* // Inspect permissions without executing
|
|
32
|
-
* console.log(op.permissions);
|
|
33
|
-
* // => [{ action: "read", table: "posts" }]
|
|
34
|
-
*
|
|
35
|
-
* // Execute with permission checks
|
|
36
|
-
* const rows = await op.run();
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
type Operation<TResult> = {
|
|
40
|
-
/** Structural permission requirements. Available immediately without execution. */
|
|
41
|
-
permissions: PermissionDescriptor[];
|
|
42
|
-
/**
|
|
43
|
-
* Checks permissions, applies permission WHERE clauses, executes the query via Drizzle,
|
|
44
|
-
* and returns the result. Throws `ForbiddenError` if the user's role lacks a required grant.
|
|
45
|
-
*
|
|
46
|
-
* @param params - Optional placeholder values for `sql.placeholder()` calls.
|
|
47
|
-
* Omit when your query does not use placeholders — the default is `{}`.
|
|
48
|
-
*/
|
|
49
|
-
run: (params?: Record<string, unknown>) => Promise<TResult>;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Supported cache backend types for {@link CacheConfig}.
|
|
53
|
-
*
|
|
54
|
-
* - `"cache-api"` — Edge-local Cloudflare Cache API (~0ms latency, per-edge-node).
|
|
55
|
-
* - `"kv"` — Global Cloudflare KV (10-50ms latency, eventually consistent).
|
|
56
|
-
*/
|
|
57
|
-
type CacheBackend = "cache-api" | "kv";
|
|
58
|
-
/**
|
|
59
|
-
* Configuration for the database cache layer.
|
|
60
|
-
*
|
|
61
|
-
* Controls how query results are cached and invalidated. Mutations automatically
|
|
62
|
-
* bump table version counters, causing subsequent reads to miss the cache.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```ts
|
|
66
|
-
* const db = createDb({
|
|
67
|
-
* d1: env.DB,
|
|
68
|
-
* schema,
|
|
69
|
-
* grants: resolvedGrants,
|
|
70
|
-
* user: currentUser,
|
|
71
|
-
* cache: {
|
|
72
|
-
* backend: "cache-api",
|
|
73
|
-
* ttl: "30s",
|
|
74
|
-
* staleWhileRevalidate: "5m",
|
|
75
|
-
* exclude: ["sessions"],
|
|
76
|
-
* },
|
|
77
|
-
* });
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
type CacheConfig = {
|
|
81
|
-
/** Which cache backend to use: edge-local Cache API or global KV. */
|
|
82
|
-
backend: CacheBackend;
|
|
83
|
-
/** KV namespace binding. Required when {@link backend} is `"kv"`. */
|
|
84
|
-
kv?: KVNamespace;
|
|
85
|
-
/** Default TTL for cached queries (e.g., `"30s"`, `"5m"`, `"1h"`). Defaults to `"60s"`. */
|
|
86
|
-
ttl?: string;
|
|
87
|
-
/** Stale-while-revalidate window (e.g., `"5m"`). Serves stale data while revalidating in the background. */
|
|
88
|
-
staleWhileRevalidate?: string;
|
|
89
|
-
/** Table names that should never be cached (e.g., `["sessions", "tokens"]`). */
|
|
90
|
-
exclude?: string[];
|
|
91
|
-
/** Observability hook called on cache hits. */
|
|
92
|
-
onHit?: (key: string, table: string) => void;
|
|
93
|
-
/** Observability hook called on cache misses. */
|
|
94
|
-
onMiss?: (key: string, table: string) => void;
|
|
95
|
-
/** Observability hook called when tables are invalidated by mutations. */
|
|
96
|
-
onInvalidate?: (tables: string[]) => void;
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* Per-query cache control options.
|
|
100
|
-
*
|
|
101
|
-
* Pass `false` to skip caching for a specific query, or an options object to
|
|
102
|
-
* override the default {@link CacheConfig} for that query.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* // Skip cache entirely
|
|
107
|
-
* db.query(posts).findMany({ cache: false });
|
|
108
|
-
*
|
|
109
|
-
* // Custom TTL and tags
|
|
110
|
-
* db.query(posts).findMany({ cache: { ttl: "5m", tags: ["user-posts"] } });
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
type QueryCacheOptions = false | {
|
|
114
|
-
/** Override the default TTL for this query (e.g., `"5m"`, `"1h"`). */
|
|
115
|
-
ttl?: string;
|
|
116
|
-
/** Override the default stale-while-revalidate window for this query. */
|
|
117
|
-
staleWhileRevalidate?: string;
|
|
118
|
-
/** Tags for targeted manual invalidation via `db.cache.invalidate({ tags })`. */
|
|
119
|
-
tags?: string[];
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Configuration for {@link createDb}.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* ```ts
|
|
126
|
-
* import { createDb } from "@cfast/db";
|
|
127
|
-
* import * as schema from "./schema";
|
|
128
|
-
*
|
|
129
|
-
* const db = createDb({
|
|
130
|
-
* d1: env.DB,
|
|
131
|
-
* schema,
|
|
132
|
-
* grants: resolvedGrants,
|
|
133
|
-
* user: { id: "user-123" },
|
|
134
|
-
* cache: { backend: "cache-api" },
|
|
135
|
-
* });
|
|
136
|
-
* ```
|
|
137
|
-
*/
|
|
138
|
-
type DbConfig = {
|
|
139
|
-
/** The Cloudflare D1 database binding from `env.DB`. */
|
|
140
|
-
d1: D1Database;
|
|
141
|
-
/**
|
|
142
|
-
* Drizzle schema object. Must be `import * as schema` so that keys match
|
|
143
|
-
* table variable names (required by Drizzle's relational query API).
|
|
144
|
-
*
|
|
145
|
-
* Typed as `Record<string, unknown>` so callers can pass `import * as schema`
|
|
146
|
-
* directly without casting -- Drizzle schemas typically include `Relations`
|
|
147
|
-
* exports alongside tables, and the `@cfast/db` runtime ignores any non-table
|
|
148
|
-
* entries when looking up tables by key.
|
|
149
|
-
*/
|
|
150
|
-
schema: Record<string, unknown>;
|
|
151
|
-
/** Resolved permission grants for the current user's role, from `resolveGrants()`. */
|
|
152
|
-
grants: Grant[];
|
|
153
|
-
/**
|
|
154
|
-
* The current user, or `null` for anonymous access.
|
|
155
|
-
* When `null`, the `"anonymous"` role is used for permission checks.
|
|
156
|
-
*/
|
|
157
|
-
user: {
|
|
158
|
-
id: string;
|
|
159
|
-
} | null;
|
|
160
|
-
/** Cache configuration, or `false` to disable caching entirely. Defaults to `{ backend: "cache-api" }`. */
|
|
161
|
-
cache?: CacheConfig | false;
|
|
162
|
-
};
|
|
163
|
-
/**
|
|
164
|
-
* Options for `db.query(table).findMany()`.
|
|
165
|
-
*
|
|
166
|
-
* The `where` condition is AND'd with any permission-based WHERE clauses
|
|
167
|
-
* resolved from the user's grants.
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```ts
|
|
171
|
-
* import { eq, desc } from "drizzle-orm";
|
|
172
|
-
*
|
|
173
|
-
* db.query(posts).findMany({
|
|
174
|
-
* columns: { id: true, title: true },
|
|
175
|
-
* where: eq(posts.category, "tech"),
|
|
176
|
-
* orderBy: desc(posts.createdAt),
|
|
177
|
-
* limit: 10,
|
|
178
|
-
* offset: 20,
|
|
179
|
-
* with: { comments: true },
|
|
180
|
-
* cache: { ttl: "5m", tags: ["posts"] },
|
|
181
|
-
* });
|
|
182
|
-
* ```
|
|
183
|
-
*/
|
|
184
|
-
type FindManyOptions = {
|
|
185
|
-
/** Column selection (e.g., `{ id: true, title: true }`). Omit to select all columns. */
|
|
186
|
-
columns?: Record<string, boolean>;
|
|
187
|
-
/** User-supplied filter condition (AND'd with permission filters at `.run()` time). */
|
|
188
|
-
where?: unknown;
|
|
189
|
-
/** Ordering expression (e.g., `desc(posts.createdAt)`). */
|
|
190
|
-
orderBy?: unknown;
|
|
191
|
-
/** Maximum number of rows to return. */
|
|
192
|
-
limit?: number;
|
|
193
|
-
/** Number of rows to skip (for offset-based pagination). */
|
|
194
|
-
offset?: number;
|
|
195
|
-
/**
|
|
196
|
-
* Drizzle relational query includes (e.g., `{ comments: true }`).
|
|
197
|
-
*
|
|
198
|
-
* Note: Permission filters are only applied to the root table, not to joined relations.
|
|
199
|
-
*/
|
|
200
|
-
with?: Record<string, unknown>;
|
|
201
|
-
/** Per-query cache control. Pass `false` to skip caching, or an object to customize. */
|
|
202
|
-
cache?: QueryCacheOptions;
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
|
-
* Options for `db.query(table).findFirst()`.
|
|
206
|
-
*
|
|
207
|
-
* Same as {@link FindManyOptions} without `limit`/`offset` (returns the first match or `undefined`).
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```ts
|
|
211
|
-
* db.query(posts).findFirst({
|
|
212
|
-
* where: eq(posts.id, "abc-123"),
|
|
213
|
-
* });
|
|
214
|
-
* ```
|
|
215
|
-
*/
|
|
216
|
-
type FindFirstOptions = Omit<FindManyOptions, "limit" | "offset">;
|
|
217
|
-
/**
|
|
218
|
-
* Parsed cursor-based pagination parameters from a request URL.
|
|
219
|
-
*
|
|
220
|
-
* Produced by {@link parseCursorParams}. Pass to `db.query(table).paginate()` for
|
|
221
|
-
* keyset pagination that avoids the offset performance cliff on large datasets.
|
|
222
|
-
*
|
|
223
|
-
* @example
|
|
224
|
-
* ```ts
|
|
225
|
-
* const params = parseCursorParams(request, { defaultLimit: 20 });
|
|
226
|
-
* const page = await db.query(posts).paginate(params).run();
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
type CursorParams = {
|
|
230
|
-
/** Discriminant for cursor-based pagination. Always `"cursor"`. */
|
|
231
|
-
type: "cursor";
|
|
232
|
-
/** The opaque cursor string from the previous page, or `null` for the first page. */
|
|
233
|
-
cursor: string | null;
|
|
234
|
-
/** Maximum items per page (clamped between 1 and `maxLimit`). */
|
|
235
|
-
limit: number;
|
|
236
|
-
};
|
|
237
|
-
/**
|
|
238
|
-
* Parsed offset-based pagination parameters from a request URL.
|
|
239
|
-
*
|
|
240
|
-
* Produced by {@link parseOffsetParams}. Pass to `db.query(table).paginate()` for
|
|
241
|
-
* traditional page-number-based pagination with total counts.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* ```ts
|
|
245
|
-
* const params = parseOffsetParams(request, { defaultLimit: 20 });
|
|
246
|
-
* const page = await db.query(posts).paginate(params).run();
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
type OffsetParams = {
|
|
250
|
-
/** Discriminant for offset-based pagination. Always `"offset"`. */
|
|
251
|
-
type: "offset";
|
|
252
|
-
/** The 1-based page number. */
|
|
253
|
-
page: number;
|
|
254
|
-
/** Maximum items per page (clamped between 1 and `maxLimit`). */
|
|
255
|
-
limit: number;
|
|
256
|
-
};
|
|
257
|
-
/**
|
|
258
|
-
* Union of cursor and offset pagination parameters.
|
|
259
|
-
*
|
|
260
|
-
* Use the `type` discriminant to determine which pagination strategy is in use.
|
|
261
|
-
* Accepted by `db.query(table).paginate()`.
|
|
262
|
-
*/
|
|
263
|
-
type PaginateParams = CursorParams | OffsetParams;
|
|
264
|
-
/**
|
|
265
|
-
* A page of results from cursor-based pagination.
|
|
266
|
-
*
|
|
267
|
-
* Use `nextCursor` to fetch the next page. When `nextCursor` is `null`, there are no more pages.
|
|
268
|
-
*
|
|
269
|
-
* @typeParam T - The row type.
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* ```ts
|
|
273
|
-
* const page: CursorPage<Post> = await db.query(posts)
|
|
274
|
-
* .paginate({ type: "cursor", cursor: null, limit: 20 })
|
|
275
|
-
* .run();
|
|
276
|
-
*
|
|
277
|
-
* if (page.nextCursor) {
|
|
278
|
-
* // Fetch next page with page.nextCursor
|
|
279
|
-
* }
|
|
280
|
-
* ```
|
|
281
|
-
*/
|
|
282
|
-
type CursorPage<T> = {
|
|
283
|
-
/** The items on this page. */
|
|
284
|
-
items: T[];
|
|
285
|
-
/** Opaque cursor for the next page, or `null` if this is the last page. */
|
|
286
|
-
nextCursor: string | null;
|
|
287
|
-
};
|
|
288
|
-
/**
|
|
289
|
-
* A page of results from offset-based pagination.
|
|
290
|
-
*
|
|
291
|
-
* Includes total counts for rendering page navigation controls.
|
|
292
|
-
*
|
|
293
|
-
* @typeParam T - The row type.
|
|
294
|
-
*
|
|
295
|
-
* @example
|
|
296
|
-
* ```ts
|
|
297
|
-
* const page: OffsetPage<Post> = await db.query(posts)
|
|
298
|
-
* .paginate({ type: "offset", page: 1, limit: 20 })
|
|
299
|
-
* .run();
|
|
300
|
-
*
|
|
301
|
-
* console.log(`Page ${page.page} of ${page.totalPages} (${page.total} total)`);
|
|
302
|
-
* ```
|
|
303
|
-
*/
|
|
304
|
-
type OffsetPage<T> = {
|
|
305
|
-
/** The items on this page. */
|
|
306
|
-
items: T[];
|
|
307
|
-
/** Total number of matching rows across all pages. */
|
|
308
|
-
total: number;
|
|
309
|
-
/** The current 1-based page number. */
|
|
310
|
-
page: number;
|
|
311
|
-
/** Total number of pages (computed as `Math.ceil(total / limit)`). */
|
|
312
|
-
totalPages: number;
|
|
313
|
-
};
|
|
314
|
-
/**
|
|
315
|
-
* Options for `db.query(table).paginate()`.
|
|
316
|
-
*
|
|
317
|
-
* Combines query filtering with pagination-specific settings. The actual pagination
|
|
318
|
-
* strategy (cursor vs. offset) is determined by the {@link PaginateParams} passed
|
|
319
|
-
* alongside these options.
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* db.query(posts).paginate(params, {
|
|
324
|
-
* where: eq(posts.published, true),
|
|
325
|
-
* orderBy: desc(posts.createdAt),
|
|
326
|
-
* cursorColumns: [posts.createdAt, posts.id],
|
|
327
|
-
* orderDirection: "desc",
|
|
328
|
-
* });
|
|
329
|
-
* ```
|
|
330
|
-
*/
|
|
331
|
-
type PaginateOptions = {
|
|
332
|
-
/** Column selection (e.g., `{ id: true, title: true }`). Omit to select all columns. */
|
|
333
|
-
columns?: Record<string, boolean>;
|
|
334
|
-
/** User-supplied filter condition (AND'd with permission filters at `.run()` time). */
|
|
335
|
-
where?: unknown;
|
|
336
|
-
/** Ordering expression for offset pagination. Ignored for cursor pagination (uses `cursorColumns` instead). */
|
|
337
|
-
orderBy?: unknown;
|
|
338
|
-
/** Drizzle column references used for cursor-based ordering and comparison. */
|
|
339
|
-
cursorColumns?: unknown[];
|
|
340
|
-
/** Sort direction for cursor pagination. Defaults to `"desc"`. */
|
|
341
|
-
orderDirection?: "asc" | "desc";
|
|
342
|
-
/**
|
|
343
|
-
* Drizzle relational query includes (e.g., `{ comments: true }`).
|
|
344
|
-
*
|
|
345
|
-
* Note: Permission filters are only applied to the root table, not to joined relations.
|
|
346
|
-
*/
|
|
347
|
-
with?: Record<string, unknown>;
|
|
348
|
-
/** Per-query cache control. Pass `false` to skip caching, or an object to customize. */
|
|
349
|
-
cache?: QueryCacheOptions;
|
|
350
|
-
};
|
|
351
|
-
/**
|
|
352
|
-
* The transaction handle passed to the `db.transaction()` callback.
|
|
353
|
-
*
|
|
354
|
-
* Exposes the read/write builder surface of {@link Db} but intentionally omits
|
|
355
|
-
* `unsafe`, `batch`, `transaction`, and `cache`. Those are off-limits inside a
|
|
356
|
-
* transaction:
|
|
357
|
-
*
|
|
358
|
-
* - `unsafe()` would let you bypass permissions mid-tx — if you need system
|
|
359
|
-
* access, use `db.unsafe().transaction(...)` on the outer handle instead.
|
|
360
|
-
* - `batch()` / nested `transaction()` calls are flattened into the parent's
|
|
361
|
-
* pending queue automatically, so the plain mutate methods are all you need.
|
|
362
|
-
* - `cache` invalidation is driven by the single commit at the end of the
|
|
363
|
-
* transaction, not per-sub-op.
|
|
364
|
-
*
|
|
365
|
-
* Writes (`tx.insert` / `tx.update` / `tx.delete`) are recorded and flushed as
|
|
366
|
-
* an atomic batch when the callback returns. Reads (`tx.query`) execute
|
|
367
|
-
* eagerly so the caller can branch on their results.
|
|
368
|
-
*/
|
|
369
|
-
type Tx = {
|
|
370
|
-
/** Same as {@link Db.query}: reads execute eagerly against the underlying db. */
|
|
371
|
-
query: <TTable extends DrizzleTable>(table: TTable) => QueryBuilder<TTable>;
|
|
372
|
-
/**
|
|
373
|
-
* Same as {@link Db.insert}: the returned Operation's `.run()` records the
|
|
374
|
-
* insert into the transaction's pending queue. `.returning().run()` records
|
|
375
|
-
* the insert and returns a promise that resolves AFTER the batch flushes.
|
|
376
|
-
*/
|
|
377
|
-
insert: <TTable extends DrizzleTable>(table: TTable) => InsertBuilder<TTable>;
|
|
378
|
-
/**
|
|
379
|
-
* Same as {@link Db.update}: the returned Operation's `.run()` records the
|
|
380
|
-
* update into the transaction's pending queue. Use relative SQL with a
|
|
381
|
-
* WHERE guard (e.g. `set({ stock: sql\`stock - ${qty}\` }).where(gte(...))`)
|
|
382
|
-
* for concurrency-safe read-modify-write.
|
|
383
|
-
*/
|
|
384
|
-
update: <TTable extends DrizzleTable>(table: TTable) => UpdateBuilder<TTable>;
|
|
385
|
-
/** Same as {@link Db.delete}: records the delete into the transaction's pending queue. */
|
|
386
|
-
delete: <TTable extends DrizzleTable>(table: TTable) => DeleteBuilder<TTable>;
|
|
387
|
-
/**
|
|
388
|
-
* Nested transaction. Flattens into the parent's pending queue so every
|
|
389
|
-
* write still commits in a single atomic batch. The nested callback's
|
|
390
|
-
* return value is propagated as usual; any error thrown aborts the
|
|
391
|
-
* entire outer transaction.
|
|
392
|
-
*
|
|
393
|
-
* Use this when a helper function wants to "own" its own tx scope but
|
|
394
|
-
* the caller already started a transaction — the helper can call
|
|
395
|
-
* `tx.transaction(...)` unconditionally and Just Work.
|
|
396
|
-
*/
|
|
397
|
-
transaction: <T>(callback: (tx: Tx) => Promise<T>) => Promise<T>;
|
|
398
|
-
};
|
|
399
|
-
/**
|
|
400
|
-
* A permission-aware database instance bound to a specific user.
|
|
401
|
-
*
|
|
402
|
-
* Created by {@link createDb}. All query and mutation methods return lazy {@link Operation}
|
|
403
|
-
* objects that check permissions at `.run()` time. Create a new instance per request --
|
|
404
|
-
* sharing across requests would apply one user's permissions to another's queries.
|
|
405
|
-
*
|
|
406
|
-
* @example
|
|
407
|
-
* ```ts
|
|
408
|
-
* // Read
|
|
409
|
-
* const posts = await db.query(postsTable).findMany().run();
|
|
410
|
-
*
|
|
411
|
-
* // Write
|
|
412
|
-
* await db.insert(postsTable).values({ title: "Hello" }).run();
|
|
413
|
-
*
|
|
414
|
-
* // Bypass permissions for system tasks
|
|
415
|
-
* await db.unsafe().delete(sessionsTable).where(expired).run();
|
|
416
|
-
* ```
|
|
417
|
-
*/
|
|
418
|
-
type Db = {
|
|
419
|
-
/**
|
|
420
|
-
* Creates a {@link QueryBuilder} for reading rows from the given table.
|
|
421
|
-
*
|
|
422
|
-
* The builder is generic over `TTable`, so `findMany`/`findFirst` return rows
|
|
423
|
-
* typed via `InferRow<TTable>` -- callers don't need to cast to `as any`.
|
|
424
|
-
*/
|
|
425
|
-
query: <TTable extends DrizzleTable>(table: TTable) => QueryBuilder<TTable>;
|
|
426
|
-
/** Creates an {@link InsertBuilder} for inserting rows into the given table. */
|
|
427
|
-
insert: <TTable extends DrizzleTable>(table: TTable) => InsertBuilder<TTable>;
|
|
428
|
-
/** Creates an {@link UpdateBuilder} for updating rows in the given table. */
|
|
429
|
-
update: <TTable extends DrizzleTable>(table: TTable) => UpdateBuilder<TTable>;
|
|
430
|
-
/** Creates a {@link DeleteBuilder} for deleting rows from the given table. */
|
|
431
|
-
delete: <TTable extends DrizzleTable>(table: TTable) => DeleteBuilder<TTable>;
|
|
432
|
-
/**
|
|
433
|
-
* Returns a new `Db` instance that skips all permission checks.
|
|
434
|
-
*
|
|
435
|
-
* Use for cron jobs, migrations, and system operations without an authenticated user.
|
|
436
|
-
* Every call site is greppable via `git grep '.unsafe()'`.
|
|
437
|
-
*/
|
|
438
|
-
unsafe: () => Db;
|
|
439
|
-
/**
|
|
440
|
-
* Groups multiple operations into a single {@link Operation} with merged, deduplicated permissions.
|
|
441
|
-
*
|
|
442
|
-
* When every operation was produced by `db.insert/update/delete`, the batch is
|
|
443
|
-
* executed via D1's native `batch()` API, which is **atomic** -- if any
|
|
444
|
-
* statement fails, the entire batch is rolled back. This is the recommended
|
|
445
|
-
* way to perform multi-step mutations that need transactional safety, such as
|
|
446
|
-
* decrementing stock across multiple products during checkout.
|
|
447
|
-
*
|
|
448
|
-
* Permissions for every sub-operation are checked **upfront**: if the user
|
|
449
|
-
* lacks any required grant, the batch throws before any SQL is issued.
|
|
450
|
-
*
|
|
451
|
-
* Operations that don't carry the internal batchable hook (for example, ops
|
|
452
|
-
* produced by `compose()` executors) cause the batch to fall back to
|
|
453
|
-
* sequential execution. This preserves backward compatibility for non-trivial
|
|
454
|
-
* compositions but loses the atomicity guarantee.
|
|
455
|
-
*/
|
|
456
|
-
batch: (operations: Operation<unknown>[]) => Operation<unknown[]>;
|
|
457
|
-
/**
|
|
458
|
-
* Runs a callback inside a transaction with atomic commit-or-rollback semantics.
|
|
459
|
-
*
|
|
460
|
-
* All writes (`tx.insert`/`tx.update`/`tx.delete`) recorded inside the callback
|
|
461
|
-
* are deferred and flushed together as a single atomic `db.batch([...])` when
|
|
462
|
-
* the callback returns successfully. If the callback throws, pending writes are
|
|
463
|
-
* discarded and the error is re-thrown — nothing reaches D1.
|
|
464
|
-
*
|
|
465
|
-
* Reads (`tx.query(...)`) execute eagerly so the caller can branch on their
|
|
466
|
-
* results. **D1 does not provide snapshot isolation across async code**, so
|
|
467
|
-
* reads inside a transaction can see concurrent writes. For concurrency-safe
|
|
468
|
-
* read-modify-write (e.g. stock decrement), combine the transaction with a
|
|
469
|
-
* relative SQL update and a WHERE guard:
|
|
470
|
-
*
|
|
471
|
-
* ```ts
|
|
472
|
-
* await db.transaction(async (tx) => {
|
|
473
|
-
* // The WHERE guard ensures the decrement only applies when stock is
|
|
474
|
-
* // still >= qty at commit time. Two concurrent transactions cannot
|
|
475
|
-
* // both oversell because D1's atomic batch re-evaluates the WHERE.
|
|
476
|
-
* await tx.update(products)
|
|
477
|
-
* .set({ stock: sql`stock - ${qty}` })
|
|
478
|
-
* .where(and(eq(products.id, pid), gte(products.stock, qty)))
|
|
479
|
-
* .run();
|
|
480
|
-
* return tx.insert(orders).values({ productId: pid, qty }).returning().run();
|
|
481
|
-
* });
|
|
482
|
-
* ```
|
|
483
|
-
*
|
|
484
|
-
* Nested `db.transaction()` calls inside the callback are flattened into the
|
|
485
|
-
* parent's pending queue so everything still commits atomically.
|
|
486
|
-
*
|
|
487
|
-
* @typeParam T - The return type of the callback.
|
|
488
|
-
* @param callback - The transaction body. Receives a `tx` handle with
|
|
489
|
-
* `query`/`insert`/`update`/`delete` methods (no `unsafe`, `batch`, or
|
|
490
|
-
* `cache` — those are intentionally off-limits inside a transaction).
|
|
491
|
-
* @returns The value returned by the callback, or rejects with whatever the
|
|
492
|
-
* callback threw (after rolling back pending writes).
|
|
493
|
-
*/
|
|
494
|
-
transaction: <T>(callback: (tx: Tx) => Promise<T>) => Promise<T>;
|
|
495
|
-
/** Cache control methods for manual invalidation. */
|
|
496
|
-
cache: {
|
|
497
|
-
/** Invalidate cached queries by tag names and/or table names. */
|
|
498
|
-
invalidate: (options: {
|
|
499
|
-
/** Tag names to invalidate (from {@link QueryCacheOptions} `tags`). */
|
|
500
|
-
tags?: string[];
|
|
501
|
-
/** Table names to invalidate (bumps their version counters). */
|
|
502
|
-
tables?: string[];
|
|
503
|
-
}) => Promise<void>;
|
|
504
|
-
};
|
|
505
|
-
/**
|
|
506
|
-
* Clears the per-instance `with` lookup cache so that the next query
|
|
507
|
-
* re-runs every grant lookup function.
|
|
508
|
-
*
|
|
509
|
-
* In production this is rarely needed because each request gets a fresh
|
|
510
|
-
* `Db` via `createDb()`. In tests that reuse a single `Db` across grant
|
|
511
|
-
* mutations (e.g. inserting a new friendship and then querying recipes),
|
|
512
|
-
* call this after the mutation to avoid stale lookup results.
|
|
513
|
-
*
|
|
514
|
-
* For finer-grained control, wrap each logical request in
|
|
515
|
-
* {@link runWithLookupCache} instead -- that scopes the cache via
|
|
516
|
-
* `AsyncLocalStorage` so it is automatically discarded at scope exit.
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* ```ts
|
|
520
|
-
* const db = createDb({ ... });
|
|
521
|
-
* await db.query(recipes).findMany().run(); // populates lookup cache
|
|
522
|
-
* await db.insert(friendGrants).values({ ... }).run(); // adds new grant
|
|
523
|
-
* db.clearLookupCache(); // drop stale lookups
|
|
524
|
-
* await db.query(recipes).findMany().run(); // sees new grant
|
|
525
|
-
* ```
|
|
526
|
-
*/
|
|
527
|
-
clearLookupCache: () => void;
|
|
528
|
-
};
|
|
529
|
-
/**
|
|
530
|
-
* Builder for read queries on a single table.
|
|
531
|
-
*
|
|
532
|
-
* Returned by `db.query(table)`. Provides `findMany`, `findFirst`, and `paginate` methods
|
|
533
|
-
* that each return an {@link Operation} with permission-aware execution.
|
|
534
|
-
*
|
|
535
|
-
* @example
|
|
536
|
-
* ```ts
|
|
537
|
-
* const builder = db.query(posts);
|
|
538
|
-
*
|
|
539
|
-
* // Fetch all visible posts
|
|
540
|
-
* const all = await builder.findMany().run();
|
|
541
|
-
*
|
|
542
|
-
* // Fetch a single post
|
|
543
|
-
* const post = await builder.findFirst({ where: eq(posts.id, id) }).run();
|
|
544
|
-
*
|
|
545
|
-
* // Paginate
|
|
546
|
-
* const page = await builder.paginate(params, { orderBy: desc(posts.createdAt) }).run();
|
|
547
|
-
* ```
|
|
548
|
-
*/
|
|
549
|
-
type QueryBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
550
|
-
/**
|
|
551
|
-
* Returns an {@link Operation} that fetches multiple rows matching the given options.
|
|
552
|
-
*
|
|
553
|
-
* The result is typed via `InferRow<TTable>`, so callers get IntelliSense on
|
|
554
|
-
* `(row) => row.title` without any cast.
|
|
555
|
-
*
|
|
556
|
-
* **Relations escape hatch (#158):** when you pass `with: { relation: true }`,
|
|
557
|
-
* Drizzle's relational query builder embeds the joined rows into the result,
|
|
558
|
-
* but `@cfast/db` cannot statically infer that shape from the
|
|
559
|
-
* `Record<string, unknown>` schema we accept here. Override the row type via
|
|
560
|
-
* the optional `TRow` generic to claim the shape you know the query will
|
|
561
|
-
* produce, instead of having to `as any` the result downstream:
|
|
562
|
-
*
|
|
563
|
-
* ```ts
|
|
564
|
-
* type RecipeWithIngredients = Recipe & { ingredients: Ingredient[] };
|
|
565
|
-
* const recipes = await db
|
|
566
|
-
* .query(recipesTable)
|
|
567
|
-
* .findMany<RecipeWithIngredients>({ with: { ingredients: true } })
|
|
568
|
-
* .run();
|
|
569
|
-
* // recipes is RecipeWithIngredients[], no cast needed
|
|
570
|
-
* ```
|
|
571
|
-
*/
|
|
572
|
-
findMany: <TRow = InferRow<TTable>>(options?: FindManyOptions) => Operation<TRow[]>;
|
|
573
|
-
/**
|
|
574
|
-
* Returns an {@link Operation} that fetches the first matching row, or `undefined`
|
|
575
|
-
* if none match. The row type is propagated from `TTable`.
|
|
576
|
-
*
|
|
577
|
-
* Same `TRow` generic escape hatch as {@link findMany} for `with`-relation
|
|
578
|
-
* shapes that the framework can't infer from the runtime-typed schema.
|
|
579
|
-
*
|
|
580
|
-
* ```ts
|
|
581
|
-
* type RecipeWithIngredients = Recipe & { ingredients: Ingredient[] };
|
|
582
|
-
* const recipe = await db
|
|
583
|
-
* .query(recipesTable)
|
|
584
|
-
* .findFirst<RecipeWithIngredients>({ where: eq(recipes.id, id), with: { ingredients: true } })
|
|
585
|
-
* .run();
|
|
586
|
-
* // recipe is RecipeWithIngredients | undefined
|
|
587
|
-
* ```
|
|
588
|
-
*/
|
|
589
|
-
findFirst: <TRow = InferRow<TTable>>(options?: FindFirstOptions) => Operation<TRow | undefined>;
|
|
590
|
-
/**
|
|
591
|
-
* Returns a paginated {@link Operation} using either cursor-based or offset-based strategy.
|
|
592
|
-
*
|
|
593
|
-
* The return type depends on the `params.type` discriminant: {@link CursorPage} for `"cursor"`,
|
|
594
|
-
* {@link OffsetPage} for `"offset"`. Each page's items are typed as `InferRow<TTable>` by
|
|
595
|
-
* default; pass an explicit `TRow` to claim a wider shape (e.g. when using
|
|
596
|
-
* `with: { ... }` to embed related rows).
|
|
597
|
-
*/
|
|
598
|
-
paginate: <TRow = InferRow<TTable>>(params: CursorParams | OffsetParams, options?: PaginateOptions) => Operation<CursorPage<TRow>> | Operation<OffsetPage<TRow>>;
|
|
599
|
-
};
|
|
600
|
-
/**
|
|
601
|
-
* Builder for insert operations on a single table.
|
|
602
|
-
*
|
|
603
|
-
* Returned by `db.insert(table)`. Chain `.values()` to set the row data,
|
|
604
|
-
* then optionally `.returning()` to get the inserted row back.
|
|
605
|
-
*
|
|
606
|
-
* @example
|
|
607
|
-
* ```ts
|
|
608
|
-
* // Insert without returning
|
|
609
|
-
* await db.insert(posts).values({ title: "Hello", authorId: user.id }).run();
|
|
610
|
-
*
|
|
611
|
-
* // Insert with returning
|
|
612
|
-
* const row = await db.insert(posts)
|
|
613
|
-
* .values({ title: "Hello", authorId: user.id })
|
|
614
|
-
* .returning()
|
|
615
|
-
* .run();
|
|
616
|
-
* ```
|
|
617
|
-
*/
|
|
618
|
-
type InsertBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
619
|
-
/** Specifies the column values to insert, returning an {@link InsertReturningBuilder}. */
|
|
620
|
-
values: (values: Record<string, unknown>) => InsertReturningBuilder<TTable>;
|
|
621
|
-
};
|
|
622
|
-
/**
|
|
623
|
-
* An insert {@link Operation} that optionally returns the inserted row via `.returning()`.
|
|
624
|
-
*
|
|
625
|
-
* Without `.returning()`, the operation resolves to `void`. With `.returning()`,
|
|
626
|
-
* it resolves to the full inserted row, typed as `InferRow<TTable>`.
|
|
627
|
-
*/
|
|
628
|
-
type InsertReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
629
|
-
/** Chains `.returning()` to get the inserted row back from D1. */
|
|
630
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
631
|
-
};
|
|
632
|
-
/**
|
|
633
|
-
* Builder for update operations on a single table.
|
|
634
|
-
*
|
|
635
|
-
* Returned by `db.update(table)`. Chain `.set()` to specify values, then `.where()`
|
|
636
|
-
* to add a condition, and optionally `.returning()` to get the updated row back.
|
|
637
|
-
*
|
|
638
|
-
* @example
|
|
639
|
-
* ```ts
|
|
640
|
-
* await db.update(posts)
|
|
641
|
-
* .set({ published: true })
|
|
642
|
-
* .where(eq(posts.id, "abc-123"))
|
|
643
|
-
* .run();
|
|
644
|
-
* ```
|
|
645
|
-
*/
|
|
646
|
-
type UpdateBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
647
|
-
/** Specifies the column values to update, returning an {@link UpdateWhereBuilder}. */
|
|
648
|
-
set: (values: Record<string, unknown>) => UpdateWhereBuilder<TTable>;
|
|
649
|
-
};
|
|
650
|
-
/**
|
|
651
|
-
* Intermediate builder requiring a WHERE condition before the update can execute.
|
|
652
|
-
*
|
|
653
|
-
* The WHERE condition is AND'd with any permission-based WHERE clauses from the user's grants.
|
|
654
|
-
*/
|
|
655
|
-
type UpdateWhereBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
656
|
-
/** Specifies the WHERE condition (AND'd with permission filters at `.run()` time). */
|
|
657
|
-
where: (condition: unknown) => UpdateReturningBuilder<TTable>;
|
|
658
|
-
};
|
|
659
|
-
/**
|
|
660
|
-
* An update {@link Operation} that optionally returns the updated row via `.returning()`.
|
|
661
|
-
*
|
|
662
|
-
* Without `.returning()`, the operation resolves to `void`. With `.returning()`,
|
|
663
|
-
* it resolves to the full updated row, typed as `InferRow<TTable>`.
|
|
664
|
-
*/
|
|
665
|
-
type UpdateReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
666
|
-
/** Chains `.returning()` to get the updated row back from D1. */
|
|
667
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
668
|
-
};
|
|
669
|
-
/**
|
|
670
|
-
* Builder for delete operations on a single table.
|
|
671
|
-
*
|
|
672
|
-
* Returned by `db.delete(table)`. Chain `.where()` to add a condition,
|
|
673
|
-
* and optionally `.returning()` to get the deleted row back.
|
|
674
|
-
*
|
|
675
|
-
* @example
|
|
676
|
-
* ```ts
|
|
677
|
-
* await db.delete(posts).where(eq(posts.id, "abc-123")).run();
|
|
678
|
-
* ```
|
|
679
|
-
*/
|
|
680
|
-
type DeleteBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
681
|
-
/** Specifies the WHERE condition (AND'd with permission filters at `.run()` time). */
|
|
682
|
-
where: (condition: unknown) => DeleteReturningBuilder<TTable>;
|
|
683
|
-
};
|
|
684
|
-
/**
|
|
685
|
-
* A delete {@link Operation} that optionally returns the deleted row via `.returning()`.
|
|
686
|
-
*
|
|
687
|
-
* Without `.returning()`, the operation resolves to `void`. With `.returning()`,
|
|
688
|
-
* it resolves to the full deleted row, typed as `InferRow<TTable>`.
|
|
689
|
-
*/
|
|
690
|
-
type DeleteReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
691
|
-
/** Chains `.returning()` to get the deleted row back from D1. */
|
|
692
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
693
|
-
};
|
|
1
|
+
import { Grant, PermissionDescriptor } from '@cfast/permissions';
|
|
2
|
+
import { D as DbConfig, a as Db, O as Operation, C as CursorParams, b as OffsetParams } from './types-FUFR36h1.js';
|
|
3
|
+
export { c as CacheBackend, d as CacheConfig, e as CursorPage, f as DeleteBuilder, g as DeleteReturningBuilder, F as FindFirstOptions, h as FindManyOptions, I as InferQueryResult, i as InferRow, j as InsertBuilder, k as InsertReturningBuilder, l as OffsetPage, P as PaginateOptions, m as PaginateParams, Q as QueryBuilder, n as QueryCacheOptions, T as TransactionResult, o as Tx, U as UpdateBuilder, p as UpdateReturningBuilder, q as UpdateWhereBuilder, W as WithCan } from './types-FUFR36h1.js';
|
|
4
|
+
import 'drizzle-orm';
|
|
694
5
|
|
|
695
6
|
/**
|
|
696
7
|
* Creates a permission-aware database instance bound to the given user.
|
|
@@ -719,7 +30,7 @@ type DeleteReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operat
|
|
|
719
30
|
* const posts = await db.query(postsTable).findMany().run();
|
|
720
31
|
* ```
|
|
721
32
|
*/
|
|
722
|
-
declare function createDb(config: DbConfig): Db
|
|
33
|
+
declare function createDb<TSchema extends Record<string, unknown>>(config: DbConfig<TSchema>): Db<TSchema>;
|
|
723
34
|
/**
|
|
724
35
|
* App-level db factory configuration. The "constants" — D1 binding,
|
|
725
36
|
* Drizzle schema, cache config — that don't change between requests.
|
|
@@ -727,7 +38,7 @@ declare function createDb(config: DbConfig): Db;
|
|
|
727
38
|
* Returned by {@link createAppDb}, called per-request with the user's
|
|
728
39
|
* resolved grants and identity to produce a fresh permission-aware {@link Db}.
|
|
729
40
|
*/
|
|
730
|
-
type AppDbConfig = {
|
|
41
|
+
type AppDbConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> = {
|
|
731
42
|
/**
|
|
732
43
|
* The Cloudflare D1 database binding. Pass either the binding directly
|
|
733
44
|
* (for tests, where the mock D1 is in scope at module load) or a
|
|
@@ -743,7 +54,7 @@ type AppDbConfig = {
|
|
|
743
54
|
* Drizzle schema. Same shape as {@link DbConfig.schema} -- pass
|
|
744
55
|
* `import * as schema from "./schema"`.
|
|
745
56
|
*/
|
|
746
|
-
schema:
|
|
57
|
+
schema: TSchema;
|
|
747
58
|
/**
|
|
748
59
|
* Cache configuration shared across every per-request `Db`. Defaults to
|
|
749
60
|
* `{ backend: "cache-api" }` to match `createDb`. Pass `false` to opt out.
|
|
@@ -759,9 +70,9 @@ type AppDbConfig = {
|
|
|
759
70
|
* `@cfast/core`'s `dbPlugin`, route loaders) can pass the factory around
|
|
760
71
|
* without re-deriving its signature.
|
|
761
72
|
*/
|
|
762
|
-
type AppDbFactory = (grants: Grant[], user: {
|
|
73
|
+
type AppDbFactory<TSchema extends Record<string, unknown> = Record<string, never>> = (grants: Grant[], user: {
|
|
763
74
|
id: string;
|
|
764
|
-
} | null) => Db
|
|
75
|
+
} | null) => Db<TSchema>;
|
|
765
76
|
/**
|
|
766
77
|
* Consolidates the three near-identical `createDb` factories that every
|
|
767
78
|
* cfast app used to define (#149):
|
|
@@ -811,7 +122,7 @@ type AppDbFactory = (grants: Grant[], user: {
|
|
|
811
122
|
* };
|
|
812
123
|
* ```
|
|
813
124
|
*/
|
|
814
|
-
declare function createAppDb(config: AppDbConfig): AppDbFactory
|
|
125
|
+
declare function createAppDb<TSchema extends Record<string, unknown>>(config: AppDbConfig<TSchema>): AppDbFactory<TSchema>;
|
|
815
126
|
|
|
816
127
|
/**
|
|
817
128
|
* A function that executes a single sub-operation within a {@link compose} executor.
|
|
@@ -1022,115 +333,57 @@ declare function parseCursorParams(request: Request, options?: PaginationOptions
|
|
|
1022
333
|
*/
|
|
1023
334
|
declare function parseOffsetParams(request: Request, options?: PaginationOptions): OffsetParams;
|
|
1024
335
|
|
|
1025
|
-
/**
|
|
1026
|
-
* Error thrown when a transaction is misused (e.g. an op is recorded after the
|
|
1027
|
-
* transaction has already committed or aborted).
|
|
1028
|
-
*/
|
|
1029
336
|
declare class TransactionError extends Error {
|
|
1030
337
|
constructor(message: string);
|
|
1031
338
|
}
|
|
1032
339
|
|
|
1033
340
|
/**
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
* are caught by `tsc` instead of failing at runtime when `INSERT` rejects
|
|
1037
|
-
* the statement.
|
|
341
|
+
* Recursively converts Date fields in a value to ISO 8601 strings for
|
|
342
|
+
* JSON serialization.
|
|
1038
343
|
*
|
|
1039
|
-
*
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
* so the helper can infer row types and forward the reference to
|
|
1046
|
-
* `db.insert()`.
|
|
1047
|
-
*/
|
|
1048
|
-
table: TTable;
|
|
1049
|
-
/**
|
|
1050
|
-
* Rows to insert. The row shape is inferred from the table's
|
|
1051
|
-
* `$inferSelect` — making a typo in a column name is a compile-time error.
|
|
1052
|
-
*
|
|
1053
|
-
* Entries are inserted in the order they appear, which lets you control
|
|
1054
|
-
* foreign-key ordering just by ordering your `entries` array
|
|
1055
|
-
* (`{ users }` before `{ posts }`, etc.).
|
|
1056
|
-
*/
|
|
1057
|
-
rows: readonly InferRow<TTable>[];
|
|
1058
|
-
};
|
|
1059
|
-
/**
|
|
1060
|
-
* Configuration passed to {@link defineSeed}.
|
|
1061
|
-
*/
|
|
1062
|
-
type SeedConfig = {
|
|
1063
|
-
/**
|
|
1064
|
-
* Ordered list of seed entries. Each entry is flushed as a batched insert
|
|
1065
|
-
* in list order, so place parent tables (users, orgs) before child tables
|
|
1066
|
-
* (posts, memberships) that reference them via foreign keys.
|
|
1067
|
-
*/
|
|
1068
|
-
entries: readonly SeedEntry[];
|
|
1069
|
-
};
|
|
1070
|
-
/**
|
|
1071
|
-
* The compiled seed returned by {@link defineSeed}.
|
|
344
|
+
* React Router loaders must return JSON-serializable data. Date objects
|
|
345
|
+
* are not JSON-serializable by default -- `JSON.stringify(new Date())`
|
|
346
|
+
* calls `Date.prototype.toJSON()` which produces an ISO string, but the
|
|
347
|
+
* resulting value on the client is a string, not a Date. This helper
|
|
348
|
+
* makes the conversion explicit and type-safe so every loader doesn't
|
|
349
|
+
* need to manually call `.toISOString()` on every date field.
|
|
1072
350
|
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
/** The frozen list of entries this seed will insert, in order. */
|
|
1079
|
-
readonly entries: readonly SeedEntry[];
|
|
1080
|
-
/**
|
|
1081
|
-
* Executes every entry against the given {@link Db} in the order they were
|
|
1082
|
-
* declared. Uses `db.unsafe()` internally so seed scripts don't need
|
|
1083
|
-
* their own grants plumbing — seeding is a system task by definition.
|
|
1084
|
-
*
|
|
1085
|
-
* Entries with an empty `rows` array are skipped so callers can leave
|
|
1086
|
-
* placeholder entries in the config without crashing the seed.
|
|
1087
|
-
*
|
|
1088
|
-
* @param db - A {@link Db} instance, typically created once at the top
|
|
1089
|
-
* of a `scripts/seed.ts` file via `createDb({...})`.
|
|
1090
|
-
*/
|
|
1091
|
-
run: (db: Db) => Promise<void>;
|
|
1092
|
-
};
|
|
1093
|
-
/**
|
|
1094
|
-
* Declares a database seed in a portable, type-safe way.
|
|
351
|
+
* Works on:
|
|
352
|
+
* - Plain objects (shallow and nested)
|
|
353
|
+
* - Arrays of objects
|
|
354
|
+
* - Single Date values
|
|
355
|
+
* - Nested arrays and objects of arbitrary depth
|
|
1095
356
|
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
357
|
+
* Non-Date primitives (string, number, boolean, null, undefined) pass
|
|
358
|
+
* through unchanged. The function is a no-op for values that don't
|
|
359
|
+
* contain Date instances.
|
|
360
|
+
*
|
|
361
|
+
* @param value - The value to convert. Typically a query result row or
|
|
362
|
+
* array of rows from `db.query(...).findMany().run()`.
|
|
363
|
+
* @returns A new value with every Date replaced by its ISO string.
|
|
1100
364
|
*
|
|
1101
365
|
* @example
|
|
1102
366
|
* ```ts
|
|
1103
|
-
*
|
|
1104
|
-
* import { defineSeed, createDb } from "@cfast/db";
|
|
1105
|
-
* import * as schema from "~/db/schema";
|
|
1106
|
-
*
|
|
1107
|
-
* const seed = defineSeed({
|
|
1108
|
-
* entries: [
|
|
1109
|
-
* {
|
|
1110
|
-
* table: schema.users,
|
|
1111
|
-
* rows: [
|
|
1112
|
-
* { id: "u-1", email: "ada@example.com", name: "Ada" },
|
|
1113
|
-
* { id: "u-2", email: "grace@example.com", name: "Grace" },
|
|
1114
|
-
* ],
|
|
1115
|
-
* },
|
|
1116
|
-
* {
|
|
1117
|
-
* table: schema.posts,
|
|
1118
|
-
* rows: [
|
|
1119
|
-
* { id: "p-1", authorId: "u-1", title: "Hello" },
|
|
1120
|
-
* ],
|
|
1121
|
-
* },
|
|
1122
|
-
* ],
|
|
1123
|
-
* });
|
|
367
|
+
* import { toJSON } from "@cfast/db";
|
|
1124
368
|
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
* await
|
|
369
|
+
* export async function loader({ context }) {
|
|
370
|
+
* const db = createDb({ ... });
|
|
371
|
+
* const posts = await db.query(postsTable).findMany().run();
|
|
372
|
+
* return { posts: toJSON(posts) };
|
|
373
|
+
* }
|
|
1128
374
|
* ```
|
|
375
|
+
*/
|
|
376
|
+
declare function toJSON<T>(value: T): DateToString<T>;
|
|
377
|
+
/**
|
|
378
|
+
* Type-level utility that converts Date fields to string in a type.
|
|
1129
379
|
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
380
|
+
* Maps `{ createdAt: Date; title: string }` to
|
|
381
|
+
* `{ createdAt: string; title: string }` so the return type of
|
|
382
|
+
* `toJSON(row)` accurately reflects the runtime shape.
|
|
1132
383
|
*/
|
|
1133
|
-
|
|
384
|
+
type DateToString<T> = T extends Date ? string : T extends Array<infer U> ? DateToString<U>[] : T extends Record<string, unknown> ? {
|
|
385
|
+
[K in keyof T]: DateToString<T[K]>;
|
|
386
|
+
} : T;
|
|
1134
387
|
|
|
1135
388
|
/**
|
|
1136
389
|
* Per-request cache that holds the resolved `with` lookup map for each grant.
|
|
@@ -1180,4 +433,4 @@ declare function createLookupCache(): LookupCache;
|
|
|
1180
433
|
*/
|
|
1181
434
|
declare function runWithLookupCache<T>(fn: () => T, cache?: LookupCache): T;
|
|
1182
435
|
|
|
1183
|
-
export { type AppDbConfig, type AppDbFactory,
|
|
436
|
+
export { type AppDbConfig, type AppDbFactory, CursorParams, type DateToString, Db, DbConfig, type LookupCache, OffsetParams, Operation, TransactionError, compose, composeSequential, composeSequentialCallback, createAppDb, createDb, createLookupCache, parseCursorParams, parseOffsetParams, runWithLookupCache, toJSON };
|