@lunora/runtime 0.0.0 → 1.0.0-alpha.10
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 +105 -0
- package/README.md +121 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +2426 -0
- package/dist/index.d.ts +2426 -0
- package/dist/index.mjs +15 -0
- package/dist/packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-ocax8v0n.mjs +78 -0
- package/dist/packem_shared/LunoraError-CL0aOtpo.mjs +46 -0
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs +8 -0
- package/dist/packem_shared/analyticsEngineSink-DqEvrQs0.mjs +141 -0
- package/dist/packem_shared/applyJurisdiction-BkZtTkct.mjs +20 -0
- package/dist/packem_shared/composeWorker-BEv9IBWS.mjs +2613 -0
- package/dist/packem_shared/createCrossShardRelationCapabilities-C0KOf7er.mjs +61 -0
- package/dist/packem_shared/createQueryCoordinator-Cbds9cUI.mjs +838 -0
- package/dist/packem_shared/decorateResponse-DbISh_Wi.mjs +233 -0
- package/dist/packem_shared/emitLogEvent-pEdtqAK8.mjs +20 -0
- package/dist/packem_shared/toAirbyteMessages-DrHdplb4.mjs +39 -0
- package/package.json +37 -17
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
|
|
2
|
+
export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-BEv9IBWS.mjs';
|
|
3
|
+
export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-C0KOf7er.mjs';
|
|
4
|
+
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-ocax8v0n.mjs';
|
|
5
|
+
export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-CL0aOtpo.mjs';
|
|
6
|
+
export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK8.mjs';
|
|
7
|
+
export { analyticsEngineSink, combineSinks, consoleSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DqEvrQs0.mjs';
|
|
8
|
+
export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-Cbds9cUI.mjs';
|
|
9
|
+
export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
|
|
10
|
+
export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DbISh_Wi.mjs';
|
|
11
|
+
export { NOOP_EXECUTION_CONTEXT } from './packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
|
|
12
|
+
|
|
13
|
+
const VERSION = "0.0.0";
|
|
14
|
+
|
|
15
|
+
export { VERSION };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
|
|
2
|
+
|
|
3
|
+
const SHARD_REGISTRY_DO_NAME = "__lunora_shard_registry__";
|
|
4
|
+
const DEFAULT_REGISTRY_CACHE_TTL_MS = 3e4;
|
|
5
|
+
const REGISTRY_BASE_URL = "https://shard-registry.internal";
|
|
6
|
+
const decodeJson = async (response) => (
|
|
7
|
+
// Response.json() throws on non-JSON; the DO always returns JSON so this
|
|
8
|
+
// only surfaces if the DO route is misbehaving — let it bubble.
|
|
9
|
+
await response.json()
|
|
10
|
+
);
|
|
11
|
+
const createDynamicShardRegistry = (options) => {
|
|
12
|
+
const instanceName = options.instanceName ?? SHARD_REGISTRY_DO_NAME;
|
|
13
|
+
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_REGISTRY_CACHE_TTL_MS;
|
|
14
|
+
const cache = /* @__PURE__ */ new Map();
|
|
15
|
+
const namespace = applyJurisdiction(options.namespace, options.jurisdiction);
|
|
16
|
+
let cachedStub;
|
|
17
|
+
const stub = () => {
|
|
18
|
+
cachedStub ??= namespace.get(namespace.idFromName(instanceName));
|
|
19
|
+
return cachedStub;
|
|
20
|
+
};
|
|
21
|
+
const post = async (path, body) => stub().fetch(
|
|
22
|
+
new Request(`${REGISTRY_BASE_URL}${path}`, {
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
headers: { "content-type": "application/json" },
|
|
25
|
+
method: "POST"
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
const get = async (path) => stub().fetch(new Request(`${REGISTRY_BASE_URL}${path}`, { method: "GET" }));
|
|
29
|
+
return {
|
|
30
|
+
invalidate(table) {
|
|
31
|
+
if (table === void 0) {
|
|
32
|
+
cache.clear();
|
|
33
|
+
} else {
|
|
34
|
+
cache.delete(table);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
async listShardKeys(table) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const cached = cache.get(table);
|
|
40
|
+
if (cached && cached.expiresAt > now) {
|
|
41
|
+
return cached.shardKeys;
|
|
42
|
+
}
|
|
43
|
+
const response = await get(`/list?table=${encodeURIComponent(table)}`);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`shard registry /list returned ${String(response.status)}`);
|
|
46
|
+
}
|
|
47
|
+
const { shardKeys } = await decodeJson(response);
|
|
48
|
+
if (cacheTtlMs > 0) {
|
|
49
|
+
cache.set(table, { expiresAt: now + cacheTtlMs, shardKeys });
|
|
50
|
+
}
|
|
51
|
+
return shardKeys;
|
|
52
|
+
},
|
|
53
|
+
async register(table, shardKey) {
|
|
54
|
+
const response = await post("/register", { shardKey, table });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`shard registry /register returned ${String(response.status)}`);
|
|
57
|
+
}
|
|
58
|
+
cache.delete(table);
|
|
59
|
+
},
|
|
60
|
+
async snapshot() {
|
|
61
|
+
const response = await get("/snapshot");
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`shard registry /snapshot returned ${String(response.status)}`);
|
|
64
|
+
}
|
|
65
|
+
const { tables } = await decodeJson(response);
|
|
66
|
+
return tables;
|
|
67
|
+
},
|
|
68
|
+
async unregister(table, shardKey) {
|
|
69
|
+
const response = await post("/unregister", { shardKey, table });
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`shard registry /unregister returned ${String(response.status)}`);
|
|
72
|
+
}
|
|
73
|
+
cache.delete(table);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class LunoraError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, { cause: options?.cause });
|
|
6
|
+
this.name = "LunoraError";
|
|
7
|
+
this.code = options?.code ?? "INTERNAL";
|
|
8
|
+
this.status = options?.status ?? 500;
|
|
9
|
+
}
|
|
10
|
+
toResponse() {
|
|
11
|
+
const body = { error: { code: this.code, message: this.message } };
|
|
12
|
+
return Response.json(body, {
|
|
13
|
+
headers: { "content-type": "application/json" },
|
|
14
|
+
status: this.status
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const hasErrorShape = (error, name) => {
|
|
19
|
+
if (!error || typeof error !== "object") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const candidate = error;
|
|
23
|
+
return candidate.name === name && typeof candidate.code === "string" && typeof candidate.status === "number" && typeof candidate.message === "string";
|
|
24
|
+
};
|
|
25
|
+
const isStructuralConflictError = (error) => hasErrorShape(error, "ConflictError");
|
|
26
|
+
const isStructuralLunoraError = (error) => hasErrorShape(error, "LunoraError");
|
|
27
|
+
const toErrorResponse = (error) => {
|
|
28
|
+
if (error instanceof LunoraError) {
|
|
29
|
+
return error.toResponse();
|
|
30
|
+
}
|
|
31
|
+
if (isStructuralLunoraError(error) || isStructuralConflictError(error)) {
|
|
32
|
+
const body2 = { error: { code: error.code, message: error.message } };
|
|
33
|
+
return Response.json(body2, {
|
|
34
|
+
headers: { "content-type": "application/json" },
|
|
35
|
+
status: error.status
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
console.error("[lunora] unhandled error:", error);
|
|
39
|
+
const body = { error: { code: "INTERNAL", message: "Internal error" } };
|
|
40
|
+
return Response.json(body, {
|
|
41
|
+
headers: { "content-type": "application/json" },
|
|
42
|
+
status: 500
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { LunoraError, isStructuralConflictError, isStructuralLunoraError, toErrorResponse };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
2
|
+
const mergeHeaders = (defaults, overrides) => {
|
|
3
|
+
if (!overrides) {
|
|
4
|
+
return { ...defaults };
|
|
5
|
+
}
|
|
6
|
+
const merged = {};
|
|
7
|
+
const seen = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const [name, value] of Object.entries(defaults)) {
|
|
9
|
+
const lower = name.toLowerCase();
|
|
10
|
+
seen.set(lower, name);
|
|
11
|
+
merged[name] = value;
|
|
12
|
+
}
|
|
13
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
14
|
+
const lower = name.toLowerCase();
|
|
15
|
+
const existing = seen.get(lower);
|
|
16
|
+
if (existing === void 0) {
|
|
17
|
+
seen.set(lower, name);
|
|
18
|
+
merged[name] = value;
|
|
19
|
+
} else {
|
|
20
|
+
merged[existing] = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return merged;
|
|
24
|
+
};
|
|
25
|
+
const consoleSink = (options = {}) => {
|
|
26
|
+
const { onlyErrors } = options;
|
|
27
|
+
return {
|
|
28
|
+
onLog: (event) => {
|
|
29
|
+
if (event.level === "error") {
|
|
30
|
+
console.error("[lunora:log]", event.functionPath, event.message);
|
|
31
|
+
} else {
|
|
32
|
+
console.log("[lunora:log]", event.functionPath, event.message);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
onRpc: (event) => {
|
|
36
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (event.ok) {
|
|
40
|
+
console.log("[lunora:rpc]", event);
|
|
41
|
+
} else {
|
|
42
|
+
console.error("[lunora:rpc]", event);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const webhookSink = (options) => {
|
|
48
|
+
const { headers, onlyErrors, transform, url } = options;
|
|
49
|
+
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers);
|
|
50
|
+
return {
|
|
51
|
+
onRpc: (event, context) => {
|
|
52
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
let payload = event;
|
|
57
|
+
if (transform) {
|
|
58
|
+
try {
|
|
59
|
+
payload = transform(event);
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (payload === null || payload === void 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const sent = fetch(url, {
|
|
68
|
+
body: JSON.stringify(payload),
|
|
69
|
+
headers: mergedHeaders,
|
|
70
|
+
method: "POST"
|
|
71
|
+
}).catch(() => {
|
|
72
|
+
});
|
|
73
|
+
if (context?.waitUntil) {
|
|
74
|
+
context.waitUntil(sent);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const sentrySink = (options) => {
|
|
82
|
+
const { capture } = options;
|
|
83
|
+
const onlyErrors = options.onlyErrors ?? true;
|
|
84
|
+
return {
|
|
85
|
+
onRpc: (event) => {
|
|
86
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
capture(event);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const analyticsEngineSink = (options) => {
|
|
97
|
+
const { dataset, onlyErrors } = options;
|
|
98
|
+
return {
|
|
99
|
+
onRpc: (event) => {
|
|
100
|
+
if (shouldSkip(event, onlyErrors)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
dataset.writeDataPoint({
|
|
105
|
+
blobs: [event.functionPath, event.ok ? "ok" : "error", event.shardKey ?? "", event.error?.code ?? "", event.fanOut?.table ?? ""],
|
|
106
|
+
doubles: [event.durationMs, event.ok ? 0 : 1, event.fanOut?.shards ?? 0, event.fanOut?.failed ?? 0],
|
|
107
|
+
indexes: [event.functionPath]
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const combineSinks = (...sinks) => {
|
|
115
|
+
return {
|
|
116
|
+
onLog: (event, context) => {
|
|
117
|
+
for (const sink of sinks) {
|
|
118
|
+
if (!sink.onLog) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
sink.onLog(event, context);
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
onRpc: (event, context) => {
|
|
128
|
+
for (const sink of sinks) {
|
|
129
|
+
if (!sink.onRpc) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
sink.onRpc(event, context);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export { analyticsEngineSink, combineSinks, consoleSink, 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 };
|