@lunora/runtime 1.0.0-alpha.3 → 1.0.0-alpha.30
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/README.md +17 -0
- package/dist/index.d.mts +1855 -1100
- package/dist/index.d.ts +1855 -1100
- package/dist/index.mjs +10 -8
- package/dist/packem_shared/{DEFAULT_REGISTRY_CACHE_TTL_MS-BpCwo_mo.mjs → DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs} +9 -5
- package/dist/packem_shared/LunoraError-Bpb9EFJ3.mjs +22 -0
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs +8 -0
- package/dist/packem_shared/analyticsEngineSink-DWUhiYHC.mjs +366 -0
- package/dist/packem_shared/applyJurisdiction-BkZtTkct.mjs +20 -0
- package/dist/packem_shared/composeIdentityResolvers-XGjO7V1J.mjs +55 -0
- package/dist/packem_shared/{composeWorker-CjF1xUIO.mjs → composeWorker-CqUlK17X.mjs} +979 -133
- package/dist/packem_shared/{createCrossShardRelationCapabilities-C0KOf7er.mjs → createCrossShardRelationCapabilities-CbcWjkAn.mjs} +4 -2
- package/dist/packem_shared/{createQueryCoordinator-DbxC7iUz.mjs → createQueryCoordinator-DNCJzOZE.mjs} +12 -3
- package/dist/packem_shared/{decorateResponse-DbISh_Wi.mjs → decorateResponse-DRWQFNhF.mjs} +50 -10
- package/dist/packem_shared/otlp-DOLuy1Aj.mjs +95 -0
- package/package.json +5 -2
- package/dist/packem_shared/LunoraError-CL0aOtpo.mjs +0 -46
- package/dist/packem_shared/analyticsEngineSink-DqEvrQs0.mjs +0 -141
- package/dist/packem_shared/resolveShard-DDkzWtrU.mjs +0 -9
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
|
|
2
|
-
export { composeWorker, createWorker, defineRpcEnvelope, withFrameworkWorker } from './packem_shared/composeWorker-
|
|
3
|
-
export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-
|
|
4
|
-
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-
|
|
5
|
-
export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-
|
|
2
|
+
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-CqUlK17X.mjs';
|
|
3
|
+
export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
|
|
4
|
+
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
|
|
5
|
+
export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
|
|
6
6
|
export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK8.mjs';
|
|
7
|
-
export { analyticsEngineSink, combineSinks, consoleSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-
|
|
8
|
-
export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-
|
|
9
|
-
export { resolveShard } from './packem_shared/
|
|
10
|
-
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-
|
|
7
|
+
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DWUhiYHC.mjs';
|
|
8
|
+
export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
|
|
9
|
+
export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
|
|
10
|
+
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DRWQFNhF.mjs';
|
|
11
|
+
export { NOOP_EXECUTION_CONTEXT } from './packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
|
|
12
|
+
export { composeIdentityResolvers, routeIdentityResolvers } from './packem_shared/composeIdentityResolvers-XGjO7V1J.mjs';
|
|
11
13
|
|
|
12
14
|
const VERSION = "0.0.0";
|
|
13
15
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
import { applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
|
|
3
|
+
|
|
1
4
|
const SHARD_REGISTRY_DO_NAME = "__lunora_shard_registry__";
|
|
2
5
|
const DEFAULT_REGISTRY_CACHE_TTL_MS = 3e4;
|
|
3
6
|
const REGISTRY_BASE_URL = "https://shard-registry.internal";
|
|
@@ -10,9 +13,10 @@ const createDynamicShardRegistry = (options) => {
|
|
|
10
13
|
const instanceName = options.instanceName ?? SHARD_REGISTRY_DO_NAME;
|
|
11
14
|
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_REGISTRY_CACHE_TTL_MS;
|
|
12
15
|
const cache = /* @__PURE__ */ new Map();
|
|
16
|
+
const namespace = applyJurisdiction(options.namespace, options.jurisdiction);
|
|
13
17
|
let cachedStub;
|
|
14
18
|
const stub = () => {
|
|
15
|
-
cachedStub ??=
|
|
19
|
+
cachedStub ??= namespace.get(namespace.idFromName(instanceName));
|
|
16
20
|
return cachedStub;
|
|
17
21
|
};
|
|
18
22
|
const post = async (path, body) => stub().fetch(
|
|
@@ -39,7 +43,7 @@ const createDynamicShardRegistry = (options) => {
|
|
|
39
43
|
}
|
|
40
44
|
const response = await get(`/list?table=${encodeURIComponent(table)}`);
|
|
41
45
|
if (!response.ok) {
|
|
42
|
-
throw new
|
|
46
|
+
throw new LunoraError(`shard registry /list returned ${String(response.status)}`);
|
|
43
47
|
}
|
|
44
48
|
const { shardKeys } = await decodeJson(response);
|
|
45
49
|
if (cacheTtlMs > 0) {
|
|
@@ -50,14 +54,14 @@ const createDynamicShardRegistry = (options) => {
|
|
|
50
54
|
async register(table, shardKey) {
|
|
51
55
|
const response = await post("/register", { shardKey, table });
|
|
52
56
|
if (!response.ok) {
|
|
53
|
-
throw new
|
|
57
|
+
throw new LunoraError(`shard registry /register returned ${String(response.status)}`);
|
|
54
58
|
}
|
|
55
59
|
cache.delete(table);
|
|
56
60
|
},
|
|
57
61
|
async snapshot() {
|
|
58
62
|
const response = await get("/snapshot");
|
|
59
63
|
if (!response.ok) {
|
|
60
|
-
throw new
|
|
64
|
+
throw new LunoraError(`shard registry /snapshot returned ${String(response.status)}`);
|
|
61
65
|
}
|
|
62
66
|
const { tables } = await decodeJson(response);
|
|
63
67
|
return tables;
|
|
@@ -65,7 +69,7 @@ const createDynamicShardRegistry = (options) => {
|
|
|
65
69
|
async unregister(table, shardKey) {
|
|
66
70
|
const response = await post("/unregister", { shardKey, table });
|
|
67
71
|
if (!response.ok) {
|
|
68
|
-
throw new
|
|
72
|
+
throw new LunoraError(`shard registry /unregister returned ${String(response.status)}`);
|
|
69
73
|
}
|
|
70
74
|
cache.delete(table);
|
|
71
75
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1, toErrorBody } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
const toErrorResponse = (error) => {
|
|
4
|
+
const { body, redacted, status } = toErrorBody(error, { fallbackCode: "INTERNAL", redactedMessage: "Internal error" });
|
|
5
|
+
if (redacted) {
|
|
6
|
+
console.error("[lunora] internal error:", error);
|
|
7
|
+
}
|
|
8
|
+
return Response.json({ error: body }, {
|
|
9
|
+
headers: { "content-type": "application/json" },
|
|
10
|
+
status
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
class LunoraError extends LunoraError$1 {
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(options?.code ?? "INTERNAL", message, { cause: options?.cause, status: options?.status });
|
|
16
|
+
}
|
|
17
|
+
toResponse() {
|
|
18
|
+
return toErrorResponse(this);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { LunoraError, toErrorResponse };
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { m as mergeHeaders, w as wrapResourceSpans, o as otlpRandomHex, a as wrapResourceMetrics, c as wrapResourceLogs, e as encodeAttribute, O as OTLP_SEVERITY, d as otlpUnixNano } from './otlp-DOLuy1Aj.mjs';
|
|
2
|
+
|
|
3
|
+
const stringifyFieldValue = (value) => {
|
|
4
|
+
if (typeof value === "string") {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
return JSON.stringify(value) ?? String(value);
|
|
9
|
+
} catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const coerceFieldValue = (value) => typeof value === "boolean" || typeof value === "number" || typeof value === "string" ? value : stringifyFieldValue(value);
|
|
14
|
+
|
|
15
|
+
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
16
|
+
const otlpTraceBody = (event, serviceName, endMs) => {
|
|
17
|
+
const attributes = [encodeAttribute("lunora.function_path", event.functionPath), encodeAttribute("lunora.ok", event.ok)];
|
|
18
|
+
if (event.shardKey !== void 0) {
|
|
19
|
+
attributes.push(encodeAttribute("lunora.shard_key", event.shardKey));
|
|
20
|
+
}
|
|
21
|
+
if (event.error) {
|
|
22
|
+
attributes.push(encodeAttribute("error.type", event.error.code), encodeAttribute("lunora.error_status", event.error.status));
|
|
23
|
+
}
|
|
24
|
+
if (event.fanOut) {
|
|
25
|
+
attributes.push(
|
|
26
|
+
encodeAttribute("lunora.fanout.table", event.fanOut.table),
|
|
27
|
+
encodeAttribute("lunora.fanout.shards", event.fanOut.shards),
|
|
28
|
+
encodeAttribute("lunora.fanout.failed", event.fanOut.failed)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const span = {
|
|
32
|
+
attributes,
|
|
33
|
+
endTimeUnixNano: otlpUnixNano(endMs),
|
|
34
|
+
// SPAN_KIND_SERVER — a dispatched RPC is server-side request handling.
|
|
35
|
+
kind: 2,
|
|
36
|
+
name: event.functionPath,
|
|
37
|
+
// Reuse the dispatch's trace context when the runtime set it (so this span
|
|
38
|
+
// shares the id it propagated as `traceparent`); else mint fresh ids.
|
|
39
|
+
spanId: event.spanId ?? otlpRandomHex(8),
|
|
40
|
+
startTimeUnixNano: otlpUnixNano(endMs - event.durationMs),
|
|
41
|
+
// STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
|
|
42
|
+
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
43
|
+
traceId: event.traceId ?? otlpRandomHex(16)
|
|
44
|
+
};
|
|
45
|
+
return wrapResourceSpans(span, "@lunora/runtime", serviceName);
|
|
46
|
+
};
|
|
47
|
+
const encodeSignalAttributes = (reserved, caller) => {
|
|
48
|
+
const byKey = /* @__PURE__ */ new Map([["lunora.function_path", encodeAttribute("lunora.function_path", reserved.functionPath)]]);
|
|
49
|
+
if (reserved.shardKey !== void 0) {
|
|
50
|
+
byKey.set("lunora.shard_key", encodeAttribute("lunora.shard_key", reserved.shardKey));
|
|
51
|
+
}
|
|
52
|
+
if (reserved.userId !== void 0) {
|
|
53
|
+
byKey.set("lunora.user_id", encodeAttribute("lunora.user_id", reserved.userId));
|
|
54
|
+
}
|
|
55
|
+
if (reserved.errorType !== void 0) {
|
|
56
|
+
byKey.set("error.type", encodeAttribute("error.type", reserved.errorType));
|
|
57
|
+
}
|
|
58
|
+
for (const [key, value] of Object.entries(caller ?? {})) {
|
|
59
|
+
byKey.set(key, encodeAttribute(key, coerceFieldValue(value)));
|
|
60
|
+
}
|
|
61
|
+
return [...byKey.values()];
|
|
62
|
+
};
|
|
63
|
+
const otlpSpanBody = (event, serviceName) => {
|
|
64
|
+
const span = {
|
|
65
|
+
attributes: encodeSignalAttributes(
|
|
66
|
+
{ errorType: event.error?.type, functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId },
|
|
67
|
+
event.attributes
|
|
68
|
+
),
|
|
69
|
+
endTimeUnixNano: otlpUnixNano(event.startTs + event.durationMs),
|
|
70
|
+
// Always SPAN_KIND_INTERNAL: only `ctx.trace` spans reach a sink. The
|
|
71
|
+
// synthetic dispatch span is buffered for the Studio waterfall and never
|
|
72
|
+
// exported, because the runtime already emits that dispatch to `onRpc` as
|
|
73
|
+
// a SERVER span — encoding it here too would duplicate it in every trace.
|
|
74
|
+
kind: 1,
|
|
75
|
+
name: event.name,
|
|
76
|
+
parentSpanId: event.parentSpanId,
|
|
77
|
+
spanId: event.spanId,
|
|
78
|
+
startTimeUnixNano: otlpUnixNano(event.startTs),
|
|
79
|
+
// STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
|
|
80
|
+
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
81
|
+
traceId: event.traceId
|
|
82
|
+
};
|
|
83
|
+
return wrapResourceSpans(span, "@lunora/runtime", serviceName);
|
|
84
|
+
};
|
|
85
|
+
const otlpMetricBody = (event, serviceName) => {
|
|
86
|
+
const timeUnixNano = otlpUnixNano(event.ts);
|
|
87
|
+
const attributes = encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey }, event.attributes);
|
|
88
|
+
const dataPoint = { asDouble: event.value, attributes, timeUnixNano };
|
|
89
|
+
if (event.kind === "gauge") {
|
|
90
|
+
return wrapResourceMetrics({ gauge: { dataPoints: [dataPoint] }, name: event.name }, "@lunora/runtime", serviceName);
|
|
91
|
+
}
|
|
92
|
+
if (event.kind === "histogram") {
|
|
93
|
+
return wrapResourceMetrics(
|
|
94
|
+
{
|
|
95
|
+
histogram: {
|
|
96
|
+
aggregationTemporality: 1,
|
|
97
|
+
dataPoints: [
|
|
98
|
+
{
|
|
99
|
+
attributes,
|
|
100
|
+
bucketCounts: ["1"],
|
|
101
|
+
count: "1",
|
|
102
|
+
explicitBounds: [],
|
|
103
|
+
max: event.value,
|
|
104
|
+
min: event.value,
|
|
105
|
+
// `startTimeUnixNano` omitted for the same reason as
|
|
106
|
+
// the Sum data point above — see the comment there.
|
|
107
|
+
sum: event.value,
|
|
108
|
+
timeUnixNano
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
name: event.name
|
|
113
|
+
},
|
|
114
|
+
"@lunora/runtime",
|
|
115
|
+
serviceName
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return wrapResourceMetrics(
|
|
119
|
+
{ name: event.name, sum: { aggregationTemporality: 1, dataPoints: [dataPoint], isMonotonic: true } },
|
|
120
|
+
"@lunora/runtime",
|
|
121
|
+
serviceName
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
const otlpLogBody = (event, serviceName) => {
|
|
125
|
+
const logRecord = {
|
|
126
|
+
// Caller-supplied structured fields become log-record attributes so a
|
|
127
|
+
// pipeline can filter/index on them; precedence per `encodeSignalAttributes`.
|
|
128
|
+
attributes: encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId }, event.fields),
|
|
129
|
+
body: { stringValue: event.message },
|
|
130
|
+
severityNumber: OTLP_SEVERITY[event.level],
|
|
131
|
+
severityText: event.level.toUpperCase(),
|
|
132
|
+
timeUnixNano: otlpUnixNano(event.ts)
|
|
133
|
+
};
|
|
134
|
+
if (event.traceId !== void 0) {
|
|
135
|
+
logRecord.traceId = event.traceId;
|
|
136
|
+
}
|
|
137
|
+
if (event.spanId !== void 0) {
|
|
138
|
+
logRecord.spanId = event.spanId;
|
|
139
|
+
}
|
|
140
|
+
return wrapResourceLogs(logRecord, "@lunora/runtime", serviceName);
|
|
141
|
+
};
|
|
142
|
+
const otlpPost = (url, body, headers, context) => {
|
|
143
|
+
try {
|
|
144
|
+
const sent = fetch(url, { body: JSON.stringify(body), headers, method: "POST" }).catch(() => {
|
|
145
|
+
});
|
|
146
|
+
if (context?.waitUntil) {
|
|
147
|
+
context.waitUntil(sent);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const consoleSink = (options = {}) => {
|
|
153
|
+
const { onlyErrors } = options;
|
|
154
|
+
return {
|
|
155
|
+
onLog: (event) => {
|
|
156
|
+
if (event.level === "error" || event.level === "fatal") {
|
|
157
|
+
console.error("[lunora:log]", event.functionPath, event.message);
|
|
158
|
+
} else {
|
|
159
|
+
console.log("[lunora:log]", event.functionPath, event.message);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
onMetric: (event) => {
|
|
163
|
+
console.log("[lunora:metric]", `${event.name}=${String(event.value)}`, event.kind, event.functionPath);
|
|
164
|
+
},
|
|
165
|
+
onRpc: (event) => {
|
|
166
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (event.ok) {
|
|
170
|
+
console.log("[lunora:rpc]", event);
|
|
171
|
+
} else {
|
|
172
|
+
console.error("[lunora:rpc]", event);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
onSpan: (event) => {
|
|
176
|
+
const status = event.ok ? "ok" : `error ${event.error?.type ?? ""}`.trim();
|
|
177
|
+
console.log("[lunora:span]", event.name, `${String(event.durationMs)}ms`, status, event.functionPath);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
const webhookSink = (options) => {
|
|
182
|
+
const { headers, onlyErrors, transform, transformLog, url } = options;
|
|
183
|
+
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers);
|
|
184
|
+
const post = (payload, context) => {
|
|
185
|
+
try {
|
|
186
|
+
const sent = fetch(url, { body: JSON.stringify(payload), headers: mergedHeaders, method: "POST" }).catch(() => {
|
|
187
|
+
});
|
|
188
|
+
if (context?.waitUntil) {
|
|
189
|
+
context.waitUntil(sent);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
onLog: (event, context) => {
|
|
196
|
+
let payload = event;
|
|
197
|
+
if (transformLog) {
|
|
198
|
+
try {
|
|
199
|
+
payload = transformLog(event);
|
|
200
|
+
} catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (payload === null || payload === void 0) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
post(payload, context);
|
|
208
|
+
},
|
|
209
|
+
onRpc: (event, context) => {
|
|
210
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
let payload = event;
|
|
215
|
+
if (transform) {
|
|
216
|
+
try {
|
|
217
|
+
payload = transform(event);
|
|
218
|
+
} catch {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (payload === null || payload === void 0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
post(payload, context);
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
const sentrySink = (options) => {
|
|
232
|
+
const { capture, captureLog } = options;
|
|
233
|
+
const onlyErrors = options.onlyErrors ?? true;
|
|
234
|
+
return {
|
|
235
|
+
// Only forward log lines when the caller wired `captureLog`; otherwise
|
|
236
|
+
// `ctx.log` output stays out of Sentry.
|
|
237
|
+
onLog: captureLog ? (event) => {
|
|
238
|
+
try {
|
|
239
|
+
captureLog(event);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
} : void 0,
|
|
243
|
+
onRpc: (event) => {
|
|
244
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
capture(event);
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
const analyticsEngineSink = (options) => {
|
|
255
|
+
const { dataset, onlyErrors } = options;
|
|
256
|
+
return {
|
|
257
|
+
onRpc: (event) => {
|
|
258
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
dataset.writeDataPoint({
|
|
263
|
+
blobs: [event.functionPath, event.ok ? "ok" : "error", event.shardKey ?? "", event.error?.code ?? "", event.fanOut?.table ?? ""],
|
|
264
|
+
doubles: [event.durationMs, event.ok ? 0 : 1, event.fanOut?.shards ?? 0, event.fanOut?.failed ?? 0],
|
|
265
|
+
indexes: [event.functionPath]
|
|
266
|
+
});
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
const pipelineLogSink = (options) => {
|
|
273
|
+
const { pipeline } = options;
|
|
274
|
+
return {
|
|
275
|
+
onLog: (event, context) => {
|
|
276
|
+
try {
|
|
277
|
+
const record = {
|
|
278
|
+
functionPath: event.functionPath,
|
|
279
|
+
level: event.level,
|
|
280
|
+
message: event.message,
|
|
281
|
+
ts: event.ts
|
|
282
|
+
};
|
|
283
|
+
if (event.fields) {
|
|
284
|
+
record.fields = event.fields;
|
|
285
|
+
}
|
|
286
|
+
if (event.shardKey !== void 0) {
|
|
287
|
+
record.shardKey = event.shardKey;
|
|
288
|
+
}
|
|
289
|
+
if (event.userId !== void 0) {
|
|
290
|
+
record.userId = event.userId;
|
|
291
|
+
}
|
|
292
|
+
if (event.traceId !== void 0) {
|
|
293
|
+
record.traceId = event.traceId;
|
|
294
|
+
}
|
|
295
|
+
if (event.spanId !== void 0) {
|
|
296
|
+
record.spanId = event.spanId;
|
|
297
|
+
}
|
|
298
|
+
const sent = pipeline.send([record]).catch(() => {
|
|
299
|
+
});
|
|
300
|
+
if (context?.waitUntil) {
|
|
301
|
+
context.waitUntil(sent);
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
const otlpSink = (options) => {
|
|
309
|
+
const { endpoint, headers, onlyErrors, token } = options;
|
|
310
|
+
const serviceName = options.serviceName ?? "lunora";
|
|
311
|
+
let base = endpoint;
|
|
312
|
+
while (base.endsWith("/")) {
|
|
313
|
+
base = base.slice(0, -1);
|
|
314
|
+
}
|
|
315
|
+
const tracesUrl = `${base}/v1/traces`;
|
|
316
|
+
const logsUrl = `${base}/v1/logs`;
|
|
317
|
+
const metricsUrl = `${base}/v1/metrics`;
|
|
318
|
+
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers, token);
|
|
319
|
+
return {
|
|
320
|
+
onLog: (event, context) => {
|
|
321
|
+
otlpPost(logsUrl, otlpLogBody(event, serviceName), mergedHeaders, context);
|
|
322
|
+
},
|
|
323
|
+
onMetric: (event, context) => {
|
|
324
|
+
otlpPost(metricsUrl, otlpMetricBody(event, serviceName), mergedHeaders, context);
|
|
325
|
+
},
|
|
326
|
+
onRpc: (event, context) => {
|
|
327
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
otlpPost(tracesUrl, otlpTraceBody(event, serviceName, Date.now()), mergedHeaders, context);
|
|
331
|
+
},
|
|
332
|
+
onSpan: (event, context) => {
|
|
333
|
+
otlpPost(tracesUrl, otlpSpanBody(event, serviceName), mergedHeaders, context);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
const combineSinks = (...sinks) => {
|
|
338
|
+
const fanOut = (method, event, context) => {
|
|
339
|
+
for (const sink of sinks) {
|
|
340
|
+
const handler = sink[method];
|
|
341
|
+
if (!handler) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
handler.call(sink, event, context);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
onLog: (event, context) => {
|
|
352
|
+
fanOut("onLog", event, context);
|
|
353
|
+
},
|
|
354
|
+
onMetric: (event, context) => {
|
|
355
|
+
fanOut("onMetric", event, context);
|
|
356
|
+
},
|
|
357
|
+
onRpc: (event, context) => {
|
|
358
|
+
fanOut("onRpc", event, context);
|
|
359
|
+
},
|
|
360
|
+
onSpan: (event, context) => {
|
|
361
|
+
fanOut("onSpan", event, context);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const applyJurisdiction = (namespace, jurisdiction) => {
|
|
2
|
+
if (jurisdiction === void 0) {
|
|
3
|
+
return namespace;
|
|
4
|
+
}
|
|
5
|
+
if (typeof namespace.jurisdiction !== "function") {
|
|
6
|
+
throw new TypeError(
|
|
7
|
+
`@lunora/runtime: Durable Object namespace does not support jurisdiction("${jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return namespace.jurisdiction(jurisdiction);
|
|
11
|
+
};
|
|
12
|
+
const resolveShard = (namespace, shardKey) => {
|
|
13
|
+
if (typeof namespace.getByName === "function") {
|
|
14
|
+
return namespace.getByName(shardKey);
|
|
15
|
+
}
|
|
16
|
+
const id = namespace.idFromName(shardKey);
|
|
17
|
+
return namespace.get(id);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { applyJurisdiction, resolveShard };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
|
|
3
|
+
const composeIdentityResolvers = (resolvers, options = {}) => {
|
|
4
|
+
const onError = options.onError ?? "fail-closed";
|
|
5
|
+
return async (request, env) => {
|
|
6
|
+
for (const resolver of resolvers) {
|
|
7
|
+
let resolved;
|
|
8
|
+
try {
|
|
9
|
+
resolved = await resolver(request, env);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (onError === "skip") {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
if (resolved) {
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const routeIdentityResolvers = (routes) => {
|
|
24
|
+
const prefixes = Object.keys(routes).filter((key) => key !== "*").toSorted((a, b) => b.length - a.length);
|
|
25
|
+
return (request, env) => {
|
|
26
|
+
const { pathname } = new URL(request.url);
|
|
27
|
+
const matched = prefixes.find((prefix) => pathname === prefix || pathname.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`));
|
|
28
|
+
const resolver = matched === void 0 ? routes["*"] : routes[matched];
|
|
29
|
+
if (resolver === void 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return resolver(request, env);
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const wrapResolverWithContract = (baseResolveIdentity, contract) => {
|
|
36
|
+
if (contract === void 0 || baseResolveIdentity === void 0) {
|
|
37
|
+
return baseResolveIdentity;
|
|
38
|
+
}
|
|
39
|
+
return async (request, env) => {
|
|
40
|
+
const resolved = await baseResolveIdentity(request, env);
|
|
41
|
+
if (!resolved) {
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
const result = contract.validate(resolved);
|
|
45
|
+
if (result.ok) {
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
|
48
|
+
if (contract.onInvalid === "reject") {
|
|
49
|
+
throw new LunoraError(`identity claims failed the declared contract: ${result.error}`, { code: "UNAUTHENTICATED", status: 401 });
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { composeIdentityResolvers, routeIdentityResolvers, wrapResolverWithContract };
|