@cfast/db 0.2.0 → 0.4.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/README.md +15 -15
- package/dist/index.d.ts +434 -26
- package/dist/index.js +351 -46
- package/llms.txt +342 -12
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -12,14 +12,17 @@ import {
|
|
|
12
12
|
} from "@cfast/permissions";
|
|
13
13
|
import { CRUD_ACTIONS } from "@cfast/permissions";
|
|
14
14
|
function resolvePermissionFilters(grants, action, table) {
|
|
15
|
+
const targetName = getTableName(table);
|
|
15
16
|
const matching = grants.filter((g) => {
|
|
16
17
|
const actionMatch = g.action === action || g.action === "manage";
|
|
17
|
-
const tableMatch = g.subject === "all" || g.subject === table ||
|
|
18
|
+
const tableMatch = g.subject === "all" || g.subject === table || getTableName(g.subject) === targetName;
|
|
18
19
|
return actionMatch && tableMatch;
|
|
19
20
|
});
|
|
20
21
|
if (matching.length === 0) return [];
|
|
21
22
|
if (matching.some((g) => !g.where)) return [];
|
|
22
|
-
return matching.filter(
|
|
23
|
+
return matching.filter(
|
|
24
|
+
(g) => !!g.where
|
|
25
|
+
);
|
|
23
26
|
}
|
|
24
27
|
function grantMatchesAction(grantAction, requiredAction) {
|
|
25
28
|
if (grantAction === requiredAction) return true;
|
|
@@ -60,6 +63,7 @@ function checkOperationPermissions(grants, descriptors) {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
// src/utils.ts
|
|
66
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
63
67
|
import { and, or } from "drizzle-orm";
|
|
64
68
|
import { getTableName as getTableName2 } from "@cfast/permissions";
|
|
65
69
|
function deduplicateDescriptors(descriptors) {
|
|
@@ -74,13 +78,46 @@ function deduplicateDescriptors(descriptors) {
|
|
|
74
78
|
}
|
|
75
79
|
return result;
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
var lookupCacheStorage = new AsyncLocalStorage();
|
|
82
|
+
function createLookupCache() {
|
|
83
|
+
return /* @__PURE__ */ new Map();
|
|
84
|
+
}
|
|
85
|
+
function runWithLookupCache(fn, cache = createLookupCache()) {
|
|
86
|
+
return lookupCacheStorage.run(cache, fn);
|
|
87
|
+
}
|
|
88
|
+
function getActiveLookupCache(fallback) {
|
|
89
|
+
return lookupCacheStorage.getStore() ?? fallback;
|
|
90
|
+
}
|
|
91
|
+
async function resolveGrantLookups(grant, user, lookupDb, cache) {
|
|
92
|
+
if (!grant.with) return {};
|
|
93
|
+
const activeCache = getActiveLookupCache(cache);
|
|
94
|
+
const cached = activeCache.get(grant);
|
|
95
|
+
if (cached) return cached;
|
|
96
|
+
const entries = Object.entries(grant.with);
|
|
97
|
+
const promise = (async () => {
|
|
98
|
+
const resolved = {};
|
|
99
|
+
await Promise.all(
|
|
100
|
+
entries.map(async ([key, fn]) => {
|
|
101
|
+
resolved[key] = await fn(user, lookupDb);
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
return resolved;
|
|
105
|
+
})();
|
|
106
|
+
activeCache.set(grant, promise);
|
|
107
|
+
return promise;
|
|
108
|
+
}
|
|
109
|
+
async function buildPermissionFilter(grants, action, table, user, unsafe, getLookupDb, cache) {
|
|
78
110
|
if (unsafe || !user) return void 0;
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
111
|
+
const matching = resolvePermissionFilters(grants, action, table);
|
|
112
|
+
if (matching.length === 0) return void 0;
|
|
81
113
|
const columns = table;
|
|
82
|
-
const
|
|
83
|
-
|
|
114
|
+
const needsLookupDb = matching.some((g) => g.with !== void 0);
|
|
115
|
+
const lookupDb = needsLookupDb ? getLookupDb() : void 0;
|
|
116
|
+
const lookupSets = await Promise.all(
|
|
117
|
+
matching.map((g) => resolveGrantLookups(g, user, lookupDb, cache))
|
|
118
|
+
);
|
|
119
|
+
const clauses = matching.map(
|
|
120
|
+
(g, i) => g.where(columns, user, lookupSets[i])
|
|
84
121
|
);
|
|
85
122
|
return or(...clauses);
|
|
86
123
|
}
|
|
@@ -191,12 +228,14 @@ function buildQueryOperation(config, db, tableKey, method, options) {
|
|
|
191
228
|
if (!config.unsafe) {
|
|
192
229
|
checkOperationPermissions(config.grants, permissions);
|
|
193
230
|
}
|
|
194
|
-
const permFilter = buildPermissionFilter(
|
|
231
|
+
const permFilter = await buildPermissionFilter(
|
|
195
232
|
config.grants,
|
|
196
233
|
"read",
|
|
197
234
|
config.table,
|
|
198
235
|
config.user,
|
|
199
|
-
config.unsafe
|
|
236
|
+
config.unsafe,
|
|
237
|
+
config.getLookupDb,
|
|
238
|
+
config.lookupCache
|
|
200
239
|
);
|
|
201
240
|
const userWhere = options?.where;
|
|
202
241
|
const combinedWhere = combineWhere(userWhere, permFilter);
|
|
@@ -243,16 +282,18 @@ function createQueryBuilder(config) {
|
|
|
243
282
|
if (!tableKey) throw new Error("Table not found in schema");
|
|
244
283
|
return tableKey;
|
|
245
284
|
}
|
|
246
|
-
function checkAndBuildWhere(extraWhere) {
|
|
285
|
+
async function checkAndBuildWhere(extraWhere) {
|
|
247
286
|
if (!config.unsafe) {
|
|
248
287
|
checkOperationPermissions(config.grants, permissions);
|
|
249
288
|
}
|
|
250
|
-
const permFilter = buildPermissionFilter(
|
|
289
|
+
const permFilter = await buildPermissionFilter(
|
|
251
290
|
config.grants,
|
|
252
291
|
"read",
|
|
253
292
|
config.table,
|
|
254
293
|
config.user,
|
|
255
|
-
config.unsafe
|
|
294
|
+
config.unsafe,
|
|
295
|
+
config.getLookupDb,
|
|
296
|
+
config.lookupCache
|
|
256
297
|
);
|
|
257
298
|
return combineWhere(
|
|
258
299
|
combineWhere(options?.where, permFilter),
|
|
@@ -282,7 +323,7 @@ function createQueryBuilder(config) {
|
|
|
282
323
|
const cursorValues = decodeCursor(params.cursor);
|
|
283
324
|
const direction = options?.orderDirection ?? "desc";
|
|
284
325
|
const cursorWhere = cursorValues ? buildCursorWhere(cursorColumns, cursorValues, direction) : void 0;
|
|
285
|
-
const combinedWhere = checkAndBuildWhere(cursorWhere);
|
|
326
|
+
const combinedWhere = await checkAndBuildWhere(cursorWhere);
|
|
286
327
|
const queryOptions = buildBaseQueryOptions(combinedWhere);
|
|
287
328
|
queryOptions.limit = params.limit + 1;
|
|
288
329
|
const queryTable = getQueryTable(db, key);
|
|
@@ -303,7 +344,7 @@ function createQueryBuilder(config) {
|
|
|
303
344
|
permissions,
|
|
304
345
|
async run(_params) {
|
|
305
346
|
const key = ensureTableKey();
|
|
306
|
-
const combinedWhere = checkAndBuildWhere();
|
|
347
|
+
const combinedWhere = await checkAndBuildWhere();
|
|
307
348
|
const queryOptions = buildBaseQueryOptions(combinedWhere);
|
|
308
349
|
queryOptions.limit = params.limit;
|
|
309
350
|
queryOptions.offset = (params.page - 1) * params.limit;
|
|
@@ -344,10 +385,11 @@ function checkIfNeeded(config, grants, permissions) {
|
|
|
344
385
|
checkOperationPermissions(grants, permissions);
|
|
345
386
|
}
|
|
346
387
|
}
|
|
347
|
-
function buildMutationWithReturning(config, permissions, tableName, buildQuery) {
|
|
388
|
+
function buildMutationWithReturning(config, permissions, tableName, buildQuery, prepareFn) {
|
|
348
389
|
const drizzleDb = drizzle2(config.d1, { schema: config.schema });
|
|
349
390
|
const runOnce = async (returning) => {
|
|
350
391
|
checkIfNeeded(config, config.grants, permissions);
|
|
392
|
+
if (prepareFn) await prepareFn();
|
|
351
393
|
const built = buildQuery(drizzleDb, returning);
|
|
352
394
|
const result = await built.execute(returning);
|
|
353
395
|
config.onMutate?.(tableName);
|
|
@@ -366,6 +408,7 @@ function buildMutationWithReturning(config, permissions, tableName, buildQuery)
|
|
|
366
408
|
}
|
|
367
409
|
};
|
|
368
410
|
returningOp[BATCHABLE] = {
|
|
411
|
+
prepare: prepareFn,
|
|
369
412
|
build: (sharedDb) => buildQuery(sharedDb, true).query,
|
|
370
413
|
tableName,
|
|
371
414
|
withResult: true
|
|
@@ -374,6 +417,7 @@ function buildMutationWithReturning(config, permissions, tableName, buildQuery)
|
|
|
374
417
|
}
|
|
375
418
|
};
|
|
376
419
|
baseOp[BATCHABLE] = {
|
|
420
|
+
prepare: prepareFn,
|
|
377
421
|
build: (sharedDb) => buildQuery(sharedDb, false).query,
|
|
378
422
|
tableName,
|
|
379
423
|
withResult: false
|
|
@@ -409,17 +453,31 @@ function createUpdateBuilder(config) {
|
|
|
409
453
|
set(values) {
|
|
410
454
|
return {
|
|
411
455
|
where(condition) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
456
|
+
let resolvedCombinedWhere;
|
|
457
|
+
let preparePromise;
|
|
458
|
+
const prepareFn = () => {
|
|
459
|
+
if (!preparePromise) {
|
|
460
|
+
preparePromise = (async () => {
|
|
461
|
+
const permFilter = await buildPermissionFilter(
|
|
462
|
+
config.grants,
|
|
463
|
+
"update",
|
|
464
|
+
config.table,
|
|
465
|
+
config.user,
|
|
466
|
+
config.unsafe,
|
|
467
|
+
config.getLookupDb,
|
|
468
|
+
config.lookupCache
|
|
469
|
+
);
|
|
470
|
+
resolvedCombinedWhere = combineWhere(
|
|
471
|
+
condition,
|
|
472
|
+
permFilter
|
|
473
|
+
);
|
|
474
|
+
})();
|
|
475
|
+
}
|
|
476
|
+
return preparePromise;
|
|
477
|
+
};
|
|
420
478
|
const buildQuery = (sharedDb, returning) => {
|
|
421
479
|
const base = sharedDb.update(config.table).set(values);
|
|
422
|
-
if (
|
|
480
|
+
if (resolvedCombinedWhere) base.where(resolvedCombinedWhere);
|
|
423
481
|
const query = returning ? base.returning() : base;
|
|
424
482
|
return {
|
|
425
483
|
query,
|
|
@@ -431,7 +489,13 @@ function createUpdateBuilder(config) {
|
|
|
431
489
|
}
|
|
432
490
|
};
|
|
433
491
|
};
|
|
434
|
-
return buildMutationWithReturning(
|
|
492
|
+
return buildMutationWithReturning(
|
|
493
|
+
config,
|
|
494
|
+
permissions,
|
|
495
|
+
tableName,
|
|
496
|
+
buildQuery,
|
|
497
|
+
prepareFn
|
|
498
|
+
);
|
|
435
499
|
}
|
|
436
500
|
};
|
|
437
501
|
}
|
|
@@ -442,17 +506,31 @@ function createDeleteBuilder(config) {
|
|
|
442
506
|
const tableName = getTableName2(config.table);
|
|
443
507
|
return {
|
|
444
508
|
where(condition) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
509
|
+
let resolvedCombinedWhere;
|
|
510
|
+
let preparePromise;
|
|
511
|
+
const prepareFn = () => {
|
|
512
|
+
if (!preparePromise) {
|
|
513
|
+
preparePromise = (async () => {
|
|
514
|
+
const permFilter = await buildPermissionFilter(
|
|
515
|
+
config.grants,
|
|
516
|
+
"delete",
|
|
517
|
+
config.table,
|
|
518
|
+
config.user,
|
|
519
|
+
config.unsafe,
|
|
520
|
+
config.getLookupDb,
|
|
521
|
+
config.lookupCache
|
|
522
|
+
);
|
|
523
|
+
resolvedCombinedWhere = combineWhere(
|
|
524
|
+
condition,
|
|
525
|
+
permFilter
|
|
526
|
+
);
|
|
527
|
+
})();
|
|
528
|
+
}
|
|
529
|
+
return preparePromise;
|
|
530
|
+
};
|
|
453
531
|
const buildQuery = (sharedDb, returning) => {
|
|
454
532
|
const base = sharedDb.delete(config.table);
|
|
455
|
-
if (
|
|
533
|
+
if (resolvedCombinedWhere) base.where(resolvedCombinedWhere);
|
|
456
534
|
const query = returning ? base.returning() : base;
|
|
457
535
|
return {
|
|
458
536
|
query,
|
|
@@ -464,7 +542,13 @@ function createDeleteBuilder(config) {
|
|
|
464
542
|
}
|
|
465
543
|
};
|
|
466
544
|
};
|
|
467
|
-
return buildMutationWithReturning(
|
|
545
|
+
return buildMutationWithReturning(
|
|
546
|
+
config,
|
|
547
|
+
permissions,
|
|
548
|
+
tableName,
|
|
549
|
+
buildQuery,
|
|
550
|
+
prepareFn
|
|
551
|
+
);
|
|
468
552
|
}
|
|
469
553
|
};
|
|
470
554
|
}
|
|
@@ -590,16 +674,156 @@ function createCacheManager(config) {
|
|
|
590
674
|
};
|
|
591
675
|
}
|
|
592
676
|
|
|
677
|
+
// src/transaction.ts
|
|
678
|
+
var TransactionError = class extends Error {
|
|
679
|
+
constructor(message) {
|
|
680
|
+
super(message);
|
|
681
|
+
this.name = "TransactionError";
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
function wrapWriteOperation(ctx, op) {
|
|
685
|
+
const record = (toQueue) => {
|
|
686
|
+
if (ctx.closed) {
|
|
687
|
+
throw new TransactionError(
|
|
688
|
+
"Cannot run an operation after the transaction has committed or aborted."
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (ctx.aborted) {
|
|
692
|
+
throw new TransactionError(
|
|
693
|
+
"Transaction has been aborted; no further operations may run."
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
ctx.pending.push({ op: toQueue });
|
|
697
|
+
};
|
|
698
|
+
const proxied = {
|
|
699
|
+
permissions: op.permissions,
|
|
700
|
+
async run() {
|
|
701
|
+
record(op);
|
|
702
|
+
return void 0;
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
if (typeof op.returning === "function") {
|
|
706
|
+
proxied.returning = () => {
|
|
707
|
+
const returningOp = op.returning();
|
|
708
|
+
return {
|
|
709
|
+
permissions: returningOp.permissions,
|
|
710
|
+
async run() {
|
|
711
|
+
record(returningOp);
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return proxied;
|
|
718
|
+
}
|
|
719
|
+
function createTxHandle(db, ctx) {
|
|
720
|
+
return {
|
|
721
|
+
query: (table) => {
|
|
722
|
+
return db.query(table);
|
|
723
|
+
},
|
|
724
|
+
insert: (table) => {
|
|
725
|
+
const realBuilder = db.insert(table);
|
|
726
|
+
return {
|
|
727
|
+
values: (values) => {
|
|
728
|
+
const realOp = realBuilder.values(values);
|
|
729
|
+
return wrapWriteOperation(ctx, realOp);
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
},
|
|
733
|
+
update: (table) => {
|
|
734
|
+
const realBuilder = db.update(table);
|
|
735
|
+
return {
|
|
736
|
+
set: (values) => {
|
|
737
|
+
const realSet = realBuilder.set(values);
|
|
738
|
+
return {
|
|
739
|
+
where: (condition) => {
|
|
740
|
+
const realOp = realSet.where(condition);
|
|
741
|
+
return wrapWriteOperation(ctx, realOp);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
},
|
|
747
|
+
delete: (table) => {
|
|
748
|
+
const realBuilder = db.delete(table);
|
|
749
|
+
return {
|
|
750
|
+
where: (condition) => {
|
|
751
|
+
const realOp = realBuilder.where(condition);
|
|
752
|
+
return wrapWriteOperation(ctx, realOp);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
},
|
|
756
|
+
transaction: (callback) => {
|
|
757
|
+
return runTransaction(db, callback, ctx);
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
async function flushWrites(db, pending) {
|
|
762
|
+
if (pending.length === 0) return;
|
|
763
|
+
const ops = pending.map((p) => p.op);
|
|
764
|
+
for (const op of ops) {
|
|
765
|
+
if (getBatchable(op) === void 0) {
|
|
766
|
+
throw new TransactionError(
|
|
767
|
+
"Internal error: a pending transaction operation was not batchable. Transactions only accept operations produced by tx.insert/update/delete."
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
await db.batch(ops).run({});
|
|
772
|
+
}
|
|
773
|
+
async function runTransaction(db, callback, parentCtx) {
|
|
774
|
+
if (parentCtx) {
|
|
775
|
+
if (parentCtx.closed || parentCtx.aborted) {
|
|
776
|
+
throw new TransactionError(
|
|
777
|
+
"Cannot start a nested transaction: parent has already committed or aborted."
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const nestedTx = createTxHandle(db, parentCtx);
|
|
781
|
+
return callback(nestedTx);
|
|
782
|
+
}
|
|
783
|
+
const ctx = {
|
|
784
|
+
pending: [],
|
|
785
|
+
closed: false,
|
|
786
|
+
aborted: false,
|
|
787
|
+
nested: false
|
|
788
|
+
};
|
|
789
|
+
const tx = createTxHandle(db, ctx);
|
|
790
|
+
let result;
|
|
791
|
+
try {
|
|
792
|
+
result = await callback(tx);
|
|
793
|
+
} catch (err) {
|
|
794
|
+
ctx.aborted = true;
|
|
795
|
+
ctx.closed = true;
|
|
796
|
+
ctx.pending.length = 0;
|
|
797
|
+
throw err;
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
await flushWrites(db, ctx.pending);
|
|
801
|
+
ctx.closed = true;
|
|
802
|
+
} catch (err) {
|
|
803
|
+
ctx.aborted = true;
|
|
804
|
+
ctx.closed = true;
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
|
|
593
810
|
// src/create-db.ts
|
|
594
811
|
function createDb(config) {
|
|
595
|
-
|
|
812
|
+
const lookupCache = createLookupCache();
|
|
813
|
+
return buildDb(config, false, lookupCache);
|
|
596
814
|
}
|
|
597
|
-
function buildDb(config, isUnsafe) {
|
|
815
|
+
function buildDb(config, isUnsafe, lookupCache) {
|
|
598
816
|
const cacheManager = config.cache === false ? null : createCacheManager(config.cache ?? { backend: "cache-api" });
|
|
599
817
|
const onMutate = (tableName) => {
|
|
600
818
|
cacheManager?.invalidateTable(tableName);
|
|
601
819
|
};
|
|
602
|
-
|
|
820
|
+
let lookupDbCache = null;
|
|
821
|
+
const getLookupDb = () => {
|
|
822
|
+
if (lookupDbCache) return lookupDbCache;
|
|
823
|
+
lookupDbCache = isUnsafe ? db : buildDb(config, true, lookupCache);
|
|
824
|
+
return lookupDbCache;
|
|
825
|
+
};
|
|
826
|
+
const db = {
|
|
603
827
|
query(table) {
|
|
604
828
|
return createQueryBuilder({
|
|
605
829
|
d1: config.d1,
|
|
@@ -607,7 +831,9 @@ function buildDb(config, isUnsafe) {
|
|
|
607
831
|
grants: config.grants,
|
|
608
832
|
user: config.user,
|
|
609
833
|
table,
|
|
610
|
-
unsafe: isUnsafe
|
|
834
|
+
unsafe: isUnsafe,
|
|
835
|
+
lookupCache,
|
|
836
|
+
getLookupDb
|
|
611
837
|
});
|
|
612
838
|
},
|
|
613
839
|
insert(table) {
|
|
@@ -618,7 +844,9 @@ function buildDb(config, isUnsafe) {
|
|
|
618
844
|
user: config.user,
|
|
619
845
|
table,
|
|
620
846
|
unsafe: isUnsafe,
|
|
621
|
-
onMutate
|
|
847
|
+
onMutate,
|
|
848
|
+
lookupCache,
|
|
849
|
+
getLookupDb
|
|
622
850
|
});
|
|
623
851
|
},
|
|
624
852
|
update(table) {
|
|
@@ -629,7 +857,9 @@ function buildDb(config, isUnsafe) {
|
|
|
629
857
|
user: config.user,
|
|
630
858
|
table,
|
|
631
859
|
unsafe: isUnsafe,
|
|
632
|
-
onMutate
|
|
860
|
+
onMutate,
|
|
861
|
+
lookupCache,
|
|
862
|
+
getLookupDb
|
|
633
863
|
});
|
|
634
864
|
},
|
|
635
865
|
delete(table) {
|
|
@@ -640,11 +870,13 @@ function buildDb(config, isUnsafe) {
|
|
|
640
870
|
user: config.user,
|
|
641
871
|
table,
|
|
642
872
|
unsafe: isUnsafe,
|
|
643
|
-
onMutate
|
|
873
|
+
onMutate,
|
|
874
|
+
lookupCache,
|
|
875
|
+
getLookupDb
|
|
644
876
|
});
|
|
645
877
|
},
|
|
646
878
|
unsafe() {
|
|
647
|
-
return buildDb(config, true);
|
|
879
|
+
return buildDb(config, true, lookupCache);
|
|
648
880
|
},
|
|
649
881
|
batch(operations) {
|
|
650
882
|
const allPermissions = deduplicateDescriptors(
|
|
@@ -654,13 +886,19 @@ function buildDb(config, isUnsafe) {
|
|
|
654
886
|
permissions: allPermissions,
|
|
655
887
|
async run(params) {
|
|
656
888
|
const p = params ?? {};
|
|
889
|
+
if (operations.length === 0) {
|
|
890
|
+
return [];
|
|
891
|
+
}
|
|
657
892
|
if (!isUnsafe) {
|
|
658
893
|
checkOperationPermissions(config.grants, allPermissions);
|
|
659
894
|
}
|
|
660
895
|
const batchables = operations.map((op) => getBatchable(op));
|
|
661
|
-
const everyOpBatchable =
|
|
896
|
+
const everyOpBatchable = batchables.every((b) => b !== void 0);
|
|
662
897
|
if (everyOpBatchable) {
|
|
663
898
|
const sharedDb = drizzle3(config.d1, { schema: config.schema });
|
|
899
|
+
await Promise.all(
|
|
900
|
+
batchables.map((b) => b.prepare?.() ?? Promise.resolve())
|
|
901
|
+
);
|
|
664
902
|
const items = batchables.map((b) => b.build(sharedDb));
|
|
665
903
|
const batchResults = await sharedDb.batch(
|
|
666
904
|
items
|
|
@@ -682,6 +920,9 @@ function buildDb(config, isUnsafe) {
|
|
|
682
920
|
}
|
|
683
921
|
};
|
|
684
922
|
},
|
|
923
|
+
transaction(callback) {
|
|
924
|
+
return runTransaction(db, callback);
|
|
925
|
+
},
|
|
685
926
|
cache: {
|
|
686
927
|
async invalidate(options) {
|
|
687
928
|
if (!cacheManager) return;
|
|
@@ -696,10 +937,27 @@ function buildDb(config, isUnsafe) {
|
|
|
696
937
|
}
|
|
697
938
|
}
|
|
698
939
|
};
|
|
940
|
+
return db;
|
|
941
|
+
}
|
|
942
|
+
function createAppDb(config) {
|
|
943
|
+
const { d1, schema, cache } = config;
|
|
944
|
+
const getD1 = typeof d1 === "function" ? d1 : () => d1;
|
|
945
|
+
return (grants, user) => createDb({
|
|
946
|
+
d1: getD1(),
|
|
947
|
+
schema,
|
|
948
|
+
grants,
|
|
949
|
+
user,
|
|
950
|
+
cache
|
|
951
|
+
});
|
|
699
952
|
}
|
|
700
953
|
|
|
701
954
|
// src/compose.ts
|
|
702
955
|
function compose(operations, executor) {
|
|
956
|
+
if (executor.length > 0 && executor.length !== operations.length) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`compose(): executor declares ${executor.length} parameter(s) but ${operations.length} operation(s) were passed. Each operation must have a corresponding run-function parameter (in array order), otherwise sub-operations will silently go uninvoked. Prefer composeSequentialCallback() for by-name binding.`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
703
961
|
const allPermissions = deduplicateDescriptors(
|
|
704
962
|
operations.flatMap((op) => op.permissions)
|
|
705
963
|
);
|
|
@@ -770,7 +1028,7 @@ function wrapForTracking(target, perms) {
|
|
|
770
1028
|
});
|
|
771
1029
|
}
|
|
772
1030
|
function createTrackingDb(real, perms) {
|
|
773
|
-
|
|
1031
|
+
const trackingDb = {
|
|
774
1032
|
query: (table) => wrapForTracking(real.query(table), perms),
|
|
775
1033
|
insert: (table) => wrapForTracking(real.insert(table), perms),
|
|
776
1034
|
update: (table) => wrapForTracking(real.update(table), perms),
|
|
@@ -787,8 +1045,23 @@ function createTrackingDb(real, perms) {
|
|
|
787
1045
|
}
|
|
788
1046
|
};
|
|
789
1047
|
},
|
|
1048
|
+
transaction: async (callback) => {
|
|
1049
|
+
const trackingTx = {
|
|
1050
|
+
query: trackingDb.query,
|
|
1051
|
+
insert: trackingDb.insert,
|
|
1052
|
+
update: trackingDb.update,
|
|
1053
|
+
delete: trackingDb.delete,
|
|
1054
|
+
transaction: (cb) => trackingDb.transaction(cb)
|
|
1055
|
+
};
|
|
1056
|
+
try {
|
|
1057
|
+
await callback(trackingTx);
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
return createSentinel();
|
|
1061
|
+
},
|
|
790
1062
|
cache: real.cache
|
|
791
1063
|
};
|
|
1064
|
+
return trackingDb;
|
|
792
1065
|
}
|
|
793
1066
|
function composeSequentialCallback(db, callback) {
|
|
794
1067
|
const collected = [];
|
|
@@ -817,11 +1090,43 @@ function composeSequentialCallback(db, callback) {
|
|
|
817
1090
|
}
|
|
818
1091
|
};
|
|
819
1092
|
}
|
|
1093
|
+
|
|
1094
|
+
// src/seed.ts
|
|
1095
|
+
function defineSeed(config) {
|
|
1096
|
+
const entries = Object.freeze(
|
|
1097
|
+
config.entries.map((entry) => ({
|
|
1098
|
+
table: entry.table,
|
|
1099
|
+
rows: Object.freeze([...entry.rows])
|
|
1100
|
+
}))
|
|
1101
|
+
);
|
|
1102
|
+
return {
|
|
1103
|
+
entries,
|
|
1104
|
+
async run(db) {
|
|
1105
|
+
const unsafeDb = db.unsafe();
|
|
1106
|
+
for (const entry of entries) {
|
|
1107
|
+
if (entry.rows.length === 0) continue;
|
|
1108
|
+
const ops = entry.rows.map(
|
|
1109
|
+
(row) => unsafeDb.insert(entry.table).values(row)
|
|
1110
|
+
);
|
|
1111
|
+
if (ops.length === 1) {
|
|
1112
|
+
await ops[0].run({});
|
|
1113
|
+
} else {
|
|
1114
|
+
await unsafeDb.batch(ops).run({});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
820
1120
|
export {
|
|
1121
|
+
TransactionError,
|
|
821
1122
|
compose,
|
|
822
1123
|
composeSequential,
|
|
823
1124
|
composeSequentialCallback,
|
|
1125
|
+
createAppDb,
|
|
824
1126
|
createDb,
|
|
1127
|
+
createLookupCache,
|
|
1128
|
+
defineSeed,
|
|
825
1129
|
parseCursorParams,
|
|
826
|
-
parseOffsetParams
|
|
1130
|
+
parseOffsetParams,
|
|
1131
|
+
runWithLookupCache
|
|
827
1132
|
};
|