@lunora/do 1.0.0-alpha.43 → 1.0.0-alpha.45
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.mts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.mjs +1 -44
- package/dist/packem_shared/ADMIN_FUNCTIONS-PxZ8Lr9e.mjs +1 -0
- package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-QYaiuty4.mjs +1 -0
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-D0wNaez7.mjs +22 -0
- package/dist/packem_shared/CDC_LOG_TABLE-E_J5LPoK.mjs +8 -0
- package/dist/packem_shared/ConflictError-C8GtJmjS.mjs +1 -0
- package/dist/packem_shared/CountRlsUnsupportedError-Cl8XpYDL.mjs +1 -0
- package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-CaO6L0Ee.mjs +31 -0
- package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-BRdmX7WW.mjs +1 -0
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-BPPeqM11.mjs +56 -0
- package/dist/packem_shared/GEO_DEFAULT_PRECISION-CWAm_oYY.mjs +1 -0
- package/dist/packem_shared/LogBuffer-bIvCelI-.mjs +1 -0
- package/dist/packem_shared/MAIL_RETENTION-KmozO2NQ.mjs +18 -0
- package/dist/packem_shared/MAX_SQL_ROWS-Bdu25ASB.mjs +2 -0
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-Ch6OW6_3.mjs +1 -0
- package/dist/packem_shared/NotFoundError-J3tjf4Uo.mjs +1 -0
- package/dist/packem_shared/NotUniqueError-BcP6GOem.mjs +1 -0
- package/dist/packem_shared/RANK_TIEBREAK-DtX8zQyc.mjs +1 -0
- package/dist/packem_shared/RLS_UNWRAP_SYMBOL-C6_WX3dG.mjs +1 -0
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-CP5wvJOW.mjs +101 -0
- package/dist/packem_shared/ReactiveCache-1_9Rs7J_.mjs +1 -0
- package/dist/packem_shared/SCAN_DEP-D_yR9EeV.mjs +1 -0
- package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-GvBy_DBz.mjs +1 -0
- package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-Caa0fR4N.mjs +1 -0
- package/dist/packem_shared/aggregateTableName-G-eXyjcz.mjs +1 -0
- package/dist/packem_shared/applyOnDelete-BvQN7pDL.mjs +1 -0
- package/dist/packem_shared/applySelect-Bq2KOrkL.mjs +1 -0
- package/dist/packem_shared/armRestore-BNzdvQ_o.mjs +1 -0
- package/dist/packem_shared/backfillAggregateIndexes-BAQ3Fwwh.mjs +1 -0
- package/dist/packem_shared/buildFtsMatch-CV0Z7PWv.mjs +1 -0
- package/dist/packem_shared/compileWhereSql-BLcfs4QW.mjs +1 -0
- package/dist/packem_shared/constant-time-equal-BVG05Guz.mjs +1 -0
- package/dist/packem_shared/context-telemetry-8eigmiNQ.mjs +1 -0
- package/dist/packem_shared/createMetrics-CyjLFXtA.mjs +1 -0
- package/dist/packem_shared/createSystemReader-DcDcrtM3.mjs +1 -0
- package/dist/packem_shared/ctx-db-idempotency-wiVoGnpQ.mjs +19 -0
- package/dist/packem_shared/ctx-db-shapes-DzX_H5q8.mjs +1 -0
- package/dist/packem_shared/diffExternalSource-DMpkJta1.mjs +1 -0
- package/dist/packem_shared/do-exec-BLe9lLrN.mjs +1 -0
- package/dist/packem_shared/do-sql-BYIQTG3z.mjs +1 -0
- package/dist/packem_shared/exportShardRows-kt42wijd.mjs +1 -0
- package/dist/packem_shared/hasTrigger-_rexbWMO.mjs +1 -0
- package/dist/packem_shared/isSoftDeleted-BvhQov04.mjs +9 -0
- package/dist/packem_shared/json-response-wrh9TBPw.mjs +1 -0
- package/dist/packem_shared/materializeExternalRows-BFmT9gsw.mjs +1 -0
- package/dist/packem_shared/renderSql-B5lF5Jd9.mjs +1 -0
- package/dist/packem_shared/runShardMigrations-bxOHpfID.mjs +5 -0
- package/dist/packem_shared/security-audit-BKUOgE0x.mjs +1 -0
- package/dist/packem_shared/selectExpiredIds-BGVP3d8-.mjs +1 -0
- package/dist/packem_shared/serialize-sql-DiRzL7A4.mjs +1 -0
- package/dist/packem_shared/serveRelationFanout-Ct5D2Tbk.mjs +1 -0
- package/dist/packem_shared/stableStringify-BjLh4gvA.mjs +1 -0
- package/dist/packem_shared/stableWireKey-YEHLaX6X.mjs +1 -0
- package/dist/packem_shared/subscriptionListDeltas-Bs69JbA8.mjs +1 -0
- package/dist/packem_shared/wire-codec-Ctnni0h6.mjs +1 -0
- package/package.json +3 -3
- package/dist/packem_shared/ADMIN_FUNCTIONS-CnZkbXH_.mjs +0 -374
- package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs +0 -56
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs +0 -84
- package/dist/packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs +0 -107
- package/dist/packem_shared/ConflictError-CLoq37xH.mjs +0 -12
- package/dist/packem_shared/CountRlsUnsupportedError-BGxj0pgS.mjs +0 -133
- package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs +0 -245
- package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs +0 -211
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs +0 -248
- package/dist/packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs +0 -114
- package/dist/packem_shared/LogBuffer-B_Ezju_N.mjs +0 -37
- package/dist/packem_shared/MAIL_RETENTION-CPpgl-dX.mjs +0 -104
- package/dist/packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs +0 -61
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs +0 -1
- package/dist/packem_shared/NotFoundError-C70b9hLw.mjs +0 -9
- package/dist/packem_shared/NotUniqueError-DbtlWwcG.mjs +0 -2031
- package/dist/packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs +0 -91
- package/dist/packem_shared/RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs +0 -141
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-B_zt4RQE.mjs +0 -7676
- package/dist/packem_shared/ReactiveCache-DnSvbjil.mjs +0 -233
- package/dist/packem_shared/SCAN_DEP-DLJF8dsj.mjs +0 -19
- package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs +0 -169
- package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs +0 -144
- package/dist/packem_shared/aggregateTableName-CxNqY1Sl.mjs +0 -64
- package/dist/packem_shared/applyOnDelete-BXSq3S70.mjs +0 -176
- package/dist/packem_shared/applySelect-WQY8m62C.mjs +0 -103
- package/dist/packem_shared/armRestore-4Px61hHS.mjs +0 -49
- package/dist/packem_shared/backfillAggregateIndexes-BGTwynwh.mjs +0 -81
- package/dist/packem_shared/buildFtsMatch-BLEMawrp.mjs +0 -38
- package/dist/packem_shared/compileWhereSql-DE6yfRcQ.mjs +0 -129
- package/dist/packem_shared/constant-time-equal-BVRWZgES.mjs +0 -12
- package/dist/packem_shared/context-telemetry-CDpyil58.mjs +0 -223
- package/dist/packem_shared/createMetrics-C4I2Yanj.mjs +0 -1
- package/dist/packem_shared/createSystemReader-D12eNH13.mjs +0 -82
- package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +0 -108
- package/dist/packem_shared/ctx-db-shapes-DTeFiHYS.mjs +0 -53
- package/dist/packem_shared/diffExternalSource-CovHfdyo.mjs +0 -44
- package/dist/packem_shared/do-exec-5eQy5cEi.mjs +0 -12
- package/dist/packem_shared/do-sql-CGAgiQUz.mjs +0 -88
- package/dist/packem_shared/exportShardRows-Dy3oFZ26.mjs +0 -157
- package/dist/packem_shared/hasTrigger-5N6_Fx0A.mjs +0 -20
- package/dist/packem_shared/isSoftDeleted-CFJmhjFP.mjs +0 -217
- package/dist/packem_shared/json-response-BdbtpOhm.mjs +0 -3
- package/dist/packem_shared/materializeExternalRows-BtEGs1Fv.mjs +0 -45
- package/dist/packem_shared/renderSql-D6eUcn2N.mjs +0 -16
- package/dist/packem_shared/runShardMigrations-BVax6rYu.mjs +0 -120
- package/dist/packem_shared/security-audit-CucgBice.mjs +0 -158
- package/dist/packem_shared/selectExpiredIds-C5W29Upb.mjs +0 -18
- package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +0 -14
- package/dist/packem_shared/serveRelationFanout-k7HTh3CC.mjs +0 -21
- package/dist/packem_shared/stableStringify-mC40mZts.mjs +0 -40
- package/dist/packem_shared/stableWireKey-DKuXO7T5.mjs +0 -6
- package/dist/packem_shared/subscriptionListDeltas-CT76bYny.mjs +0 -126
- package/dist/packem_shared/wire-codec-CzQc1pvf.mjs +0 -225
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
|
-
import { ConflictError } from './ConflictError-CLoq37xH.mjs';
|
|
4
|
-
|
|
5
|
-
const CDC_LOG_TABLE = "__cdc_log";
|
|
6
|
-
const migrateCdcLog = (sql$1) => {
|
|
7
|
-
runDrizzle(
|
|
8
|
-
sql$1,
|
|
9
|
-
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(CDC_LOG_TABLE)} (
|
|
10
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11
|
-
ts REAL NOT NULL,
|
|
12
|
-
${sql.identifier("table")} TEXT NOT NULL,
|
|
13
|
-
id TEXT NOT NULL,
|
|
14
|
-
op TEXT NOT NULL,
|
|
15
|
-
doc TEXT
|
|
16
|
-
)`
|
|
17
|
-
);
|
|
18
|
-
};
|
|
19
|
-
const appendCdcChange = (sql$1, ts, table, id, op, doc) => {
|
|
20
|
-
const docValue = doc === void 0 ? null : JSON.stringify(doc);
|
|
21
|
-
runDrizzle(
|
|
22
|
-
sql$1,
|
|
23
|
-
sql`INSERT INTO ${sql.identifier(CDC_LOG_TABLE)} (ts, ${sql.identifier("table")}, id, op, doc) VALUES (${ts}, ${table}, ${id}, ${op}, ${docValue})`
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
const readCdcChanges = (sql$1, options = {}) => {
|
|
27
|
-
const sinceSeq = options.sinceSeq ?? 0;
|
|
28
|
-
const limit = Math.max(1, Math.min(options.limit ?? 1e3, 1e4));
|
|
29
|
-
const tableFilter = options.tables && options.tables.size > 0 ? sql` AND ${sql.identifier("table")} IN (${sql.join(
|
|
30
|
-
[...options.tables].map((table) => sql`${table}`),
|
|
31
|
-
sql`, `
|
|
32
|
-
)})` : sql``;
|
|
33
|
-
const rows = runDrizzle(
|
|
34
|
-
sql$1,
|
|
35
|
-
sql`SELECT seq, ts, ${sql.identifier("table")}, id, op, doc FROM ${sql.identifier(CDC_LOG_TABLE)} WHERE seq > ${sinceSeq}${tableFilter} ORDER BY seq ASC LIMIT ${limit}`
|
|
36
|
-
).toArray();
|
|
37
|
-
const changes = rows.map((row) => {
|
|
38
|
-
const base = { id: row.id, op: row.op, seq: row.seq, table: row.table, ts: row.ts };
|
|
39
|
-
return row.doc === null ? base : { ...base, doc: JSON.parse(row.doc) };
|
|
40
|
-
});
|
|
41
|
-
return { changes, cursor: changes.at(-1)?.seq ?? sinceSeq };
|
|
42
|
-
};
|
|
43
|
-
const trimCdcChanges = (sql$1, throughSeq) => {
|
|
44
|
-
runDrizzle(sql$1, sql`DELETE FROM ${sql.identifier(CDC_LOG_TABLE)} WHERE seq <= ${throughSeq}`);
|
|
45
|
-
};
|
|
46
|
-
const readCdcCursor = (sql$1) => {
|
|
47
|
-
const seqRow = runDrizzle(sql$1, sql`SELECT seq FROM sqlite_sequence WHERE name = ${CDC_LOG_TABLE}`).toArray();
|
|
48
|
-
const fromSequence = seqRow[0]?.seq;
|
|
49
|
-
if (typeof fromSequence === "number") {
|
|
50
|
-
return fromSequence;
|
|
51
|
-
}
|
|
52
|
-
const rows = runDrizzle(sql$1, sql`SELECT MAX(seq) AS seq FROM ${sql.identifier(CDC_LOG_TABLE)}`).toArray();
|
|
53
|
-
return rows[0]?.seq ?? 0;
|
|
54
|
-
};
|
|
55
|
-
const minCdcSeq = (sql$1) => {
|
|
56
|
-
const rows = runDrizzle(sql$1, sql`SELECT MIN(seq) AS seq FROM ${sql.identifier(CDC_LOG_TABLE)}`).toArray();
|
|
57
|
-
return rows[0]?.seq ?? void 0;
|
|
58
|
-
};
|
|
59
|
-
const CDC_META_TABLE = "__cdc_meta";
|
|
60
|
-
const migrateCdcMeta = (sql$1) => {
|
|
61
|
-
runDrizzle(sql$1, sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(CDC_META_TABLE)} (id INTEGER PRIMARY KEY CHECK (id = 1), epoch TEXT NOT NULL)`);
|
|
62
|
-
};
|
|
63
|
-
const readCdcEpoch = (sql$1) => {
|
|
64
|
-
migrateCdcMeta(sql$1);
|
|
65
|
-
const rows = runDrizzle(sql$1, sql`SELECT epoch FROM ${sql.identifier(CDC_META_TABLE)} WHERE id = 1`).toArray();
|
|
66
|
-
const existing = rows[0]?.epoch;
|
|
67
|
-
if (typeof existing === "string" && existing.length > 0) {
|
|
68
|
-
return existing;
|
|
69
|
-
}
|
|
70
|
-
const minted = crypto.randomUUID();
|
|
71
|
-
runDrizzle(sql$1, sql`INSERT INTO ${sql.identifier(CDC_META_TABLE)} (id, epoch) VALUES (1, ${minted})`);
|
|
72
|
-
return minted;
|
|
73
|
-
};
|
|
74
|
-
const bumpCdcEpoch = (sql$1) => {
|
|
75
|
-
migrateCdcMeta(sql$1);
|
|
76
|
-
const next = crypto.randomUUID();
|
|
77
|
-
runDrizzle(sql$1, sql`INSERT INTO ${sql.identifier(CDC_META_TABLE)} (id, epoch) VALUES (1, ${next}) ON CONFLICT(id) DO UPDATE SET epoch = excluded.epoch`);
|
|
78
|
-
return next;
|
|
79
|
-
};
|
|
80
|
-
const applyCdcChange = async (writer, change) => {
|
|
81
|
-
if (change.op === "delete") {
|
|
82
|
-
await writer.delete(change.id, change.table);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const document = change.doc ?? {};
|
|
86
|
-
try {
|
|
87
|
-
await writer.insert(change.table, document, { allowExplicitId: true });
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (!(error instanceof ConflictError)) {
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
const fields = {};
|
|
93
|
-
for (const [key, value] of Object.entries(document)) {
|
|
94
|
-
if (key !== "_id") {
|
|
95
|
-
fields[key] = value;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
await writer.replace(change.id, fields, void 0, { allowExplicitId: true });
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
const applyCdcChanges = async (writer, changes) => {
|
|
102
|
-
for (const change of changes) {
|
|
103
|
-
await applyCdcChange(writer, change);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export { CDC_LOG_TABLE, CDC_META_TABLE, appendCdcChange, applyCdcChanges, bumpCdcEpoch, migrateCdcLog, migrateCdcMeta, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
class ConflictError extends LunoraError {
|
|
4
|
-
/** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
|
|
5
|
-
kind;
|
|
6
|
-
constructor(message = "Optimistic concurrency conflict", kind = "conflict") {
|
|
7
|
-
super("CONFLICT", message, { name: "ConflictError" });
|
|
8
|
-
this.kind = kind;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export { ConflictError };
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
class CountRlsUnsupportedError extends LunoraError {
|
|
4
|
-
constructor(table) {
|
|
5
|
-
super(
|
|
6
|
-
"COUNT_RLS_UNSUPPORTED",
|
|
7
|
-
table === void 0 ? "count() is not supported in an RLS-restricted context" : `count() is not supported on table "${table}" inside an RLS-restricted context`
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
const mergeWhere = (left, right) => {
|
|
12
|
-
if (!left) {
|
|
13
|
-
return right;
|
|
14
|
-
}
|
|
15
|
-
if (!right) {
|
|
16
|
-
return left;
|
|
17
|
-
}
|
|
18
|
-
return { AND: [left, right] };
|
|
19
|
-
};
|
|
20
|
-
const BOOLEAN_COMBINATORS = /* @__PURE__ */ new Set(["AND", "NOT", "OR"]);
|
|
21
|
-
const NOT_EQ = /* @__PURE__ */ Symbol("not-eq");
|
|
22
|
-
const resolveEqValue = (value) => {
|
|
23
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
24
|
-
const operatorKeys = Object.keys(value);
|
|
25
|
-
if (operatorKeys.length === 1 && operatorKeys[0] === "eq") {
|
|
26
|
-
return value.eq;
|
|
27
|
-
}
|
|
28
|
-
return NOT_EQ;
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
};
|
|
32
|
-
const parseRequestedEqKeys = (requested, accept) => {
|
|
33
|
-
const resolved = {};
|
|
34
|
-
for (const [key, raw] of Object.entries(requested)) {
|
|
35
|
-
if (BOOLEAN_COMBINATORS.has(key) || !accept(key)) {
|
|
36
|
-
return void 0;
|
|
37
|
-
}
|
|
38
|
-
const value = resolveEqValue(raw);
|
|
39
|
-
if (value === NOT_EQ) {
|
|
40
|
-
return void 0;
|
|
41
|
-
}
|
|
42
|
-
resolved[key] = value;
|
|
43
|
-
}
|
|
44
|
-
return resolved;
|
|
45
|
-
};
|
|
46
|
-
const reconcileStaticWhere = (staticWhere, resolved, requested, crossCheckRequested) => {
|
|
47
|
-
const merged = { ...resolved };
|
|
48
|
-
if (!staticWhere) {
|
|
49
|
-
return merged;
|
|
50
|
-
}
|
|
51
|
-
for (const [key, value] of Object.entries(staticWhere)) {
|
|
52
|
-
if (key in merged) {
|
|
53
|
-
if (merged[key] !== value) {
|
|
54
|
-
return void 0;
|
|
55
|
-
}
|
|
56
|
-
} else if (crossCheckRequested && key in requested) {
|
|
57
|
-
if (requested[key] !== value) {
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
merged[key] = value;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return merged;
|
|
65
|
-
};
|
|
66
|
-
const planAggregateLookup = (index, requestedWhere) => {
|
|
67
|
-
const by = index.by ?? [];
|
|
68
|
-
const requested = requestedWhere ?? {};
|
|
69
|
-
const resolved = parseRequestedEqKeys(requested, (key) => by.includes(key));
|
|
70
|
-
if (resolved === void 0) {
|
|
71
|
-
return void 0;
|
|
72
|
-
}
|
|
73
|
-
for (const key of by) {
|
|
74
|
-
if (!(key in resolved)) {
|
|
75
|
-
return void 0;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return reconcileStaticWhere(index.where, resolved, requested, true);
|
|
79
|
-
};
|
|
80
|
-
const collectPartialKey = (index, requestedWhere, byFields) => {
|
|
81
|
-
const partial = parseRequestedEqKeys(requestedWhere ?? {}, (key) => byFields.has(key));
|
|
82
|
-
if (partial === void 0) {
|
|
83
|
-
return void 0;
|
|
84
|
-
}
|
|
85
|
-
return reconcileStaticWhere(index.where, partial, {}, false);
|
|
86
|
-
};
|
|
87
|
-
const selectIndexForReducer = (indexes, op, field, requestedWhere) => {
|
|
88
|
-
let best;
|
|
89
|
-
for (const index of indexes) {
|
|
90
|
-
if (index.op !== op) {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (op !== "count" && index.field !== field) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const key = planAggregateLookup(index, requestedWhere);
|
|
97
|
-
if (!key) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (!best || (index.by?.length ?? 0) > (best.index.by?.length ?? 0)) {
|
|
101
|
-
best = { index, key };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return best;
|
|
105
|
-
};
|
|
106
|
-
const selectIndexForCount = (indexes, requestedWhere) => selectIndexForReducer(indexes, "count", void 0, requestedWhere);
|
|
107
|
-
const selectIndexForAggregate = (indexes, op, field, requestedWhere) => selectIndexForReducer(indexes, op, field, requestedWhere);
|
|
108
|
-
const selectIndexForGroupBy = (indexes, op, field, by, requestedWhere) => {
|
|
109
|
-
const requestedFields = new Set(by);
|
|
110
|
-
for (const index of indexes) {
|
|
111
|
-
if (index.op !== op) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (op !== "count" && index.field !== field) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
const indexBy = index.by ?? [];
|
|
118
|
-
if (indexBy.length !== requestedFields.size) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (!indexBy.every((key) => requestedFields.has(key))) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const partial = collectPartialKey(index, requestedWhere, requestedFields);
|
|
125
|
-
if (partial === void 0) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
return { index, partial };
|
|
129
|
-
}
|
|
130
|
-
return void 0;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy };
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
const DATA_MIGRATION_STATE_TABLE = "__lunora_migrations";
|
|
4
|
-
const DEFAULT_BATCH_SIZE = 100;
|
|
5
|
-
const STALE_CLAIM_TIMEOUT_MS = 3e4;
|
|
6
|
-
const CLAIM_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
7
|
-
const runSql = (sql, query, ...params) => {
|
|
8
|
-
const runner = sql.exec;
|
|
9
|
-
return runner.call(sql, query, ...params);
|
|
10
|
-
};
|
|
11
|
-
const ensureStateTable = (sql) => {
|
|
12
|
-
runSql(
|
|
13
|
-
sql,
|
|
14
|
-
`CREATE TABLE IF NOT EXISTS "${DATA_MIGRATION_STATE_TABLE}" (
|
|
15
|
-
id TEXT PRIMARY KEY,
|
|
16
|
-
direction TEXT NOT NULL,
|
|
17
|
-
status TEXT NOT NULL,
|
|
18
|
-
cursor TEXT,
|
|
19
|
-
processed INTEGER NOT NULL DEFAULT 0,
|
|
20
|
-
changed INTEGER NOT NULL DEFAULT 0,
|
|
21
|
-
started_at REAL,
|
|
22
|
-
updated_at REAL,
|
|
23
|
-
error TEXT
|
|
24
|
-
)`
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
const readState = (sql, id) => {
|
|
28
|
-
const rows = runSql(sql, `SELECT * FROM "${DATA_MIGRATION_STATE_TABLE}" WHERE id = ?`, id).toArray();
|
|
29
|
-
const row = rows[0];
|
|
30
|
-
if (!row) {
|
|
31
|
-
return void 0;
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
changed: row.changed,
|
|
35
|
-
// eslint-disable-next-line unicorn/no-null -- mirrors the SQLite `cursor` column: a missing cursor is NULL, not undefined
|
|
36
|
-
cursor: typeof row.cursor === "string" ? row.cursor : null,
|
|
37
|
-
direction: row.direction === "down" ? "down" : "up",
|
|
38
|
-
processed: row.processed,
|
|
39
|
-
startedAt: typeof row.started_at === "number" ? row.started_at : void 0,
|
|
40
|
-
status: row.status === "completed" || row.status === "failed" ? row.status : "in_progress"
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
const deleteState = (sql, id) => {
|
|
44
|
-
runSql(sql, `DELETE FROM "${DATA_MIGRATION_STATE_TABLE}" WHERE id = ?`, id);
|
|
45
|
-
};
|
|
46
|
-
const persistState = (sql, state) => {
|
|
47
|
-
runSql(
|
|
48
|
-
sql,
|
|
49
|
-
`INSERT INTO "${DATA_MIGRATION_STATE_TABLE}"
|
|
50
|
-
(id, direction, status, cursor, processed, changed, started_at, updated_at, error)
|
|
51
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
52
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
53
|
-
direction = excluded.direction,
|
|
54
|
-
status = excluded.status,
|
|
55
|
-
cursor = excluded.cursor,
|
|
56
|
-
processed = excluded.processed,
|
|
57
|
-
changed = excluded.changed,
|
|
58
|
-
updated_at = excluded.updated_at,
|
|
59
|
-
error = excluded.error`,
|
|
60
|
-
state.id,
|
|
61
|
-
state.direction,
|
|
62
|
-
state.status,
|
|
63
|
-
state.cursor,
|
|
64
|
-
state.processed,
|
|
65
|
-
state.changed,
|
|
66
|
-
state.startedAt,
|
|
67
|
-
state.updatedAt,
|
|
68
|
-
state.error
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
const claimMigration = (sql, id, direction, now) => {
|
|
72
|
-
runSql(
|
|
73
|
-
sql,
|
|
74
|
-
`INSERT INTO "${DATA_MIGRATION_STATE_TABLE}"
|
|
75
|
-
(id, direction, status, cursor, processed, changed, started_at, updated_at, error)
|
|
76
|
-
VALUES (?, ?, 'in_progress', NULL, 0, 0, ?, ?, NULL)
|
|
77
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
78
|
-
status = 'in_progress',
|
|
79
|
-
updated_at = excluded.updated_at
|
|
80
|
-
WHERE
|
|
81
|
-
"${DATA_MIGRATION_STATE_TABLE}".direction <> excluded.direction
|
|
82
|
-
OR "${DATA_MIGRATION_STATE_TABLE}".status <> 'in_progress'
|
|
83
|
-
OR "${DATA_MIGRATION_STATE_TABLE}".updated_at IS NULL
|
|
84
|
-
OR "${DATA_MIGRATION_STATE_TABLE}".updated_at <= excluded.updated_at - ${String(STALE_CLAIM_TIMEOUT_MS)}`,
|
|
85
|
-
id,
|
|
86
|
-
direction,
|
|
87
|
-
now,
|
|
88
|
-
now
|
|
89
|
-
);
|
|
90
|
-
return runSql(sql, `SELECT changes() AS changed`).one().changed > 0;
|
|
91
|
-
};
|
|
92
|
-
const releaseClaim = (sql, id) => {
|
|
93
|
-
runSql(sql, `UPDATE "${DATA_MIGRATION_STATE_TABLE}" SET updated_at = 0 WHERE id = ? AND status = 'in_progress'`, id);
|
|
94
|
-
};
|
|
95
|
-
const touchClaim = (sql, id, now) => {
|
|
96
|
-
runSql(sql, `UPDATE "${DATA_MIGRATION_STATE_TABLE}" SET updated_at = ? WHERE id = ? AND status = 'in_progress'`, now, id);
|
|
97
|
-
};
|
|
98
|
-
const readMigrationStatus = (sql, id) => {
|
|
99
|
-
const exists = runSql(
|
|
100
|
-
sql,
|
|
101
|
-
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
|
|
102
|
-
DATA_MIGRATION_STATE_TABLE
|
|
103
|
-
).toArray();
|
|
104
|
-
if (exists.length === 0) {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
const filter = id === void 0 ? " ORDER BY id" : " WHERE id = ?";
|
|
108
|
-
const params = id === void 0 ? [] : [id];
|
|
109
|
-
const rows = runSql(sql, `SELECT * FROM "${DATA_MIGRATION_STATE_TABLE}"${filter}`, ...params).toArray();
|
|
110
|
-
return rows.map((row) => {
|
|
111
|
-
return {
|
|
112
|
-
changed: row.changed,
|
|
113
|
-
cursor: typeof row.cursor === "string" ? row.cursor : null,
|
|
114
|
-
direction: row.direction === "down" ? "down" : "up",
|
|
115
|
-
error: typeof row.error === "string" ? row.error : null,
|
|
116
|
-
id: row.id,
|
|
117
|
-
processed: row.processed,
|
|
118
|
-
startedAt: typeof row.started_at === "number" ? row.started_at : null,
|
|
119
|
-
status: row.status === "completed" || row.status === "failed" ? row.status : "in_progress",
|
|
120
|
-
updatedAt: typeof row.updated_at === "number" ? row.updated_at : null
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
const runDataMigration = async (options) => {
|
|
125
|
-
const { migration, sql, writer } = options;
|
|
126
|
-
const direction = options.direction ?? "up";
|
|
127
|
-
const dryRun = options.dryRun ?? false;
|
|
128
|
-
const clock = options.clock ?? (() => Date.now());
|
|
129
|
-
const maxBatches = options.maxBatches ?? Number.POSITIVE_INFINITY;
|
|
130
|
-
const batchSize = options.batchSize ?? migration.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
131
|
-
const transform = direction === "up" ? migration.up : migration.down;
|
|
132
|
-
if (!transform) {
|
|
133
|
-
throw new LunoraError("INTERNAL", `data migration "${migration.id}" has no \`${direction}\` transform`);
|
|
134
|
-
}
|
|
135
|
-
let cursor = null;
|
|
136
|
-
let processed = 0;
|
|
137
|
-
let changed = 0;
|
|
138
|
-
let startedAt = clock();
|
|
139
|
-
if (!dryRun) {
|
|
140
|
-
ensureStateTable(sql);
|
|
141
|
-
const existing = readState(sql, migration.id);
|
|
142
|
-
if (existing?.direction === direction && existing.status === "completed") {
|
|
143
|
-
return { changed: existing.changed, cursor: null, direction, dryRun, id: migration.id, processed: existing.processed, status: "completed" };
|
|
144
|
-
}
|
|
145
|
-
if (existing && existing.direction !== direction) {
|
|
146
|
-
deleteState(sql, migration.id);
|
|
147
|
-
}
|
|
148
|
-
const claimed = claimMigration(sql, migration.id, direction, clock());
|
|
149
|
-
if (!claimed) {
|
|
150
|
-
const active = readState(sql, migration.id);
|
|
151
|
-
return {
|
|
152
|
-
changed: active?.changed ?? 0,
|
|
153
|
-
cursor: active?.cursor ?? cursor,
|
|
154
|
-
direction,
|
|
155
|
-
dryRun,
|
|
156
|
-
id: migration.id,
|
|
157
|
-
processed: active?.processed ?? 0,
|
|
158
|
-
status: active?.status ?? "in_progress"
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const resume = existing?.direction === direction ? existing : void 0;
|
|
162
|
-
if (resume) {
|
|
163
|
-
cursor = resume.cursor;
|
|
164
|
-
processed = resume.processed;
|
|
165
|
-
changed = resume.changed;
|
|
166
|
-
startedAt = resume.startedAt ?? startedAt;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
let isDone = false;
|
|
170
|
-
let batches = 0;
|
|
171
|
-
let lastHeartbeatAt = startedAt;
|
|
172
|
-
try {
|
|
173
|
-
while (!isDone && batches < maxBatches) {
|
|
174
|
-
const batch = await writer.findMany(migration.table, { cursor, limit: batchSize });
|
|
175
|
-
for (const document of batch.page) {
|
|
176
|
-
processed += 1;
|
|
177
|
-
const next = transform(document);
|
|
178
|
-
if (next !== void 0) {
|
|
179
|
-
changed += 1;
|
|
180
|
-
if (!dryRun) {
|
|
181
|
-
await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] }, void 0, {
|
|
182
|
-
allowExplicitId: true
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (!dryRun) {
|
|
187
|
-
const now = clock();
|
|
188
|
-
if (now - lastHeartbeatAt >= CLAIM_HEARTBEAT_INTERVAL_MS) {
|
|
189
|
-
touchClaim(sql, migration.id, now);
|
|
190
|
-
lastHeartbeatAt = now;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
cursor = batch.continueCursor;
|
|
195
|
-
isDone = batch.isDone;
|
|
196
|
-
batches += 1;
|
|
197
|
-
if (!dryRun) {
|
|
198
|
-
const batchEndedAt = clock();
|
|
199
|
-
persistState(sql, {
|
|
200
|
-
changed,
|
|
201
|
-
// eslint-disable-next-line unicorn/no-null -- bound to the SQLite cursor column: a finished run stores NULL
|
|
202
|
-
cursor: isDone ? null : cursor,
|
|
203
|
-
direction,
|
|
204
|
-
// eslint-disable-next-line unicorn/no-null -- bound to the SQLite error column: a clean batch stores NULL
|
|
205
|
-
error: null,
|
|
206
|
-
id: migration.id,
|
|
207
|
-
processed,
|
|
208
|
-
startedAt,
|
|
209
|
-
status: isDone ? "completed" : "in_progress",
|
|
210
|
-
updatedAt: batchEndedAt
|
|
211
|
-
});
|
|
212
|
-
lastHeartbeatAt = batchEndedAt;
|
|
213
|
-
try {
|
|
214
|
-
await options.onBatch?.({ batches, changed, processed });
|
|
215
|
-
} catch {
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
if (!dryRun) {
|
|
221
|
-
persistState(sql, {
|
|
222
|
-
changed,
|
|
223
|
-
cursor,
|
|
224
|
-
direction,
|
|
225
|
-
error: error instanceof Error ? error.message : String(error),
|
|
226
|
-
id: migration.id,
|
|
227
|
-
processed,
|
|
228
|
-
startedAt,
|
|
229
|
-
status: "failed",
|
|
230
|
-
updatedAt: clock()
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
if (!dryRun && !isDone) {
|
|
236
|
-
try {
|
|
237
|
-
releaseClaim(sql, migration.id);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
console.warn(`data migration "${migration.id}": releaseClaim failed`, error);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return { changed, cursor: isDone ? null : cursor, direction, dryRun, id: migration.id, processed, status: isDone ? "completed" : "in_progress" };
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration };
|