@lunora/runtime 1.0.0-alpha.34 → 1.0.0-alpha.36
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 +1853 -1012
- package/dist/index.d.ts +1853 -1012
- package/dist/index.mjs +6 -3
- package/dist/packem_shared/HEALTH_PATH-e5J_NHBx.mjs +150 -0
- package/dist/packem_shared/{analyticsEngineSink-DE48mQA9.mjs → analyticsEngineSink-yLFNjHDt.mjs} +74 -17
- package/dist/packem_shared/argsFromQuery-c-U1WRy-.mjs +121 -0
- package/dist/packem_shared/{composeWorker-DAwO9LLs.mjs → composeWorker-DiWwOXXt.mjs} +475 -94
- package/dist/packem_shared/createKvCursorStore-g8aA6B4L.mjs +165 -0
- package/dist/packem_shared/emitLogEvent-BlGMnKZK.mjs +1 -0
- package/dist/packem_shared/method-guard-Qzw99aCj.mjs +3 -0
- package/dist/packem_shared/observability--NOFYBFc.mjs +47 -0
- package/dist/packem_shared/otlp-resource-Dow6-F_u.mjs +163 -0
- package/package.json +3 -3
- package/dist/packem_shared/emitLogEvent-pEdtqAK8.mjs +0 -20
- package/dist/packem_shared/otlp-DOLuy1Aj.mjs +0 -95
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
|
|
2
|
-
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-
|
|
2
|
+
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-DiWwOXXt.mjs';
|
|
3
3
|
export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
|
|
4
4
|
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
|
|
5
5
|
export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
|
|
6
|
+
export { createKvCursorStore, createMemoryCursorStore, defineExportSink, r2Sink, runExportTap, sanitizeChange, webhookExportSink } from './packem_shared/createKvCursorStore-g8aA6B4L.mjs';
|
|
7
|
+
export { HEALTH_PATH, HEALTH_READY_PATH, buildHealthRoutes, d1Probe, durableObjectProbe, presenceProbe } from './packem_shared/HEALTH_PATH-e5J_NHBx.mjs';
|
|
6
8
|
export { LOG_ARCHIVE_PATH, resolveLogArchiveFromEnv } from './packem_shared/LOG_ARCHIVE_PATH-CNs0bznX.mjs';
|
|
7
|
-
export { emitLogEvent, emitRpcEvent } from './packem_shared/
|
|
8
|
-
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-
|
|
9
|
+
export { e as emitLogEvent, a as emitRpcEvent } from './packem_shared/observability--NOFYBFc.mjs';
|
|
10
|
+
export { analyticsEngineSink, combineSinks, consoleSink, otlpSink, pipelineLogSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-yLFNjHDt.mjs';
|
|
9
11
|
export { D as DEFAULT_LOG_COLUMNS, a as DEFAULT_LOG_LIMIT, c as createPipelineLogReader } from './packem_shared/pipeline-log-reader-BXULGNC3.mjs';
|
|
10
12
|
export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
|
|
11
13
|
export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
|
|
14
|
+
export { argsFromQuery, buildRestRoutes, createRestRateLimit, readShardKey, restSurfaceFromRegistry } from './packem_shared/argsFromQuery-c-U1WRy-.mjs';
|
|
12
15
|
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DRWQFNhF.mjs';
|
|
13
16
|
export { LOG_ARCHIVE_NOT_CONFIGURED } from './packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-acNcguqc.mjs';
|
|
14
17
|
export { NOOP_EXECUTION_CONTEXT } from './packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
|
|
2
|
+
import { m as methodGuard } from './method-guard-Qzw99aCj.mjs';
|
|
3
|
+
|
|
4
|
+
const HEALTH_PATH = "/_lunora/health";
|
|
5
|
+
const HEALTH_READY_PATH = "/_lunora/health/ready";
|
|
6
|
+
const toCheckerType = (kind) => {
|
|
7
|
+
if (kind === "liveness") {
|
|
8
|
+
return ["liveness"];
|
|
9
|
+
}
|
|
10
|
+
if (kind === "readiness") {
|
|
11
|
+
return ["readiness"];
|
|
12
|
+
}
|
|
13
|
+
return ["liveness", "readiness"];
|
|
14
|
+
};
|
|
15
|
+
class HealthRegistry {
|
|
16
|
+
#checkers = /* @__PURE__ */ new Map();
|
|
17
|
+
addChecker(name, run, options) {
|
|
18
|
+
this.#checkers.set(name, { run, types: options.type });
|
|
19
|
+
}
|
|
20
|
+
async getReport(filter) {
|
|
21
|
+
const selected = [...this.#checkers].filter(([, checker]) => filter === void 0 || checker.types.includes(filter));
|
|
22
|
+
const entries = await Promise.all(selected.map(async ([name, checker]) => [name, await checker.run()]));
|
|
23
|
+
return {
|
|
24
|
+
healthy: entries.every(([, result]) => result.health.healthy),
|
|
25
|
+
report: Object.fromEntries(entries)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const buildRegistry = (probes) => {
|
|
30
|
+
const registry = new HealthRegistry();
|
|
31
|
+
const criticalNames = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const probe of probes) {
|
|
33
|
+
if (probe.critical) {
|
|
34
|
+
criticalNames.add(probe.name);
|
|
35
|
+
}
|
|
36
|
+
registry.addChecker(
|
|
37
|
+
probe.name,
|
|
38
|
+
async () => {
|
|
39
|
+
let result;
|
|
40
|
+
try {
|
|
41
|
+
result = await probe.check();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
result = { healthy: false, message: error instanceof Error ? error.message : "probe failed" };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
health: {
|
|
47
|
+
healthy: result.healthy,
|
|
48
|
+
...result.message === void 0 ? {} : { message: result.message }
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
{ type: toCheckerType(probe.kind) }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return { criticalNames, registry };
|
|
56
|
+
};
|
|
57
|
+
const buildBody = (report, criticalNames, posture, appName, appVersion) => {
|
|
58
|
+
const checks = [];
|
|
59
|
+
let anyCriticalDown = false;
|
|
60
|
+
let anyDown = false;
|
|
61
|
+
for (const [name, entry] of Object.entries(report)) {
|
|
62
|
+
const critical = criticalNames.has(name);
|
|
63
|
+
const up = entry.health.healthy;
|
|
64
|
+
if (!up) {
|
|
65
|
+
anyDown = true;
|
|
66
|
+
if (critical) {
|
|
67
|
+
anyCriticalDown = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
checks.push({
|
|
71
|
+
critical,
|
|
72
|
+
...posture === "admin" && entry.health.message !== void 0 ? { message: entry.health.message } : {},
|
|
73
|
+
name,
|
|
74
|
+
status: up ? "up" : "down"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
checks.sort((a, b) => a.name.localeCompare(b.name));
|
|
78
|
+
let status = "healthy";
|
|
79
|
+
if (anyCriticalDown) {
|
|
80
|
+
status = "unhealthy";
|
|
81
|
+
} else if (anyDown) {
|
|
82
|
+
status = "degraded";
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
anyCriticalDown,
|
|
86
|
+
body: { appName, appVersion, checks, status, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const buildHealthRoutes = (deps) => {
|
|
90
|
+
const { appName = "lunora", appVersion = "0.0.0", auth = "public", isAdmin, resolveProbes } = deps;
|
|
91
|
+
const gate = (request) => {
|
|
92
|
+
if (auth === "admin" && !isAdmin(request)) {
|
|
93
|
+
throw new LunoraError("health endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const respond = async (request, env, probeKind) => {
|
|
97
|
+
const wrongMethod = methodGuard(request, ["GET", "HEAD"]);
|
|
98
|
+
if (wrongMethod) {
|
|
99
|
+
return wrongMethod;
|
|
100
|
+
}
|
|
101
|
+
gate(request);
|
|
102
|
+
const { criticalNames, registry } = buildRegistry(resolveProbes(env));
|
|
103
|
+
const { healthy, report } = await registry.getReport(probeKind === "readiness" ? "readiness" : void 0);
|
|
104
|
+
const { anyCriticalDown, body } = buildBody(report, criticalNames, auth, appName, appVersion);
|
|
105
|
+
const down = probeKind === "readiness" ? !healthy : anyCriticalDown;
|
|
106
|
+
return Response.json(body, { headers: { "cache-control": "no-store" }, status: down ? 503 : 200 });
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
[HEALTH_PATH]: (request, env) => respond(request, env, "aggregate"),
|
|
110
|
+
[HEALTH_READY_PATH]: (request, env) => respond(request, env, "readiness")
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
const durableObjectProbe = (name, namespace, shardKey) => {
|
|
114
|
+
return {
|
|
115
|
+
check: async () => {
|
|
116
|
+
try {
|
|
117
|
+
const stub = namespace.get(namespace.idFromName(shardKey));
|
|
118
|
+
await stub.fetch(new Request("https://shard.internal/_lunora/status", { method: "GET" }));
|
|
119
|
+
return { healthy: true };
|
|
120
|
+
} catch {
|
|
121
|
+
return { healthy: false, message: "durable object unreachable" };
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
critical: true,
|
|
125
|
+
name
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const d1Probe = (name, database) => {
|
|
129
|
+
return {
|
|
130
|
+
check: async () => {
|
|
131
|
+
try {
|
|
132
|
+
await database.prepare("SELECT 1").first();
|
|
133
|
+
return { healthy: true };
|
|
134
|
+
} catch {
|
|
135
|
+
return { healthy: false, message: "d1 query failed" };
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
critical: true,
|
|
139
|
+
name
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
const presenceProbe = (name, bound) => {
|
|
143
|
+
return {
|
|
144
|
+
check: () => bound ? { healthy: true } : { healthy: false, message: "binding not configured" },
|
|
145
|
+
critical: false,
|
|
146
|
+
name
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export { HEALTH_PATH, HEALTH_READY_PATH, buildHealthRoutes, d1Probe, durableObjectProbe, presenceProbe };
|
package/dist/packem_shared/{analyticsEngineSink-DE48mQA9.mjs → analyticsEngineSink-yLFNjHDt.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
2
|
|
|
3
3
|
const stringifyFieldValue = (value) => {
|
|
4
4
|
if (typeof value === "string") {
|
|
@@ -12,12 +12,31 @@ const stringifyFieldValue = (value) => {
|
|
|
12
12
|
};
|
|
13
13
|
const coerceFieldValue = (value) => typeof value === "boolean" || typeof value === "number" || typeof value === "string" ? value : stringifyFieldValue(value);
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const otlpTraceBody = (event, serviceName, endMs) => {
|
|
15
|
+
const otlpTraceBody = (event, serviceName, endMs, resourceAttributes) => {
|
|
17
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
|
+
}
|
|
18
36
|
if (event.shardKey !== void 0) {
|
|
19
37
|
attributes.push(encodeAttribute("lunora.shard_key", event.shardKey));
|
|
20
38
|
}
|
|
39
|
+
attributes.push(encodeAttribute("http.response.status_code", event.error?.status ?? 200));
|
|
21
40
|
if (event.error) {
|
|
22
41
|
attributes.push(encodeAttribute("error.type", event.error.code), encodeAttribute("lunora.error_status", event.error.status));
|
|
23
42
|
}
|
|
@@ -42,7 +61,22 @@ const otlpTraceBody = (event, serviceName, endMs) => {
|
|
|
42
61
|
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
43
62
|
traceId: event.traceId ?? otlpRandomHex(16)
|
|
44
63
|
};
|
|
45
|
-
|
|
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);
|
|
46
80
|
};
|
|
47
81
|
const encodeSignalAttributes = (reserved, caller) => {
|
|
48
82
|
const byKey = /* @__PURE__ */ new Map([["lunora.function_path", encodeAttribute("lunora.function_path", reserved.functionPath)]]);
|
|
@@ -60,7 +94,7 @@ const encodeSignalAttributes = (reserved, caller) => {
|
|
|
60
94
|
}
|
|
61
95
|
return [...byKey.values()];
|
|
62
96
|
};
|
|
63
|
-
const otlpSpanBody = (event, serviceName) => {
|
|
97
|
+
const otlpSpanBody = (event, serviceName, resourceAttributes) => {
|
|
64
98
|
const span = {
|
|
65
99
|
attributes: encodeSignalAttributes(
|
|
66
100
|
{ errorType: event.error?.type, functionPath: event.functionPath, shardKey: event.shardKey, userId: event.userId },
|
|
@@ -80,14 +114,14 @@ const otlpSpanBody = (event, serviceName) => {
|
|
|
80
114
|
status: event.ok ? { code: 1 } : { code: 2, message: event.error?.message ?? "" },
|
|
81
115
|
traceId: event.traceId
|
|
82
116
|
};
|
|
83
|
-
return wrapResourceSpans(span, "@lunora/runtime", serviceName);
|
|
117
|
+
return wrapResourceSpans(span, "@lunora/runtime", serviceName, resourceAttributes);
|
|
84
118
|
};
|
|
85
|
-
const otlpMetricBody = (event, serviceName) => {
|
|
119
|
+
const otlpMetricBody = (event, serviceName, resourceAttributes) => {
|
|
86
120
|
const timeUnixNano = otlpUnixNano(event.ts);
|
|
87
121
|
const attributes = encodeSignalAttributes({ functionPath: event.functionPath, shardKey: event.shardKey }, event.attributes);
|
|
88
122
|
const dataPoint = { asDouble: event.value, attributes, timeUnixNano };
|
|
89
123
|
if (event.kind === "gauge") {
|
|
90
|
-
return wrapResourceMetrics({ gauge: { dataPoints: [dataPoint] }, name: event.name }, "@lunora/runtime", serviceName);
|
|
124
|
+
return wrapResourceMetrics({ gauge: { dataPoints: [dataPoint] }, name: event.name }, "@lunora/runtime", serviceName, resourceAttributes);
|
|
91
125
|
}
|
|
92
126
|
if (event.kind === "histogram") {
|
|
93
127
|
return wrapResourceMetrics(
|
|
@@ -112,16 +146,18 @@ const otlpMetricBody = (event, serviceName) => {
|
|
|
112
146
|
name: event.name
|
|
113
147
|
},
|
|
114
148
|
"@lunora/runtime",
|
|
115
|
-
serviceName
|
|
149
|
+
serviceName,
|
|
150
|
+
resourceAttributes
|
|
116
151
|
);
|
|
117
152
|
}
|
|
118
153
|
return wrapResourceMetrics(
|
|
119
154
|
{ name: event.name, sum: { aggregationTemporality: 1, dataPoints: [dataPoint], isMonotonic: true } },
|
|
120
155
|
"@lunora/runtime",
|
|
121
|
-
serviceName
|
|
156
|
+
serviceName,
|
|
157
|
+
resourceAttributes
|
|
122
158
|
);
|
|
123
159
|
};
|
|
124
|
-
const otlpLogBody = (event, serviceName) => {
|
|
160
|
+
const otlpLogBody = (event, serviceName, resourceAttributes) => {
|
|
125
161
|
const logRecord = {
|
|
126
162
|
// Caller-supplied structured fields become log-record attributes so a
|
|
127
163
|
// pipeline can filter/index on them; precedence per `encodeSignalAttributes`.
|
|
@@ -137,7 +173,7 @@ const otlpLogBody = (event, serviceName) => {
|
|
|
137
173
|
if (event.spanId !== void 0) {
|
|
138
174
|
logRecord.spanId = event.spanId;
|
|
139
175
|
}
|
|
140
|
-
return wrapResourceLogs(logRecord, "@lunora/runtime", serviceName);
|
|
176
|
+
return wrapResourceLogs(logRecord, "@lunora/runtime", serviceName, resourceAttributes);
|
|
141
177
|
};
|
|
142
178
|
const OTLP_GZIP_THRESHOLD = 1024;
|
|
143
179
|
const gzipEncode = async (text) => {
|
|
@@ -155,6 +191,8 @@ const otlpPost = (url, body, headers, context) => {
|
|
|
155
191
|
} catch {
|
|
156
192
|
}
|
|
157
193
|
};
|
|
194
|
+
|
|
195
|
+
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
158
196
|
const consoleSink = (options = {}) => {
|
|
159
197
|
const { onlyErrors } = options;
|
|
160
198
|
return {
|
|
@@ -312,8 +350,27 @@ const pipelineLogSink = (options) => {
|
|
|
312
350
|
};
|
|
313
351
|
};
|
|
314
352
|
const otlpSink = (options) => {
|
|
315
|
-
const { endpoint, headers, onlyErrors, token } = options;
|
|
353
|
+
const { deploymentEnvironment, detectResources, endpoint, headers, onlyErrors, resourceAttributes, serviceNamespace, serviceVersion, token } = options;
|
|
316
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
|
+
};
|
|
317
374
|
let base = endpoint;
|
|
318
375
|
while (base.endsWith("/")) {
|
|
319
376
|
base = base.slice(0, -1);
|
|
@@ -324,19 +381,19 @@ const otlpSink = (options) => {
|
|
|
324
381
|
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers, token);
|
|
325
382
|
return {
|
|
326
383
|
onLog: (event, context) => {
|
|
327
|
-
otlpPost(logsUrl, otlpLogBody(event, serviceName), mergedHeaders, context);
|
|
384
|
+
otlpPost(logsUrl, otlpLogBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
328
385
|
},
|
|
329
386
|
onMetric: (event, context) => {
|
|
330
|
-
otlpPost(metricsUrl, otlpMetricBody(event, serviceName), mergedHeaders, context);
|
|
387
|
+
otlpPost(metricsUrl, otlpMetricBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
331
388
|
},
|
|
332
389
|
onRpc: (event, context) => {
|
|
333
390
|
if (shouldSkip(event, onlyErrors)) {
|
|
334
391
|
return;
|
|
335
392
|
}
|
|
336
|
-
otlpPost(tracesUrl, otlpTraceBody(event, serviceName, Date.now()), mergedHeaders, context);
|
|
393
|
+
otlpPost(tracesUrl, otlpTraceBody(event, serviceName, Date.now(), resourceAttributesFor(context)), mergedHeaders, context);
|
|
337
394
|
},
|
|
338
395
|
onSpan: (event, context) => {
|
|
339
|
-
otlpPost(tracesUrl, otlpSpanBody(event, serviceName), mergedHeaders, context);
|
|
396
|
+
otlpPost(tracesUrl, otlpSpanBody(event, serviceName, resourceAttributesFor(context)), mergedHeaders, context);
|
|
340
397
|
}
|
|
341
398
|
};
|
|
342
399
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { m as methodGuard } from './method-guard-Qzw99aCj.mjs';
|
|
2
|
+
|
|
3
|
+
const REST_PATH_PREFIX = "/_lunora/rest";
|
|
4
|
+
const splitFunctionPath = (functionPath) => {
|
|
5
|
+
const colon = functionPath.indexOf(":");
|
|
6
|
+
if (colon <= 0 || colon >= functionPath.length - 1 || functionPath.indexOf(":", colon + 1) !== -1) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
return { name: functionPath.slice(colon + 1), namespace: functionPath.slice(0, colon) };
|
|
10
|
+
};
|
|
11
|
+
const restPathForFunction = (functionPath) => {
|
|
12
|
+
const parts = splitFunctionPath(functionPath);
|
|
13
|
+
if (parts === void 0) {
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
return `${REST_PATH_PREFIX}/${parts.namespace}/${parts.name}`;
|
|
17
|
+
};
|
|
18
|
+
const restMethodForKind = (kind) => kind === "query" ? "GET" : "POST";
|
|
19
|
+
const describeRestSurface = (procedures) => {
|
|
20
|
+
const entries = [];
|
|
21
|
+
for (const procedure of procedures) {
|
|
22
|
+
if (procedure.exposure?.rest !== true || procedure.kind === "stream") {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const parts = splitFunctionPath(procedure.functionPath);
|
|
26
|
+
const path = restPathForFunction(procedure.functionPath);
|
|
27
|
+
if (parts === void 0 || path === void 0) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
entries.push({
|
|
31
|
+
functionPath: procedure.functionPath,
|
|
32
|
+
kind: procedure.kind,
|
|
33
|
+
method: restMethodForKind(procedure.kind),
|
|
34
|
+
name: parts.name,
|
|
35
|
+
namespace: parts.namespace,
|
|
36
|
+
path
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
40
|
+
return entries;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const registryToSurfaceInput = (functions) => Object.entries(functions).map(([functionPath, entry]) => {
|
|
44
|
+
return { exposure: entry.expose, functionPath, kind: entry.kind };
|
|
45
|
+
});
|
|
46
|
+
const restSurfaceFromRegistry = (functions) => describeRestSurface(registryToSurfaceInput(functions));
|
|
47
|
+
const readShardKey = (url, request) => {
|
|
48
|
+
const fromQuery = url.searchParams.get("shardKey");
|
|
49
|
+
if (fromQuery !== null && fromQuery !== "") {
|
|
50
|
+
return fromQuery;
|
|
51
|
+
}
|
|
52
|
+
const fromHeader = request.headers.get("x-lunora-shard-key");
|
|
53
|
+
return fromHeader === null || fromHeader === "" ? void 0 : fromHeader;
|
|
54
|
+
};
|
|
55
|
+
const argsFromQuery = (url) => {
|
|
56
|
+
const args = {};
|
|
57
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
58
|
+
if (key === "shardKey") {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
args[key] = JSON.parse(value);
|
|
63
|
+
} catch {
|
|
64
|
+
args[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
};
|
|
69
|
+
const buildRestRoutes = (deps) => {
|
|
70
|
+
const { functions, invoke, rateLimit, readJsonBody } = deps;
|
|
71
|
+
const routes = {};
|
|
72
|
+
for (const entry of restSurfaceFromRegistry(functions)) {
|
|
73
|
+
const allowed = entry.kind === "query" ? ["GET", "POST"] : ["POST"];
|
|
74
|
+
routes[entry.path] = async (request, env, _url, context) => {
|
|
75
|
+
const wrongMethod = methodGuard(request, allowed);
|
|
76
|
+
if (wrongMethod) {
|
|
77
|
+
return wrongMethod;
|
|
78
|
+
}
|
|
79
|
+
const url = new URL(request.url);
|
|
80
|
+
if (rateLimit) {
|
|
81
|
+
const limited = await rateLimit(request, entry.functionPath);
|
|
82
|
+
if (limited) {
|
|
83
|
+
return limited;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let args;
|
|
87
|
+
if (request.method === "GET") {
|
|
88
|
+
args = argsFromQuery(url);
|
|
89
|
+
} else {
|
|
90
|
+
args = request.body === null ? {} : await readJsonBody(request);
|
|
91
|
+
}
|
|
92
|
+
const shardKey = readShardKey(url, request);
|
|
93
|
+
return invoke({
|
|
94
|
+
args,
|
|
95
|
+
env,
|
|
96
|
+
functionPath: entry.functionPath,
|
|
97
|
+
request,
|
|
98
|
+
...shardKey === void 0 ? {} : { shardKey },
|
|
99
|
+
...context?.waitUntil === void 0 ? {} : { waitUntil: (promise) => context.waitUntil?.(promise) }
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return routes;
|
|
104
|
+
};
|
|
105
|
+
const createRestRateLimit = (limiter, options) => async (request, functionPath) => {
|
|
106
|
+
const key = options.key ? options.key(request, functionPath) : request.headers.get("cf-connecting-ip") ?? void 0;
|
|
107
|
+
const status = await limiter.limit(options.name, key === void 0 ? {} : { key });
|
|
108
|
+
if (status.ok) {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
const retryAfterSeconds = Math.max(1, Math.ceil(status.retryAfter / 1e3));
|
|
112
|
+
return Response.json(
|
|
113
|
+
{ error: { code: "RATE_LIMITED", message: "Rate limit exceeded" } },
|
|
114
|
+
{
|
|
115
|
+
headers: { "content-type": "application/json", "retry-after": String(retryAfterSeconds) },
|
|
116
|
+
status: 429
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export { argsFromQuery, buildRestRoutes, createRestRateLimit, readShardKey, restSurfaceFromRegistry };
|