@lunora/do 1.0.0-alpha.33 → 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.mjs CHANGED
@@ -26,7 +26,7 @@ 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-afQHUhyA.mjs';
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';
@@ -27,6 +27,30 @@ import { a as selectShapeMemberIds, s as selectShapeRows } from './ctx-db-shapes
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 line = JSON.stringify({
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
- if (input.level === "error") {
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);
@@ -4082,28 +4157,55 @@ class ShardDO {
4082
4157
  * Unlike request-log args, `ctx.log` args are NOT redacted: the developer
4083
4158
  * chose to log them, exactly like a raw `console.log`.
4084
4159
  */
4085
- recordUserLog(functionPath, level, args, sink) {
4160
+ recordUserLog(functionPath, level, args, message, fields, sink) {
4161
+ const trace = parseTraceparent(this.getCurrentTraceparent());
4086
4162
  const event = {
4087
4163
  args,
4164
+ fields,
4088
4165
  functionPath,
4089
4166
  level,
4090
- message: renderLogMessage(args),
4167
+ message,
4091
4168
  shardKey: this.state.id?.name,
4169
+ spanId: trace?.parentSpanId,
4170
+ traceId: trace?.traceId,
4092
4171
  ts: Date.now(),
4093
4172
  userId: this.getCurrentUserId()
4094
4173
  };
4095
- this.logs.push({ functionPath, level: level === "log" ? "info" : level, message: event.message, timestamp: event.ts });
4174
+ this.logs.push({ fields, functionPath, level: BUFFER_LEVEL[level], message, timestamp: event.ts });
4096
4175
  try {
4097
4176
  emitLogEvent(event);
4098
4177
  } catch {
4099
4178
  }
4100
4179
  if (sink?.onLog) {
4101
4180
  try {
4102
- sink.onLog(event);
4181
+ sink.onLog(event, { waitUntil: this.state.waitUntil?.bind(this.state) });
4103
4182
  } catch {
4104
4183
  }
4105
4184
  }
4106
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
+ }
4107
4209
  /**
4108
4210
  * Assemble the per-socket {@link LifecycleDispatchInfo} from its attachment:
4109
4211
  * the verified identity to replay and the {@link LifecycleEvent} the hooks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/do",
3
- "version": "1.0.0-alpha.33",
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,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.5",
49
+ "@lunora/errors": "1.0.0-alpha.6",
50
50
  "@lunora/fingerprint": "1.0.0-alpha.3",
51
51
  "@visulima/redact": "3.0.0",
52
52
  "drizzle-orm": "^0.45.2"