@cfast/db 0.0.1 → 0.1.1

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
@@ -31,7 +31,7 @@ type Operation<TResult> = {
31
31
  *
32
32
  * @param params - Placeholder values for `sql.placeholder()` calls. Pass `{}` when no placeholders are used.
33
33
  */
34
- run: (params: Record<string, unknown>) => Promise<TResult>;
34
+ run: (params?: Record<string, unknown>) => Promise<TResult>;
35
35
  };
36
36
  /**
37
37
  * Supported cache backend types for {@link CacheConfig}.
@@ -542,7 +542,7 @@ declare function createDb(config: DbConfig): Db;
542
542
  * Each `RunFn` corresponds to one of the operations passed to `compose()`,
543
543
  * preserving the same positional order.
544
544
  */
545
- type RunFn = (params: Record<string, unknown>) => Promise<unknown>;
545
+ type RunFn = (params?: Record<string, unknown>) => Promise<unknown>;
546
546
  /**
547
547
  * Merges multiple {@link Operation | Operations} into a single operation with combined,
548
548
  * deduplicated permissions and an executor function for controlling data flow.
@@ -579,6 +579,27 @@ type RunFn = (params: Record<string, unknown>) => Promise<unknown>;
579
579
  * ```
580
580
  */
581
581
  declare function compose<TResult>(operations: Operation<unknown>[], executor: (...runs: RunFn[]) => TResult | Promise<TResult>): Operation<TResult>;
582
+ /**
583
+ * Runs multiple {@link Operation | Operations} sequentially and returns their results as an array.
584
+ *
585
+ * This is a shorthand for the common `compose` pattern where operations are
586
+ * simply awaited in order with no data dependencies between them:
587
+ *
588
+ * ```ts
589
+ * // Before: verbose
590
+ * compose([op1, op2], async (run1, run2) => {
591
+ * await run1({});
592
+ * await run2({});
593
+ * });
594
+ *
595
+ * // After: concise
596
+ * composeSequential([op1, op2]);
597
+ * ```
598
+ *
599
+ * @param operations - The operations to run in order.
600
+ * @returns A single {@link Operation} that runs all operations sequentially and returns their results.
601
+ */
602
+ declare function composeSequential(operations: Operation<unknown>[]): Operation<unknown[]>;
582
603
 
583
604
  /**
584
605
  * Options for controlling default and maximum pagination limits.
@@ -633,4 +654,4 @@ declare function parseCursorParams(request: Request, options?: PaginationOptions
633
654
  */
634
655
  declare function parseOffsetParams(request: Request, options?: PaginationOptions): OffsetParams;
635
656
 
636
- export { type CacheBackend, type CacheConfig, type CursorPage, type CursorParams, type Db, type DbConfig, type DeleteBuilder, type DeleteReturningBuilder, type FindFirstOptions, type FindManyOptions, type InsertBuilder, type InsertReturningBuilder, type OffsetPage, type OffsetParams, type Operation, type PaginateOptions, type PaginateParams, type QueryBuilder, type QueryCacheOptions, type UpdateBuilder, type UpdateReturningBuilder, type UpdateWhereBuilder, compose, createDb, parseCursorParams, parseOffsetParams };
657
+ export { type CacheBackend, type CacheConfig, type CursorPage, type CursorParams, type Db, type DbConfig, type DeleteBuilder, type DeleteReturningBuilder, type FindFirstOptions, type FindManyOptions, type InsertBuilder, type InsertReturningBuilder, type OffsetPage, type OffsetParams, type Operation, type PaginateOptions, type PaginateParams, type QueryBuilder, type QueryCacheOptions, type UpdateBuilder, type UpdateReturningBuilder, type UpdateWhereBuilder, compose, composeSequential, createDb, parseCursorParams, parseOffsetParams };
package/dist/index.js CHANGED
@@ -583,9 +583,10 @@ function buildDb(config, isUnsafe) {
583
583
  return {
584
584
  permissions: allPermissions,
585
585
  async run(params) {
586
+ const p = params ?? {};
586
587
  const results = [];
587
588
  for (const op of operations) {
588
- results.push(await op.run(params));
589
+ results.push(await op.run(p));
589
590
  }
590
591
  return results;
591
592
  }
@@ -620,8 +621,24 @@ function compose(operations, executor) {
620
621
  }
621
622
  };
622
623
  }
