@cfast/db 0.5.0 → 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 +5 -305
- package/dist/index.js +79 -33
- package/dist/seed.d.ts +258 -0
- package/dist/seed.js +378 -0
- package/dist/types-FUFR36h1.d.ts +221 -0
- package/llms.txt +142 -16
- package/package.json +11 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,205 +1,7 @@
|
|
|
1
|
-
import { Grant,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
$inferSelect: infer R;
|
|
6
|
-
} ? R : Record<string, unknown>;
|
|
7
|
-
/** @internal */
|
|
8
|
-
type FindTableKeyByName<TSchema extends TablesRelationalConfig, TTableName extends string> = {
|
|
9
|
-
[K in keyof TSchema]: TSchema[K]["dbName"] extends TTableName ? K : never;
|
|
10
|
-
}[keyof TSchema];
|
|
11
|
-
/** @internal */
|
|
12
|
-
type LookupTableConfig<TFullSchema extends Record<string, unknown>, TTable> = TTable extends Table<infer TTableConfig> ? FindTableKeyByName<ExtractTablesWithRelations<TFullSchema>, TTableConfig["name"]> extends infer TKey extends keyof ExtractTablesWithRelations<TFullSchema> ? ExtractTablesWithRelations<TFullSchema>[TKey] : never : never;
|
|
13
|
-
type InferQueryResult<TFullSchema extends Record<string, unknown>, TTable, TConfig> = [TFullSchema] extends [Record<string, never>] ? InferRow<TTable> : LookupTableConfig<TFullSchema, TTable> extends infer TTableConfig extends TableRelationalConfig ? BuildQueryResult<ExtractTablesWithRelations<TFullSchema>, TTableConfig, TConfig extends Record<string, unknown> ? TConfig : true> : InferRow<TTable>;
|
|
14
|
-
type Operation<TResult> = {
|
|
15
|
-
permissions: PermissionDescriptor[];
|
|
16
|
-
run: (params?: Record<string, unknown>) => Promise<TResult>;
|
|
17
|
-
};
|
|
18
|
-
type CacheBackend = "cache-api" | "kv";
|
|
19
|
-
type CacheConfig = {
|
|
20
|
-
backend: CacheBackend;
|
|
21
|
-
kv?: KVNamespace;
|
|
22
|
-
ttl?: string;
|
|
23
|
-
staleWhileRevalidate?: string;
|
|
24
|
-
exclude?: string[];
|
|
25
|
-
onHit?: (key: string, table: string) => void;
|
|
26
|
-
onMiss?: (key: string, table: string) => void;
|
|
27
|
-
onInvalidate?: (tables: string[]) => void;
|
|
28
|
-
};
|
|
29
|
-
type QueryCacheOptions = false | {
|
|
30
|
-
ttl?: string;
|
|
31
|
-
staleWhileRevalidate?: string;
|
|
32
|
-
tags?: string[];
|
|
33
|
-
};
|
|
34
|
-
type DbConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> = {
|
|
35
|
-
d1: D1Database;
|
|
36
|
-
schema: TSchema;
|
|
37
|
-
grants: Grant[];
|
|
38
|
-
user: {
|
|
39
|
-
id: string;
|
|
40
|
-
} | null;
|
|
41
|
-
cache?: CacheConfig | false;
|
|
42
|
-
};
|
|
43
|
-
type FindManyOptions = {
|
|
44
|
-
columns?: Record<string, boolean>;
|
|
45
|
-
where?: unknown;
|
|
46
|
-
orderBy?: unknown;
|
|
47
|
-
limit?: number;
|
|
48
|
-
offset?: number;
|
|
49
|
-
with?: Record<string, unknown>;
|
|
50
|
-
cache?: QueryCacheOptions;
|
|
51
|
-
};
|
|
52
|
-
type FindFirstOptions = Omit<FindManyOptions, "limit" | "offset">;
|
|
53
|
-
type CursorParams = {
|
|
54
|
-
type: "cursor";
|
|
55
|
-
cursor: string | null;
|
|
56
|
-
limit: number;
|
|
57
|
-
};
|
|
58
|
-
type OffsetParams = {
|
|
59
|
-
type: "offset";
|
|
60
|
-
page: number;
|
|
61
|
-
limit: number;
|
|
62
|
-
};
|
|
63
|
-
type PaginateParams = CursorParams | OffsetParams;
|
|
64
|
-
type CursorPage<T> = {
|
|
65
|
-
items: T[];
|
|
66
|
-
nextCursor: string | null;
|
|
67
|
-
};
|
|
68
|
-
type OffsetPage<T> = {
|
|
69
|
-
items: T[];
|
|
70
|
-
total: number;
|
|
71
|
-
page: number;
|
|
72
|
-
totalPages: number;
|
|
73
|
-
};
|
|
74
|
-
type PaginateOptions = {
|
|
75
|
-
columns?: Record<string, boolean>;
|
|
76
|
-
where?: unknown;
|
|
77
|
-
orderBy?: unknown;
|
|
78
|
-
cursorColumns?: unknown[];
|
|
79
|
-
orderDirection?: "asc" | "desc";
|
|
80
|
-
with?: Record<string, unknown>;
|
|
81
|
-
cache?: QueryCacheOptions;
|
|
82
|
-
};
|
|
83
|
-
type TransactionResult<T> = {
|
|
84
|
-
result: T;
|
|
85
|
-
meta: {
|
|
86
|
-
changes: number;
|
|
87
|
-
writeResults: D1Result[];
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
type Tx<TSchema extends Record<string, unknown> = Record<string, never>> = {
|
|
91
|
-
query: <TTable extends DrizzleTable>(table: TTable) => QueryBuilder<TTable, TSchema>;
|
|
92
|
-
insert: <TTable extends DrizzleTable>(table: TTable) => InsertBuilder<TTable>;
|
|
93
|
-
update: <TTable extends DrizzleTable>(table: TTable) => UpdateBuilder<TTable>;
|
|
94
|
-
delete: <TTable extends DrizzleTable>(table: TTable) => DeleteBuilder<TTable>;
|
|
95
|
-
transaction: <T>(callback: (tx: Tx<TSchema>) => Promise<T>) => Promise<T>;
|
|
96
|
-
};
|
|
97
|
-
type Db<TSchema extends Record<string, unknown> = Record<string, never>> = {
|
|
98
|
-
query: <TTable extends DrizzleTable>(table: TTable) => QueryBuilder<TTable, TSchema>;
|
|
99
|
-
insert: <TTable extends DrizzleTable>(table: TTable) => InsertBuilder<TTable>;
|
|
100
|
-
update: <TTable extends DrizzleTable>(table: TTable) => UpdateBuilder<TTable>;
|
|
101
|
-
delete: <TTable extends DrizzleTable>(table: TTable) => DeleteBuilder<TTable>;
|
|
102
|
-
unsafe: () => Db<TSchema>;
|
|
103
|
-
batch: (operations: Operation<unknown>[]) => Operation<unknown[]>;
|
|
104
|
-
/**
|
|
105
|
-
* Runs a callback inside a transaction with atomic commit-or-rollback semantics.
|
|
106
|
-
*
|
|
107
|
-
* All writes (`tx.insert`/`tx.update`/`tx.delete`) recorded inside the callback
|
|
108
|
-
* are deferred and flushed together as a single atomic `db.batch([...])` when
|
|
109
|
-
* the callback returns successfully. If the callback throws, pending writes are
|
|
110
|
-
* discarded and the error is re-thrown — nothing reaches D1.
|
|
111
|
-
*
|
|
112
|
-
* Reads (`tx.query(...)`) execute eagerly so the caller can branch on their
|
|
113
|
-
* results. **D1 does not provide snapshot isolation across async code**, so
|
|
114
|
-
* reads inside a transaction can see concurrent writes. For concurrency-safe
|
|
115
|
-
* read-modify-write (e.g. stock decrement), combine the transaction with a
|
|
116
|
-
* relative SQL update and a WHERE guard:
|
|
117
|
-
*
|
|
118
|
-
* ```ts
|
|
119
|
-
* await db.transaction(async (tx) => {
|
|
120
|
-
* // The WHERE guard ensures the decrement only applies when stock is
|
|
121
|
-
* // still >= qty at commit time. Two concurrent transactions cannot
|
|
122
|
-
* // both oversell because D1's atomic batch re-evaluates the WHERE.
|
|
123
|
-
* await tx.update(products)
|
|
124
|
-
* .set({ stock: sql`stock - ${qty}` })
|
|
125
|
-
* .where(and(eq(products.id, pid), gte(products.stock, qty)))
|
|
126
|
-
* .run();
|
|
127
|
-
* return tx.insert(orders).values({ productId: pid, qty }).returning().run();
|
|
128
|
-
* });
|
|
129
|
-
* ```
|
|
130
|
-
*
|
|
131
|
-
* Nested `db.transaction()` calls inside the callback are flattened into the
|
|
132
|
-
* parent's pending queue so everything still commits atomically.
|
|
133
|
-
*
|
|
134
|
-
* @typeParam T - The return type of the callback.
|
|
135
|
-
* @param callback - The transaction body. Receives a `tx` handle with
|
|
136
|
-
* `query`/`insert`/`update`/`delete` methods (no `unsafe`, `batch`, or
|
|
137
|
-
* `cache` — those are intentionally off-limits inside a transaction).
|
|
138
|
-
* @returns A {@link TransactionResult} containing the callback's return value
|
|
139
|
-
* and transaction metadata (`meta.changes`, `meta.writeResults`), or rejects
|
|
140
|
-
* with whatever the callback threw (after rolling back pending writes).
|
|
141
|
-
*/
|
|
142
|
-
transaction: <T>(callback: (tx: Tx<TSchema>) => Promise<T>) => Promise<TransactionResult<T>>;
|
|
143
|
-
/** Cache control methods for manual invalidation. */
|
|
144
|
-
cache: {
|
|
145
|
-
/** Invalidate cached queries by tag names and/or table names. */
|
|
146
|
-
invalidate: (options: {
|
|
147
|
-
/** Tag names to invalidate (from {@link QueryCacheOptions} `tags`). */
|
|
148
|
-
tags?: string[];
|
|
149
|
-
/** Table names to invalidate (bumps their version counters). */
|
|
150
|
-
tables?: string[];
|
|
151
|
-
}) => Promise<void>;
|
|
152
|
-
};
|
|
153
|
-
/**
|
|
154
|
-
* Clears the per-instance `with` lookup cache so that the next query
|
|
155
|
-
* re-runs every grant lookup function.
|
|
156
|
-
*
|
|
157
|
-
* In production this is rarely needed because each request gets a fresh
|
|
158
|
-
* `Db` via `createDb()`. In tests that reuse a single `Db` across grant
|
|
159
|
-
* mutations (e.g. inserting a new friendship and then querying recipes),
|
|
160
|
-
* call this after the mutation to avoid stale lookup results.
|
|
161
|
-
*
|
|
162
|
-
* For finer-grained control, wrap each logical request in
|
|
163
|
-
* {@link runWithLookupCache} instead -- that scopes the cache via
|
|
164
|
-
* `AsyncLocalStorage` so it is automatically discarded at scope exit.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const db = createDb({ ... });
|
|
169
|
-
* await db.query(recipes).findMany().run(); // populates lookup cache
|
|
170
|
-
* await db.insert(friendGrants).values({ ... }).run(); // adds new grant
|
|
171
|
-
* db.clearLookupCache(); // drop stale lookups
|
|
172
|
-
* await db.query(recipes).findMany().run(); // sees new grant
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
clearLookupCache: () => void;
|
|
176
|
-
};
|
|
177
|
-
type QueryBuilder<TTable extends DrizzleTable = DrizzleTable, TSchema extends Record<string, unknown> = Record<string, never>> = {
|
|
178
|
-
findMany: <TConfig extends FindManyOptions = Record<string, never>, TRow = InferQueryResult<TSchema, TTable, TConfig>>(options?: TConfig) => Operation<TRow[]>;
|
|
179
|
-
findFirst: <TConfig extends FindFirstOptions = Record<string, never>, TRow = InferQueryResult<TSchema, TTable, TConfig>>(options?: TConfig) => Operation<TRow | undefined>;
|
|
180
|
-
paginate: <TRow = InferRow<TTable>>(params: CursorParams | OffsetParams, options?: PaginateOptions) => Operation<CursorPage<TRow>> | Operation<OffsetPage<TRow>>;
|
|
181
|
-
};
|
|
182
|
-
type InsertBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
183
|
-
values: (values: Record<string, unknown>) => InsertReturningBuilder<TTable>;
|
|
184
|
-
};
|
|
185
|
-
type InsertReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
186
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
187
|
-
};
|
|
188
|
-
type UpdateBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
189
|
-
set: (values: Record<string, unknown>) => UpdateWhereBuilder<TTable>;
|
|
190
|
-
};
|
|
191
|
-
type UpdateWhereBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
192
|
-
where: (condition: unknown) => UpdateReturningBuilder<TTable>;
|
|
193
|
-
};
|
|
194
|
-
type UpdateReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
195
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
196
|
-
};
|
|
197
|
-
type DeleteBuilder<TTable extends DrizzleTable = DrizzleTable> = {
|
|
198
|
-
where: (condition: unknown) => DeleteReturningBuilder<TTable>;
|
|
199
|
-
};
|
|
200
|
-
type DeleteReturningBuilder<TTable extends DrizzleTable = DrizzleTable> = Operation<void> & {
|
|
201
|
-
returning: () => Operation<InferRow<TTable>>;
|
|
202
|
-
};
|
|
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';
|
|
203
5
|
|
|
204
6
|
/**
|
|
205
7
|
* Creates a permission-aware database instance bound to the given user.
|
|
@@ -535,108 +337,6 @@ declare class TransactionError extends Error {
|
|
|
535
337
|
constructor(message: string);
|
|
536
338
|
}
|
|
537
339
|
|
|
538
|
-
/**
|
|
539
|
-
* A single seed entry — every row in `rows` is inserted into `table` at seed
|
|
540
|
-
* time. Row shape is inferred from the Drizzle table so typos in column names
|
|
541
|
-
* are caught by `tsc` instead of failing at runtime when `INSERT` rejects
|
|
542
|
-
* the statement.
|
|
543
|
-
*
|
|
544
|
-
* @typeParam TTable - The Drizzle table reference (e.g. `typeof usersTable`).
|
|
545
|
-
*/
|
|
546
|
-
type SeedEntry<TTable extends DrizzleTable = DrizzleTable> = {
|
|
547
|
-
/**
|
|
548
|
-
* The Drizzle table to insert into. Must be imported from your schema
|
|
549
|
-
* (`import { users } from "~/db/schema"`) rather than passed as a string
|
|
550
|
-
* so the helper can infer row types and forward the reference to
|
|
551
|
-
* `db.insert()`.
|
|
552
|
-
*/
|
|
553
|
-
table: TTable;
|
|
554
|
-
/**
|
|
555
|
-
* Rows to insert. The row shape is inferred from the table's
|
|
556
|
-
* `$inferSelect` — making a typo in a column name is a compile-time error.
|
|
557
|
-
*
|
|
558
|
-
* Entries are inserted in the order they appear, which lets you control
|
|
559
|
-
* foreign-key ordering just by ordering your `entries` array
|
|
560
|
-
* (`{ users }` before `{ posts }`, etc.).
|
|
561
|
-
*/
|
|
562
|
-
rows: readonly InferRow<TTable>[];
|
|
563
|
-
};
|
|
564
|
-
/**
|
|
565
|
-
* Configuration passed to {@link defineSeed}.
|
|
566
|
-
*/
|
|
567
|
-
type SeedConfig = {
|
|
568
|
-
/**
|
|
569
|
-
* Ordered list of seed entries. Each entry is flushed as a batched insert
|
|
570
|
-
* in list order, so place parent tables (users, orgs) before child tables
|
|
571
|
-
* (posts, memberships) that reference them via foreign keys.
|
|
572
|
-
*/
|
|
573
|
-
entries: readonly SeedEntry[];
|
|
574
|
-
};
|
|
575
|
-
/**
|
|
576
|
-
* The compiled seed returned by {@link defineSeed}.
|
|
577
|
-
*
|
|
578
|
-
* Holds a frozen copy of the entry list so runner callers can introspect
|
|
579
|
-
* what would be seeded, plus a `run(db)` method that actually executes the
|
|
580
|
-
* inserts against a real {@link Db} instance.
|
|
581
|
-
*/
|
|
582
|
-
type Seed = {
|
|
583
|
-
/** The frozen list of entries this seed will insert, in order. */
|
|
584
|
-
readonly entries: readonly SeedEntry[];
|
|
585
|
-
/**
|
|
586
|
-
* Executes every entry against the given {@link Db} in the order they were
|
|
587
|
-
* declared. Uses `db.unsafe()` internally so seed scripts don't need
|
|
588
|
-
* their own grants plumbing — seeding is a system task by definition.
|
|
589
|
-
*
|
|
590
|
-
* Entries with an empty `rows` array are skipped so callers can leave
|
|
591
|
-
* placeholder entries in the config without crashing the seed.
|
|
592
|
-
*
|
|
593
|
-
* @param db - A {@link Db} instance, typically created once at the top
|
|
594
|
-
* of a `scripts/seed.ts` file via `createDb({...})`.
|
|
595
|
-
*/
|
|
596
|
-
run: (db: Db) => Promise<void>;
|
|
597
|
-
};
|
|
598
|
-
/**
|
|
599
|
-
* Declares a database seed in a portable, type-safe way.
|
|
600
|
-
*
|
|
601
|
-
* The canonical replacement for hand-rolled `scripts/seed.ts` files that
|
|
602
|
-
* called `db.mutate("tablename")` (a method that never existed) or reached
|
|
603
|
-
* straight into raw Drizzle. Use the scaffolded `scripts/seed.ts` in a
|
|
604
|
-
* `create-cfast` project for a ready-made example.
|
|
605
|
-
*
|
|
606
|
-
* @example
|
|
607
|
-
* ```ts
|
|
608
|
-
* // scripts/seed.ts
|
|
609
|
-
* import { defineSeed, createDb } from "@cfast/db";
|
|
610
|
-
* import * as schema from "~/db/schema";
|
|
611
|
-
*
|
|
612
|
-
* const seed = defineSeed({
|
|
613
|
-
* entries: [
|
|
614
|
-
* {
|
|
615
|
-
* table: schema.users,
|
|
616
|
-
* rows: [
|
|
617
|
-
* { id: "u-1", email: "ada@example.com", name: "Ada" },
|
|
618
|
-
* { id: "u-2", email: "grace@example.com", name: "Grace" },
|
|
619
|
-
* ],
|
|
620
|
-
* },
|
|
621
|
-
* {
|
|
622
|
-
* table: schema.posts,
|
|
623
|
-
* rows: [
|
|
624
|
-
* { id: "p-1", authorId: "u-1", title: "Hello" },
|
|
625
|
-
* ],
|
|
626
|
-
* },
|
|
627
|
-
* ],
|
|
628
|
-
* });
|
|
629
|
-
*
|
|
630
|
-
* // In a worker/runner that already has a real D1 binding:
|
|
631
|
-
* const db = createDb({ d1, schema, grants: [], user: null });
|
|
632
|
-
* await seed.run(db);
|
|
633
|
-
* ```
|
|
634
|
-
*
|
|
635
|
-
* @param config - The {@link SeedConfig} with the ordered list of entries.
|
|
636
|
-
* @returns A {@link Seed} with a `.run(db)` executor.
|
|
637
|
-
*/
|
|
638
|
-
declare function defineSeed(config: SeedConfig): Seed;
|
|
639
|
-
|
|
640
340
|
/**
|
|
641
341
|
* Recursively converts Date fields in a value to ISO 8601 strings for
|
|
642
342
|
* JSON serialization.
|
|
@@ -733,4 +433,4 @@ declare function createLookupCache(): LookupCache;
|
|
|
733
433
|
*/
|
|
734
434
|
declare function runWithLookupCache<T>(fn: () => T, cache?: LookupCache): T;
|
|
735
435
|
|
|
736
|
-
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 };
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { drizzle as drizzle4 } from "drizzle-orm/d1";
|
|
3
3
|
|
|
4
4
|
// src/query-builder.ts
|
|
5
|
-
import { count } from "drizzle-orm";
|
|
5
|
+
import { count, sql, or as drizzleOr } from "drizzle-orm";
|
|
6
6
|
import { drizzle } from "drizzle-orm/d1";
|
|
7
|
+
import { getGrantedActions } from "@cfast/permissions";
|
|
7
8
|
|
|
8
9
|
// src/permissions.ts
|
|
9
10
|
import {
|
|
@@ -220,6 +221,53 @@ function getTableKey(schema, table) {
|
|
|
220
221
|
function getQueryTable(db, key) {
|
|
221
222
|
return db.query[key];
|
|
222
223
|
}
|
|
224
|
+
var CAN_PREFIX = "_can_";
|
|
225
|
+
async function buildCanExtras(config, grantedActions, queriedAction) {
|
|
226
|
+
const extras = {};
|
|
227
|
+
for (const ga of grantedActions) {
|
|
228
|
+
const alias = `${CAN_PREFIX}${ga.action}`;
|
|
229
|
+
if (ga.action === queriedAction) {
|
|
230
|
+
extras[alias] = sql`1`.as(alias);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (ga.unrestricted) {
|
|
234
|
+
extras[alias] = sql`1`.as(alias);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const needsLookupDb = ga.grants.some((g) => g.with !== void 0);
|
|
238
|
+
const lookupDb = needsLookupDb ? config.getLookupDb() : void 0;
|
|
239
|
+
const lookupSets = await Promise.all(
|
|
240
|
+
ga.grants.map((g) => resolveGrantLookups(g, config.user, lookupDb, config.lookupCache))
|
|
241
|
+
);
|
|
242
|
+
const columns = config.table;
|
|
243
|
+
const clauses = ga.grants.map(
|
|
244
|
+
(g, i) => g.where ? g.where(columns, config.user, lookupSets[i]) : void 0
|
|
245
|
+
).filter((c) => c !== void 0);
|
|
246
|
+
if (clauses.length === 0) {
|
|
247
|
+
extras[alias] = sql`1`.as(alias);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const combined = clauses.length === 1 ? clauses[0] : drizzleOr(...clauses);
|
|
251
|
+
extras[alias] = sql`CASE WHEN ${combined} THEN 1 ELSE 0 END`.as(alias);
|
|
252
|
+
}
|
|
253
|
+
return extras;
|
|
254
|
+
}
|
|
255
|
+
function attachCan(row, grantedActions) {
|
|
256
|
+
const can = {};
|
|
257
|
+
const grantedSet = new Set(grantedActions.map((ga) => ga.action));
|
|
258
|
+
const allActions = ["read", "create", "update", "delete"];
|
|
259
|
+
for (const action of allActions) {
|
|
260
|
+
const key = `${CAN_PREFIX}${action}`;
|
|
261
|
+
if (key in row) {
|
|
262
|
+
can[action] = row[key] === 1 || row[key] === true;
|
|
263
|
+
delete row[key];
|
|
264
|
+
} else if (!grantedSet.has(action)) {
|
|
265
|
+
can[action] = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
row._can = can;
|
|
269
|
+
return row;
|
|
270
|
+
}
|
|
223
271
|
function buildQueryOperation(config, db, tableKey, method, options) {
|
|
224
272
|
const permissions = makePermissions(config.unsafe, "read", config.table);
|
|
225
273
|
return {
|
|
@@ -244,8 +292,32 @@ function buildQueryOperation(config, db, tableKey, method, options) {
|
|
|
244
292
|
queryOptions.where = combinedWhere;
|
|
245
293
|
}
|
|
246
294
|
delete queryOptions.cache;
|
|
295
|
+
const grantedActions = !config.unsafe && config.user ? getGrantedActions(config.grants, config.table) : [];
|
|
296
|
+
if (grantedActions.length > 0) {
|
|
297
|
+
const canExtras = await buildCanExtras(config, grantedActions, "read");
|
|
298
|
+
const userExtras = queryOptions.extras;
|
|
299
|
+
queryOptions.extras = userExtras ? { ...userExtras, ...canExtras } : canExtras;
|
|
300
|
+
}
|
|
247
301
|
const queryTable = getQueryTable(db, tableKey);
|
|
248
302
|
const result = await queryTable[method](queryOptions);
|
|
303
|
+
if (grantedActions.length > 0) {
|
|
304
|
+
if (method === "findFirst" && result != null) {
|
|
305
|
+
attachCan(result, grantedActions);
|
|
306
|
+
} else if (method === "findMany" && Array.isArray(result)) {
|
|
307
|
+
for (const row of result) {
|
|
308
|
+
attachCan(row, grantedActions);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} else if (!config.unsafe && config.user) {
|
|
312
|
+
const emptyCan = { read: false, create: false, update: false, delete: false };
|
|
313
|
+
if (method === "findFirst" && result != null) {
|
|
314
|
+
result._can = { ...emptyCan };
|
|
315
|
+
} else if (method === "findMany" && Array.isArray(result)) {
|
|
316
|
+
for (const row of result) {
|
|
317
|
+
row._can = { ...emptyCan };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
249
321
|
return method === "findFirst" ? result ?? void 0 : result;
|
|
250
322
|
}
|
|
251
323
|
};
|
|
@@ -583,8 +655,8 @@ function createCacheManager(config) {
|
|
|
583
655
|
const defaultTtl = config.ttl ?? "60s";
|
|
584
656
|
const excludedTables = new Set(config.exclude ?? []);
|
|
585
657
|
return {
|
|
586
|
-
generateKey(
|
|
587
|
-
return `cfast:${role}:v${tableVersion}:${simpleHash(
|
|
658
|
+
generateKey(sql2, role, tableVersion) {
|
|
659
|
+
return `cfast:${role}:v${tableVersion}:${simpleHash(sql2)}`;
|
|
588
660
|
},
|
|
589
661
|
getTableVersion(table) {
|
|
590
662
|
return tableVersions.get(table) ?? 0;
|
|
@@ -952,7 +1024,8 @@ function buildDb(config, isUnsafe, lookupCache) {
|
|
|
952
1024
|
},
|
|
953
1025
|
clearLookupCache() {
|
|
954
1026
|
lookupCache.clear();
|
|
955
|
-
}
|
|
1027
|
+
},
|
|
1028
|
+
_schema: config.schema
|
|
956
1029
|
};
|
|
957
1030
|
return db;
|
|
958
1031
|
}
|
|
@@ -1077,7 +1150,8 @@ function createTrackingDb(real, perms) {
|
|
|
1077
1150
|
return createSentinel();
|
|
1078
1151
|
},
|
|
1079
1152
|
cache: real.cache,
|
|
1080
|
-
clearLookupCache: () => real.clearLookupCache()
|
|
1153
|
+
clearLookupCache: () => real.clearLookupCache(),
|
|
1154
|
+
_schema: real._schema
|
|
1081
1155
|
};
|
|
1082
1156
|
return trackingDb;
|
|
1083
1157
|
}
|
|
@@ -1109,33 +1183,6 @@ function composeSequentialCallback(db, callback) {
|
|
|
1109
1183
|
};
|
|
1110
1184
|
}
|
|
1111
1185
|
|
|
1112
|
-
// src/seed.ts
|
|
1113
|
-
function defineSeed(config) {
|
|
1114
|
-
const entries = Object.freeze(
|
|
1115
|
-
config.entries.map((entry) => ({
|
|
1116
|
-
table: entry.table,
|
|
1117
|
-
rows: Object.freeze([...entry.rows])
|
|
1118
|
-
}))
|
|
1119
|
-
);
|
|
1120
|
-
return {
|
|
1121
|
-
entries,
|
|
1122
|
-
async run(db) {
|
|
1123
|
-
const unsafeDb = db.unsafe();
|
|
1124
|
-
for (const entry of entries) {
|
|
1125
|
-
if (entry.rows.length === 0) continue;
|
|
1126
|
-
const ops = entry.rows.map(
|
|
1127
|
-
(row) => unsafeDb.insert(entry.table).values(row)
|
|
1128
|
-
);
|
|
1129
|
-
if (ops.length === 1) {
|
|
1130
|
-
await ops[0].run({});
|
|
1131
|
-
} else {
|
|
1132
|
-
await unsafeDb.batch(ops).run({});
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1186
|
// src/json.ts
|
|
1140
1187
|
function toJSON(value) {
|
|
1141
1188
|
return convertDates(value);
|
|
@@ -1164,7 +1211,6 @@ export {
|
|
|
1164
1211
|
createAppDb,
|
|
1165
1212
|
createDb,
|
|
1166
1213
|
createLookupCache,
|
|
1167
|
-
defineSeed,
|
|
1168
1214
|
parseCursorParams,
|
|
1169
1215
|
parseOffsetParams,
|
|
1170
1216
|
runWithLookupCache,
|