@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
package/dist/index.mjs ADDED
@@ -0,0 +1,37 @@
1
+ export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow } from './packem_shared/exportShardRows-DZEhUeyI.mjs';
2
+ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs';
3
+ export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
4
+ export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs';
5
+ export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent } from './packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs';
6
+ export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-DZQtH02h.mjs';
7
+ export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs';
8
+ export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
9
+ export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
10
+ export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
11
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-D_UiYJFk.mjs';
12
+ export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
13
+ export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
14
+ export { default as NotFoundError } from './packem_shared/NotFoundError-CMuMZt81.mjs';
15
+ export { armRestore, readBookmark } from './packem_shared/armRestore-BJk53Ro8.mjs';
16
+ export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-BvZdFUBT.mjs';
17
+ export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
18
+ export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-1hDydFyv.mjs';
19
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-C6lDaesn.mjs';
20
+ export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs';
21
+ export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BQ-8ZlZ1.mjs';
22
+ export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
23
+ export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
24
+ export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
25
+ export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs';
26
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-DKwBF3Jp.mjs';
27
+ export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs';
28
+ export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs';
29
+ export { createSystemReader } from './packem_shared/createSystemReader-8CzSZP9V.mjs';
30
+ export { ConflictError } from './packem_shared/ConflictError-C0STs6bU.mjs';
31
+ export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
32
+ export { compileWhereSql } from './packem_shared/compileWhereSql-CXrhFA3G.mjs';
33
+ export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-DSycmnDf.mjs';
34
+ export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BZsOqDXP.mjs';
35
+ export { runShardMigrations } from './packem_shared/runShardMigrations-nIwoQeOK.mjs';
36
+ export { stableStringify } from './packem_shared/stableStringify-CyHKJXre.mjs';
37
+ export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-ce84gpwL.mjs';
@@ -0,0 +1,316 @@
1
+ const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2
+ const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
3
+ const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
4
+ const ADMIN_FUNCTIONS = {
5
+ applyCdc: "__lunora_admin__:applyCdc",
6
+ cdcSync: "__lunora_admin__:cdcSync",
7
+ clearCapturedMail: "__lunora_admin__:clearCapturedMail",
8
+ clearTable: "__lunora_admin__:clearTable",
9
+ createWorkflowInstance: "__lunora_admin__:createWorkflowInstance",
10
+ deleteRows: "__lunora_admin__:deleteRows",
11
+ describeTable: "__lunora_admin__:describeTable",
12
+ describeTables: "__lunora_admin__:describeTables",
13
+ exportShard: "__lunora_admin__:exportShard",
14
+ facetColumn: "__lunora_admin__:facetColumn",
15
+ getAdvisories: "__lunora_admin__:getAdvisories",
16
+ getAuditLog: "__lunora_admin__:getAuditLog",
17
+ getAuthMetrics: "__lunora_admin__:getAuthMetrics",
18
+ getCapturedMail: "__lunora_admin__:getCapturedMail",
19
+ getFunctionStats: "__lunora_admin__:getFunctionStats",
20
+ listSubscriptions: "__lunora_admin__:listSubscriptions",
21
+ listTableIndexes: "__lunora_admin__:listTableIndexes",
22
+ getLogs: "__lunora_admin__:getLogs",
23
+ getMetrics: "__lunora_admin__:getMetrics",
24
+ getPitrBookmark: "__lunora_admin__:getPitrBookmark",
25
+ getRequestLog: "__lunora_admin__:getRequestLog",
26
+ getSecurityAudit: "__lunora_admin__:getSecurityAudit",
27
+ getSettings: "__lunora_admin__:getSettings",
28
+ // eslint-disable-next-line no-secrets/no-secrets -- reserved admin RPC path constant, not a credential
29
+ getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus",
30
+ importShard: "__lunora_admin__:importShard",
31
+ listFlags: "__lunora_admin__:listFlags",
32
+ listQueues: "__lunora_admin__:listQueues",
33
+ listTables: "__lunora_admin__:listTables",
34
+ listWorkflows: "__lunora_admin__:listWorkflows",
35
+ maskPolicies: "__lunora_admin__:maskPolicies",
36
+ migrationStatus: "__lunora_admin__:migrationStatus",
37
+ pitrRestore: "__lunora_admin__:pitrRestore",
38
+ rankBefore: "__lunora_admin__:rankBefore",
39
+ rankPage: "__lunora_admin__:rankPage",
40
+ readTablePage: "__lunora_admin__:readTablePage",
41
+ recordAuthEvent: "__lunora_admin__:recordAuthEvent",
42
+ recordContainerEvent: "__lunora_admin__:recordContainerEvent",
43
+ recordMail: "__lunora_admin__:recordMail",
44
+ rlsPolicies: "__lunora_admin__:rlsPolicies",
45
+ runAs: "__lunora_admin__:runAs",
46
+ runMigration: "__lunora_admin__:runMigration",
47
+ runSql: "__lunora_admin__:runSql",
48
+ sendTestMail: "__lunora_admin__:sendTestMail",
49
+ storageOrphans: "__lunora_admin__:storageOrphans",
50
+ storageReferences: "__lunora_admin__:storageReferences",
51
+ storageRules: "__lunora_admin__:storageRules",
52
+ studioFeatures: "__lunora_admin__:studioFeatures",
53
+ writeRow: "__lunora_admin__:writeRow"
54
+ };
55
+ const DEFAULT_PAGE_SIZE = 50;
56
+ const MAX_PAGE_SIZE = 500;
57
+ const DEFAULT_FACET_LIMIT = 30;
58
+ const MAX_FACET_LIMIT = 200;
59
+ const DOC_COLUMN = "__doc__";
60
+ const safeParseObject = (text) => {
61
+ try {
62
+ const value = JSON.parse(text);
63
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
64
+ } catch {
65
+ return void 0;
66
+ }
67
+ };
68
+ const expandDocumentRows = (columns, rows) => {
69
+ if (!columns.includes(DOC_COLUMN)) {
70
+ return { columns, rows };
71
+ }
72
+ const parsed = [];
73
+ for (const row of rows) {
74
+ const raw = row[DOC_COLUMN];
75
+ const documentData = typeof raw === "string" ? safeParseObject(raw) : void 0;
76
+ if (documentData === void 0) {
77
+ return { columns, rows };
78
+ }
79
+ const meta = Object.fromEntries(Object.entries(row).filter(([column]) => column !== DOC_COLUMN));
80
+ parsed.push({ ...meta, ...documentData });
81
+ }
82
+ const metaColumns = columns.filter((name) => name !== DOC_COLUMN);
83
+ const documentKeys = [];
84
+ const seen = new Set(metaColumns);
85
+ for (const documentData of parsed) {
86
+ for (const key of Object.keys(documentData)) {
87
+ if (!seen.has(key)) {
88
+ seen.add(key);
89
+ documentKeys.push(key);
90
+ }
91
+ }
92
+ }
93
+ return { columns: [...metaColumns, ...documentKeys], rows: parsed };
94
+ };
95
+ const escapeLike = (value) => value.replaceAll(/[\\%_]/g, (character) => `\\${character}`);
96
+ const isInternalTable = (name) => name.startsWith("sqlite_") || name.startsWith("_cf_") || name.startsWith("__miniflare") || name.startsWith("__lunora") || name.includes("__fts_");
97
+ const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
98
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
99
+ const countRows = (sql, quotedTable) => {
100
+ const row = sql.exec(`SELECT COUNT(*) AS c FROM ${quotedTable}`).one();
101
+ return Number(row.c);
102
+ };
103
+ const listTables = (sql) => {
104
+ const names = sql.exec("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").toArray();
105
+ const tables = [];
106
+ for (const { name } of names) {
107
+ if (isInternalTable(name)) {
108
+ continue;
109
+ }
110
+ tables.push({ name, rowCount: countRows(sql, quoteIdentifier(name)) });
111
+ }
112
+ return tables;
113
+ };
114
+ const tableExists = (sql, table) => sql.exec("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1", table).toArray().length > 0;
115
+ const FILTER_SQL_OPERATOR = { eq: "=", gt: ">", gte: ">=", lt: "<", lte: "<=", ne: "<>" };
116
+ const filterValueText = (value) => {
117
+ if (typeof value === "string") {
118
+ return value;
119
+ }
120
+ return typeof value === "number" || typeof value === "boolean" ? String(value) : "";
121
+ };
122
+ const resolveColumnExpression = (column, physicalColumns) => {
123
+ const isPhysical = physicalColumns.includes(column);
124
+ const isDocumentStored = physicalColumns.includes(DOC_COLUMN);
125
+ if (!isPhysical && !isDocumentStored) {
126
+ return void 0;
127
+ }
128
+ return isPhysical ? { expression: quoteIdentifier(column), params: [] } : { expression: `json_extract(${quoteIdentifier(DOC_COLUMN)}, ?)`, params: [`$."${column.replaceAll('"', '""')}"`] };
129
+ };
130
+ const buildFilterClause = (clause, physicalColumns) => {
131
+ const resolved = resolveColumnExpression(clause.column, physicalColumns);
132
+ if (resolved === void 0) {
133
+ return void 0;
134
+ }
135
+ const { expression, params: pathParameters } = resolved;
136
+ if (clause.operator === "contains") {
137
+ return {
138
+ params: [...pathParameters, `%${escapeLike(filterValueText(clause.value))}%`],
139
+ sql: String.raw`CAST(${expression} AS TEXT) LIKE ? ESCAPE '\'`
140
+ };
141
+ }
142
+ return { params: [...pathParameters, clause.value], sql: `${expression} ${FILTER_SQL_OPERATOR[clause.operator]} ?` };
143
+ };
144
+ const buildTablePredicate = (columns, needle, filters) => {
145
+ const conjuncts = [];
146
+ const parameters = [];
147
+ if (needle !== "" && columns.length > 0) {
148
+ const pattern = `%${escapeLike(needle)}%`;
149
+ conjuncts.push(`(${columns.map((name) => String.raw`CAST(${quoteIdentifier(name)} AS TEXT) LIKE ? ESCAPE '\'`).join(" OR ")})`);
150
+ parameters.push(...columns.map(() => pattern));
151
+ }
152
+ for (const clause of filters ?? []) {
153
+ const built = buildFilterClause(clause, columns);
154
+ if (built !== void 0) {
155
+ conjuncts.push(`(${built.sql})`);
156
+ parameters.push(...built.params);
157
+ }
158
+ }
159
+ return conjuncts.length === 0 ? void 0 : { parameters, where: conjuncts.join(" AND ") };
160
+ };
161
+ const buildOrderBy = (orderBy, physicalColumns) => {
162
+ if (orderBy === void 0) {
163
+ return void 0;
164
+ }
165
+ const resolved = resolveColumnExpression(orderBy.column, physicalColumns);
166
+ if (resolved === void 0) {
167
+ return void 0;
168
+ }
169
+ const keyword = orderBy.direction === "desc" ? "DESC" : "ASC";
170
+ return { params: resolved.params, sql: `${resolved.expression} ${keyword}` };
171
+ };
172
+ const readTablePage = (sql, options) => {
173
+ const { table } = options;
174
+ if (isInternalTable(table) || !tableExists(sql, table)) {
175
+ throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
176
+ }
177
+ const limit = clamp(Math.trunc(options.limit ?? DEFAULT_PAGE_SIZE), 1, MAX_PAGE_SIZE);
178
+ const offset = Math.max(0, Math.trunc(options.offset ?? 0));
179
+ const quoted = quoteIdentifier(table);
180
+ const columns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((column) => column.name);
181
+ const needle = options.search?.trim() ?? "";
182
+ const withReferences = (page) => {
183
+ if (options.refs === void 0) {
184
+ return page;
185
+ }
186
+ const references = {};
187
+ for (const column of page.columns) {
188
+ const target = options.refs[column];
189
+ if (target !== void 0) {
190
+ references[column] = target;
191
+ }
192
+ }
193
+ return Object.keys(references).length > 0 ? { ...page, refs: references } : page;
194
+ };
195
+ const predicate = buildTablePredicate(columns, needle, options.filters);
196
+ const order = buildOrderBy(options.orderBy, columns);
197
+ const whereSql = predicate === void 0 ? "" : ` WHERE ${predicate.where}`;
198
+ const orderSql = order === void 0 ? "" : ` ORDER BY ${order.sql}`;
199
+ const whereParams = predicate?.parameters ?? [];
200
+ const orderParams = order?.params ?? [];
201
+ const total = predicate === void 0 ? countRows(sql, quoted) : Number(sql.exec(`SELECT COUNT(*) AS c FROM ${quoted}${whereSql}`, ...whereParams).one().c);
202
+ const rawRows = sql.exec(`SELECT * FROM ${quoted}${whereSql}${orderSql} LIMIT ? OFFSET ?`, ...whereParams, ...orderParams, limit, offset).toArray();
203
+ return withReferences({ ...expandDocumentRows(columns, rawRows), total });
204
+ };
205
+ const selectMatchingIds = (sql, options) => {
206
+ const { table } = options;
207
+ if (isInternalTable(table) || !tableExists(sql, table)) {
208
+ throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
209
+ }
210
+ const limit = clamp(Math.trunc(options.limit ?? MAX_PAGE_SIZE), 1, MAX_PAGE_SIZE);
211
+ const quoted = quoteIdentifier(table);
212
+ const columns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((column) => column.name);
213
+ const needle = options.search?.trim() ?? "";
214
+ const predicate = buildTablePredicate(columns, needle, options.filters);
215
+ const fetched = predicate === void 0 ? sql.exec(`SELECT id FROM ${quoted} LIMIT ?`, limit + 1).toArray() : sql.exec(`SELECT id FROM ${quoted} WHERE ${predicate.where} LIMIT ?`, ...predicate.parameters, limit + 1).toArray();
216
+ const hasMore = fetched.length > limit;
217
+ const ids = (hasMore ? fetched.slice(0, limit) : fetched).map((row) => row.id);
218
+ return { hasMore, ids };
219
+ };
220
+ const knownDisplayColumns = (sql, quotedTable, physicalColumns) => {
221
+ const known = new Set(physicalColumns.filter((name) => name !== DOC_COLUMN));
222
+ if (!physicalColumns.includes(DOC_COLUMN)) {
223
+ return known;
224
+ }
225
+ const sample = sql.exec(`SELECT ${quoteIdentifier(DOC_COLUMN)} AS doc FROM ${quotedTable} LIMIT ?`, MAX_PAGE_SIZE).toArray();
226
+ for (const { doc } of sample) {
227
+ const documentData = typeof doc === "string" ? safeParseObject(doc) : void 0;
228
+ if (documentData !== void 0) {
229
+ for (const key of Object.keys(documentData)) {
230
+ known.add(key);
231
+ }
232
+ }
233
+ }
234
+ return known;
235
+ };
236
+ const facetColumn = (sql, options) => {
237
+ const { column, table } = options;
238
+ if (isInternalTable(table) || !tableExists(sql, table)) {
239
+ throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
240
+ }
241
+ const quoted = quoteIdentifier(table);
242
+ const physicalColumns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((info) => info.name);
243
+ if (!knownDisplayColumns(sql, quoted, physicalColumns).has(column)) {
244
+ throw Object.assign(new Error(`unknown column: ${column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
245
+ }
246
+ const resolved = resolveColumnExpression(column, physicalColumns);
247
+ if (resolved === void 0) {
248
+ throw Object.assign(new Error(`unknown column: ${column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
249
+ }
250
+ const limit = clamp(Math.trunc(options.limit ?? DEFAULT_FACET_LIMIT), 1, MAX_FACET_LIMIT);
251
+ const needle = options.search?.trim() ?? "";
252
+ const predicate = buildTablePredicate(physicalColumns, needle, options.filters);
253
+ const whereSql = predicate === void 0 ? "" : ` WHERE ${predicate.where}`;
254
+ const whereParams = predicate?.parameters ?? [];
255
+ const rows = sql.exec(
256
+ `SELECT ${resolved.expression} AS value, COUNT(*) AS count FROM ${quoted}${whereSql} GROUP BY ${resolved.expression} ORDER BY count DESC LIMIT ?`,
257
+ ...resolved.params,
258
+ ...whereParams,
259
+ ...resolved.params,
260
+ limit + 1
261
+ ).toArray();
262
+ const truncated = rows.length > limit;
263
+ const kept = truncated ? rows.slice(0, limit) : rows;
264
+ return {
265
+ truncated,
266
+ values: kept.map((row) => {
267
+ return { count: Number(row.count), value: row.value };
268
+ })
269
+ };
270
+ };
271
+ const findStorageReferences = (sql, storageColumns, keys) => {
272
+ const references = {};
273
+ const scanned = keys.slice(0, MAX_PAGE_SIZE);
274
+ for (const key of scanned) {
275
+ references[key] = [];
276
+ }
277
+ if (scanned.length === 0) {
278
+ return { references, storageColumns };
279
+ }
280
+ const placeholders = scanned.map(() => "?").join(", ");
281
+ for (const [table, columns] of Object.entries(storageColumns)) {
282
+ if (isInternalTable(table) || !tableExists(sql, table)) {
283
+ continue;
284
+ }
285
+ const quoted = quoteIdentifier(table);
286
+ const physicalColumns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((column) => column.name);
287
+ for (const column of columns) {
288
+ const resolved = resolveColumnExpression(column, physicalColumns);
289
+ if (resolved === void 0) {
290
+ continue;
291
+ }
292
+ const rows = sql.exec(
293
+ `SELECT id, ${resolved.expression} AS ref FROM ${quoted} WHERE ${resolved.expression} IN (${placeholders})`,
294
+ ...resolved.params,
295
+ ...resolved.params,
296
+ ...scanned
297
+ ).toArray();
298
+ for (const row of rows) {
299
+ references[row.ref]?.push({ column, id: row.id, table });
300
+ }
301
+ }
302
+ }
303
+ return { references, storageColumns };
304
+ };
305
+ const summarizeSubscriptions = (attachments) => {
306
+ const connections = attachments.map((attachment, id) => {
307
+ const subscriptions = Object.values(attachment.subs ?? {}).map((sub) => {
308
+ return { args: sub.args, functionPath: sub.functionPath, table: sub.table };
309
+ });
310
+ return { admin: attachment.admin === true, id, subscriptions };
311
+ });
312
+ const totalSubscriptions = connections.reduce((sum, connection) => sum + connection.subscriptions.length, 0);
313
+ return { connections, totalConnections: connections.length, totalSubscriptions };
314
+ };
315
+
316
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
@@ -0,0 +1,54 @@
1
+ const COUNT_OPTION_KEYS = /* @__PURE__ */ new Set(["baseWhere", "relationBaseWhere", "restrictsCounts", "where"]);
2
+ const matchesStaticWhere = (document, predicate) => {
3
+ for (const [field, expected] of Object.entries(predicate)) {
4
+ const actual = document[field];
5
+ if (expected !== null && typeof expected === "object" && !Array.isArray(expected)) {
6
+ const operatorKeys = Object.keys(expected);
7
+ if (operatorKeys.length === 1 && operatorKeys[0] === "eq") {
8
+ if (actual !== expected.eq) {
9
+ return false;
10
+ }
11
+ continue;
12
+ }
13
+ return false;
14
+ }
15
+ if (actual !== expected) {
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ };
21
+ const normalizeCountArgument = (argument) => {
22
+ if (argument === void 0) {
23
+ return {};
24
+ }
25
+ if (typeof argument !== "object" || Array.isArray(argument)) {
26
+ return { where: argument };
27
+ }
28
+ const keys = Object.keys(argument);
29
+ if (keys.length === 0) {
30
+ return {};
31
+ }
32
+ if (keys.every((key) => COUNT_OPTION_KEYS.has(key))) {
33
+ return argument;
34
+ }
35
+ return { where: argument };
36
+ };
37
+ const AGGREGATE_SQL_FUNCTION = { avg: "AVG", count: "COUNT", max: "MAX", min: "MIN", sum: "SUM" };
38
+ const aggregateSqlFunction = (op) => {
39
+ const sqlFunction = AGGREGATE_SQL_FUNCTION[op];
40
+ if (sqlFunction === void 0) {
41
+ throw new Error(`unknown aggregate op "${op}": expected one of ${Object.keys(AGGREGATE_SQL_FUNCTION).join(", ")}`);
42
+ }
43
+ return sqlFunction;
44
+ };
45
+ const throwingScheduler = {
46
+ runAfter: () => {
47
+ throw new Error("ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
48
+ },
49
+ runAt: () => {
50
+ throw new Error("ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
51
+ }
52
+ };
53
+
54
+ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler };
@@ -0,0 +1,84 @@
1
+ const AUTH_METRICS_TABLE = "__lunora_auth_metrics";
2
+ const AUTH_METRICS_BUCKETS_TABLE = "__lunora_auth_metrics_buckets";
3
+ const AUTH_METRICS_ROW_KEY = "app";
4
+ const AUTH_METRICS_BUCKET_MS = 6e4;
5
+ const AUTH_METRICS_BUCKET_RETENTION = 1440;
6
+ const runSql = (sql, query, ...params) => {
7
+ const runner = sql.exec;
8
+ return runner.call(sql, query, ...params);
9
+ };
10
+ const bucketFloor = (ts) => Math.floor(ts / AUTH_METRICS_BUCKET_MS) * AUTH_METRICS_BUCKET_MS;
11
+ const ensureAuthMetricsTables = (sql) => {
12
+ runSql(
13
+ sql,
14
+ `CREATE TABLE IF NOT EXISTS "${AUTH_METRICS_TABLE}" (
15
+ key TEXT PRIMARY KEY,
16
+ attempts INTEGER NOT NULL DEFAULT 0,
17
+ failures INTEGER NOT NULL DEFAULT 0,
18
+ since_ms INTEGER NOT NULL DEFAULT 0
19
+ )`
20
+ );
21
+ runSql(
22
+ sql,
23
+ `CREATE TABLE IF NOT EXISTS "${AUTH_METRICS_BUCKETS_TABLE}" (
24
+ bucket_ms INTEGER PRIMARY KEY,
25
+ attempts INTEGER NOT NULL DEFAULT 0,
26
+ failures INTEGER NOT NULL DEFAULT 0
27
+ )`
28
+ );
29
+ };
30
+ const recordAuthEvent = (sql, input) => {
31
+ ensureAuthMetricsTables(sql);
32
+ const failureCount = input.outcome === "fail" ? 1 : 0;
33
+ runSql(
34
+ sql,
35
+ `INSERT INTO "${AUTH_METRICS_TABLE}" (key, attempts, failures, since_ms)
36
+ VALUES (?, 1, ?, ?)
37
+ ON CONFLICT(key) DO UPDATE SET
38
+ attempts = attempts + 1,
39
+ failures = failures + excluded.failures,
40
+ since_ms = CASE WHEN since_ms = 0 THEN excluded.since_ms ELSE since_ms END`,
41
+ AUTH_METRICS_ROW_KEY,
42
+ failureCount,
43
+ input.ts
44
+ );
45
+ const bucket = bucketFloor(input.ts);
46
+ runSql(
47
+ sql,
48
+ `INSERT INTO "${AUTH_METRICS_BUCKETS_TABLE}" (bucket_ms, attempts, failures)
49
+ VALUES (?, 1, ?)
50
+ ON CONFLICT(bucket_ms) DO UPDATE SET
51
+ attempts = attempts + 1,
52
+ failures = failures + excluded.failures`,
53
+ bucket,
54
+ failureCount
55
+ );
56
+ runSql(
57
+ sql,
58
+ `DELETE FROM "${AUTH_METRICS_BUCKETS_TABLE}"
59
+ WHERE bucket_ms <= (
60
+ SELECT MAX(bucket_ms) - ? FROM "${AUTH_METRICS_BUCKETS_TABLE}"
61
+ )`,
62
+ AUTH_METRICS_BUCKET_RETENTION * AUTH_METRICS_BUCKET_MS
63
+ );
64
+ };
65
+ const readAuthMetrics = (sql) => {
66
+ ensureAuthMetricsTables(sql);
67
+ const row = runSql(
68
+ sql,
69
+ `SELECT attempts, failures, since_ms FROM "${AUTH_METRICS_TABLE}" WHERE key = ?`,
70
+ AUTH_METRICS_ROW_KEY
71
+ ).toArray()[0];
72
+ const attempts = row?.attempts ?? 0;
73
+ const failures = row?.failures ?? 0;
74
+ const sinceMs = row?.since_ms ?? 0;
75
+ const history = runSql(
76
+ sql,
77
+ `SELECT bucket_ms, attempts, failures FROM "${AUTH_METRICS_BUCKETS_TABLE}" ORDER BY bucket_ms ASC`
78
+ ).toArray().map((bucket) => {
79
+ return { attempts: bucket.attempts, bucketMs: bucket.bucket_ms, failures: bucket.failures };
80
+ });
81
+ return { attempts, failureRate: attempts === 0 ? 0 : failures / attempts, failures, history, sinceMs };
82
+ };
83
+
84
+ export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_ROW_KEY, AUTH_METRICS_TABLE, ensureAuthMetricsTables, readAuthMetrics, recordAuthEvent };
@@ -0,0 +1,107 @@
1
+ import { sql } from 'drizzle-orm';
2
+ import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
3
+ import { ConflictError } from './ConflictError-C0STs6bU.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);
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);
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 };
@@ -0,0 +1,13 @@
1
+ class ConflictError extends Error {
2
+ code = "CONFLICT";
3
+ /** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
4
+ kind;
5
+ status = 409;
6
+ constructor(message = "Optimistic concurrency conflict", kind = "conflict") {
7
+ super(message);
8
+ this.name = "ConflictError";
9
+ this.kind = kind;
10
+ }
11
+ }
12
+
13
+ export { ConflictError };