@lunora/do 0.0.0 → 1.0.0-alpha.10

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.
Files changed (50) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +115 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.mts +6347 -0
  5. package/dist/index.d.ts +6347 -0
  6. package/dist/index.mjs +37 -0
  7. package/dist/packem_shared/ADMIN_FUNCTIONS-D_UiYJFk.mjs +316 -0
  8. package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs +54 -0
  9. package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs +84 -0
  10. package/dist/packem_shared/CDC_LOG_TABLE-DSycmnDf.mjs +107 -0
  11. package/dist/packem_shared/ConflictError-C0STs6bU.mjs +13 -0
  12. package/dist/packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs +133 -0
  13. package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs +237 -0
  14. package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs +209 -0
  15. package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs +248 -0
  16. package/dist/packem_shared/LogBuffer-B_Ezju_N.mjs +37 -0
  17. package/dist/packem_shared/MAIL_RETENTION-CPpgl-dX.mjs +104 -0
  18. package/dist/packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs +29 -0
  19. package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs +1 -0
  20. package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +10 -0
  21. package/dist/packem_shared/NotUniqueError-DZQtH02h.mjs +1835 -0
  22. package/dist/packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs +91 -0
  23. package/dist/packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs +132 -0
  24. package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-DKwBF3Jp.mjs +4996 -0
  25. package/dist/packem_shared/ReactiveCache-1hDydFyv.mjs +232 -0
  26. package/dist/packem_shared/SCAN_DEP-DLJF8dsj.mjs +19 -0
  27. package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs +180 -0
  28. package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs +146 -0
  29. package/dist/packem_shared/aggregateTableName-CxNqY1Sl.mjs +64 -0
  30. package/dist/packem_shared/applyOnDelete-BQ-8ZlZ1.mjs +175 -0
  31. package/dist/packem_shared/applySelect-BvZdFUBT.mjs +101 -0
  32. package/dist/packem_shared/armRestore-BJk53Ro8.mjs +55 -0
  33. package/dist/packem_shared/backfillAggregateIndexes-BZsOqDXP.mjs +81 -0
  34. package/dist/packem_shared/buildFtsMatch-BLEMawrp.mjs +38 -0
  35. package/dist/packem_shared/compileWhereSql-CXrhFA3G.mjs +127 -0
  36. package/dist/packem_shared/createSystemReader-8CzSZP9V.mjs +80 -0
  37. package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
  38. package/dist/packem_shared/ctx-db-shapes-DVoeZpo-.mjs +53 -0
  39. package/dist/packem_shared/do-exec-5eQy5cEi.mjs +12 -0
  40. package/dist/packem_shared/do-sql-BCHCWtrD.mjs +87 -0
  41. package/dist/packem_shared/exportShardRows-DZEhUeyI.mjs +156 -0
  42. package/dist/packem_shared/hasTrigger-5N6_Fx0A.mjs +20 -0
  43. package/dist/packem_shared/renderSql-D6eUcn2N.mjs +16 -0
  44. package/dist/packem_shared/runShardMigrations-nIwoQeOK.mjs +105 -0
  45. package/dist/packem_shared/security-audit-CucgBice.mjs +158 -0
  46. package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
  47. package/dist/packem_shared/serveRelationFanout-C6lDaesn.mjs +24 -0
  48. package/dist/packem_shared/stableStringify-CyHKJXre.mjs +30 -0
  49. package/dist/packem_shared/subscriptionListDeltas-ce84gpwL.mjs +111 -0
  50. package/package.json +41 -17
