@lunora/do 1.0.0-alpha.32 → 1.0.0-alpha.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4148 -4184
- package/dist/index.d.ts +4148 -4184
- package/dist/index.mjs +6 -6
- package/dist/packem_shared/{CDC_LOG_TABLE-DjJEHiM2.mjs → CDC_LOG_TABLE-uwOJxJJZ.mjs} +1 -1
- package/dist/packem_shared/{NotUniqueError-BigrdT_W.mjs → NotUniqueError-Ca21uDuU.mjs} +3 -3
- package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-BWz51mpB.mjs → ROOT_DO_SIZE_WARN_BYTES-B5c6kQIN.mjs} +180 -38
- package/dist/packem_shared/{isSoftDeleted-i5kKh6Up.mjs → isSoftDeleted-YLKR6JYw.mjs} +39 -5
- package/dist/packem_shared/{materializeExternalRows-FyA5Rwn4.mjs → materializeExternalRows-DlQWMlw_.mjs} +5 -2
- package/dist/packem_shared/{runShardMigrations-BGx4v2B6.mjs → runShardMigrations-DFzx6Qld.mjs} +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -3,13 +3,13 @@ export { AGGREGATE_SQL_FUNCTION, aggregateSqlFunction, matchesStaticWhere, norma
|
|
|
3
3
|
export { aggregateTableName, coerceAggregateNumber, encodeAggregateKey, foldAggregateTally, readAggregateValue } from './packem_shared/aggregateTableName-CxNqY1Sl.mjs';
|
|
4
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 { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-
|
|
6
|
+
export { NotUniqueError, assertValidClientId, createShardCtxDb, normalizeIdStructurally } from './packem_shared/NotUniqueError-Ca21uDuU.mjs';
|
|
7
7
|
export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } from './packem_shared/DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs';
|
|
8
8
|
export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
|
|
9
9
|
export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
|
|
10
10
|
export { diffExternalSource } from './packem_shared/diffExternalSource-CovHfdyo.mjs';
|
|
11
|
-
export { materializeExternalRows, materializeExternalRowsIncremental, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-
|
|
12
|
-
export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick } from './packem_shared/isSoftDeleted-
|
|
11
|
+
export { materializeExternalRows, materializeExternalRowsIncremental, readExternalSourceBaseline, runExternalSourceTick } from './packem_shared/materializeExternalRows-DlQWMlw_.mjs';
|
|
12
|
+
export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick } from './packem_shared/isSoftDeleted-YLKR6JYw.mjs';
|
|
13
13
|
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';
|
|
14
14
|
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-CAHLZMj8.mjs';
|
|
15
15
|
export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
|
|
@@ -26,16 +26,16 @@ export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_share
|
|
|
26
26
|
export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
|
|
27
27
|
export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
|
|
28
28
|
export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs';
|
|
29
|
-
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-
|
|
29
|
+
export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-B5c6kQIN.mjs';
|
|
30
30
|
export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-D99roc-r.mjs';
|
|
31
31
|
export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-iFAA8FbD.mjs';
|
|
32
32
|
export { createSystemReader } from './packem_shared/createSystemReader-D12eNH13.mjs';
|
|
33
33
|
export { ConflictError } from './packem_shared/ConflictError-CLoq37xH.mjs';
|
|
34
34
|
export { hasTrigger, runTriggers } from './packem_shared/hasTrigger-5N6_Fx0A.mjs';
|
|
35
35
|
export { compileWhereSql } from './packem_shared/compileWhereSql-DE6yfRcQ.mjs';
|
|
36
|
-
export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-
|
|
36
|
+
export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
37
37
|
export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-DDoT-UUI.mjs';
|
|
38
|
-
export { runShardMigrations } from './packem_shared/runShardMigrations-
|
|
38
|
+
export { runShardMigrations } from './packem_shared/runShardMigrations-DFzx6Qld.mjs';
|
|
39
39
|
export { stableStringify } from './packem_shared/stableStringify-mC40mZts.mjs';
|
|
40
40
|
export { stableWireKey } from './packem_shared/stableWireKey-DKuXO7T5.mjs';
|
|
41
41
|
export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-CT76bYny.mjs';
|
|
@@ -79,7 +79,7 @@ const bumpCdcEpoch = (sql$1) => {
|
|
|
79
79
|
};
|
|
80
80
|
const applyCdcChange = async (writer, change) => {
|
|
81
81
|
if (change.op === "delete") {
|
|
82
|
-
await writer.delete(change.id);
|
|
82
|
+
await writer.delete(change.id, change.table);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
const document = change.doc ?? {};
|
|
@@ -3,8 +3,8 @@ import { sql } from 'drizzle-orm';
|
|
|
3
3
|
import { matchesStaticWhere, aggregateSqlFunction, normalizeCountArgument, throwingScheduler } from './AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs';
|
|
4
4
|
import { encodeAggregateKey, foldAggregateTally, aggregateTableName, coerceAggregateNumber, readAggregateValue } from './aggregateTableName-CxNqY1Sl.mjs';
|
|
5
5
|
import { mergeWhere, CountRlsUnsupportedError, selectIndexForGroupBy, selectIndexForCount, selectIndexForAggregate } from './CountRlsUnsupportedError-BGxj0pgS.mjs';
|
|
6
|
-
import { appendCdcChange } from './CDC_LOG_TABLE-
|
|
7
|
-
export { CDC_LOG_TABLE, applyCdcChanges, bumpCdcEpoch, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges } from './CDC_LOG_TABLE-
|
|
6
|
+
import { appendCdcChange } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
7
|
+
export { CDC_LOG_TABLE, applyCdcChanges, bumpCdcEpoch, minCdcSeq, readCdcChanges, readCdcCursor, readCdcEpoch, trimCdcChanges } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
8
8
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
9
9
|
import { i as isFtsAvailable, D as DOC_COLUMN$1, r as rowToDocument, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT, d as aggUpsertSql, j as jsonPathSql, q as quoteIdentifier, t as tableColumns, e as qualifiedJsonPathSql } from './do-sql-BCHCWtrD.mjs';
|
|
10
10
|
import { param } from './renderSql-D6eUcn2N.mjs';
|
|
@@ -23,7 +23,7 @@ import { runTriggers } from './hasTrigger-5N6_Fx0A.mjs';
|
|
|
23
23
|
import { compileWhereSql } from './compileWhereSql-DE6yfRcQ.mjs';
|
|
24
24
|
export { backfillAggregateIndexes, backfillRankIndexes } from './backfillAggregateIndexes-DDoT-UUI.mjs';
|
|
25
25
|
export { C as CLIENT_WATERMARK_TABLE, G as GLOBAL_SHAPE_SNAPSHOT_TABLE, I as IDEMPOTENCY_TABLE, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, e as deleteGlobalShapeSnapshotsForConnection, m as migrateClientWatermark, b as migrateGlobalShapeSnapshot, r as readClientWatermark, f as readGlobalShapeSnapshot, g as readIdempotent, t as trimIdempotent, w as writeGlobalShapeSnapshot, h as writeIdempotent } from './ctx-db-idempotency-BdcNpvY4.mjs';
|
|
26
|
-
export { runShardMigrations } from './runShardMigrations-
|
|
26
|
+
export { runShardMigrations } from './runShardMigrations-DFzx6Qld.mjs';
|
|
27
27
|
export { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-Cz9dHyh1.mjs';
|
|
28
28
|
|
|
29
29
|
const rankIndexFieldsUnchanged = (index, previous, next) => {
|
|
@@ -21,12 +21,36 @@ import { i as isDevEnvironment, c as buildSettings, b as buildSecurityAudit } fr
|
|
|
21
21
|
import { runReadonlySql } from './MAX_SQL_ROWS-iFAA8FbD.mjs';
|
|
22
22
|
import { ConflictError } from './ConflictError-CLoq37xH.mjs';
|
|
23
23
|
import { e as deleteGlobalShapeSnapshotsForConnection, g as readIdempotent, h as writeIdempotent, t as trimIdempotent, r as readClientWatermark, m as migrateClientWatermark, c as advanceClientWatermark, d as deleteGlobalShapeSnapshot, f as readGlobalShapeSnapshot, w as writeGlobalShapeSnapshot } from './ctx-db-idempotency-BdcNpvY4.mjs';
|
|
24
|
-
import { CDC_LOG_TABLE, readCdcChanges, readCdcCursor, readCdcEpoch, minCdcSeq, bumpCdcEpoch } from './CDC_LOG_TABLE-
|
|
24
|
+
import { CDC_LOG_TABLE, readCdcChanges, readCdcCursor, readCdcEpoch, minCdcSeq, bumpCdcEpoch } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
25
25
|
import { stableStringify } from './stableStringify-mC40mZts.mjs';
|
|
26
26
|
import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes-Cz9dHyh1.mjs';
|
|
27
27
|
|
|
28
28
|
const MAX_BATCH_ENTRIES = 500;
|
|
29
29
|
|
|
30
|
+
const BUFFER_LEVEL = {
|
|
31
|
+
debug: "debug",
|
|
32
|
+
error: "error",
|
|
33
|
+
fatal: "error",
|
|
34
|
+
info: "info",
|
|
35
|
+
log: "info",
|
|
36
|
+
trace: "debug",
|
|
37
|
+
warn: "warn"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const HEX_ONLY = /^[0-9a-f]+$/;
|
|
41
|
+
const parseTraceparent = (header) => {
|
|
42
|
+
if (header === null || header === void 0) {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
const parts = header.trim().toLowerCase().split("-");
|
|
46
|
+
const [version, traceId, parentSpanId, flags] = parts;
|
|
47
|
+
if (parts.length < 4 || version === void 0 || version.length !== 2 || !HEX_ONLY.test(version) || version === "ff" || // Version 00 forbids trailing fields; only a future version may carry them.
|
|
48
|
+
version === "00" && parts.length !== 4 || traceId === void 0 || parentSpanId === void 0 || flags === void 0 || flags.length !== 2 || !HEX_ONLY.test(flags) || traceId.length !== 32 || parentSpanId.length !== 16 || !HEX_ONLY.test(traceId) || !HEX_ONLY.test(parentSpanId) || traceId === "00000000000000000000000000000000" || parentSpanId === "0000000000000000") {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
return { parentSpanId, traceId };
|
|
52
|
+
};
|
|
53
|
+
|
|
30
54
|
const evictOldestEntry = (map, capacity) => {
|
|
31
55
|
if (map.size < capacity) {
|
|
32
56
|
return;
|
|
@@ -1158,6 +1182,35 @@ const createRelayLink = (host) => {
|
|
|
1158
1182
|
return parsed === void 0 ? new OwnerRelay(host, name) : new RelayMember(host, parsed.ownerKey, parsed.relayIndex);
|
|
1159
1183
|
};
|
|
1160
1184
|
|
|
1185
|
+
const stringifyFieldValue = (value) => {
|
|
1186
|
+
if (typeof value === "string") {
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
return JSON.stringify(value) ?? String(value);
|
|
1191
|
+
} catch {
|
|
1192
|
+
return String(value);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
const coerceFieldValue = (value) => typeof value === "boolean" || typeof value === "number" || typeof value === "string" ? value : stringifyFieldValue(value);
|
|
1196
|
+
const normalizeLogFields = (perCall, bound) => {
|
|
1197
|
+
if (perCall === void 0 && bound === void 0) {
|
|
1198
|
+
return void 0;
|
|
1199
|
+
}
|
|
1200
|
+
const merged = {};
|
|
1201
|
+
if (bound !== void 0) {
|
|
1202
|
+
for (const [key, value] of Object.entries(bound)) {
|
|
1203
|
+
merged[key] = coerceFieldValue(value);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (perCall !== void 0) {
|
|
1207
|
+
for (const [key, value] of Object.entries(perCall)) {
|
|
1208
|
+
merged[key] = coerceFieldValue(value);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return Object.keys(merged).length === 0 ? void 0 : merged;
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1161
1214
|
const REQUEST_LOG_TABLE = "__lunora_reqlog__";
|
|
1162
1215
|
const REQUEST_LOG_RETENTION = 1e3;
|
|
1163
1216
|
const REQUEST_LOG_EVENT_SOURCE = "lunora";
|
|
@@ -1271,18 +1324,40 @@ const renderLogMessage = (args) => args.map((value) => {
|
|
|
1271
1324
|
return String(value);
|
|
1272
1325
|
}
|
|
1273
1326
|
}).join(" ");
|
|
1327
|
+
const isLogFields = (value) => {
|
|
1328
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
const proto = Object.getPrototypeOf(value);
|
|
1332
|
+
return proto === Object.prototype || proto === null;
|
|
1333
|
+
};
|
|
1334
|
+
const parseLogArgs = (args, boundFields) => {
|
|
1335
|
+
if (args.length === 2 && typeof args[0] === "string" && isLogFields(args[1])) {
|
|
1336
|
+
return { fields: normalizeLogFields(args[1], boundFields), message: args[0] };
|
|
1337
|
+
}
|
|
1338
|
+
return { fields: normalizeLogFields(void 0, boundFields), message: renderLogMessage(args) };
|
|
1339
|
+
};
|
|
1274
1340
|
const emitLogEvent = (input) => {
|
|
1275
|
-
const
|
|
1341
|
+
const payload = {
|
|
1342
|
+
fields: input.fields,
|
|
1276
1343
|
function: input.functionPath,
|
|
1277
1344
|
level: input.level,
|
|
1278
1345
|
message: input.message,
|
|
1279
1346
|
shard: input.shardKey,
|
|
1280
1347
|
source: REQUEST_LOG_EVENT_SOURCE,
|
|
1348
|
+
spanId: input.spanId,
|
|
1349
|
+
traceId: input.traceId,
|
|
1281
1350
|
ts: input.ts,
|
|
1282
1351
|
type: LOG_EVENT_TYPE,
|
|
1283
1352
|
userId: input.userId
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1353
|
+
};
|
|
1354
|
+
let line;
|
|
1355
|
+
try {
|
|
1356
|
+
line = JSON.stringify(payload);
|
|
1357
|
+
} catch {
|
|
1358
|
+
line = JSON.stringify({ ...payload, fields: void 0 });
|
|
1359
|
+
}
|
|
1360
|
+
if (input.level === "error" || input.level === "fatal") {
|
|
1286
1361
|
console.error(line);
|
|
1287
1362
|
} else if (input.level === "warn") {
|
|
1288
1363
|
console.warn(line);
|
|
@@ -2088,6 +2163,25 @@ class ShardDO {
|
|
|
2088
2163
|
static resetRootSizeWarning() {
|
|
2089
2164
|
ShardDO.rootSizeWarned = false;
|
|
2090
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Compute the shared poll alarm's next wake time from both tiers' signals.
|
|
2168
|
+
* Global shapes need the fixed `GLOBAL_SHAPE_POLL_INTERVAL_MS` floor whenever
|
|
2169
|
+
* any are subscribed (a global table has no per-DO op-log, so it can only be
|
|
2170
|
+
* polled). External-source ingest instead reports the earliest NEXT-DUE
|
|
2171
|
+
* timestamp across its non-manual sources (or `undefined` when none exist) —
|
|
2172
|
+
* a source with a large `refresh.everyMs` must sleep until it's actually due,
|
|
2173
|
+
* not spin at the global-shape floor. Returns `undefined` when NEITHER tier
|
|
2174
|
+
* has pending work, so the DO can go fully idle instead of re-arming for no
|
|
2175
|
+
* reason; otherwise the earlier of the two candidate times (never later than
|
|
2176
|
+
* `nowMs`, so a source that's already due arms essentially immediately).
|
|
2177
|
+
*/
|
|
2178
|
+
static nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, nowMs) {
|
|
2179
|
+
const globalTarget = globalShapesRemaining > 0 ? nowMs + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS : void 0;
|
|
2180
|
+
if (globalTarget === void 0) {
|
|
2181
|
+
return nextSourceDueAt === void 0 ? void 0 : Math.max(nextSourceDueAt, nowMs);
|
|
2182
|
+
}
|
|
2183
|
+
return nextSourceDueAt === void 0 ? globalTarget : Math.min(globalTarget, nextSourceDueAt);
|
|
2184
|
+
}
|
|
2091
2185
|
state;
|
|
2092
2186
|
env;
|
|
2093
2187
|
/**
|
|
@@ -2709,31 +2803,38 @@ class ShardDO {
|
|
|
2709
2803
|
webSocketError(_ws, _error) {
|
|
2710
2804
|
}
|
|
2711
2805
|
/**
|
|
2712
|
-
* Durable Object alarm handler — the heartbeat for `.global()`-table shapes
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
2715
|
-
*
|
|
2716
|
-
*
|
|
2717
|
-
*
|
|
2806
|
+
* Durable Object alarm handler — the heartbeat for `.global()`-table shapes
|
|
2807
|
+
* AND external-source (`.source(...)`) ingest, which share one alarm. The
|
|
2808
|
+
* runtime wakes this when the poll alarm armed by `scheduleGlobalPoll` fires;
|
|
2809
|
+
* it refreshes every subscribed global shape (diff-poke from the global
|
|
2810
|
+
* backend), materializes any due sourced tables, and re-arms at
|
|
2811
|
+
* {@link ShardDO.nextPollAlarmTarget} — the fixed floor while global shapes
|
|
2812
|
+
* are subscribed, or the earliest source next-due time when only ingest
|
|
2813
|
+
* remains (so a 1-hour-`refresh` source sleeps ~1 hour instead of waking
|
|
2814
|
+
* every 2 s). With neither tier pending, the alarm is not re-armed and the DO
|
|
2815
|
+
* goes idle. A base-only / global-free / source-free DO never arms it, so
|
|
2816
|
+
* this stays dormant there.
|
|
2718
2817
|
*/
|
|
2719
2818
|
async alarm() {
|
|
2720
2819
|
this.globalPollScheduled = false;
|
|
2721
|
-
let
|
|
2820
|
+
let globalShapesRemaining;
|
|
2722
2821
|
try {
|
|
2723
|
-
|
|
2822
|
+
globalShapesRemaining = await this.pollGlobalShapes();
|
|
2724
2823
|
} catch (error) {
|
|
2725
2824
|
this.recordShapeError("shape:poll", error);
|
|
2726
|
-
|
|
2825
|
+
globalShapesRemaining = 1;
|
|
2727
2826
|
}
|
|
2827
|
+
let nextSourceDueAt;
|
|
2728
2828
|
try {
|
|
2729
|
-
|
|
2829
|
+
nextSourceDueAt = await this.pollExternalSources();
|
|
2730
2830
|
} catch (error) {
|
|
2731
2831
|
this.recordShapeError("source:poll", error);
|
|
2732
|
-
|
|
2832
|
+
nextSourceDueAt = Date.now() + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS;
|
|
2733
2833
|
}
|
|
2734
2834
|
await this.flushChangedTables();
|
|
2735
|
-
|
|
2736
|
-
|
|
2835
|
+
const nextAlarmAt = ShardDO.nextPollAlarmTarget(globalShapesRemaining, nextSourceDueAt, Date.now());
|
|
2836
|
+
if (nextAlarmAt !== void 0) {
|
|
2837
|
+
await this.scheduleGlobalPoll(nextAlarmAt);
|
|
2737
2838
|
}
|
|
2738
2839
|
}
|
|
2739
2840
|
/**
|
|
@@ -3881,18 +3982,25 @@ class ShardDO {
|
|
|
3881
3982
|
/**
|
|
3882
3983
|
* Poll external-source (`.source(...)`) tables once (plan 077): materialize
|
|
3883
3984
|
* each sourced table's freshly-pulled tenant slice into this DO's SQLite. The
|
|
3884
|
-
* base `ShardDO` has no sourced tables, so it returns `
|
|
3885
|
-
* stays dormant — zero behavior change for every existing DO. The
|
|
3886
|
-
* subclass overrides it to, per sourced table, build a
|
|
3887
|
-
* writer, read the tenant slice from Hyperdrive under this
|
|
3888
|
-
* run `runExternalSourceTick` (read local baseline → diff
|
|
3889
|
-
* validated CDC writer).
|
|
3890
|
-
*
|
|
3891
|
-
*
|
|
3985
|
+
* base `ShardDO` has no sourced tables, so it returns `undefined` and the
|
|
3986
|
+
* ingest tier stays dormant — zero behavior change for every existing DO. The
|
|
3987
|
+
* codegen subclass overrides it to, per sourced table, build a
|
|
3988
|
+
* `createShardCtxDb` writer, read the tenant slice from Hyperdrive under this
|
|
3989
|
+
* DO's shard key, and run `runExternalSourceTick` (read local baseline → diff
|
|
3990
|
+
* → apply via the validated CDC writer).
|
|
3991
|
+
*
|
|
3992
|
+
* Returns the EARLIEST next-due timestamp (absolute epoch ms) across every
|
|
3993
|
+
* non-manual sourced table — `polledAt.get(table) ?? now` plus that source's
|
|
3994
|
+
* `refresh.everyMs` — or `undefined` when every sourced table is
|
|
3995
|
+
* `refresh: "manual"` (or there are none). NOT a bare active count: the
|
|
3996
|
+
* shared poll alarm ({@link ShardDO.alarm}) uses this to re-arm at the exact
|
|
3997
|
+
* next time ingest needs to run, instead of spinning at the fixed
|
|
3998
|
+
* `GLOBAL_SHAPE_POLL_INTERVAL_MS` floor for a source whose `refresh.everyMs`
|
|
3999
|
+
* is, say, an hour away.
|
|
3892
4000
|
*/
|
|
3893
4001
|
// eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass implements the real Hyperdrive-backed poll
|
|
3894
4002
|
pollExternalSources() {
|
|
3895
|
-
return Promise.resolve(0);
|
|
4003
|
+
return Promise.resolve(void 0);
|
|
3896
4004
|
}
|
|
3897
4005
|
/**
|
|
3898
4006
|
* Arm the shared poll alarm for external-source ingest (plan 077). The alarm is
|
|
@@ -4049,28 +4157,55 @@ class ShardDO {
|
|
|
4049
4157
|
* Unlike request-log args, `ctx.log` args are NOT redacted: the developer
|
|
4050
4158
|
* chose to log them, exactly like a raw `console.log`.
|
|
4051
4159
|
*/
|
|
4052
|
-
recordUserLog(functionPath, level, args, sink) {
|
|
4160
|
+
recordUserLog(functionPath, level, args, message, fields, sink) {
|
|
4161
|
+
const trace = parseTraceparent(this.getCurrentTraceparent());
|
|
4053
4162
|
const event = {
|
|
4054
4163
|
args,
|
|
4164
|
+
fields,
|
|
4055
4165
|
functionPath,
|
|
4056
4166
|
level,
|
|
4057
|
-
message
|
|
4167
|
+
message,
|
|
4058
4168
|
shardKey: this.state.id?.name,
|
|
4169
|
+
spanId: trace?.parentSpanId,
|
|
4170
|
+
traceId: trace?.traceId,
|
|
4059
4171
|
ts: Date.now(),
|
|
4060
4172
|
userId: this.getCurrentUserId()
|
|
4061
4173
|
};
|
|
4062
|
-
this.logs.push({ functionPath, level: level
|
|
4174
|
+
this.logs.push({ fields, functionPath, level: BUFFER_LEVEL[level], message, timestamp: event.ts });
|
|
4063
4175
|
try {
|
|
4064
4176
|
emitLogEvent(event);
|
|
4065
4177
|
} catch {
|
|
4066
4178
|
}
|
|
4067
4179
|
if (sink?.onLog) {
|
|
4068
4180
|
try {
|
|
4069
|
-
sink.onLog(event);
|
|
4181
|
+
sink.onLog(event, { waitUntil: this.state.waitUntil?.bind(this.state) });
|
|
4070
4182
|
} catch {
|
|
4071
4183
|
}
|
|
4072
4184
|
}
|
|
4073
4185
|
}
|
|
4186
|
+
/**
|
|
4187
|
+
* Build the `ctx.log` logger for one dispatched function. Each severity method
|
|
4188
|
+
* accepts either the structured form (`(message, fields)`) or console-style
|
|
4189
|
+
* varargs (see {@link parseLogArgs}); `with(fields)` returns a child that
|
|
4190
|
+
* stamps `fields` onto every line. The generated `buildCtx` calls this once
|
|
4191
|
+
* per dispatch and assigns the result to `ctx.log`.
|
|
4192
|
+
*/
|
|
4193
|
+
makeLogger(functionPath, sink, boundFields) {
|
|
4194
|
+
const emit = (level, args) => {
|
|
4195
|
+
const { fields, message } = parseLogArgs(args, boundFields);
|
|
4196
|
+
this.recordUserLog(functionPath, level, args, message, fields, sink);
|
|
4197
|
+
};
|
|
4198
|
+
return {
|
|
4199
|
+
debug: (...args) => emit("debug", args),
|
|
4200
|
+
error: (...args) => emit("error", args),
|
|
4201
|
+
fatal: (...args) => emit("fatal", args),
|
|
4202
|
+
info: (...args) => emit("info", args),
|
|
4203
|
+
log: (...args) => emit("log", args),
|
|
4204
|
+
trace: (...args) => emit("trace", args),
|
|
4205
|
+
warn: (...args) => emit("warn", args),
|
|
4206
|
+
with: (fields) => this.makeLogger(functionPath, sink, boundFields ? { ...boundFields, ...fields } : fields)
|
|
4207
|
+
};
|
|
4208
|
+
}
|
|
4074
4209
|
/**
|
|
4075
4210
|
* Assemble the per-socket {@link LifecycleDispatchInfo} from its attachment:
|
|
4076
4211
|
* the verified identity to replay and the {@link LifecycleEvent} the hooks
|
|
@@ -6171,13 +6306,20 @@ class ShardDO {
|
|
|
6171
6306
|
}
|
|
6172
6307
|
}
|
|
6173
6308
|
/**
|
|
6174
|
-
* Arm the poll alarm
|
|
6175
|
-
*
|
|
6176
|
-
*
|
|
6177
|
-
*
|
|
6178
|
-
*
|
|
6179
|
-
|
|
6180
|
-
|
|
6309
|
+
* Arm the poll alarm if one isn't already pending. Idempotent — every
|
|
6310
|
+
* global-shape seed calls it, but only the first arms the alarm. Degrades to
|
|
6311
|
+
* a no-op when the runtime exposes no `setAlarm` (the unit harness): a global
|
|
6312
|
+
* shape is then seed-only, which the poll-loop tests assert by driving
|
|
6313
|
+
* {@link ShardDO.alarm} directly.
|
|
6314
|
+
*
|
|
6315
|
+
* `atMs` lets {@link ShardDO.alarm} re-arm at a computed target — the
|
|
6316
|
+
* earlier of the fixed global-shape floor and the earliest external-source
|
|
6317
|
+
* next-due time — instead of always the fixed floor. Every OTHER caller
|
|
6318
|
+
* (a fresh global-shape seed, {@link ShardDO.scheduleSourcePoll}'s initial
|
|
6319
|
+
* kick) omits it and gets the original `GLOBAL_SHAPE_POLL_INTERVAL_MS`
|
|
6320
|
+
* default, since neither knows a more precise due time yet.
|
|
6321
|
+
*/
|
|
6322
|
+
async scheduleGlobalPoll(atMs) {
|
|
6181
6323
|
if (this.globalPollScheduled) {
|
|
6182
6324
|
return;
|
|
6183
6325
|
}
|
|
@@ -6187,7 +6329,7 @@ class ShardDO {
|
|
|
6187
6329
|
}
|
|
6188
6330
|
this.globalPollScheduled = true;
|
|
6189
6331
|
try {
|
|
6190
|
-
await setAlarm.call(this.state.storage, Date.now() + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS);
|
|
6332
|
+
await setAlarm.call(this.state.storage, atMs ?? Date.now() + ShardDO.GLOBAL_SHAPE_POLL_INTERVAL_MS);
|
|
6191
6333
|
} catch {
|
|
6192
6334
|
this.globalPollScheduled = false;
|
|
6193
6335
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
import { sql } from 'drizzle-orm';
|
|
3
3
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
4
|
-
import { runExternalSourceTick, materializeExternalRowsIncremental } from './materializeExternalRows-
|
|
4
|
+
import { runExternalSourceTick, materializeExternalRowsIncremental } from './materializeExternalRows-DlQWMlw_.mjs';
|
|
5
5
|
|
|
6
6
|
const SOURCE_CURSOR_TABLE = "__lunora_source_cursor";
|
|
7
7
|
const serializeCursor = (value) => {
|
|
@@ -96,6 +96,28 @@ const writeSourceCursor = (sql$1, table, shardKey, state) => {
|
|
|
96
96
|
);
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
const normalizeSourceValue = (value) => {
|
|
100
|
+
if (value instanceof Date) {
|
|
101
|
+
return value.toISOString();
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "bigint") {
|
|
104
|
+
return String(value);
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.map((element) => normalizeSourceValue(element));
|
|
108
|
+
}
|
|
109
|
+
if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
110
|
+
return normalizeSourceDocument(value);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
};
|
|
114
|
+
const normalizeSourceDocument = (document) => {
|
|
115
|
+
const normalized = {};
|
|
116
|
+
for (const [key, value] of Object.entries(document)) {
|
|
117
|
+
normalized[key] = normalizeSourceValue(value);
|
|
118
|
+
}
|
|
119
|
+
return normalized;
|
|
120
|
+
};
|
|
99
121
|
const liftSourceId = (row, options = {}) => {
|
|
100
122
|
const { idColumn = "id", map } = options;
|
|
101
123
|
const idValue = row[idColumn];
|
|
@@ -107,7 +129,7 @@ const liftSourceId = (row, options = {}) => {
|
|
|
107
129
|
}
|
|
108
130
|
const id = String(idValue);
|
|
109
131
|
if (map) {
|
|
110
|
-
return { ...map(row), _id: id };
|
|
132
|
+
return normalizeSourceDocument({ ...map(row), _id: id });
|
|
111
133
|
}
|
|
112
134
|
const body = {};
|
|
113
135
|
for (const [key, value] of Object.entries(row)) {
|
|
@@ -115,7 +137,7 @@ const liftSourceId = (row, options = {}) => {
|
|
|
115
137
|
body[key] = value;
|
|
116
138
|
}
|
|
117
139
|
}
|
|
118
|
-
return { ...body, _id: id };
|
|
140
|
+
return normalizeSourceDocument({ ...body, _id: id });
|
|
119
141
|
};
|
|
120
142
|
const isSourceDue = (refresh, lastPolledMs, nowMs) => {
|
|
121
143
|
if (refresh === "manual") {
|
|
@@ -157,7 +179,12 @@ const pullExternalSourceIncrementalTick = async (sql, writer, client, table, sou
|
|
|
157
179
|
let applied;
|
|
158
180
|
if (state.watermark === null || reconcileDue) {
|
|
159
181
|
slice = await pullAndLift(client, source.query, tenantParameters, source);
|
|
160
|
-
|
|
182
|
+
const { softDeleteColumn } = source;
|
|
183
|
+
const documents = softDeleteColumn ? slice.documents.filter((_document, index) => {
|
|
184
|
+
const row = slice.rows[index];
|
|
185
|
+
return !(row && isSoftDeleted(row, softDeleteColumn));
|
|
186
|
+
}) : slice.documents;
|
|
187
|
+
({ applied } = await runExternalSourceTick(sql, writer, documents, { columns: source.columns, table }));
|
|
161
188
|
} else {
|
|
162
189
|
slice = await pullAndLift(client, cursor.query, [...tenantParameters, deserializeCursor(state.watermark)], source);
|
|
163
190
|
const { softDeleteColumn } = source;
|
|
@@ -176,8 +203,15 @@ const pullExternalSourceIncrementalTick = async (sql, writer, client, table, sou
|
|
|
176
203
|
`external-source: table "${table}" (mode "incremental") pulled ${String(slice.rows.length)} rows but none carry the cursor column "${cursor.column}" — the seed \`query\` must project it (matching \`cursor.query\`'s alias), or the watermark can never advance.`
|
|
177
204
|
);
|
|
178
205
|
}
|
|
206
|
+
const noRowCarriesCursor = slice.rows.length > 0 && slice.rows.every((row) => row[cursor.column] === null || row[cursor.column] === void 0);
|
|
207
|
+
if (!fullPull && noRowCarriesCursor) {
|
|
208
|
+
throw new LunoraError(
|
|
209
|
+
"INTERNAL",
|
|
210
|
+
`external-source: table "${table}" (mode "incremental") pulled ${String(slice.rows.length)} rows but none carry the cursor column "${cursor.column}" — \`cursor.query\` must project it (matching the seed \`query\`'s alias), or the watermark can never advance and every tick re-pulls the same stranded slice.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
179
213
|
writeSourceCursor(sql, table, shardKey, { lastReconcileMs: fullPull ? nowMs : state.lastReconcileMs, watermark });
|
|
180
214
|
return { applied };
|
|
181
215
|
};
|
|
182
216
|
|
|
183
|
-
export { isSoftDeleted, isSourceDue, liftSourceId, pullExternalSourceIncrementalTick, pullExternalSourceTick };
|
|
217
|
+
export { isSoftDeleted, isSourceDue, liftSourceId, normalizeSourceValue, pullExternalSourceIncrementalTick, pullExternalSourceTick };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyCdcChanges } from './CDC_LOG_TABLE-
|
|
1
|
+
import { applyCdcChanges } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
2
2
|
import { s as selectShapeRows } from './ctx-db-shapes-Cz9dHyh1.mjs';
|
|
3
3
|
import { diffExternalSource, projectExternalSourceRow } from './diffExternalSource-CovHfdyo.mjs';
|
|
4
4
|
import { stableStringify } from './stableStringify-mC40mZts.mjs';
|
|
@@ -15,7 +15,10 @@ const materializeExternalRowsIncremental = async (writer, pulled, options) => {
|
|
|
15
15
|
const value = projectExternalSourceRow(source, columns);
|
|
16
16
|
const id = String(value._id);
|
|
17
17
|
if (deletedIds?.has(id)) {
|
|
18
|
-
|
|
18
|
+
const existing = await writer.get(id, table);
|
|
19
|
+
if (existing) {
|
|
20
|
+
changes.push({ id, op: "delete", seq: 0, table, ts: 0 });
|
|
21
|
+
}
|
|
19
22
|
continue;
|
|
20
23
|
}
|
|
21
24
|
const stored = await writer.get(id, table);
|
package/dist/packem_shared/{runShardMigrations-BGx4v2B6.mjs → runShardMigrations-DFzx6Qld.mjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
2
|
import { aggregateTableName } from './aggregateTableName-CxNqY1Sl.mjs';
|
|
3
|
-
import { migrateCdcLog, migrateCdcMeta } from './CDC_LOG_TABLE-
|
|
3
|
+
import { migrateCdcLog, migrateCdcMeta } from './CDC_LOG_TABLE-uwOJxJJZ.mjs';
|
|
4
4
|
import { m as migrateClientWatermark, a as migrateIdempotency, b as migrateGlobalShapeSnapshot } from './ctx-db-idempotency-BdcNpvY4.mjs';
|
|
5
5
|
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
6
6
|
import { D as DOC_COLUMN, j as jsonPathSql, c as createIndexSql, t as tableColumns, i as isFtsAvailable, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-BCHCWtrD.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/do",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.34",
|
|
4
4
|
"description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
50
|
-
"@lunora/fingerprint": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.6",
|
|
50
|
+
"@lunora/fingerprint": "1.0.0-alpha.3",
|
|
51
51
|
"@visulima/redact": "3.0.0",
|
|
52
52
|
"drizzle-orm": "^0.45.2"
|
|
53
53
|
},
|