@lunora/runtime 1.0.0-alpha.37 → 1.0.0-alpha.39
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 +359 -8
- package/dist/index.d.ts +359 -8
- package/dist/index.mjs +1 -22
- package/dist/packem_shared/DEFAULT_LOG_COLUMNS-CpZ9kd-q.mjs +1 -0
- package/dist/packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-BBf3mNrD.mjs +1 -0
- package/dist/packem_shared/GET_AUTH_AUDIT_LOG_OP-DmxHQbZT.mjs +1 -0
- package/dist/packem_shared/HEALTH_PATH-DiZqjEGp.mjs +1 -0
- package/dist/packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs +1 -0
- package/dist/packem_shared/LOG_ARCHIVE_PATH-e8U3ftfM.mjs +1 -0
- package/dist/packem_shared/LunoraError-C08OP5Uq.mjs +1 -0
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs +1 -0
- package/dist/packem_shared/analyticsEngineSink-DOoyBx4o.mjs +1 -0
- package/dist/packem_shared/applyJurisdiction-uRQLx282.mjs +1 -0
- package/dist/packem_shared/argsFromQuery-0KrWTkNx.mjs +1 -0
- package/dist/packem_shared/composeIdentityResolvers-DlBbYmBJ.mjs +1 -0
- package/dist/packem_shared/composeWorker-D7Zi6kZR.mjs +6 -0
- package/dist/packem_shared/createCrossShardRelationCapabilities-B2EKbSEs.mjs +1 -0
- package/dist/packem_shared/createKvCursorStore-C24tEuYk.mjs +3 -0
- package/dist/packem_shared/createQueryCoordinator-DLsrcOkB.mjs +1 -0
- package/dist/packem_shared/createShardClient-62qcYKGl.mjs +1 -0
- package/dist/packem_shared/decorateResponse-C6TZSzID.mjs +1 -0
- package/dist/packem_shared/emitLogEvent-CEbCh6FQ.mjs +1 -0
- package/dist/packem_shared/memoizeIdentity-DRYIK1yT.mjs +1 -0
- package/dist/packem_shared/method-guard-BbuR0VfS.mjs +1 -0
- package/dist/packem_shared/observability-B0MYwNDB.mjs +1 -0
- package/dist/packem_shared/otlp-resource-cAjGEywx.mjs +1 -0
- package/dist/packem_shared/pipeline-log-reader-tDuvc_Ty.mjs +1 -0
- package/dist/packem_shared/toAirbyteMessages-DBTuFjb5.mjs +1 -0
- package/package.json +1 -1
- package/dist/packem_shared/DEFAULT_LOG_COLUMNS-B7H3YdJ3.mjs +0 -2
- package/dist/packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs +0 -79
- package/dist/packem_shared/HEALTH_PATH-e5J_NHBx.mjs +0 -150
- package/dist/packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-acNcguqc.mjs +0 -3
- package/dist/packem_shared/LOG_ARCHIVE_PATH-CNs0bznX.mjs +0 -125
- package/dist/packem_shared/LunoraError-Bpb9EFJ3.mjs +0 -22
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs +0 -8
- package/dist/packem_shared/analyticsEngineSink-yLFNjHDt.mjs +0 -429
- package/dist/packem_shared/applyJurisdiction-BkZtTkct.mjs +0 -20
- package/dist/packem_shared/argsFromQuery-c-U1WRy-.mjs +0 -121
- package/dist/packem_shared/composeIdentityResolvers-XGjO7V1J.mjs +0 -55
- package/dist/packem_shared/composeWorker-DiWwOXXt.mjs +0 -3738
- package/dist/packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs +0 -63
- package/dist/packem_shared/createKvCursorStore-g8aA6B4L.mjs +0 -165
- package/dist/packem_shared/createQueryCoordinator-DNCJzOZE.mjs +0 -847
- package/dist/packem_shared/decorateResponse-DRWQFNhF.mjs +0 -273
- package/dist/packem_shared/emitLogEvent-BlGMnKZK.mjs +0 -1
- package/dist/packem_shared/method-guard-Qzw99aCj.mjs +0 -3
- package/dist/packem_shared/observability--NOFYBFc.mjs +0 -47
- package/dist/packem_shared/otlp-resource-Dow6-F_u.mjs +0 -163
- package/dist/packem_shared/pipeline-log-reader-BXULGNC3.mjs +0 -135
- package/dist/packem_shared/toAirbyteMessages-DrHdplb4.mjs +0 -39
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { createR2Sql } from '@lunora/bindings/r2sql';
|
|
2
|
-
import { LOG_ARCHIVE_NOT_CONFIGURED } from './LOG_ARCHIVE_NOT_CONFIGURED-acNcguqc.mjs';
|
|
3
|
-
import { L as LOG_LEVEL_ORDER, c as createPipelineLogReader } from './pipeline-log-reader-BXULGNC3.mjs';
|
|
4
|
-
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
5
|
-
|
|
6
|
-
const LOG_ARCHIVE_PATH = "/_lunora/admin/logs/archive";
|
|
7
|
-
const LOG_ARCHIVE_TABLE_ENV = "LUNORA_LOG_ARCHIVE_TABLE";
|
|
8
|
-
const LOG_ARCHIVE_NAMESPACE_ENV = "LUNORA_LOG_ARCHIVE_NAMESPACE";
|
|
9
|
-
const resolveLogArchiveFromEnv = (environment) => {
|
|
10
|
-
if (typeof environment !== "object" || environment === null) {
|
|
11
|
-
return void 0;
|
|
12
|
-
}
|
|
13
|
-
const record = environment;
|
|
14
|
-
const table = record[LOG_ARCHIVE_TABLE_ENV];
|
|
15
|
-
if (typeof table !== "string" || table === "") {
|
|
16
|
-
return void 0;
|
|
17
|
-
}
|
|
18
|
-
const namespace = record[LOG_ARCHIVE_NAMESPACE_ENV];
|
|
19
|
-
return { table, ...typeof namespace === "string" && namespace !== "" ? { namespace } : {} };
|
|
20
|
-
};
|
|
21
|
-
const LOG_LEVELS = new Set(LOG_LEVEL_ORDER);
|
|
22
|
-
const optionalString = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
23
|
-
const parseLevel = (value, field) => {
|
|
24
|
-
if (value === void 0) {
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
if (typeof value !== "string" || !LOG_LEVELS.has(value)) {
|
|
28
|
-
throw new LunoraError(`logs archive: invalid \`${field}\` — expected one of ${LOG_LEVEL_ORDER.join(", ")}`, { code: "BAD_REQUEST", status: 400 });
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
};
|
|
32
|
-
const parseFiniteNumber = (value, field) => {
|
|
33
|
-
if (value === void 0) {
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
37
|
-
throw new LunoraError(`logs archive: invalid \`${field}\` — expected a finite number`, { code: "BAD_REQUEST", status: 400 });
|
|
38
|
-
}
|
|
39
|
-
return value;
|
|
40
|
-
};
|
|
41
|
-
const parseQuery = (body) => {
|
|
42
|
-
const query = {};
|
|
43
|
-
const assignString = (field) => {
|
|
44
|
-
const value = optionalString(body[field]);
|
|
45
|
-
if (value !== void 0) {
|
|
46
|
-
query[field] = value;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
assignString("functionPath");
|
|
50
|
-
assignString("functionPathPrefix");
|
|
51
|
-
assignString("traceId");
|
|
52
|
-
assignString("shardKey");
|
|
53
|
-
assignString("userId");
|
|
54
|
-
const level = parseLevel(body["level"], "level");
|
|
55
|
-
if (level !== void 0) {
|
|
56
|
-
query.level = level;
|
|
57
|
-
}
|
|
58
|
-
const minLevel = parseLevel(body["minLevel"], "minLevel");
|
|
59
|
-
if (minLevel !== void 0) {
|
|
60
|
-
query.minLevel = minLevel;
|
|
61
|
-
}
|
|
62
|
-
const sinceTs = parseFiniteNumber(body["sinceTs"], "sinceTs");
|
|
63
|
-
if (sinceTs !== void 0) {
|
|
64
|
-
query.sinceTs = sinceTs;
|
|
65
|
-
}
|
|
66
|
-
const untilTs = parseFiniteNumber(body["untilTs"], "untilTs");
|
|
67
|
-
if (untilTs !== void 0) {
|
|
68
|
-
query.untilTs = untilTs;
|
|
69
|
-
}
|
|
70
|
-
const limit = parseFiniteNumber(body["limit"], "limit");
|
|
71
|
-
if (limit !== void 0) {
|
|
72
|
-
query.limit = limit;
|
|
73
|
-
}
|
|
74
|
-
const rawCursor = body["cursor"];
|
|
75
|
-
if (typeof rawCursor === "object" && rawCursor !== null) {
|
|
76
|
-
const cursorTs = parseFiniteNumber(rawCursor["ts"], "cursor.ts");
|
|
77
|
-
if (cursorTs !== void 0) {
|
|
78
|
-
query.cursor = { ts: cursorTs };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return query;
|
|
82
|
-
};
|
|
83
|
-
const resolveCredentials = (environment) => {
|
|
84
|
-
const accountId = environment.R2_SQL_ACCOUNT_ID ?? environment.CLOUDFLARE_ACCOUNT_ID;
|
|
85
|
-
const apiToken = environment.R2_SQL_TOKEN;
|
|
86
|
-
const bucket = environment.R2_SQL_BUCKET;
|
|
87
|
-
const missing = [];
|
|
88
|
-
if (accountId === void 0 || accountId === "") {
|
|
89
|
-
missing.push("R2_SQL_ACCOUNT_ID");
|
|
90
|
-
}
|
|
91
|
-
if (apiToken === void 0 || apiToken === "") {
|
|
92
|
-
missing.push("R2_SQL_TOKEN");
|
|
93
|
-
}
|
|
94
|
-
if (bucket === void 0 || bucket === "") {
|
|
95
|
-
missing.push("R2_SQL_BUCKET");
|
|
96
|
-
}
|
|
97
|
-
if (missing.length > 0) {
|
|
98
|
-
throw new LunoraError(
|
|
99
|
-
`log archive not configured (missing ${missing.join(", ")}). The Pipeline must write to an R2 Data Catalog (Iceberg) table, and R2_SQL_ACCOUNT_ID / R2_SQL_TOKEN / R2_SQL_BUCKET must be set — see the observability docs.`,
|
|
100
|
-
{ code: LOG_ARCHIVE_NOT_CONFIGURED, status: 400 }
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return { accountId, apiToken, bucket };
|
|
104
|
-
};
|
|
105
|
-
const buildLogArchiveAdminRoutes = (deps) => {
|
|
106
|
-
const { createReader = createPipelineLogReader, readJsonBody, requireAdminOption } = deps;
|
|
107
|
-
const handleLogArchive = async (request, env) => {
|
|
108
|
-
if (request.method !== "POST") {
|
|
109
|
-
throw new LunoraError("Log-archive endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
|
|
110
|
-
}
|
|
111
|
-
const config = requireAdminOption(request, deps.logArchive, {
|
|
112
|
-
code: LOG_ARCHIVE_NOT_CONFIGURED,
|
|
113
|
-
message: "log archive requires a `logArchive` config (an R2 Data Catalog table) on the worker — see the observability docs"
|
|
114
|
-
});
|
|
115
|
-
const { accountId, apiToken, bucket } = resolveCredentials(env ?? {});
|
|
116
|
-
const query = parseQuery(await readJsonBody(request));
|
|
117
|
-
const client = createR2Sql({ accountId, apiToken, bucket });
|
|
118
|
-
const reader = createReader(client, { columnMap: config.columnMap, namespace: config.namespace, table: config.table });
|
|
119
|
-
const page = await reader.query(query);
|
|
120
|
-
return Response.json(page, { headers: { "content-type": "application/json" }, status: 200 });
|
|
121
|
-
};
|
|
122
|
-
return { [LOG_ARCHIVE_PATH]: handleLogArchive };
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
export { LOG_ARCHIVE_NOT_CONFIGURED, LOG_ARCHIVE_PATH, buildLogArchiveAdminRoutes, resolveLogArchiveFromEnv };
|
|
@@ -1,22 +0,0 @@
|
|
|
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 };
|
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import { w as wrapResourceSpans, o as otlpRandomHex, c as wrapResourceMetrics, e as wrapResourceLogs, f as encodeAttribute, g as otlpUnixNano, O as OTLP_SEVERITY, h as mergeHeaders, m as mergeResourceAttributes } from './otlp-resource-Dow6-F_u.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 otlpTraceBody = (event, serviceName, endMs, resourceAttributes) => {
|
|
16
|
-
const attributes = [encodeAttribute("lunora.function_path", event.functionPath), encodeAttribute("lunora.ok", event.ok)];
|
|
17
|
-
if (event.method !== void 0) {
|
|
18
|
-
attributes.push(encodeAttribute("http.request.method", event.method));
|
|
19
|
-
}
|
|
20
|
-
if (event.path !== void 0) {
|
|
21
|
-
attributes.push(encodeAttribute("url.path", event.path));
|
|
22
|
-
}
|
|
23
|
-
attributes.push(encodeAttribute("http.route", event.functionPath));
|
|
24
|
-
if (event.scheme !== void 0) {
|
|
25
|
-
attributes.push(encodeAttribute("url.scheme", event.scheme));
|
|
26
|
-
}
|
|
27
|
-
if (event.host !== void 0) {
|
|
28
|
-
attributes.push(encodeAttribute("server.address", event.host));
|
|
29
|
-
}
|
|
30
|
-
if (event.port !== void 0) {
|
|
31
|
-
attributes.push(encodeAttribute("server.port", event.port));
|
|
32
|
-
}
|
|
33
|
-
if (event.userAgent !== void 0) {
|
|
34
|
-
attributes.push(encodeAttribute("user_agent.original", event.userAgent));
|
|
35
|
-
}
|
|
36
|
-
if (event.shardKey !== void 0) {
|
|
37
|
-
attributes.push(encodeAttribute("lunora.shard_key", event.shardKey));
|
|
38
|
-
}
|
|
39
|
-
attributes.push(encodeAttribute("http.response.status_code", event.error?.status ?? 200));
|
|
40
|
-
if (event.error) {
|
|
41
|
-
attributes.push(encodeAttribute("error.type", event.error.code), encodeAttribute("lunora.error_status", event.error.status));
|
|
42
|
-
}
|
|
43
|
-
if (event.fanOut) {
|
|
44
|
-
attributes.push(
|
|
45
|
-
encodeAttribute("lunora.fanout.table", event.fanOut.table),
|
|
46
|
-
encodeAttribute("lunora.fanout.shards", event.fanOut.shards),
|
|
47
|
-
encodeAttribute("lunora.fanout.failed", event.fanOut.failed)
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
const span = {
|
|
51
|
-
attributes,
|
|
52
|
-
endTimeUnixNano: otlpUnixNano(endMs),
|
|
53
|
-
// SPAN_KIND_SERVER — a dispatched RPC is server-side request handling.
|
|
54
|
-
kind: 2,
|
|
55
|
-
name: event.functionPath,
|
|
56
|
-
// Reuse the dispatch's trace context when the runtime set it (so this span
|
|
57
|
-
// shares the id it propagated as `traceparent`); else mint fresh ids.
|
|
58
|
-
spanId: event.spanId ?? otlpRandomHex(8),
|
|
59
|
-
startTimeUnixNano: otlpUnixNano(endMs - event.durationMs),
|
|
60
|
-
// STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
|
|
61
|
-
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
62
|
-
traceId: event.traceId ?? otlpRandomHex(16)
|
|
63
|
-
};
|
|
64
|
-
if (event.parentSpanId !== void 0) {
|
|
65
|
-
span.parentSpanId = event.parentSpanId;
|
|
66
|
-
}
|
|
67
|
-
if (event.traceFlags !== void 0) {
|
|
68
|
-
span.flags = event.traceFlags;
|
|
69
|
-
}
|
|
70
|
-
if (event.error) {
|
|
71
|
-
span.events = [
|
|
72
|
-
{
|
|
73
|
-
attributes: [encodeAttribute("exception.type", event.error.code), encodeAttribute("exception.message", event.error.message)],
|
|
74
|
-
name: "exception",
|
|
75
|
-
timeUnixNano: otlpUnixNano(endMs)
|
|
76
|
-
}
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
return wrapResourceSpans(span, "@lunora/runtime", serviceName, resourceAttributes);
|
|
80
|
-
};
|
|
81
|
-
const encodeSignalAttributes = (reserved, caller) => {
|
|
82
|
-
const byKey = /* @__PURE__ */ new Map([["lunora.function_path", encodeAttribute("lunora.function_path", reserved.functionPath)]]);
|
|
83
|
-
if (reserved.shardKey !== void 0) {
|
|
84
|
-
byKey.set("lunora.shard_key", encodeAttribute("lunora.shard_key", reserved.shardKey));
|
|
85
|
-
}
|
|
86
|
-
if (reserved.userId !== void 0) {
|
|
87
|
-
byKey.set("lunora.user_id", encodeAttribute("lunora.user_id", reserved.userId));
|
|
88
|
-
}
|
|
89
|
-
if (reserved.errorType !== void 0) {
|
|
90
|
-
byKey.set("error.type", encodeAttribute("error.type", reserved.errorType));
|
|
91
|
-
}
|
|
92
|
-
for (const [key, value] of Object.entries(caller ?? {})) {
|
|
93
|
-
byKey.set(key, encodeAttribute(key, coerceFieldValue(value)));
|
|
94
|
-
}
|
|
95
|
-
return [...byKey.values()];
|
|
96
|
-
};
|
|
97
|
-
const otlpSpanBody = (event, serviceName, resourceAttributes) => {
|
|
98
|
-
const span = {
|
|
99
|
-
attributes: encodeSignalAttributes(
|
|
100
|
-
{ errorType: event.error?.type, functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId },
|
|
101
|
-
event.attributes
|
|
102
|
-
),
|
|
103
|
-
endTimeUnixNano: otlpUnixNano(event.startTs + event.durationMs),
|
|
104
|
-
// Always SPAN_KIND_INTERNAL: only `ctx.trace` spans reach a sink. The
|
|
105
|
-
// synthetic dispatch span is buffered for the Studio waterfall and never
|
|
106
|
-
// exported, because the runtime already emits that dispatch to `onRpc` as
|
|
107
|
-
// a SERVER span — encoding it here too would duplicate it in every trace.
|
|
108
|
-
kind: 1,
|
|
109
|
-
name: event.name,
|
|
110
|
-
parentSpanId: event.parentSpanId,
|
|
111
|
-
spanId: event.spanId,
|
|
112
|
-
startTimeUnixNano: otlpUnixNano(event.startTs),
|
|
113
|
-
// STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
|
|
114
|
-
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
115
|
-
traceId: event.traceId
|
|
116
|
-
};
|
|
117
|
-
return wrapResourceSpans(span, "@lunora/runtime", serviceName, resourceAttributes);
|
|
118
|
-
};
|
|
119
|
-
const otlpMetricBody = (event, serviceName, resourceAttributes) => {
|
|
120
|
-
const timeUnixNano = otlpUnixNano(event.ts);
|
|
121
|
-
const attributes = encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey }, event.attributes);
|
|
122
|
-
const dataPoint = { asDouble: event.value, attributes, timeUnixNano };
|
|
123
|
-
if (event.kind === "gauge") {
|
|
124
|
-
return wrapResourceMetrics({ gauge: { dataPoints: [dataPoint] }, name: event.name }, "@lunora/runtime", serviceName, resourceAttributes);
|
|
125
|
-
}
|
|
126
|
-
if (event.kind === "histogram") {
|
|
127
|
-
return wrapResourceMetrics(
|
|
128
|
-
{
|
|
129
|
-
histogram: {
|
|
130
|
-
aggregationTemporality: 1,
|
|
131
|
-
dataPoints: [
|
|
132
|
-
{
|
|
133
|
-
attributes,
|
|
134
|
-
bucketCounts: ["1"],
|
|
135
|
-
count: "1",
|
|
136
|
-
explicitBounds: [],
|
|
137
|
-
max: event.value,
|
|
138
|
-
min: event.value,
|
|
139
|
-
// `startTimeUnixNano` omitted for the same reason as
|
|
140
|
-
// the Sum data point above — see the comment there.
|
|
141
|
-
sum: event.value,
|
|
142
|
-
timeUnixNano
|
|
143
|
-
}
|
|
144
|
-
]
|
|
145
|
-
},
|
|
146
|
-
name: event.name
|
|
147
|
-
},
|
|
148
|
-
"@lunora/runtime",
|
|
149
|
-
serviceName,
|
|
150
|
-
resourceAttributes
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
return wrapResourceMetrics(
|
|
154
|
-
{ name: event.name, sum: { aggregationTemporality: 1, dataPoints: [dataPoint], isMonotonic: true } },
|
|
155
|
-
"@lunora/runtime",
|
|
156
|
-
serviceName,
|
|
157
|
-
resourceAttributes
|
|
158
|
-
);
|
|
159
|
-
};
|
|
160
|
-
const otlpLogBody = (event, serviceName, resourceAttributes) => {
|
|
161
|
-
const logRecord = {
|
|
162
|
-
// Caller-supplied structured fields become log-record attributes so a
|
|
163
|
-
// pipeline can filter/index on them; precedence per `encodeSignalAttributes`.
|
|
164
|
-
attributes: encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId }, event.fields),
|
|
165
|
-
body: { stringValue: event.message },
|
|
166
|
-
severityNumber: OTLP_SEVERITY[event.level],
|
|
167
|
-
severityText: event.level.toUpperCase(),
|
|
168
|
-
timeUnixNano: otlpUnixNano(event.ts)
|
|
169
|
-
};
|
|
170
|
-
if (event.traceId !== void 0) {
|
|
171
|
-
logRecord.traceId = event.traceId;
|
|
172
|
-
}
|
|
173
|
-
if (event.spanId !== void 0) {
|
|
174
|
-
logRecord.spanId = event.spanId;
|
|
175
|
-
}
|
|
176
|
-
return wrapResourceLogs(logRecord, "@lunora/runtime", serviceName, resourceAttributes);
|
|
177
|
-
};
|
|
178
|
-
const OTLP_GZIP_THRESHOLD = 1024;
|
|
179
|
-
const gzipEncode = async (text) => {
|
|
180
|
-
const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
181
|
-
return new Response(stream).arrayBuffer();
|
|
182
|
-
};
|
|
183
|
-
const otlpPost = (url, body, headers, context) => {
|
|
184
|
-
try {
|
|
185
|
-
const json = JSON.stringify(body);
|
|
186
|
-
const sent = (json.length < OTLP_GZIP_THRESHOLD ? fetch(url, { body: json, headers, method: "POST" }) : gzipEncode(json).then((gz) => fetch(url, { body: gz, headers: { ...headers, "content-encoding": "gzip" }, method: "POST" }))).catch(() => {
|
|
187
|
-
});
|
|
188
|
-
if (context?.waitUntil) {
|
|
189
|
-
context.waitUntil(sent);
|
|
190
|
-
}
|
|
191
|
-
} catch {
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
196
|
-
const consoleSink = (options = {}) => {
|
|
197
|
-
const { onlyErrors } = options;
|
|
198
|
-
return {
|
|
199
|
-
onLog: (event) => {
|
|
200
|
-
if (event.level === "error" || event.level === "fatal") {
|
|
201
|
-
console.error("[lunora:log]", event.functionPath, event.message);
|
|
202
|
-
} else {
|
|
203
|
-
console.log("[lunora:log]", event.functionPath, event.message);
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
onMetric: (event) => {
|
|
207
|
-
console.log("[lunora:metric]", `${event.name}=${String(event.value)}`, event.kind, event.functionPath);
|
|
208
|
-
},
|
|
209
|
-
onRpc: (event) => {
|
|
210
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (event.ok) {
|
|
214
|
-
console.log("[lunora:rpc]", event);
|
|
215
|
-
} else {
|
|
216
|
-
console.error("[lunora:rpc]", event);
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
onSpan: (event) => {
|
|
220
|
-
const status = event.ok ? "ok" : `error ${event.error?.type ?? ""}`.trim();
|
|
221
|
-
console.log("[lunora:span]", event.name, `${String(event.durationMs)}ms`, status, event.functionPath);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
};
|
|
225
|
-
const webhookSink = (options) => {
|
|
226
|
-
const { headers, onlyErrors, transform, transformLog, url } = options;
|
|
227
|
-
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers);
|
|
228
|
-
const post = (payload, context) => {
|
|
229
|
-
try {
|
|
230
|
-
const sent = fetch(url, { body: JSON.stringify(payload), headers: mergedHeaders, method: "POST" }).catch(() => {
|
|
231
|
-
});
|
|
232
|
-
if (context?.waitUntil) {
|
|
233
|
-
context.waitUntil(sent);
|
|
234
|
-
}
|
|
235
|
-
} catch {
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
return {
|
|
239
|
-
onLog: (event, context) => {
|
|
240
|
-
let payload = event;
|
|
241
|
-
if (transformLog) {
|
|
242
|
-
try {
|
|
243
|
-
payload = transformLog(event);
|
|
244
|
-
} catch {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (payload === null || payload === void 0) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
post(payload, context);
|
|
252
|
-
},
|
|
253
|
-
onRpc: (event, context) => {
|
|
254
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
let payload = event;
|
|
259
|
-
if (transform) {
|
|
260
|
-
try {
|
|
261
|
-
payload = transform(event);
|
|
262
|
-
} catch {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (payload === null || payload === void 0) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
post(payload, context);
|
|
270
|
-
} catch {
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
const sentrySink = (options) => {
|
|
276
|
-
const { capture, captureLog } = options;
|
|
277
|
-
const onlyErrors = options.onlyErrors ?? true;
|
|
278
|
-
return {
|
|
279
|
-
// Only forward log lines when the caller wired `captureLog`; otherwise
|
|
280
|
-
// `ctx.log` output stays out of Sentry.
|
|
281
|
-
onLog: captureLog ? (event) => {
|
|
282
|
-
try {
|
|
283
|
-
captureLog(event);
|
|
284
|
-
} catch {
|
|
285
|
-
}
|
|
286
|
-
} : void 0,
|
|
287
|
-
onRpc: (event) => {
|
|
288
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
capture(event);
|
|
293
|
-
} catch {
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
const analyticsEngineSink = (options) => {
|
|
299
|
-
const { dataset, onlyErrors } = options;
|
|
300
|
-
return {
|
|
301
|
-
onRpc: (event) => {
|
|
302
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
try {
|
|
306
|
-
dataset.writeDataPoint({
|
|
307
|
-
blobs: [event.functionPath, event.ok ? "ok" : "error", event.shardKey ?? "", event.error?.code ?? "", event.fanOut?.table ?? ""],
|
|
308
|
-
doubles: [event.durationMs, event.ok ? 0 : 1, event.fanOut?.shards ?? 0, event.fanOut?.failed ?? 0],
|
|
309
|
-
indexes: [event.functionPath]
|
|
310
|
-
});
|
|
311
|
-
} catch {
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
const pipelineLogSink = (options) => {
|
|
317
|
-
const { pipeline, serializeFields } = options;
|
|
318
|
-
return {
|
|
319
|
-
onLog: (event, context) => {
|
|
320
|
-
try {
|
|
321
|
-
const record = {
|
|
322
|
-
functionPath: event.functionPath,
|
|
323
|
-
level: event.level,
|
|
324
|
-
message: event.message,
|
|
325
|
-
ts: event.ts
|
|
326
|
-
};
|
|
327
|
-
if (event.fields) {
|
|
328
|
-
record.fields = serializeFields === true ? JSON.stringify(event.fields) : event.fields;
|
|
329
|
-
}
|
|
330
|
-
if (event.shardKey !== void 0) {
|
|
331
|
-
record.shardKey = event.shardKey;
|
|
332
|
-
}
|
|
333
|
-
if (event.userId !== void 0) {
|
|
334
|
-
record.userId = event.userId;
|
|
335
|
-
}
|
|
336
|
-
if (event.traceId !== void 0) {
|
|
337
|
-
record.traceId = event.traceId;
|
|
338
|
-
}
|
|
339
|
-
if (event.spanId !== void 0) {
|
|
340
|
-
record.spanId = event.spanId;
|
|
341
|
-
}
|
|
342
|
-
const sent = pipeline.send([record]).catch(() => {
|
|
343
|
-
});
|
|
344
|
-
if (context?.waitUntil) {
|
|
345
|
-
context.waitUntil(sent);
|
|
346
|
-
}
|
|
347
|
-
} catch {
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
};
|
|
352
|
-
const otlpSink = (options) => {
|
|
353
|
-
const { deploymentEnvironment, detectResources, endpoint, headers, onlyErrors, resourceAttributes, serviceNamespace, serviceVersion, token } = options;
|
|
354
|
-
const serviceName = options.serviceName ?? "lunora";
|
|
355
|
-
const staticAttributes = {
|
|
356
|
-
...serviceVersion === void 0 ? {} : { "service.version": serviceVersion },
|
|
357
|
-
...serviceNamespace === void 0 ? {} : { "service.namespace": serviceNamespace },
|
|
358
|
-
...deploymentEnvironment === void 0 ? {} : { "deployment.environment": deploymentEnvironment },
|
|
359
|
-
...resourceAttributes
|
|
360
|
-
};
|
|
361
|
-
const mergedByContext = /* @__PURE__ */ new WeakMap();
|
|
362
|
-
const resourceAttributesFor = (context) => {
|
|
363
|
-
if (detectResources !== true || context?.resourceAttributes === void 0) {
|
|
364
|
-
return staticAttributes;
|
|
365
|
-
}
|
|
366
|
-
const memoized = mergedByContext.get(context);
|
|
367
|
-
if (memoized !== void 0) {
|
|
368
|
-
return memoized;
|
|
369
|
-
}
|
|
370
|
-
const merged = mergeResourceAttributes(context.resourceAttributes(), staticAttributes);
|
|
371
|
-
mergedByContext.set(context, merged);
|
|
372
|
-
return merged;
|
|
373
|
-
};
|
|
374
|
-
let base = endpoint;
|
|
375
|
-
while (base.endsWith("/")) {
|
|
376
|
-
base = base.slice(0, -1);
|
|
377
|
-
}
|
|
378
|
-
const tracesUrl = `${base}/v1/traces`;
|
|
379
|
-
const logsUrl = `${base}/v1/logs`;
|
|
380
|
-
const metricsUrl = `${base}/v1/metrics`;
|
|
381
|
-
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers, token);
|
|
382
|
-
return {
|
|
383
|
-
onLog: (event, context) => {
|
|
384
|
-
otlpPost(logsUrl, otlpLogBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
385
|
-
},
|
|
386
|
-
onMetric: (event, context) => {
|
|
387
|
-
otlpPost(metricsUrl, otlpMetricBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
388
|
-
},
|
|
389
|
-
onRpc: (event, context) => {
|
|
390
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
otlpPost(tracesUrl, otlpTraceBody(event, serviceName, Date.now(), resourceAttributesFor(context)), mergedHeaders, context);
|
|
394
|
-
},
|
|
395
|
-
onSpan: (event, context) => {
|
|
396
|
-
otlpPost(tracesUrl, otlpSpanBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
};
|
|
400
|
-
const combineSinks = (...sinks) => {
|
|
401
|
-
const fanOut = (method, event, context) => {
|
|
402
|
-
for (const sink of sinks) {
|
|
403
|
-
const handler = sink[method];
|
|
404
|
-
if (!handler) {
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
try {
|
|
408
|
-
handler.call(sink, event, context);
|
|
409
|
-
} catch {
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
return {
|
|
414
|
-
onLog: (event, context) => {
|
|
415
|
-
fanOut("onLog", event, context);
|
|
416
|
-
},
|
|
417
|
-
onMetric: (event, context) => {
|
|
418
|
-
fanOut("onMetric", event, context);
|
|
419
|
-
},
|
|
420
|
-
onRpc: (event, context) => {
|
|
421
|
-
fanOut("onRpc", event, context);
|
|
422
|
-
},
|
|
423
|
-
onSpan: (event, context) => {
|
|
424
|
-
fanOut("onSpan", event, context);
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink };
|
|
@@ -1,20 +0,0 @@
|
|
|
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 };
|