@@ -0,0 +1,175 @@
1
+ import { applySelect } from './applySelect-BvZdFUBT.mjs';
2
+
3
+ const projectChildren = (documents, nested) => nested.select ? applySelect(documents, nested.select, nested.with) : documents;
4
+ const fanOutScalarCounts = async (counter, tableName, whereField, values, policyWhere) => {
5
+ const pairs = await Promise.all(
6
+ values.map(async (value) => {
7
+ const countWhere = policyWhere ? { AND: [{ [whereField]: value }, policyWhere] } : { [whereField]: value };
8
+ return [value, await counter(tableName, countWhere)];
9
+ })
10
+ );
11
+ return new Map(pairs);
12
+ };
13
+ const distinctValues = (rows, field) => {
14
+ const seen = /* @__PURE__ */ new Set();
15
+ for (const row of rows) {
16
+ const value = row[field];
17
+ if (value !== null && value !== void 0) {
18
+ seen.add(value);
19
+ }
20
+ }
21
+ return [...seen];
22
+ };
23
+ const resolveWith = async (options) => {
24
+ const { groupedCounter, fetcher, parents, relationBaseWhere, schema, tableName, with: withInput } = options;
25
+ if (parents.length === 0) {
26
+ return;
27
+ }
28
+ const parentDefinition = schema.tables[tableName];
29
+ if (!parentDefinition) {
30
+ throw new Error(`unknown table: ${tableName}`);
31
+ }
32
+ const relationMap = parentDefinition.relationMap ?? {};
33
+ const requireRelation = (name) => {
34
+ const relation = relationMap[name];
35
+ if (!relation) {
36
+ throw new Error(`unknown relation "${name}" on table "${tableName}"`);
37
+ }
38
+ return relation;
39
+ };
40
+ const loadOne = async (name, relation, nested) => {
41
+ const fkValues = distinctValues(parents, relation.field);
42
+ if (fkValues.length === 0) {
43
+ for (const parent of parents) {
44
+ parent[name] = null;
45
+ }
46
+ return;
47
+ }
48
+ const { page } = await fetcher(relation.table, {
49
+ baseWhere: relationBaseWhere?.(relation.table),
50
+ relationBaseWhere,
51
+ where: { [relation.references]: { in: fkValues } },
52
+ with: nested.with
53
+ });
54
+ const byReference = /* @__PURE__ */ new Map();
55
+ for (const child of page) {
56
+ byReference.set(child[relation.references], child);
57
+ }
58
+ for (const parent of parents) {
59
+ const child = byReference.get(parent[relation.field]);
60
+ parent[name] = child ? projectChildren([child], nested)[0] ?? null : null;
61
+ }
62
+ };
63
+ const loadMany = async (name, relation, nested) => {
64
+ const referenceValues = distinctValues(parents, relation.references);
65
+ if (referenceValues.length === 0) {
66
+ for (const parent of parents) {
67
+ parent[name] = [];
68
+ }
69
+ return;
70
+ }
71
+ const fkFilter = { [relation.field]: { in: referenceValues } };
72
+ const where = nested.where ? { AND: [nested.where, fkFilter] } : fkFilter;
73
+ const { page } = await fetcher(relation.table, {
74
+ baseWhere: relationBaseWhere?.(relation.table),
75
+ orderBy: nested.orderBy,
76
+ relationBaseWhere,
77
+ where,
78
+ with: nested.with
79
+ });
80
+ const groups = /* @__PURE__ */ new Map();
81
+ for (const child of page) {
82
+ const key = child[relation.field];
83
+ const group = groups.get(key);
84
+ if (group) {
85
+ group.push(child);
86
+ } else {
87
+ groups.set(key, [child]);
88
+ }
89
+ }
90
+ const cap = typeof nested.limit === "number" ? Math.max(0, Math.floor(nested.limit)) : void 0;
91
+ for (const parent of parents) {
92
+ const group = groups.get(parent[relation.references]) ?? [];
93
+ parent[name] = projectChildren(cap === void 0 ? group : group.slice(0, cap), nested);
94
+ }
95
+ };
96
+ const resolveCounts = async (countInput) => {
97
+ for (const name of Object.keys(countInput)) {
98
+ const relation = requireRelation(name);
99
+ const [whereField, parentField] = relation.kind === "many" ? [relation.field, relation.references] : [relation.references, relation.field];
100
+ const policyWhere = relationBaseWhere?.(relation.table);
101
+ const values = distinctValues(parents, parentField);
102
+ const countByValue = values.length === 0 ? /* @__PURE__ */ new Map() : await groupedCounter(relation.table, whereField, values, policyWhere);
103
+ for (const parent of parents) {
104
+ const counts = parent["_count"] ?? {};
105
+ const parentValue = parent[parentField];
106
+ counts[name] = parentValue === null || parentValue === void 0 ? 0 : countByValue.get(parentValue) ?? 0;
107
+ parent["_count"] = counts;
108
+ }
109
+ }
110
+ };
111
+ for (const [name, value] of Object.entries(withInput)) {
112
+ if (value === void 0 || value === false) {
113
+ continue;
114
+ }
115
+ if (name === "_count") {
116
+ await resolveCounts(value);
117
+ continue;
118
+ }
119
+ const relation = requireRelation(name);
120
+ const nested = value === true ? {} : value;
121
+ await (relation.kind === "one" ? loadOne(name, relation, nested) : loadMany(name, relation, nested));
122
+ }
123
+ };
124
+ const applyRelationOnDelete = async (options, holderTable, relation) => {
125
+ const { deletedId, deletedReference, findHolders, onCascade, onRestrict, onSetNull, tableName } = options;
126
+ const referencedValue = relation.references === "_id" ? deletedId : deletedReference(relation.references);
127
+ if (referencedValue === null || referencedValue === void 0) {
128
+ return;
129
+ }
130
+ const holders = await findHolders(holderTable, relation.field, referencedValue);
131
+ if (holders.length === 0) {
132
+ return;
133
+ }
134
+ if (relation.onDelete === "restrict") {
135
+ onRestrict(`cannot delete "${tableName}" row: "${holderTable}.${relation.field}" still references it`);
136
+ }
137
+ for (const holder of holders) {
138
+ const holderId = holder["_id"];
139
+ if (typeof holderId !== "string") {
140
+ continue;
141
+ }
142
+ await (relation.onDelete === "cascade" ? onCascade(holderTable, holderId) : onSetNull(holderTable, holderId, relation.field));
143
+ }
144
+ };
145
+ const applyOnDelete = async (options) => {
146
+ const { schema, tableName } = options;
147
+ if (!schema.tables[tableName]) {
148
+ throw new Error(`unknown table: ${tableName}`);
149
+ }
150
+ for (const [holderTable, holderDefinition] of Object.entries(schema.tables)) {
151
+ const relations = holderDefinition.relationMap;
152
+ if (!relations) {
153
+ continue;
154
+ }
155
+ for (const relation of Object.values(relations)) {
156
+ if (relation.kind !== "one" || relation.table !== tableName || !relation.onDelete) {
157
+ continue;
158
+ }
159
+ await applyRelationOnDelete(options, holderTable, relation);
160
+ }
161
+ }
162
+ };
163
+ const runRowValidators = (definition, document) => {
164
+ for (const [field, validator] of Object.entries(definition.shape)) {
165
+ if (!(field in document)) {
166
+ continue;
167
+ }
168
+ if (typeof validator.parse !== "function") {
169
+ continue;
170
+ }
171
+ validator.parse(document[field]);
172
+ }
173
+ };
174
+
175
+ export { applyOnDelete, distinctValues, fanOutScalarCounts, resolveWith, runRowValidators };
@@ -0,0 +1,101 @@
1
+ const TIEBREAK_FIELD = "id";
2
+ const ID_FIELDS = /* @__PURE__ */ new Set(["_id", "id"]);
3
+ const normalizeOrderKeys = (orderBy) => {
4
+ const keys = [];
5
+ for (const entry of orderBy ?? []) {
6
+ for (const [field, direction] of Object.entries(entry)) {
7
+ keys.push({ direction, field });
8
+ }
9
+ }
10
+ if (keys.length === 0) {
11
+ return [{ direction: "asc", field: "_creationTime" }];
12
+ }
13
+ return keys;
14
+ };
15
+ const toBase64 = (text) => {
16
+ const bytes = new TextEncoder().encode(text);
17
+ let binary = "";
18
+ for (const byte of bytes) {
19
+ binary += String.fromCodePoint(byte);
20
+ }
21
+ return btoa(binary);
22
+ };
23
+ const fromBase64 = (encoded) => {
24
+ const binary = atob(encoded);
25
+ const bytes = Uint8Array.from(binary, (character) => character.codePointAt(0) ?? 0);
26
+ return new TextDecoder().decode(bytes);
27
+ };
28
+ const encodeCursor = (record, keys) => {
29
+ const values = keys.map((key) => record[key.field]);
30
+ values.push(record["_id"]);
31
+ return toBase64(JSON.stringify(values));
32
+ };
33
+ const invalidCursor = () => Object.assign(new TypeError("invalid cursor"), { code: "BAD_REQUEST", name: "LunoraError", status: 400 });
34
+ const decodeCursor = (cursor) => {
35
+ let decoded;
36
+ try {
37
+ decoded = JSON.parse(fromBase64(cursor));
38
+ } catch {
39
+ throw invalidCursor();
40
+ }
41
+ if (!Array.isArray(decoded)) {
42
+ throw invalidCursor();
43
+ }
44
+ return decoded;
45
+ };
46
+ const buildSeekWhere = (keys, cursorValues) => {
47
+ const columns = keys.some((key) => ID_FIELDS.has(key.field)) ? keys : [...keys, { direction: "asc", field: TIEBREAK_FIELD }];
48
+ const branches = [];
49
+ for (const [pivot, pivotColumn] of columns.entries()) {
50
+ const conditions = [];
51
+ for (const [prefix, prefixColumn] of columns.slice(0, pivot).entries()) {
52
+ conditions.push({ [prefixColumn.field]: { eq: cursorValues[prefix] } });
53
+ }
54
+ const strictOperator = pivotColumn.direction === "desc" ? "lt" : "gt";
55
+ conditions.push({ [pivotColumn.field]: { [strictOperator]: cursorValues[pivot] } });
56
+ const [first] = conditions;
57
+ branches.push(conditions.length === 1 && first !== void 0 ? first : { AND: conditions });
58
+ }
59
+ return { OR: branches };
60
+ };
61
+ const seekBeforeOperator = (direction, isFinal) => {
62
+ if (direction === "desc") {
63
+ return isFinal ? "gte" : "gt";
64
+ }
65
+ return isFinal ? "lte" : "lt";
66
+ };
67
+ const buildSeekBeforeWhere = (keys, cursorValues) => {
68
+ const columns = keys.some((key) => ID_FIELDS.has(key.field)) ? keys : [...keys, { direction: "asc", field: TIEBREAK_FIELD }];
69
+ const branches = [];
70
+ for (const [pivot, pivotColumn] of columns.entries()) {
71
+ const conditions = [];
72
+ for (const [prefix, prefixColumn] of columns.slice(0, pivot).entries()) {
73
+ conditions.push({ [prefixColumn.field]: { eq: cursorValues[prefix] } });
74
+ }
75
+ const isFinal = pivot === columns.length - 1;
76
+ const operator = seekBeforeOperator(pivotColumn.direction, isFinal);
77
+ conditions.push({ [pivotColumn.field]: { [operator]: cursorValues[pivot] } });
78
+ const [first] = conditions;
79
+ branches.push(conditions.length === 1 && first !== void 0 ? first : { AND: conditions });
80
+ }
81
+ return { OR: branches };
82
+ };
83
+ const SELECT_SYSTEM_FIELDS = ["_id", "_creationTime"];
84
+ const applySelect = (page, select, withInput) => {
85
+ if (!select) {
86
+ return page;
87
+ }
88
+ const keep = /* @__PURE__ */ new Set([...select, ...SELECT_SYSTEM_FIELDS, ...withInput ? Object.keys(withInput) : []]);
89
+ return page.map((document) => {
90
+ const projected = {};
91
+ for (const key of keep) {
92
+ if (key in document) {
93
+ projected[key] = document[key];
94
+ }
95
+ }
96
+ return projected;
97
+ });
98
+ };
99
+ const softDeleteScope = (softDeleteMode, includeDeleted) => softDeleteMode && includeDeleted !== true ? { [softDeleteMode.field]: { isNull: true } } : void 0;
100
+
101
+ export { applySelect, buildSeekBeforeWhere, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope };
@@ -0,0 +1,55 @@
1
+ const pitrUnavailable = () => Object.assign(
2
+ new Error("native PITR is unavailable here (local dev or a non-SQLite Durable Object); use `lunora backup restore` for off-platform recovery"),
3
+ {
4
+ code: "PITR_UNAVAILABLE",
5
+ name: "LunoraError",
6
+ status: 409
7
+ }
8
+ );
9
+ const toTime = (time) => {
10
+ if (typeof time === "number") {
11
+ return time;
12
+ }
13
+ const parsed = Date.parse(time);
14
+ if (Number.isNaN(parsed)) {
15
+ throw Object.assign(new Error(`pitr: invalid time "${time}" — expected epoch-ms or an ISO timestamp`), {
16
+ code: "BAD_REQUEST",
17
+ name: "LunoraError",
18
+ status: 400
19
+ });
20
+ }
21
+ return parsed;
22
+ };
23
+ const readBookmark = async (storage, time) => {
24
+ if (!storage.getCurrentBookmark) {
25
+ throw pitrUnavailable();
26
+ }
27
+ const current = await storage.getCurrentBookmark();
28
+ if (time === void 0 || !storage.getBookmarkForTime) {
29
+ return { current };
30
+ }
31
+ return { current, forTime: await storage.getBookmarkForTime(toTime(time)) };
32
+ };
33
+ const armRestore = async (storage, args) => {
34
+ if (!storage.onNextSessionRestoreBookmark) {
35
+ throw pitrUnavailable();
36
+ }
37
+ let target = args.bookmark;
38
+ if (target === void 0) {
39
+ if (args.time === void 0) {
40
+ throw Object.assign(new Error("pitrRestore: provide a `bookmark` or a `time` to restore to"), {
41
+ code: "BAD_REQUEST",
42
+ name: "LunoraError",
43
+ status: 400
44
+ });
45
+ }
46
+ if (!storage.getBookmarkForTime) {
47
+ throw pitrUnavailable();
48
+ }
49
+ target = await storage.getBookmarkForTime(toTime(args.time));
50
+ }
51
+ const undoBookmark = await storage.onNextSessionRestoreBookmark(target);
52
+ return { restoredTo: target, undoBookmark };
53
+ };
54
+
55
+ export { armRestore, readBookmark };
@@ -0,0 +1,81 @@
1
+ import { sql } from 'drizzle-orm';
2
+ import { matchesStaticWhere } from './AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs';
3
+ import { encodeAggregateKey, foldAggregateTally, aggregateTableName } from './aggregateTableName-CxNqY1Sl.mjs';
4
+ import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
5
+ import { D as DOC_COLUMN, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-BCHCWtrD.mjs';
6
+ import { param } from './renderSql-D6eUcn2N.mjs';
7
+ import { sortColumnName, matchesRankStaticWhere, encodePartitionKey, rankTableName } from './RANK_TIEBREAK-CXhdcA1o.mjs';
8
+ import { s as serializeSqlValue } from './serialize-sql-BlRUoiQe.mjs';
9
+
10
+ const backfillAggregateIndex = (sql$1, tableName, index) => {
11
+ const aggTable = aggregateTableName(tableName, index.name);
12
+ const existing = runDrizzle(sql$1, sql`SELECT COUNT(*) AS count FROM ${sql.identifier(aggTable)}`).one();
13
+ if (existing.count > 0) {
14
+ return;
15
+ }
16
+ const by = index.by ?? [];
17
+ const tallies = /* @__PURE__ */ new Map();
18
+ const rows = runDrizzle(sql$1, sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN)} FROM ${sql.identifier(tableName)}`).toArray();
19
+ for (const row of rows) {
20
+ const record = rowToDocument(row);
21
+ if (!record || index.where && !matchesStaticWhere(record, index.where)) {
22
+ continue;
23
+ }
24
+ const encoded = encodeAggregateKey(by, record);
25
+ foldAggregateTally(tallies, encoded, index, record);
26
+ }
27
+ for (const [encoded, tally] of tallies) {
28
+ runDrizzle(
29
+ sql$1,
30
+ sql`INSERT INTO ${sql.identifier(aggTable)} (${AGG_KEY}, ${AGG_VALUE}, ${AGG_COUNT}) VALUES (${encoded}, ${tally.value}, ${tally.count})`
31
+ );
32
+ }
33
+ };
34
+ const backfillAggregateIndexes = (sql, schema) => {
35
+ for (const [tableName, definition] of Object.entries(schema.tables)) {
36
+ if (definition.shardMode?.kind === "global" || !definition.aggregateIndexes) {
37
+ continue;
38
+ }
39
+ for (const index of definition.aggregateIndexes) {
40
+ backfillAggregateIndex(sql, tableName, index);
41
+ }
42
+ }
43
+ };
44
+ const backfillRankIndex = (sql$1, tableName, index) => {
45
+ const rankTable = rankTableName(tableName, index.name);
46
+ const existing = runDrizzle(sql$1, sql`SELECT COUNT(*) AS count FROM ${sql.identifier(rankTable)}`).one();
47
+ if (existing.count > 0) {
48
+ return;
49
+ }
50
+ const sortColumns = index.sortBy.map((_, i) => sortColumnName(i));
51
+ const columnsSql = sql.join(
52
+ ["__id__", "__partition__", ...sortColumns].map((column) => sql.identifier(column)),
53
+ sql`, `
54
+ );
55
+ const rows = runDrizzle(sql$1, sql`SELECT id, _creationTime, ${sql.identifier(DOC_COLUMN)} FROM ${sql.identifier(tableName)}`).toArray();
56
+ for (const row of rows) {
57
+ const record = rowToDocument(row);
58
+ if (!record || index.where && !matchesRankStaticWhere(record, index.where)) {
59
+ continue;
60
+ }
61
+ const partitionKey = encodePartitionKey(index.partitionBy ?? [], record);
62
+ const sortValues = index.sortBy.map((key) => serializeSqlValue(record[key.field] ?? null));
63
+ const valuesSql = sql.join(
64
+ [record["_id"], partitionKey, ...sortValues].map((value) => param(value)),
65
+ sql`, `
66
+ );
67
+ runDrizzle(sql$1, sql`INSERT INTO ${sql.identifier(rankTable)} (${columnsSql}) VALUES (${valuesSql})`);
68
+ }
69
+ };
70
+ const backfillRankIndexes = (sql, schema) => {
71
+ for (const [tableName, definition] of Object.entries(schema.tables)) {
72
+ if (definition.shardMode?.kind === "global" || !definition.rankIndexes) {
73
+ continue;
74
+ }
75
+ for (const index of definition.rankIndexes) {
76
+ backfillRankIndex(sql, tableName, index);
77
+ }
78
+ }
79
+ };
80
+
81
+ export { backfillAggregateIndexes, backfillRankIndexes };
@@ -0,0 +1,38 @@
1
+ const ftsTableName = (table, indexName) => `${table}__fts_${indexName}`;
2
+ const tokenizeSearch = (query) => query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
3
+ const buildFtsMatch = (tokens) => tokens.map((token, index) => index === tokens.length - 1 ? `"${token}"*` : `"${token}"`).join(" AND ");
4
+ const stringifySearchText = (value) => {
5
+ if (typeof value === "string") {
6
+ return value;
7
+ }
8
+ if (value === null || value === void 0) {
9
+ return "";
10
+ }
11
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
12
+ return String(value);
13
+ }
14
+ return JSON.stringify(value) ?? "";
15
+ };
16
+ const scoreDocument = (text, tokens) => {
17
+ const documentTokens = tokenizeSearch(text);
18
+ if (documentTokens.length === 0) {
19
+ return 0;
20
+ }
21
+ let score = 0;
22
+ for (const [index, token] of tokens.entries()) {
23
+ const isLast = index === tokens.length - 1;
24
+ let occurrences = 0;
25
+ for (const documentToken of documentTokens) {
26
+ if (isLast ? documentToken.startsWith(token) : documentToken === token) {
27
+ occurrences += 1;
28
+ }
29
+ }
30
+ if (occurrences === 0) {
31
+ return 0;
32
+ }
33
+ score += occurrences;
34
+ }
35
+ return score;
36
+ };
37
+
38
+ export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch };
@@ -0,0 +1,127 @@
1
+ import { sql } from 'drizzle-orm';
2
+
3
+ const RELATION_EXISTS_KEY = "__relationExists";
4
+ const OPERATOR_KEYS = ["eq", "ne", "lt", "lte", "gt", "gte", "in", "notIn", "isNull", "contains"];
5
+ const OPERATOR_KEY_SET = new Set(OPERATOR_KEYS);
6
+ const BINARY_COMPARATORS = { eq: "=", gt: ">", gte: ">=", lt: "<", lte: "<=", ne: "<>" };
7
+ const isOperatorObject = (value) => {
8
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
9
+ return false;
10
+ }
11
+ const keys = Object.keys(value);
12
+ return keys.length > 0 && keys.every((key) => OPERATOR_KEY_SET.has(key));
13
+ };
14
+ const compileContains = (reference, value, strategy) => {
15
+ const term = sql`${strategy.serialize(value)}`;
16
+ return strategy.likeContains ? strategy.likeContains(reference, term) : sql`${reference} LIKE '%' || ${term} || '%'`;
17
+ };
18
+ const compileComparator = (reference, operator, comparator, value, strategy) => {
19
+ if (value === null) {
20
+ return operator === "ne" ? sql`${reference} IS NOT NULL` : sql`${reference} IS NULL`;
21
+ }
22
+ return sql`${reference} ${sql.raw(comparator)} ${strategy.serialize(value)}`;
23
+ };
24
+ const compileInList = (reference, keyword, value, strategy) => {
25
+ const items = Array.isArray(value) ? value : [];
26
+ if (items.length === 0) {
27
+ return keyword === "IN" ? sql`0 = 1` : sql`1 = 1`;
28
+ }
29
+ const list = sql.join(
30
+ items.map((item) => sql`${strategy.serialize(item)}`),
31
+ sql`, `
32
+ );
33
+ return keyword === "IN" ? sql`${reference} IN (${list})` : sql`${reference} NOT IN (${list})`;
34
+ };
35
+ const compileFieldOperators = (reference, operators, strategy) => {
36
+ const record = operators;
37
+ const clauses = [];
38
+ for (const operator of OPERATOR_KEYS) {
39
+ if (!(operator in record)) {
40
+ continue;
41
+ }
42
+ const value = record[operator];
43
+ const comparator = BINARY_COMPARATORS[operator];
44
+ if (comparator) {
45
+ clauses.push(compileComparator(reference, operator, comparator, value, strategy));
46
+ } else if (operator === "isNull") {
47
+ clauses.push(value ? sql`${reference} IS NULL` : sql`${reference} IS NOT NULL`);
48
+ } else if (operator === "contains") {
49
+ clauses.push(compileContains(reference, value, strategy));
50
+ } else {
51
+ clauses.push(compileInList(reference, operator === "in" ? "IN" : "NOT IN", value, strategy));
52
+ }
53
+ }
54
+ return clauses;
55
+ };
56
+ const compileField = (field, value, strategy) => {
57
+ const reference = strategy.fieldRef(field);
58
+ if (isOperatorObject(value)) {
59
+ return compileFieldOperators(reference, value, strategy);
60
+ }
61
+ if (value === null) {
62
+ return [sql`${reference} IS NULL`];
63
+ }
64
+ return [sql`${reference} = ${strategy.serialize(value)}`];
65
+ };
66
+ const joinClauses = (clauses, connector) => {
67
+ if (clauses.length === 0) {
68
+ return void 0;
69
+ }
70
+ if (clauses.length === 1) {
71
+ return clauses[0];
72
+ }
73
+ return sql.join(
74
+ clauses.map((clause) => sql`(${clause})`),
75
+ sql` ${sql.raw(connector)} `
76
+ );
77
+ };
78
+ const compileGroup = (value, connector, strategy) => {
79
+ const branches = Array.isArray(value) ? value : [];
80
+ const parts = [];
81
+ for (const branch of branches) {
82
+ const compiled = compileNode(branch ?? {}, strategy);
83
+ if (compiled) {
84
+ parts.push(compiled);
85
+ }
86
+ }
87
+ if (parts.length === 0) {
88
+ return connector === "OR" ? sql`0 = 1` : void 0;
89
+ }
90
+ return joinClauses(parts, connector);
91
+ };
92
+ const STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["AND", "NOT", "OR", RELATION_EXISTS_KEY]);
93
+ const compileStructuralKey = (key, value, strategy) => {
94
+ if (key === RELATION_EXISTS_KEY) {
95
+ if (!strategy.relationExists) {
96
+ throw new Error("encountered a relation EXISTS marker without a relationExists strategy hook");
97
+ }
98
+ return strategy.relationExists(value);
99
+ }
100
+ if (key === "NOT") {
101
+ const inner = compileNode(value ?? {}, strategy);
102
+ return inner ? sql`NOT (${inner})` : void 0;
103
+ }
104
+ return compileGroup(value, key, strategy);
105
+ };
106
+ const compileNode = (where, strategy) => {
107
+ const clauses = [];
108
+ for (const [key, value] of Object.entries(where)) {
109
+ if (STRUCTURAL_KEYS.has(key)) {
110
+ const compiled = compileStructuralKey(key, value, strategy);
111
+ if (compiled) {
112
+ clauses.push(compiled);
113
+ }
114
+ } else {
115
+ clauses.push(...compileField(key, value, strategy));
116
+ }
117
+ }
118
+ return joinClauses(clauses, "AND");
119
+ };
120
+ const compileWhereSql = (where, strategy) => {
121
+ if (!where || Object.keys(where).length === 0) {
122
+ return void 0;
123
+ }
124
+ return compileNode(where, strategy);
125
+ };
126
+
127
+ export { compileWhereSql };
@@ -0,0 +1,80 @@
1
+ const toScheduledFunctionDoc = (record) => {
2
+ const doc = {
3
+ args: record["args"] ?? {},
4
+ enqueuedAt: typeof record["enqueuedAt"] === "number" ? record["enqueuedAt"] : 0,
5
+ functionPath: typeof record["functionPath"] === "string" ? record["functionPath"] : "",
6
+ id: typeof record["id"] === "string" ? record["id"] : "",
7
+ scheduledFor: typeof record["scheduledFor"] === "number" ? record["scheduledFor"] : 0
8
+ };
9
+ if (typeof record["attempts"] === "number") {
10
+ doc.attempts = record["attempts"];
11
+ }
12
+ if (typeof record["shardKey"] === "string") {
13
+ doc.shardKey = record["shardKey"];
14
+ }
15
+ return doc;
16
+ };
17
+ const toStorageMetadata = (object) => {
18
+ const meta = {
19
+ key: typeof object["key"] === "string" ? object["key"] : "",
20
+ size: typeof object["size"] === "number" ? object["size"] : 0
21
+ };
22
+ const contentType = object["httpMetadata"]?.contentType ?? object["contentType"];
23
+ if (typeof contentType === "string") {
24
+ meta.contentType = contentType;
25
+ }
26
+ if (object["customMetadata"] && typeof object["customMetadata"] === "object") {
27
+ meta.customMetadata = object["customMetadata"];
28
+ }
29
+ if (typeof object["sha256"] === "string") {
30
+ meta.sha256 = object["sha256"];
31
+ }
32
+ const { uploaded } = object;
33
+ if (typeof uploaded === "number") {
34
+ meta.uploaded = uploaded;
35
+ } else if (uploaded instanceof Date) {
36
+ meta.uploaded = uploaded.getTime();
37
+ }
38
+ return meta;
39
+ };
40
+ const createSystemReader = (options = {}) => {
41
+ const requireScheduler = () => {
42
+ if (!options.scheduler) {
43
+ throw new Error('ctx.db.system.query("_scheduled_functions"): no scheduler configured. Pass `scheduler` to createShardDO().');
44
+ }
45
+ return options.scheduler;
46
+ };
47
+ const requireStorage = () => {
48
+ if (!options.storage) {
49
+ throw new Error('ctx.db.system.query("_storage"): no storage configured. Pass `storage` to createShardDO().');
50
+ }
51
+ return options.storage;
52
+ };
53
+ const collectScheduled = async () => {
54
+ const records = await requireScheduler().list();
55
+ return records.map((record) => toScheduledFunctionDoc(record));
56
+ };
57
+ const collectStorage = async () => {
58
+ const { objects } = await requireStorage().list();
59
+ return objects.map((object) => toStorageMetadata(object));
60
+ };
61
+ const queryReader = ((table) => {
62
+ if (table === "_scheduled_functions") {
63
+ return { collect: collectScheduled };
64
+ }
65
+ return { collect: collectStorage };
66
+ });
67
+ return {
68
+ get: (async (table, id) => {
69
+ if (table === "_scheduled_functions") {
70
+ const record = await requireScheduler().get(id);
71
+ return record ? toScheduledFunctionDoc(record) : null;
72
+ }
73
+ const meta = await requireStorage().getMetadata(id);
74
+ return meta ? toStorageMetadata(meta) : null;
75
+ }),
76
+ query: queryReader
77
+ };
78
+ };
79
+
80
+ export { createSystemReader };