@cfast/db 0.3.0 → 0.4.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/README.md +15 -15
- package/dist/index.d.ts +457 -26
- package/dist/index.js +221 -6
- package/llms.txt +317 -11
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -63,6 +63,7 @@ function checkOperationPermissions(grants, descriptors) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/utils.ts
|
|
66
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
66
67
|
import { and, or } from "drizzle-orm";
|
|
67
68
|
import { getTableName as getTableName2 } from "@cfast/permissions";
|
|
68
69
|
function deduplicateDescriptors(descriptors) {
|
|
@@ -77,12 +78,20 @@ function deduplicateDescriptors(descriptors) {
|
|
|
77
78
|
}
|
|
78
79
|
return result;
|
|
79
80
|
}
|
|
81
|
+
var lookupCacheStorage = new AsyncLocalStorage();
|
|
80
82
|
function createLookupCache() {
|
|
81
83
|
return /* @__PURE__ */ new Map();
|
|
82
84
|
}
|
|
85
|
+
function runWithLookupCache(fn, cache = createLookupCache()) {
|
|
86
|
+
return lookupCacheStorage.run(cache, fn);
|
|
87
|
+
}
|
|
88
|
+
function getActiveLookupCache(fallback) {
|
|
89
|
+
return lookupCacheStorage.getStore() ?? fallback;
|
|
90
|
+
}
|
|
83
91
|
async function resolveGrantLookups(grant, user, lookupDb, cache) {
|
|
84
92
|
if (!grant.with) return {};
|
|
85
|
-
const
|
|
93
|
+
const activeCache = getActiveLookupCache(cache);
|
|
94
|
+
const cached = activeCache.get(grant);
|
|
86
95
|
if (cached) return cached;
|
|
87
96
|
const entries = Object.entries(grant.with);
|
|
88
97
|
const promise = (async () => {
|
|
@@ -94,7 +103,7 @@ async function resolveGrantLookups(grant, user, lookupDb, cache) {
|
|
|
94
103
|
);
|
|
95
104
|
return resolved;
|
|
96
105
|
})();
|
|
97
|
-
|
|
106
|
+
activeCache.set(grant, promise);
|
|
98
107
|
return promise;
|
|
99
108
|
}
|
|
100
109
|
async function buildPermissionFilter(grants, action, table, user, unsafe, getLookupDb, cache) {
|
|
@@ -665,6 +674,139 @@ function createCacheManager(config) {
|
|
|
665
674
|
};
|
|
666
675
|
}
|
|
667
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
|
+
|
|
668
810
|
// src/create-db.ts
|
|
669
811
|
function createDb(config) {
|
|
670
812
|
const lookupCache = createLookupCache();
|
|
@@ -744,11 +886,14 @@ function buildDb(config, isUnsafe, lookupCache) {
|
|
|
744
886
|
permissions: allPermissions,
|
|
745
887
|
async run(params) {
|
|
746
888
|
const p = params ?? {};
|
|
889
|
+
if (operations.length === 0) {
|
|
890
|
+
return [];
|
|
891
|
+
}
|
|
747
892
|
if (!isUnsafe) {
|
|
748
893
|
checkOperationPermissions(config.grants, allPermissions);
|
|
749
894
|
}
|
|
750
895
|
const batchables = operations.map((op) => getBatchable(op));
|
|
751
|
-
const everyOpBatchable =
|
|
896
|
+
const everyOpBatchable = batchables.every((b) => b !== void 0);
|
|
752
897
|
if (everyOpBatchable) {
|
|
753
898
|
const sharedDb = drizzle3(config.d1, { schema: config.schema });
|
|
754
899
|
await Promise.all(
|
|
@@ -775,6 +920,9 @@ function buildDb(config, isUnsafe, lookupCache) {
|
|
|
775
920
|
}
|
|
776
921
|
};
|
|
777
922
|
},
|
|
923
|
+
transaction(callback) {
|
|
924
|
+
return runTransaction(db, callback);
|
|
925
|
+
},
|
|
778
926
|
cache: {
|
|
779
927
|
async invalidate(options) {
|
|
780
928
|
if (!cacheManager) return;
|
|
@@ -787,13 +935,32 @@ function buildDb(config, isUnsafe, lookupCache) {
|
|
|
787
935
|
}
|
|
788
936
|
}
|
|
789
937
|
}
|
|
938
|
+
},
|
|
939
|
+
clearLookupCache() {
|
|
940
|
+
lookupCache.clear();
|
|
790
941
|
}
|
|
791
942
|
};
|
|
792
943
|
return db;
|
|
793
944
|
}
|
|
945
|
+
function createAppDb(config) {
|
|
946
|
+
const { d1, schema, cache } = config;
|
|
947
|
+
const getD1 = typeof d1 === "function" ? d1 : () => d1;
|
|
948
|
+
return (grants, user) => createDb({
|
|
949
|
+
d1: getD1(),
|
|
950
|
+
schema,
|
|
951
|
+
grants,
|
|
952
|
+
user,
|
|
953
|
+
cache
|
|
954
|
+
});
|
|
955
|
+
}
|
|
794
956
|
|
|
795
957
|
// src/compose.ts
|
|
796
958
|
function compose(operations, executor) {
|
|
959
|
+
if (executor.length > 0 && executor.length !== operations.length) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
`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.`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
797
964
|
const allPermissions = deduplicateDescriptors(
|
|
798
965
|
operations.flatMap((op) => op.permissions)
|
|
799
966
|
);
|
|
@@ -864,7 +1031,7 @@ function wrapForTracking(target, perms) {
|
|
|
864
1031
|
});
|
|
865
1032
|
}
|
|
866
1033
|
function createTrackingDb(real, perms) {
|
|
867
|
-
|
|
1034
|
+
const trackingDb = {
|
|
868
1035
|
query: (table) => wrapForTracking(real.query(table), perms),
|
|
869
1036
|
insert: (table) => wrapForTracking(real.insert(table), perms),
|
|
870
1037
|
update: (table) => wrapForTracking(real.update(table), perms),
|
|
@@ -881,8 +1048,24 @@ function createTrackingDb(real, perms) {
|
|
|
881
1048
|
}
|
|
882
1049
|
};
|
|
883
1050
|
},
|
|
884
|
-
|
|
1051
|
+
transaction: async (callback) => {
|
|
1052
|
+
const trackingTx = {
|
|
1053
|
+
query: trackingDb.query,
|
|
1054
|
+
insert: trackingDb.insert,
|
|
1055
|
+
update: trackingDb.update,
|
|
1056
|
+
delete: trackingDb.delete,
|
|
1057
|
+
transaction: (cb) => trackingDb.transaction(cb)
|
|
1058
|
+
};
|
|
1059
|
+
try {
|
|
1060
|
+
await callback(trackingTx);
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
return createSentinel();
|
|
1064
|
+
},
|
|
1065
|
+
cache: real.cache,
|
|
1066
|
+
clearLookupCache: () => real.clearLookupCache()
|
|
885
1067
|
};
|
|
1068
|
+
return trackingDb;
|
|
886
1069
|
}
|
|
887
1070
|
function composeSequentialCallback(db, callback) {
|
|
888
1071
|
const collected = [];
|
|
@@ -911,11 +1094,43 @@ function composeSequentialCallback(db, callback) {
|
|
|
911
1094
|
}
|
|
912
1095
|
};
|
|
913
1096
|
}
|
|
1097
|
+
|
|
1098
|
+
// src/seed.ts
|
|
1099
|
+
function defineSeed(config) {
|
|
1100
|
+
const entries = Object.freeze(
|
|
1101
|
+
config.entries.map((entry) => ({
|
|
1102
|
+
table: entry.table,
|
|
1103
|
+
rows: Object.freeze([...entry.rows])
|
|
1104
|
+
}))
|
|
1105
|
+
);
|
|
1106
|
+
return {
|
|
1107
|
+
entries,
|
|
1108
|
+
async run(db) {
|
|
1109
|
+
const unsafeDb = db.unsafe();
|
|
1110
|
+
for (const entry of entries) {
|
|
1111
|
+
if (entry.rows.length === 0) continue;
|
|
1112
|
+
const ops = entry.rows.map(
|
|
1113
|
+
(row) => unsafeDb.insert(entry.table).values(row)
|
|
1114
|
+
);
|
|
1115
|
+
if (ops.length === 1) {
|
|
1116
|
+
await ops[0].run({});
|
|
1117
|
+
} else {
|
|
1118
|
+
await unsafeDb.batch(ops).run({});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
914
1124
|
export {
|
|
1125
|
+
TransactionError,
|
|
915
1126
|
compose,
|
|
916
1127
|
composeSequential,
|
|
917
1128
|
composeSequentialCallback,
|
|
1129
|
+
createAppDb,
|
|
918
1130
|
createDb,
|
|
1131
|
+
createLookupCache,
|
|
1132
|
+
defineSeed,
|
|
919
1133
|
parseCursorParams,
|
|
920
|
-
parseOffsetParams
|
|
1134
|
+
parseOffsetParams,
|
|
1135
|
+
runWithLookupCache
|
|
921
1136
|
};
|