624
+ function composeSequential(operations) {
625
+ const allPermissions = deduplicateDescriptors(
626
+ operations.flatMap((op) => op.permissions)
627
+ );
628
+ return {
629
+ permissions: allPermissions,
630
+ async run() {
631
+ const results = [];
632
+ for (const op of operations) {
633
+ results.push(await op.run({}));
634
+ }
635
+ return results;
636
+ }
637
+ };
638
+ }
623
639
  export {
624
640
  compose,
641
+ composeSequential,
625
642
  createDb,
626
643
  parseCursorParams,
627
644
  parseOffsetParams
package/llms.txt ADDED
@@ -0,0 +1,191 @@
1
+ # @cfast/db
2
+
3
+ > Lazy, permission-aware Drizzle operations for Cloudflare D1 -- application-level Row-Level Security.
4
+
5
+ ## When to use
6
+
7
+ Use `@cfast/db` whenever you need to read or write a D1 database in a cfast app. Every query goes through permission checks automatically. You never call Drizzle directly.
8
+
9
+ ## Key concepts
10
+
11
+ - **Operations are lazy.** Every `db.query/insert/update/delete` call returns an `Operation<TResult>` with `.permissions` (inspectable immediately) and `.run(params)` (executes with permission checks). Nothing touches D1 until you call `.run()`.
12
+ - **Permission checks are structural.** `.run()` always checks the user's grants before executing SQL. Row-level WHERE clauses from grants are injected automatically.
13
+ - **One Db per request.** `createDb()` captures the user at creation time. Never share a `Db` across requests.
14
+ - **`db.unsafe()` is the only escape hatch.** Returns a `Db` that skips all permission checks. Greppable via `git grep '.unsafe()'`.
15
+
16
+ ## API Reference
17
+
18
+ ### createDb(config): Db
19
+
20
+ ```typescript
21
+ import { createDb } from "@cfast/db";
22
+ import * as schema from "./schema"; // must be `import *`
23
+
24
+ const db = createDb({
25
+ d1: D1Database, // env.DB
26
+ schema: Record<string, DrizzleTable>,
27
+ grants: Grant[], // from resolveGrants(permissions, roles)
28
+ user: { id: string } | null, // null = anonymous
29
+ cache?: CacheConfig | false, // default: { backend: "cache-api" }
30
+ });
31
+ ```
32
+
33
+ ### Operation<TResult>
34
+
35
+ ```typescript
36
+ type Operation<TResult> = {
37
+ permissions: PermissionDescriptor[]; // [{ action, table }] -- available immediately
38
+ run: (params?: Record<string, unknown>) => Promise<TResult>;
39
+ };
40
+ ```
41
+
42
+ ### Reads
43
+
44
+ ```typescript
45
+ db.query(table).findMany(options?): Operation<unknown[]>
46
+ db.query(table).findFirst(options?): Operation<unknown | undefined>
47
+ db.query(table).paginate(params, options?): Operation<CursorPage | OffsetPage>
48
+ ```
49
+
50
+ FindManyOptions: `{ columns?, where?, orderBy?, limit?, offset?, with?, cache? }`
51
+ FindFirstOptions: same without `limit`/`offset`.
52
+
53
+ ### Writes
54
+
55
+ ```typescript
56
+ db.insert(table).values({...}): InsertReturningBuilder // .run() or .returning().run()
57
+ db.update(table).set({...}).where(cond): UpdateReturningBuilder
58
+ db.delete(table).where(cond): DeleteReturningBuilder
59
+ ```
60
+
61
+ All write builders are `Operation<void>` with an optional `.returning()` that changes return type.
62
+
63
+ ### compose(operations, executor): Operation<TResult>
64
+
65
+ ```typescript
66
+ import { compose } from "@cfast/db";
67
+
68
+ const op = compose(
69
+ [updatePost, insertAuditLog],
70
+ async (doUpdate, doAudit) => {
71
+ const result = await doUpdate();
72
+ await doAudit();
73
+ return result;
74
+ },
75
+ );
76
+ op.permissions; // merged + deduplicated from both operations
77
+ await op.run(); // executor runs, each sub-op checks its own permissions
78
+ ```
79
+
80
+ ### composeSequential(operations): Operation<unknown[]>
81
+
82
+ ```typescript
83
+ import { composeSequential } from "@cfast/db";
84
+
85
+ const op = composeSequential([updatePost, insertAuditLog]);
86
+ op.permissions; // merged + deduplicated from all operations
87
+ await op.run(); // runs operations in order, returns results array
88
+ ```
89
+
90
+ Runs operations in order, returns results array. Shorthand for `compose` when there are no data dependencies between operations.
91
+
92
+ ### db.batch(operations): Operation<unknown[]>
93
+
94
+ Runs operations sequentially with merged permissions. Not D1 native batch.
95
+
96
+ ### db.unsafe(): Db
97
+
98
+ Returns a new Db that skips permission checks. Use for cron jobs, migrations, system tasks.
99
+
100
+ ### Pagination helpers
101
+
102
+ ```typescript
103
+ import { parseCursorParams, parseOffsetParams } from "@cfast/db";
104
+
105
+ const params = parseCursorParams(request, { defaultLimit: 20, maxLimit: 100 });
106
+ const page = await db.query(posts).paginate(params, {
107
+ cursorColumns: [posts.createdAt, posts.id],
108
+ orderDirection: "desc",
109
+ }).run();
110
+ // CursorPage: { items, nextCursor }
111
+
112
+ const offsetParams = parseOffsetParams(request);
113
+ const page2 = await db.query(posts).paginate(offsetParams).run();
114
+ // OffsetPage: { items, total, page, totalPages }
115
+ ```
116
+
117
+ ### Cache
118
+
119
+ ```typescript
120
+ cache: {
121
+ backend: "cache-api" | "kv",
122
+ ttl?: "30s",
123
+ staleWhileRevalidate?: "5m",
124
+ exclude?: ["sessions"],
125
+ kv?: KVNamespace, // required for "kv" backend
126
+ onHit?, onMiss?, onInvalidate?,
127
+ }
128
+ ```
129
+
130
+ Per-query: `db.query(t).findMany({ cache: false })` or `{ cache: { ttl: "5m", tags: ["posts"] } }`.
131
+ Manual invalidation: `await db.cache.invalidate({ tags: ["posts"], tables: ["posts"] })`.
132
+
133
+ ## Usage Examples
134
+
135
+ ### Standard loader pattern
136
+
137
+ ```typescript
138
+ export async function loader({ context, request }) {
139
+ const { user, grants } = await auth.requireUser(request);
140
+ const db = createDb({ d1: context.env.DB, schema, grants, user });
141
+
142
+ const posts = await db.query(postsTable).findMany({
143
+ where: eq(postsTable.published, true),
144
+ orderBy: desc(postsTable.createdAt),
145
+ limit: 10,
146
+ }).run();
147
+
148
+ return { posts };
149
+ }
150
+ ```
151
+
152
+ ### Compose a multi-step action
153
+
154
+ ```typescript
155
+ export async function action({ context, request }) {
156
+ const { user, grants } = await auth.requireUser(request);
157
+ const db = createDb({ d1: context.env.DB, schema, grants, user });
158
+
159
+ const workflow = compose(
160
+ [
161
+ db.update(posts).set({ published: true }).where(eq(posts.id, id)),
162
+ db.insert(auditLogs).values({ action: "publish", targetId: id, userId: user.id }),
163
+ ],
164
+ async (doUpdate, doAudit) => {
165
+ await doUpdate();
166
+ await doAudit();
167
+ },
168
+ );
169
+
170
+ await workflow.run();
171
+ }
172
+ ```
173
+
174
+ ## Integration
175
+
176
+ - **@cfast/permissions** -- `grants` come from `resolveGrants(permissions, user.roles)`. Permission WHERE clauses are defined via `grant()` in your permissions config.
177
+ - **@cfast/auth** -- `auth.requireUser(request)` returns `{ user, grants }` which you pass directly to `createDb()`.
178
+ - **@cfast/actions** -- Actions define operations using `@cfast/db`. The framework extracts `.permissions` for client-side UI adaptation.
179
+
180
+ ## Common Mistakes
181
+
182
+ - **Forgetting `.run()`** -- Operations are lazy. `db.query(t).findMany()` returns an Operation, not results. You must call `.run()`.
183
+ - **Sharing a Db across requests** -- The Db captures the user. Create a new one per request.
184
+ - **Using `import { posts }` instead of `import * as schema`** -- The `schema` config must be `import *` so Drizzle's relational API can look up tables by key name.
185
+ - **Using `db.unsafe()` for admin endpoints** -- Use a role with `grant("manage", "all")` instead. Reserve `unsafe()` for genuinely user-less contexts (cron, migrations).
186
+ - **Expecting relational `with` to have permission filters** -- Permission WHERE clauses only apply to the root table, not joined relations.
187
+
188
+ ## See Also
189
+
190
+ - `@cfast/permissions` -- Defines the grants checked by every Operation.
191
+ - `@cfast/actions` -- Surfaces Operation `.permissions` to the client.
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@cfast/db",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Permission-aware Drizzle queries for Cloudflare D1",
5
+ "keywords": [
6
+ "cfast",
7
+ "cloudflare-workers",
8
+ "drizzle",
9
+ "d1",
10
+ "permissions",
11
+ "database"
12
+ ],
5
13
  "license": "MIT",
6
14
  "repository": {
7
15
  "type": "git",
@@ -18,7 +26,8 @@
18
26
  }
19
27
  },
20
28
  "files": [
21
- "dist"
29
+ "dist",
30
+ "llms.txt"
22
31
  ],
23
32
  "sideEffects": false,
24
33
  "publishConfig": {
@@ -28,7 +37,7 @@
28
37
  "drizzle-orm": ">=0.35"
29
38
  },
30
39
  "dependencies": {
31
- "@cfast/permissions": "0.0.1"
40
+ "@cfast/permissions": "0.1.0"
32
41
  },
33
42
  "peerDependenciesMeta": {
34
43
  "@cloudflare/workers-types": {