@lunora/do 1.0.0-alpha.4 → 1.0.0-alpha.41
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/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +5236 -3449
- package/dist/index.d.ts +5236 -3449
- package/dist/index.mjs +34 -25
- package/dist/packem_shared/{ADMIN_FUNCTIONS-Dzdqq5J2.mjs → ADMIN_FUNCTIONS-CnZkbXH_.mjs} +69 -8
- package/dist/packem_shared/{AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs → AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs} +5 -3
- package/dist/packem_shared/{CDC_LOG_TABLE-Ctdmxmrv.mjs → CDC_LOG_TABLE-uwOJxJJZ.mjs} +8 -4
- package/dist/packem_shared/{ConflictError-C0STs6bU.mjs → ConflictError-CLoq37xH.mjs} +4 -5
- package/dist/packem_shared/{CountRlsUnsupportedError-28ZvvwKS.mjs → CountRlsUnsupportedError-BGxj0pgS.mjs} +4 -4
- package/dist/packem_shared/{DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs → DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs} +11 -3
- package/dist/packem_shared/{DEFAULT_MAX_RELATION_KEYS-DyVYReuT.mjs → DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs} +58 -7
- package/dist/packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs +114 -0
- package/dist/packem_shared/{MAX_SQL_ROWS-dDcFE1YZ.mjs → MAX_SQL_ROWS-iFAA8FbD.mjs} +35 -3
- package/dist/packem_shared/NotFoundError-C70b9hLw.mjs +9 -0
- package/dist/packem_shared/{NotUniqueError-B6tzuDmM.mjs → NotUniqueError-DbtlWwcG.mjs} +400 -114
- package/dist/packem_shared/{rank-CrkEIpF4.mjs → RANK_TIEBREAK-CXhdcA1o.mjs} +2 -13
- package/dist/packem_shared/{RLS_UNWRAP_SYMBOL-u3UlnCH5.mjs → RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs} +22 -9
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-B4Cpm7Qv.mjs +7653 -0
- package/dist/packem_shared/{ReactiveCache-ByVzgH3d.mjs → ReactiveCache-DnSvbjil.mjs} +4 -30
- package/dist/packem_shared/{SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs → SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs} +16 -27
- package/dist/packem_shared/{SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs → SHARD_REGISTRY_DO_NAME-D99roc-r.mjs} +12 -14
- package/dist/packem_shared/{applyOnDelete-CMif2RKw.mjs → applyOnDelete-BXSq3S70.mjs} +23 -12
- package/dist/packem_shared/{buildSeekWhere-lVsNXSLy.mjs → applySelect-WQY8m62C.mjs} +21 -2
- package/dist/packem_shared/{armRestore-BJk53Ro8.mjs → armRestore-4Px61hHS.mjs} +4 -10
- package/dist/packem_shared/{backfillAggregateIndexes-BbVPvciS.mjs → backfillAggregateIndexes-BGTwynwh.mjs} +4 -3
- package/dist/packem_shared/{compileWhereSql-CXrhFA3G.mjs → compileWhereSql-DE6yfRcQ.mjs} +5 -3
- package/dist/packem_shared/constant-time-equal-BVRWZgES.mjs +12 -0
- package/dist/packem_shared/context-telemetry-CDpyil58.mjs +223 -0
- package/dist/packem_shared/createMetrics-C4I2Yanj.mjs +1 -0
- package/dist/packem_shared/{createSystemReader-8CzSZP9V.mjs → createSystemReader-D12eNH13.mjs} +4 -2
- package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
- package/dist/packem_shared/ctx-db-shapes-DTeFiHYS.mjs +53 -0
- package/dist/packem_shared/diffExternalSource-CovHfdyo.mjs +44 -0
- package/dist/packem_shared/{do-sql-BCHCWtrD.mjs → do-sql-CGAgiQUz.mjs} +2 -1
- package/dist/packem_shared/{exportShardRows-DZEhUeyI.mjs → exportShardRows-Dy3oFZ26.mjs} +4 -3
- package/dist/packem_shared/isSoftDeleted-CFJmhjFP.mjs +217 -0
- package/dist/packem_shared/json-response-BdbtpOhm.mjs +3 -0
- package/dist/packem_shared/materializeExternalRows-BtEGs1Fv.mjs +45 -0
- package/dist/packem_shared/{runShardMigrations-PabobOjF.mjs → runShardMigrations-BVax6rYu.mjs} +21 -4
- package/dist/packem_shared/selectExpiredIds-C5W29Upb.mjs +18 -0
- package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
- package/dist/packem_shared/{serveRelationFanout-CFBKWJ8Q.mjs → serveRelationFanout-k7HTh3CC.mjs} +4 -7
- package/dist/packem_shared/stableStringify-mC40mZts.mjs +40 -0
- package/dist/packem_shared/stableWireKey-DKuXO7T5.mjs +6 -0
- package/dist/packem_shared/subscriptionListDeltas-CT76bYny.mjs +126 -0
- package/dist/packem_shared/wire-codec-CzQc1pvf.mjs +225 -0
- package/package.json +5 -3
- package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +0 -10
- package/dist/packem_shared/RANK_TIEBREAK-C6blLR5K.mjs +0 -1
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-3lZ2yigq.mjs +0 -4003
- package/dist/packem_shared/ctx-db-idempotency-DkC9rP91.mjs +0 -35
package/dist/index.mjs
CHANGED
|
@@ -1,35 +1,44 @@
|
|
|
1
|
-
export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow } from './packem_shared/exportShardRows-
|
|
2
|
-
export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/AGGREGATE_SQL_FUNCTION-
|
|
1
|
+
export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow } from './packem_shared/exportShardRows-Dy3oFZ26.mjs';
|
|
2
|
+
export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, normalizeCountArgument, throwingScheduler } from './packem_shared/AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs';
|
|
3
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-
|
|
4
|
+
export { CountRlsUnsupportedError, mergeWhere, planAggregateLookup, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy } from './packem_shared/CountRlsUnsupportedError-BGxj0pgS.mjs';
|
|
5
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 {
|
|
7
|
-
export {
|
|
6
|
+
export { c as createMetrics, a as createTracer, d as dispatchRootSpan } from './packem_shared/context-telemetry-CDpyil58.mjs';
|
|
7
|
+
export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-DbtlWwcG.mjs';
|
|
8
|
+
export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
|
|
8
9
|
export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
|
|
9
10
|
export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
|
|
11
|
+
export { diffExternalSource } from './packem_shared/diffExternalSource-CovHfdyo.mjs';
|
|
12
|
+
export { materializeExternalRows, materializeExternalRowsIncremental, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-BtEGs1Fv.mjs';
|
|
13
|
+
export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick } from './packem_shared/isSoftDeleted-CFJmhjFP.mjs';
|
|
10
14
|
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 {
|
|
15
|
+
export { GEO_DEFAULT_PRECISION, boundingBoxGeohashes, coveringGeohashes, encodeGeohash, haversineMeters, pointInBoundingBox } from './packem_shared/GEO_DEFAULT_PRECISION-BWnsNmpP.mjs';
|
|
16
|
+
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-CnZkbXH_.mjs';
|
|
12
17
|
export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
|
|
13
18
|
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-
|
|
15
|
-
export { armRestore, readBookmark } from './packem_shared/armRestore-
|
|
16
|
-
export { buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys } from './packem_shared/
|
|
17
|
-
export {
|
|
18
|
-
export { ReactiveCache, reactiveCacheKey
|
|
19
|
-
export { serveRelationFanout } from './packem_shared/serveRelationFanout-
|
|
20
|
-
export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-
|
|
21
|
-
export { applyOnDelete, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-
|
|
22
|
-
export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-
|
|
19
|
+
export { default as NotFoundError } from './packem_shared/NotFoundError-C70b9hLw.mjs';
|
|
20
|
+
export { armRestore, readBookmark } from './packem_shared/armRestore-4Px61hHS.mjs';
|
|
21
|
+
export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-WQY8m62C.mjs';
|
|
22
|
+
export { RANK_TIEBREAK, encodePartitionKey, matchesRankStaticWhere, rankTableName, resolveRankPartition, sortColumnName } from './packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs';
|
|
23
|
+
export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-DnSvbjil.mjs';
|
|
24
|
+
export { serveRelationFanout } from './packem_shared/serveRelationFanout-k7HTh3CC.mjs';
|
|
25
|
+
export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs';
|
|
26
|
+
export { applyOnDelete, fanOutScalarCounts, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-BXSq3S70.mjs';
|
|
27
|
+
export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs';
|
|
23
28
|
export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
|
|
24
29
|
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-
|
|
26
|
-
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO
|
|
27
|
-
export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-
|
|
28
|
-
export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-
|
|
29
|
-
export { createSystemReader } from './packem_shared/createSystemReader-
|
|
30
|
-
export { ConflictError } from './packem_shared/ConflictError-
|
|
30
|
+
export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs';
|
|
31
|
+
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-B4Cpm7Qv.mjs';
|
|
32
|
+
export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs';
|
|
33
|
+
export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs';
|
|
34
|
+
export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
|
|
35
|
+
export { ConflictError } from './packem_shared/ConflictError-CLoq37xH.mjs';
|
|
31
36
|
export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
export {
|
|
35
|
-
export {
|
|
37
|
+
export { selectExpiredIds } from './packem_shared/selectExpiredIds-C5W29Upb.mjs';
|
|
38
|
+
export { compileWhereSql } from './packem_shared/compileWhereSql-DE6yfRcQ.mjs';
|
|
39
|
+
export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
40
|
+
export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BGTwynwh.mjs';
|
|
41
|
+
export { runShardMigrations } from './packem_shared/runShardMigrations-BVax6rYu.mjs';
|
|
42
|
+
export { stableStringify } from './packem_shared/stableStringify-mC40mZts.mjs';
|
|
43
|
+
export { stableWireKey } from './packem_shared/stableWireKey-DKuXO7T5.mjs';
|
|
44
|
+
export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-CT76bYny.mjs';
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
4
|
+
|
|
1
5
|
const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
|
|
2
6
|
const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
|
|
7
|
+
const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
|
|
3
8
|
const ADMIN_FUNCTIONS = {
|
|
4
9
|
applyCdc: "__lunora_admin__:applyCdc",
|
|
10
|
+
assignIssue: "__lunora_admin__:assignIssue",
|
|
5
11
|
cdcSync: "__lunora_admin__:cdcSync",
|
|
6
12
|
clearCapturedMail: "__lunora_admin__:clearCapturedMail",
|
|
13
|
+
clearQueueMessages: "__lunora_admin__:clearQueueMessages",
|
|
7
14
|
clearTable: "__lunora_admin__:clearTable",
|
|
8
15
|
createWorkflowInstance: "__lunora_admin__:createWorkflowInstance",
|
|
9
16
|
deleteRows: "__lunora_admin__:deleteRows",
|
|
@@ -15,18 +22,27 @@ const ADMIN_FUNCTIONS = {
|
|
|
15
22
|
getAuditLog: "__lunora_admin__:getAuditLog",
|
|
16
23
|
getAuthMetrics: "__lunora_admin__:getAuthMetrics",
|
|
17
24
|
getCapturedMail: "__lunora_admin__:getCapturedMail",
|
|
25
|
+
getFanoutMetrics: "__lunora_admin__:getFanoutMetrics",
|
|
18
26
|
getFunctionStats: "__lunora_admin__:getFunctionStats",
|
|
27
|
+
getIssues: "__lunora_admin__:getIssues",
|
|
28
|
+
getMetricHistory: "__lunora_admin__:getMetricHistory",
|
|
29
|
+
getMetricSeries: "__lunora_admin__:getMetricSeries",
|
|
19
30
|
listSubscriptions: "__lunora_admin__:listSubscriptions",
|
|
20
31
|
listTableIndexes: "__lunora_admin__:listTableIndexes",
|
|
21
32
|
getLogs: "__lunora_admin__:getLogs",
|
|
22
33
|
getMetrics: "__lunora_admin__:getMetrics",
|
|
23
34
|
getPitrBookmark: "__lunora_admin__:getPitrBookmark",
|
|
35
|
+
getQueueMessages: "__lunora_admin__:getQueueMessages",
|
|
24
36
|
getRequestLog: "__lunora_admin__:getRequestLog",
|
|
25
37
|
getSecurityAudit: "__lunora_admin__:getSecurityAudit",
|
|
26
38
|
getSettings: "__lunora_admin__:getSettings",
|
|
39
|
+
getTraces: "__lunora_admin__:getTraces",
|
|
27
40
|
// eslint-disable-next-line no-secrets/no-secrets -- reserved admin RPC path constant, not a credential
|
|
28
41
|
getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus",
|
|
42
|
+
ignoreIssue: "__lunora_admin__:ignoreIssue",
|
|
29
43
|
importShard: "__lunora_admin__:importShard",
|
|
44
|
+
listFlags: "__lunora_admin__:listFlags",
|
|
45
|
+
listQueues: "__lunora_admin__:listQueues",
|
|
30
46
|
listTables: "__lunora_admin__:listTables",
|
|
31
47
|
listWorkflows: "__lunora_admin__:listWorkflows",
|
|
32
48
|
maskPolicies: "__lunora_admin__:maskPolicies",
|
|
@@ -38,11 +54,16 @@ const ADMIN_FUNCTIONS = {
|
|
|
38
54
|
recordAuthEvent: "__lunora_admin__:recordAuthEvent",
|
|
39
55
|
recordContainerEvent: "__lunora_admin__:recordContainerEvent",
|
|
40
56
|
recordMail: "__lunora_admin__:recordMail",
|
|
57
|
+
recordQueueMessage: "__lunora_admin__:recordQueueMessage",
|
|
58
|
+
replayQueueMessage: "__lunora_admin__:replayQueueMessage",
|
|
59
|
+
resolveIssue: "__lunora_admin__:resolveIssue",
|
|
41
60
|
rlsPolicies: "__lunora_admin__:rlsPolicies",
|
|
42
61
|
runAs: "__lunora_admin__:runAs",
|
|
43
62
|
runMigration: "__lunora_admin__:runMigration",
|
|
44
63
|
runSql: "__lunora_admin__:runSql",
|
|
64
|
+
sendQueueMessage: "__lunora_admin__:sendQueueMessage",
|
|
45
65
|
sendTestMail: "__lunora_admin__:sendTestMail",
|
|
66
|
+
setIssueSeverity: "__lunora_admin__:setIssueSeverity",
|
|
46
67
|
storageOrphans: "__lunora_admin__:storageOrphans",
|
|
47
68
|
storageReferences: "__lunora_admin__:storageReferences",
|
|
48
69
|
storageRules: "__lunora_admin__:storageRules",
|
|
@@ -91,7 +112,6 @@ const expandDocumentRows = (columns, rows) => {
|
|
|
91
112
|
};
|
|
92
113
|
const escapeLike = (value) => value.replaceAll(/[\\%_]/g, (character) => `\\${character}`);
|
|
93
114
|
const isInternalTable = (name) => name.startsWith("sqlite_") || name.startsWith("_cf_") || name.startsWith("__miniflare") || name.startsWith("__lunora") || name.includes("__fts_");
|
|
94
|
-
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
95
115
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
96
116
|
const countRows = (sql, quotedTable) => {
|
|
97
117
|
const row = sql.exec(`SELECT COUNT(*) AS c FROM ${quotedTable}`).one();
|
|
@@ -169,7 +189,7 @@ const buildOrderBy = (orderBy, physicalColumns) => {
|
|
|
169
189
|
const readTablePage = (sql, options) => {
|
|
170
190
|
const { table } = options;
|
|
171
191
|
if (isInternalTable(table) || !tableExists(sql, table)) {
|
|
172
|
-
throw
|
|
192
|
+
throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
|
|
173
193
|
}
|
|
174
194
|
const limit = clamp(Math.trunc(options.limit ?? DEFAULT_PAGE_SIZE), 1, MAX_PAGE_SIZE);
|
|
175
195
|
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
@@ -195,14 +215,17 @@ const readTablePage = (sql, options) => {
|
|
|
195
215
|
const orderSql = order === void 0 ? "" : ` ORDER BY ${order.sql}`;
|
|
196
216
|
const whereParams = predicate?.parameters ?? [];
|
|
197
217
|
const orderParams = order?.params ?? [];
|
|
198
|
-
|
|
218
|
+
let total;
|
|
219
|
+
if (!options.skipCount) {
|
|
220
|
+
total = predicate === void 0 ? countRows(sql, quoted) : Number(sql.exec(`SELECT COUNT(*) AS c FROM ${quoted}${whereSql}`, ...whereParams).one().c);
|
|
221
|
+
}
|
|
199
222
|
const rawRows = sql.exec(`SELECT * FROM ${quoted}${whereSql}${orderSql} LIMIT ? OFFSET ?`, ...whereParams, ...orderParams, limit, offset).toArray();
|
|
200
223
|
return withReferences({ ...expandDocumentRows(columns, rawRows), total });
|
|
201
224
|
};
|
|
202
225
|
const selectMatchingIds = (sql, options) => {
|
|
203
226
|
const { table } = options;
|
|
204
227
|
if (isInternalTable(table) || !tableExists(sql, table)) {
|
|
205
|
-
throw
|
|
228
|
+
throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
|
|
206
229
|
}
|
|
207
230
|
const limit = clamp(Math.trunc(options.limit ?? MAX_PAGE_SIZE), 1, MAX_PAGE_SIZE);
|
|
208
231
|
const quoted = quoteIdentifier(table);
|
|
@@ -233,16 +256,16 @@ const knownDisplayColumns = (sql, quotedTable, physicalColumns) => {
|
|
|
233
256
|
const facetColumn = (sql, options) => {
|
|
234
257
|
const { column, table } = options;
|
|
235
258
|
if (isInternalTable(table) || !tableExists(sql, table)) {
|
|
236
|
-
throw
|
|
259
|
+
throw new LunoraError("UNKNOWN_TABLE", `unknown table: ${table}`, { status: 404 });
|
|
237
260
|
}
|
|
238
261
|
const quoted = quoteIdentifier(table);
|
|
239
262
|
const physicalColumns = sql.exec(`PRAGMA table_info(${quoted})`).toArray().map((info) => info.name);
|
|
240
263
|
if (!knownDisplayColumns(sql, quoted, physicalColumns).has(column)) {
|
|
241
|
-
throw
|
|
264
|
+
throw new LunoraError("UNKNOWN_COLUMN", `unknown column: ${column}`, { status: 404 });
|
|
242
265
|
}
|
|
243
266
|
const resolved = resolveColumnExpression(column, physicalColumns);
|
|
244
267
|
if (resolved === void 0) {
|
|
245
|
-
throw
|
|
268
|
+
throw new LunoraError("UNKNOWN_COLUMN", `unknown column: ${column}`, { status: 404 });
|
|
246
269
|
}
|
|
247
270
|
const limit = clamp(Math.trunc(options.limit ?? DEFAULT_FACET_LIMIT), 1, MAX_FACET_LIMIT);
|
|
248
271
|
const needle = options.search?.trim() ?? "";
|
|
@@ -309,5 +332,43 @@ const summarizeSubscriptions = (attachments) => {
|
|
|
309
332
|
const totalSubscriptions = connections.reduce((sum, connection) => sum + connection.subscriptions.length, 0);
|
|
310
333
|
return { connections, totalConnections: connections.length, totalSubscriptions };
|
|
311
334
|
};
|
|
335
|
+
const DEFAULT_FANOUT_TOPIC_LIMIT = 20;
|
|
336
|
+
const createFanoutCounters = () => {
|
|
337
|
+
return { maxMs: 0, passes: 0, peakSocketsIterated: 0, socketsDelivered: 0, socketsIterated: 0, totalMs: 0 };
|
|
338
|
+
};
|
|
339
|
+
const recordFanoutPass = (counters, iterated, delivered, ms) => {
|
|
340
|
+
return {
|
|
341
|
+
maxMs: Math.max(counters.maxMs, ms),
|
|
342
|
+
passes: counters.passes + 1,
|
|
343
|
+
peakSocketsIterated: Math.max(counters.peakSocketsIterated, iterated),
|
|
344
|
+
socketsDelivered: counters.socketsDelivered + delivered,
|
|
345
|
+
socketsIterated: counters.socketsIterated + iterated,
|
|
346
|
+
totalMs: counters.totalMs + ms
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
const summarizeFanoutTopics = (attachments, limit = DEFAULT_FANOUT_TOPIC_LIMIT) => {
|
|
350
|
+
const shapeCounts = /* @__PURE__ */ new Map();
|
|
351
|
+
const whisperCounts = /* @__PURE__ */ new Map();
|
|
352
|
+
for (const attachment of attachments) {
|
|
353
|
+
for (const shape of Object.values(attachment.shapes ?? {})) {
|
|
354
|
+
const key = shape.name ?? "(unknown shape)";
|
|
355
|
+
shapeCounts.set(key, (shapeCounts.get(key) ?? 0) + 1);
|
|
356
|
+
}
|
|
357
|
+
for (const topic of attachment.whispers ?? []) {
|
|
358
|
+
whisperCounts.set(topic, (whisperCounts.get(topic) ?? 0) + 1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const topics = [
|
|
362
|
+
...[...shapeCounts].map(([topic, subscribers]) => {
|
|
363
|
+
return { kind: "shape", subscribers, topic };
|
|
364
|
+
}),
|
|
365
|
+
...[...whisperCounts].map(([topic, subscribers]) => {
|
|
366
|
+
return { kind: "whisper", subscribers, topic };
|
|
367
|
+
})
|
|
368
|
+
];
|
|
369
|
+
topics.sort((a, b) => b.subscribers - a.subscribers || a.topic.localeCompare(b.topic));
|
|
370
|
+
const peakSubscribers = topics[0]?.subscribers ?? 0;
|
|
371
|
+
return { peakSubscribers, topics: topics.slice(0, limit), totalConnections: attachments.length };
|
|
372
|
+
};
|
|
312
373
|
|
|
313
|
-
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
|
|
374
|
+
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, DEFAULT_FANOUT_TOPIC_LIMIT, FLAGS_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, createFanoutCounters, facetColumn, findStorageReferences, listTables, readTablePage, recordFanoutPass, selectMatchingIds, summarizeFanoutTopics, summarizeSubscriptions };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const COUNT_OPTION_KEYS = /* @__PURE__ */ new Set(["baseWhere", "relationBaseWhere", "restrictsCounts", "where"]);
|
|
2
4
|
const matchesStaticWhere = (document, predicate) => {
|
|
3
5
|
for (const [field, expected] of Object.entries(predicate)) {
|
|
@@ -38,16 +40,16 @@ const AGGREGATE_SQL_FUNCTION = { avg: "AVG", count: "COUNT", max: "MAX", min: "M
|
|
|
38
40
|
const aggregateSqlFunction = (op) => {
|
|
39
41
|
const sqlFunction = AGGREGATE_SQL_FUNCTION[op];
|
|
40
42
|
if (sqlFunction === void 0) {
|
|
41
|
-
throw new
|
|
43
|
+
throw new LunoraError("INTERNAL", `unknown aggregate op "${op}": expected one of ${Object.keys(AGGREGATE_SQL_FUNCTION).join(", ")}`);
|
|
42
44
|
}
|
|
43
45
|
return sqlFunction;
|
|
44
46
|
};
|
|
45
47
|
const throwingScheduler = {
|
|
46
48
|
runAfter: () => {
|
|
47
|
-
throw new
|
|
49
|
+
throw new LunoraError("INTERNAL", "ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
|
|
48
50
|
},
|
|
49
51
|
runAt: () => {
|
|
50
|
-
throw new
|
|
52
|
+
throw new LunoraError("INTERNAL", "ctx.scheduler: no scheduler configured for triggers. Pass `scheduler` to the ctx-db factory.");
|
|
51
53
|
}
|
|
52
54
|
};
|
|
53
55
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
2
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
|
-
import { ConflictError } from './ConflictError-
|
|
3
|
+
import { ConflictError } from './ConflictError-CLoq37xH.mjs';
|
|
4
4
|
|
|
5
5
|
const CDC_LOG_TABLE = "__cdc_log";
|
|
6
6
|
const migrateCdcLog = (sql$1) => {
|
|
@@ -26,9 +26,13 @@ const appendCdcChange = (sql$1, ts, table, id, op, doc) => {
|
|
|
26
26
|
const readCdcChanges = (sql$1, options = {}) => {
|
|
27
27
|
const sinceSeq = options.sinceSeq ?? 0;
|
|
28
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``;
|
|
29
33
|
const rows = runDrizzle(
|
|
30
34
|
sql$1,
|
|
31
|
-
sql`SELECT seq, ts, ${sql.identifier("table")}, id, op, doc FROM ${sql.identifier(CDC_LOG_TABLE)} WHERE seq > ${sinceSeq} ORDER BY seq ASC LIMIT ${limit}`
|
|
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}`
|
|
32
36
|
).toArray();
|
|
33
37
|
const changes = rows.map((row) => {
|
|
34
38
|
const base = { id: row.id, op: row.op, seq: row.seq, table: row.table, ts: row.ts };
|
|
@@ -75,7 +79,7 @@ const bumpCdcEpoch = (sql$1) => {
|
|
|
75
79
|
};
|
|
76
80
|
const applyCdcChange = async (writer, change) => {
|
|
77
81
|
if (change.op === "delete") {
|
|
78
|
-
await writer.delete(change.id);
|
|
82
|
+
await writer.delete(change.id, change.table);
|
|
79
83
|
return;
|
|
80
84
|
}
|
|
81
85
|
const document = change.doc ?? {};
|
|
@@ -91,7 +95,7 @@ const applyCdcChange = async (writer, change) => {
|
|
|
91
95
|
fields[key] = value;
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
|
-
await writer.replace(change.id, fields);
|
|
98
|
+
await writer.replace(change.id, fields, void 0, { allowExplicitId: true });
|
|
95
99
|
}
|
|
96
100
|
};
|
|
97
101
|
const applyCdcChanges = async (writer, changes) => {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
class ConflictError extends LunoraError {
|
|
3
4
|
/** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
|
|
4
5
|
kind;
|
|
5
|
-
status = 409;
|
|
6
6
|
constructor(message = "Optimistic concurrency conflict", kind = "conflict") {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "ConflictError";
|
|
7
|
+
super("CONFLICT", message, { name: "ConflictError" });
|
|
9
8
|
this.kind = kind;
|
|
10
9
|
}
|
|
11
10
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
status = 422;
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
class CountRlsUnsupportedError extends LunoraError {
|
|
5
4
|
constructor(table) {
|
|
6
5
|
super(
|
|
6
|
+
"COUNT_RLS_UNSUPPORTED",
|
|
7
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
8
|
);
|
|
9
9
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const DATA_MIGRATION_STATE_TABLE = "__lunora_migrations";
|
|
2
4
|
const DEFAULT_BATCH_SIZE = 100;
|
|
3
5
|
const STALE_CLAIM_TIMEOUT_MS = 3e4;
|
|
@@ -128,7 +130,7 @@ const runDataMigration = async (options) => {
|
|
|
128
130
|
const batchSize = options.batchSize ?? migration.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
129
131
|
const transform = direction === "up" ? migration.up : migration.down;
|
|
130
132
|
if (!transform) {
|
|
131
|
-
throw new
|
|
133
|
+
throw new LunoraError("INTERNAL", `data migration "${migration.id}" has no \`${direction}\` transform`);
|
|
132
134
|
}
|
|
133
135
|
let cursor = null;
|
|
134
136
|
let processed = 0;
|
|
@@ -176,7 +178,9 @@ const runDataMigration = async (options) => {
|
|
|
176
178
|
if (next !== void 0) {
|
|
177
179
|
changed += 1;
|
|
178
180
|
if (!dryRun) {
|
|
179
|
-
await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] }
|
|
181
|
+
await writer.replace(String(document["_id"]), { ...next, _creationTime: document["_creationTime"], _id: document["_id"] }, void 0, {
|
|
182
|
+
allowExplicitId: true
|
|
183
|
+
});
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
if (!dryRun) {
|
|
@@ -229,7 +233,11 @@ const runDataMigration = async (options) => {
|
|
|
229
233
|
throw error;
|
|
230
234
|
}
|
|
231
235
|
if (!dryRun && !isDone) {
|
|
232
|
-
|
|
236
|
+
try {
|
|
237
|
+
releaseClaim(sql, migration.id);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn(`data migration "${migration.id}": releaseClaim failed`, error);
|
|
240
|
+
}
|
|
233
241
|
}
|
|
234
242
|
return { changed, cursor: isDone ? null : cursor, direction, dryRun, id: migration.id, processed, status: isDone ? "completed" : "in_progress" };
|
|
235
243
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { distinctValues } from './applyOnDelete-BXSq3S70.mjs';
|
|
2
3
|
|
|
3
4
|
const RELATION_EXISTS_KEY = "__relationExists";
|
|
4
5
|
|
|
@@ -43,7 +44,7 @@ const containsRelationPredicate = (where, schema, tableName) => {
|
|
|
43
44
|
};
|
|
44
45
|
const assertFlatPredicate = (where, schema, tableName, op) => {
|
|
45
46
|
if (where && containsRelationPredicate(where, schema, tableName)) {
|
|
46
|
-
throw new
|
|
47
|
+
throw new LunoraError("INTERNAL", `relation-crossing predicates are not supported in ${op}() — use them in findMany/findFirst or an RLS read policy`);
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
50
|
const projectChildKeys = async (relation, childWhere, projectField, context, escalatable) => {
|
|
@@ -58,7 +59,8 @@ const projectChildKeys = async (relation, childWhere, projectField, context, esc
|
|
|
58
59
|
if (escalatable) {
|
|
59
60
|
return KEY_OVERFLOW;
|
|
60
61
|
}
|
|
61
|
-
throw new
|
|
62
|
+
throw new LunoraError(
|
|
63
|
+
"INTERNAL",
|
|
62
64
|
`relation predicate on "${relation.table}" matched ${String(keys.length)} rows, exceeding the ${String(context.maxRelationKeys)}-key limit; narrow the predicate (a same-shard EXISTS push-down lifts this cap)`
|
|
63
65
|
);
|
|
64
66
|
}
|
|
@@ -67,7 +69,7 @@ const projectChildKeys = async (relation, childWhere, projectField, context, esc
|
|
|
67
69
|
const compileOperator = async (operator, relation, childWhere, context, escalatable) => {
|
|
68
70
|
const meta = RELATION_OPERATOR_META[operator];
|
|
69
71
|
if (!meta) {
|
|
70
|
-
throw new
|
|
72
|
+
throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
|
|
71
73
|
}
|
|
72
74
|
const { clause, project } = joinColumns(relation);
|
|
73
75
|
const keys = await projectChildKeys(relation, meta.negateChild ? { NOT: childWhere } : childWhere, project, context, escalatable);
|
|
@@ -85,7 +87,7 @@ const compileOperator = async (operator, relation, childWhere, context, escalata
|
|
|
85
87
|
const buildExistsMarker = async (operator, relation, childWhere, parentTable, context) => {
|
|
86
88
|
const meta = RELATION_OPERATOR_META[operator];
|
|
87
89
|
if (!meta) {
|
|
88
|
-
throw new
|
|
90
|
+
throw new LunoraError("INTERNAL", `unknown relation operator "${operator}"`);
|
|
89
91
|
}
|
|
90
92
|
const base = context.relationBaseWhere?.(relation.table);
|
|
91
93
|
const predicatePart = meta.negateChild ? { NOT: childWhere } : childWhere;
|
|
@@ -97,7 +99,7 @@ const buildExistsMarker = async (operator, relation, childWhere, parentTable, co
|
|
|
97
99
|
const assertCardinality = (operator, name, relation) => {
|
|
98
100
|
const meta = RELATION_OPERATOR_META[operator];
|
|
99
101
|
if (meta && meta.kind !== relation.kind) {
|
|
100
|
-
throw new
|
|
102
|
+
throw new LunoraError("INTERNAL", `relation operator "${operator}" requires a to-${meta.kind} relation, but "${name}" is to-${relation.kind}`);
|
|
101
103
|
}
|
|
102
104
|
};
|
|
103
105
|
const resolveRelationNode = async (name, relation, predicate, parentTable, context) => {
|
|
@@ -156,5 +158,54 @@ const resolveRelationPredicates = async (where, options) => {
|
|
|
156
158
|
schema: options.schema
|
|
157
159
|
});
|
|
158
160
|
};
|
|
161
|
+
const firstShardedHit = (branches, schema, tableName) => {
|
|
162
|
+
for (const branch of branches) {
|
|
163
|
+
const hit = findShardedRelationTarget(branch, schema, tableName);
|
|
164
|
+
if (hit) {
|
|
165
|
+
return hit;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return void 0;
|
|
169
|
+
};
|
|
170
|
+
const inspectKey = (key, value, schema, tableName) => {
|
|
171
|
+
if (key === "AND" || key === "OR") {
|
|
172
|
+
return firstShardedHit(branchesOf(value), schema, tableName);
|
|
173
|
+
}
|
|
174
|
+
if (key === "NOT") {
|
|
175
|
+
return firstShardedHit([value ?? {}], schema, tableName);
|
|
176
|
+
}
|
|
177
|
+
const relation = schema.tables[tableName]?.relationMap?.[key];
|
|
178
|
+
if (!relation || !isRelationPredicate(value)) {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
if (schema.tables[relation.table]?.shardMode?.kind === "shardBy") {
|
|
182
|
+
return { relation: key, target: relation.table };
|
|
183
|
+
}
|
|
184
|
+
return firstShardedHit(Object.values(value), schema, relation.table);
|
|
185
|
+
};
|
|
186
|
+
const findShardedRelationTarget = (where, schema, tableName) => {
|
|
187
|
+
for (const key of Object.keys(where)) {
|
|
188
|
+
const hit = inspectKey(key, where[key], schema, tableName);
|
|
189
|
+
if (hit) {
|
|
190
|
+
return hit;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return void 0;
|
|
194
|
+
};
|
|
195
|
+
const assertShapeShardable = (effectiveWhere, schema, table) => {
|
|
196
|
+
if (!effectiveWhere) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const offending = findShardedRelationTarget(effectiveWhere, schema, table);
|
|
200
|
+
if (!offending) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
throw Object.assign(
|
|
204
|
+
new Error(
|
|
205
|
+
`shape on "${table}" joins the sharded table "${offending.target}" via relation "${offending.relation}" — a live shape cannot replicate rows that live in another shard's Durable Object. Fix it by (a) denormalizing the joined columns into "${table}", or (b) moving "${offending.target}" to .global() so it is served through the latency-tiered D1 shape tier.`
|
|
206
|
+
),
|
|
207
|
+
{ code: "SHAPE_CROSS_SHARD_JOIN", name: "LunoraError", status: 400 }
|
|
208
|
+
);
|
|
209
|
+
};
|
|
159
210
|
|
|
160
|
-
export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
|
|
211
|
+
export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, assertShapeShardable, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
|
2
|
+
const GEO_DEFAULT_PRECISION = 9;
|
|
3
|
+
const EARTH_RADIUS_METERS = 63710088e-1;
|
|
4
|
+
const CELL_WIDTH_METERS = [Number.POSITIVE_INFINITY, 5009400, 1252300, 156500, 39100, 4900, 1200, 152.9, 38.2, 4.77, 1.19, 0.149, 0.037];
|
|
5
|
+
const clampPrecision = (precision) => Math.min(Math.max(Math.trunc(precision), 1), 12);
|
|
6
|
+
const encodeGeohash = (point, precision) => {
|
|
7
|
+
const chars = clampPrecision(precision);
|
|
8
|
+
let latMin = -90;
|
|
9
|
+
let latMax = 90;
|
|
10
|
+
let lngMin = -180;
|
|
11
|
+
let lngMax = 180;
|
|
12
|
+
let hash = "";
|
|
13
|
+
let bit = 0;
|
|
14
|
+
let index = 0;
|
|
15
|
+
let even = true;
|
|
16
|
+
while (hash.length < chars) {
|
|
17
|
+
if (even) {
|
|
18
|
+
const mid = (lngMin + lngMax) / 2;
|
|
19
|
+
if (point.lng >= mid) {
|
|
20
|
+
index = index * 2 + 1;
|
|
21
|
+
lngMin = mid;
|
|
22
|
+
} else {
|
|
23
|
+
index *= 2;
|
|
24
|
+
lngMax = mid;
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
const mid = (latMin + latMax) / 2;
|
|
28
|
+
if (point.lat >= mid) {
|
|
29
|
+
index = index * 2 + 1;
|
|
30
|
+
latMin = mid;
|
|
31
|
+
} else {
|
|
32
|
+
index *= 2;
|
|
33
|
+
latMax = mid;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
even = !even;
|
|
37
|
+
if (bit < 4) {
|
|
38
|
+
bit += 1;
|
|
39
|
+
} else {
|
|
40
|
+
hash += BASE32[index] ?? "0";
|
|
41
|
+
bit = 0;
|
|
42
|
+
index = 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return hash;
|
|
46
|
+
};
|
|
47
|
+
const haversineMeters = (a, b) => {
|
|
48
|
+
const toRadians = (degrees) => degrees * Math.PI / 180;
|
|
49
|
+
const dLat = toRadians(b.lat - a.lat);
|
|
50
|
+
const dLng = toRadians(b.lng - a.lng);
|
|
51
|
+
const lat1 = toRadians(a.lat);
|
|
52
|
+
const lat2 = toRadians(b.lat);
|
|
53
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
|
|
54
|
+
return 2 * EARTH_RADIUS_METERS * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
55
|
+
};
|
|
56
|
+
const precisionForRadius = (radiusMeters) => {
|
|
57
|
+
for (let length = CELL_WIDTH_METERS.length - 1; length >= 1; length -= 1) {
|
|
58
|
+
const width = CELL_WIDTH_METERS[length];
|
|
59
|
+
if (width !== void 0 && width >= radiusMeters) {
|
|
60
|
+
return length;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return 1;
|
|
64
|
+
};
|
|
65
|
+
const NEIGHBOURS = {
|
|
66
|
+
east: ["bc01fg45238967deuvhjyznpkmstqrwx", "bc01fg45238967deuvhjyznpkmstqrwx"],
|
|
67
|
+
north: ["p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx"],
|
|
68
|
+
south: ["14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp"],
|
|
69
|
+
west: ["238967debc01fg45kmstqrwxuvhjyznp", "238967debc01fg45kmstqrwxuvhjyznp"]
|
|
70
|
+
};
|
|
71
|
+
const BORDERS = {
|
|
72
|
+
east: ["bcfguvyz", "prxz"],
|
|
73
|
+
north: ["prxz", "bcfguvyz"],
|
|
74
|
+
south: ["028b", "0145hjnp"],
|
|
75
|
+
west: ["0145hjnp", "028b"]
|
|
76
|
+
};
|
|
77
|
+
const adjacent = (hash, direction) => {
|
|
78
|
+
const lower = hash.toLowerCase();
|
|
79
|
+
const lastChar = lower.at(-1) ?? "";
|
|
80
|
+
let base = lower.slice(0, -1);
|
|
81
|
+
const type = lower.length % 2 === 0 ? 0 : 1;
|
|
82
|
+
if (BORDERS[direction][type].includes(lastChar) && base !== "") {
|
|
83
|
+
base = adjacent(base, direction);
|
|
84
|
+
}
|
|
85
|
+
return base + (BASE32[NEIGHBOURS[direction][type].indexOf(lastChar)] ?? "");
|
|
86
|
+
};
|
|
87
|
+
const coveringGeohashes = (center, radiusMeters) => {
|
|
88
|
+
const precision = precisionForRadius(Math.max(radiusMeters, 1));
|
|
89
|
+
const origin = encodeGeohash(center, precision);
|
|
90
|
+
const north = adjacent(origin, "north");
|
|
91
|
+
const south = adjacent(origin, "south");
|
|
92
|
+
const cells = [
|
|
93
|
+
origin,
|
|
94
|
+
north,
|
|
95
|
+
south,
|
|
96
|
+
adjacent(origin, "east"),
|
|
97
|
+
adjacent(origin, "west"),
|
|
98
|
+
adjacent(north, "east"),
|
|
99
|
+
adjacent(north, "west"),
|
|
100
|
+
adjacent(south, "east"),
|
|
101
|
+
adjacent(south, "west")
|
|
102
|
+
];
|
|
103
|
+
return [...new Set(cells)];
|
|
104
|
+
};
|
|
105
|
+
const pointInBoundingBox = (point, box) => point.lat >= box.sw.lat && point.lat <= box.ne.lat && point.lng >= box.sw.lng && point.lng <= box.ne.lng;
|
|
106
|
+
const boundingBoxCenter = (box) => {
|
|
107
|
+
return { lat: (box.sw.lat + box.ne.lat) / 2, lng: (box.sw.lng + box.ne.lng) / 2 };
|
|
108
|
+
};
|
|
109
|
+
const boundingBoxGeohashes = (box) => {
|
|
110
|
+
const center = boundingBoxCenter(box);
|
|
111
|
+
return coveringGeohashes(center, Math.max(haversineMeters(center, box.ne), 1));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export { GEO_DEFAULT_PRECISION, boundingBoxCenter, boundingBoxGeohashes, coveringGeohashes, encodeGeohash, haversineMeters, pointInBoundingBox };
|
|
@@ -1,10 +1,42 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const MAX_SQL_ROWS = 1e3;
|
|
2
4
|
const READONLY_LEAD = /^(?:explain\s+(?:query\s+plan\s+)?)?(?:select|with)\b/iu;
|
|
3
5
|
const FORBIDDEN_KEYWORD = /\b(?:alter|attach|create|delete|detach|drop|insert|pragma|reindex|replace|truncate|update|vacuum)\b/iu;
|
|
4
|
-
const LEADING_NOISE = /^(?:\s|--[^\n]*\n?|\/\*[\s\S]*?\*\/)+/u;
|
|
5
6
|
const TRAILING_SEMICOLON = /;\s*$/u;
|
|
6
|
-
const
|
|
7
|
-
const
|
|
7
|
+
const WHITESPACE = /\s/u;
|
|
8
|
+
const skipLineComment = (sql, from) => {
|
|
9
|
+
let index = from + 2;
|
|
10
|
+
while (index < sql.length && sql[index] !== "\n") {
|
|
11
|
+
index += 1;
|
|
12
|
+
}
|
|
13
|
+
return index;
|
|
14
|
+
};
|
|
15
|
+
const skipBlockComment = (sql, from) => {
|
|
16
|
+
const close = sql.indexOf("*/", from + 2);
|
|
17
|
+
return close === -1 ? -1 : close + 2;
|
|
18
|
+
};
|
|
19
|
+
const stripLeading = (sql) => {
|
|
20
|
+
let index = 0;
|
|
21
|
+
while (index < sql.length) {
|
|
22
|
+
const char = sql[index];
|
|
23
|
+
if (char !== void 0 && WHITESPACE.test(char)) {
|
|
24
|
+
index += 1;
|
|
25
|
+
} else if (char === "-" && sql[index + 1] === "-") {
|
|
26
|
+
index = skipLineComment(sql, index);
|
|
27
|
+
} else if (char === "/" && sql[index + 1] === "*") {
|
|
28
|
+
const next = skipBlockComment(sql, index);
|
|
29
|
+
if (next === -1) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
index = next;
|
|
33
|
+
} else {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return sql.slice(index);
|
|
38
|
+
};
|
|
39
|
+
const sqlError = (message, code) => new LunoraError(code, message, { status: 400 });
|
|
8
40
|
const assertReadonly = (query) => {
|
|
9
41
|
const trimmed = stripLeading(query).trim();
|
|
10
42
|
if (trimmed === "") {
|