@lunora/do 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 +115 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +6347 -0
- package/dist/index.d.ts +6347 -0
- package/dist/index.mjs +37 -0
- package/dist/packem_shared/ADMIN_FUNCTIONS-D_UiYJFk.mjs +316 -0
- package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs +54 -0
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs +84 -0
- package/dist/packem_shared/CDC_LOG_TABLE-DSycmnDf.mjs +107 -0
- package/dist/packem_shared/ConflictError-C0STs6bU.mjs +13 -0
- package/dist/packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs +133 -0
- package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs +237 -0
- package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-DU-Y4-LJ.mjs +209 -0
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs +248 -0
- package/dist/packem_shared/LogBuffer-B_Ezju_N.mjs +37 -0
- package/dist/packem_shared/MAIL_RETENTION-CPpgl-dX.mjs +104 -0
- package/dist/packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs +29 -0
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs +1 -0
- package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +10 -0
- package/dist/packem_shared/NotUniqueError-DZQtH02h.mjs +1835 -0
- package/dist/packem_shared/RANK_TIEBREAK-CXhdcA1o.mjs +91 -0
- package/dist/packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs +132 -0
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-DKwBF3Jp.mjs +4996 -0
- package/dist/packem_shared/ReactiveCache-1hDydFyv.mjs +232 -0
- package/dist/packem_shared/SCAN_DEP-DLJF8dsj.mjs +19 -0
- package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs +180 -0
- package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs +146 -0
- package/dist/packem_shared/aggregateTableName-CxNqY1Sl.mjs +64 -0
- package/dist/packem_shared/applyOnDelete-BQ-8ZlZ1.mjs +175 -0
- package/dist/packem_shared/applySelect-BvZdFUBT.mjs +101 -0
- package/dist/packem_shared/armRestore-BJk53Ro8.mjs +55 -0
- package/dist/packem_shared/backfillAggregateIndexes-BZsOqDXP.mjs +81 -0
- package/dist/packem_shared/buildFtsMatch-BLEMawrp.mjs +38 -0
- package/dist/packem_shared/compileWhereSql-CXrhFA3G.mjs +127 -0
- package/dist/packem_shared/createSystemReader-8CzSZP9V.mjs +80 -0
- package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
- package/dist/packem_shared/ctx-db-shapes-DVoeZpo-.mjs +53 -0
- package/dist/packem_shared/do-exec-5eQy5cEi.mjs +12 -0
- package/dist/packem_shared/do-sql-BCHCWtrD.mjs +87 -0
- package/dist/packem_shared/exportShardRows-DZEhUeyI.mjs +156 -0
- package/dist/packem_shared/hasTrigger-5N6_Fx0A.mjs +20 -0
- package/dist/packem_shared/renderSql-D6eUcn2N.mjs +16 -0
- package/dist/packem_shared/runShardMigrations-nIwoQeOK.mjs +105 -0
- package/dist/packem_shared/security-audit-CucgBice.mjs +158 -0
- package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
- package/dist/packem_shared/serveRelationFanout-C6lDaesn.mjs +24 -0
- package/dist/packem_shared/stableStringify-CyHKJXre.mjs +30 -0
- package/dist/packem_shared/subscriptionListDeltas-ce84gpwL.mjs +111 -0
- package/package.json +41 -17
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const SECRET_NAME_PATTERN = /key|secret|token|password|passwd|credential|private|auth|bearer|session|cookie|salt|signature|dsn|webhook/iu;
|
|
2
|
+
const LUNORA_INTERNAL_VARS = /* @__PURE__ */ new Set(["LUNORA_ADMIN_TOKEN", "LUNORA_WS_BEARER"]);
|
|
3
|
+
const WORKER_URL_VARS = ["CF_PAGES_URL", "WORKER_URL", "LUNORA_WORKER_URL"];
|
|
4
|
+
const ENVIRONMENT_VARS = ["CF_ENV", "ENVIRONMENT", "WORKER_ENV", "NODE_ENV"];
|
|
5
|
+
const redact = (value) => {
|
|
6
|
+
if (value.length <= 4) {
|
|
7
|
+
return "••••";
|
|
8
|
+
}
|
|
9
|
+
return `${value.slice(0, 4)}${"•".repeat(Math.min(8, value.length - 4))}`;
|
|
10
|
+
};
|
|
11
|
+
const looksSecret = (name) => SECRET_NAME_PATTERN.test(name);
|
|
12
|
+
const bindingType = (value) => {
|
|
13
|
+
const has = (key) => typeof value[key] === "function";
|
|
14
|
+
if (has("idFromName") || has("idFromString")) {
|
|
15
|
+
return "durable-object";
|
|
16
|
+
}
|
|
17
|
+
if (has("createMultipartUpload") || has("put") && has("get") && has("list") && has("head")) {
|
|
18
|
+
return "r2";
|
|
19
|
+
}
|
|
20
|
+
if (has("getWithMetadata")) {
|
|
21
|
+
return "kv";
|
|
22
|
+
}
|
|
23
|
+
if (has("prepare") && has("dump")) {
|
|
24
|
+
return "d1";
|
|
25
|
+
}
|
|
26
|
+
if (has("send") && has("sendBatch")) {
|
|
27
|
+
return "queue";
|
|
28
|
+
}
|
|
29
|
+
if (has("fetch") && !has("get")) {
|
|
30
|
+
return "service";
|
|
31
|
+
}
|
|
32
|
+
return "object";
|
|
33
|
+
};
|
|
34
|
+
const readDeployInfo = (env) => {
|
|
35
|
+
const deploy = {};
|
|
36
|
+
for (const key of WORKER_URL_VARS) {
|
|
37
|
+
if (typeof env[key] === "string" && env[key] !== "") {
|
|
38
|
+
deploy.workerUrl = env[key];
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const key of ENVIRONMENT_VARS) {
|
|
43
|
+
if (typeof env[key] === "string" && env[key] !== "") {
|
|
44
|
+
deploy.environment = env[key];
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const versionMetadata = env["CF_VERSION_METADATA"];
|
|
49
|
+
if (versionMetadata !== null && typeof versionMetadata === "object") {
|
|
50
|
+
const meta = versionMetadata;
|
|
51
|
+
if (typeof meta["id"] === "string") {
|
|
52
|
+
deploy.deploymentId = meta["id"];
|
|
53
|
+
}
|
|
54
|
+
if (typeof meta["tag"] === "string") {
|
|
55
|
+
deploy.versionTag = meta["tag"];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return deploy;
|
|
59
|
+
};
|
|
60
|
+
const DEV_ENVIRONMENT_PATTERN = /^(?:dev(?:elopment)?|local(?:host)?|test)$/iu;
|
|
61
|
+
const isDevEnvironment = (rawEnv) => {
|
|
62
|
+
const env = rawEnv ?? {};
|
|
63
|
+
for (const key of ENVIRONMENT_VARS) {
|
|
64
|
+
const value = env[key];
|
|
65
|
+
if (typeof value === "string" && DEV_ENVIRONMENT_PATTERN.test(value)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
71
|
+
const buildSettings = (rawEnv) => {
|
|
72
|
+
const env = rawEnv ?? {};
|
|
73
|
+
const settings = [];
|
|
74
|
+
for (const [name, value] of Object.entries(env)) {
|
|
75
|
+
if (LUNORA_INTERNAL_VARS.has(name)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
const secret = looksSecret(name);
|
|
80
|
+
settings.push({
|
|
81
|
+
kind: secret ? "secret" : "var",
|
|
82
|
+
name,
|
|
83
|
+
// Both vars and secrets are masked — a "var" may still hold a
|
|
84
|
+
// sensitive value, and the studio never needs the raw text.
|
|
85
|
+
value: redact(value)
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
90
|
+
settings.push({ kind: "var", name, value: String(value) });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (value !== null && typeof value === "object") {
|
|
94
|
+
settings.push({ bindingType: bindingType(value), kind: "binding", name, value: null });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
settings.push({ kind: "var", name, value: null });
|
|
98
|
+
}
|
|
99
|
+
settings.sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
+
return { deploy: readDeployInfo(env), settings };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const MIN_ADMIN_TOKEN_LENGTH = 24;
|
|
104
|
+
const MIN_AUTH_SECRET_LENGTH = 32;
|
|
105
|
+
const LEVEL_ORDER = { error: 0, info: 2, warning: 1 };
|
|
106
|
+
const DISABLED_ENV_VALUES = /* @__PURE__ */ new Set(["0", "disabled", "false", "no", "off"]);
|
|
107
|
+
const ENABLED_ENV_VALUES = /* @__PURE__ */ new Set(["1", "enabled", "on", "true", "yes"]);
|
|
108
|
+
const readFlag = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
109
|
+
const auditAuthSecret = (env) => {
|
|
110
|
+
const authSecret = env["AUTH_SECRET"] ?? env["BETTER_AUTH_SECRET"];
|
|
111
|
+
const length = typeof authSecret === "string" ? authSecret.trim().length : 0;
|
|
112
|
+
if (length > 0 && length < MIN_AUTH_SECRET_LENGTH) {
|
|
113
|
+
return [{ detail: { length, min: MIN_AUTH_SECRET_LENGTH }, kind: "auth-secret-weak", level: "warning" }];
|
|
114
|
+
}
|
|
115
|
+
return [];
|
|
116
|
+
};
|
|
117
|
+
const auditCors = (env) => {
|
|
118
|
+
const allowedOrigins = readFlag(env["LUNORA_ALLOWED_ORIGINS"]);
|
|
119
|
+
const corsCredentials = ENABLED_ENV_VALUES.has(readFlag(env["LUNORA_CORS_ALLOW_CREDENTIALS"]) ?? "");
|
|
120
|
+
const hasWildcard = allowedOrigins?.split(",").some((entry) => entry.trim() === "*") ?? false;
|
|
121
|
+
return hasWildcard && corsCredentials ? [{ kind: "cors-wildcard-credentials", level: "error" }] : [];
|
|
122
|
+
};
|
|
123
|
+
const auditSecurityLayers = (env, dev) => {
|
|
124
|
+
if (dev) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const findings = [];
|
|
128
|
+
if (DISABLED_ENV_VALUES.has(readFlag(env["LUNORA_SECURITY_HEADERS"]) ?? "")) {
|
|
129
|
+
findings.push({ kind: "security-headers-disabled", level: "warning" });
|
|
130
|
+
}
|
|
131
|
+
if (DISABLED_ENV_VALUES.has(readFlag(env["LUNORA_SECURITY_CSRF"]) ?? "")) {
|
|
132
|
+
findings.push({ kind: "csrf-disabled", level: "warning" });
|
|
133
|
+
}
|
|
134
|
+
if (readFlag(env["BETTER_AUTH_URL"])?.startsWith("http://") === true) {
|
|
135
|
+
findings.push({ kind: "cookies-insecure", level: "warning" });
|
|
136
|
+
}
|
|
137
|
+
return findings;
|
|
138
|
+
};
|
|
139
|
+
const buildSecurityAudit = (rawEnv) => {
|
|
140
|
+
const env = rawEnv ?? {};
|
|
141
|
+
const findings = [];
|
|
142
|
+
const adminToken = env["LUNORA_ADMIN_TOKEN"];
|
|
143
|
+
if (typeof adminToken === "string" && adminToken.length > 0 && adminToken.length < MIN_ADMIN_TOKEN_LENGTH) {
|
|
144
|
+
findings.push({ detail: { length: adminToken.length, min: MIN_ADMIN_TOKEN_LENGTH }, kind: "admin-token-weak", level: "warning" });
|
|
145
|
+
}
|
|
146
|
+
const wsBearer = env["LUNORA_WS_BEARER"];
|
|
147
|
+
const dev = isDevEnvironment(env);
|
|
148
|
+
if (typeof wsBearer !== "string" || wsBearer === "") {
|
|
149
|
+
findings.push({ kind: "ws-gate-open", level: dev ? "info" : "error" });
|
|
150
|
+
}
|
|
151
|
+
if (dev) {
|
|
152
|
+
findings.push({ kind: "dev-args-unredacted", level: "warning" });
|
|
153
|
+
}
|
|
154
|
+
findings.push(...auditAuthSecret(env), ...auditCors(env), ...auditSecurityLayers(env, dev));
|
|
155
|
+
return { findings: findings.toSorted((a, b) => LEVEL_ORDER[a.level] - LEVEL_ORDER[b.level]) };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export { MIN_ADMIN_TOKEN_LENGTH as M, MIN_AUTH_SECRET_LENGTH as a, buildSecurityAudit as b, buildSettings as c, isDevEnvironment as i };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const serializeSqlValue = (value) => {
|
|
2
|
+
if (typeof value === "boolean") {
|
|
3
|
+
return value ? 1 : 0;
|
|
4
|
+
}
|
|
5
|
+
if (value === null || typeof value === "string" || typeof value === "number") {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === "bigint") {
|
|
9
|
+
return value.toString();
|
|
10
|
+
}
|
|
11
|
+
return JSON.stringify(value);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { serializeSqlValue as s };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-D_UiYJFk.mjs';
|
|
2
|
+
|
|
3
|
+
const serveRelationFanout = async (schema, database, functionPath, args) => {
|
|
4
|
+
const table = typeof args["table"] === "string" ? args["table"] : "";
|
|
5
|
+
const definition = schema.tables[table];
|
|
6
|
+
if (!definition) {
|
|
7
|
+
throw Object.assign(new Error(`${RELATION_FUNCTION_PREFIX} unknown table "${table}"`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
|
|
8
|
+
}
|
|
9
|
+
if (definition.shardMode?.kind === "global") {
|
|
10
|
+
throw Object.assign(new Error(`${RELATION_FUNCTION_PREFIX} table "${table}" is global, not shard-local`), {
|
|
11
|
+
code: "BAD_REQUEST",
|
|
12
|
+
name: "LunoraError",
|
|
13
|
+
status: 400
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const where = args["where"] ?? void 0;
|
|
17
|
+
if (functionPath === `${RELATION_FUNCTION_PREFIX}count`) {
|
|
18
|
+
return database.count(table, where);
|
|
19
|
+
}
|
|
20
|
+
const result = await database.findMany(table, { orderBy: args["orderBy"], where, with: args["with"] });
|
|
21
|
+
return result.page;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { serveRelationFanout };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const compareKeys = (a, b) => {
|
|
2
|
+
if (a < b) {
|
|
3
|
+
return -1;
|
|
4
|
+
}
|
|
5
|
+
return a > b ? 1 : 0;
|
|
6
|
+
};
|
|
7
|
+
const stableStringify = (value) => {
|
|
8
|
+
if (value === void 0) {
|
|
9
|
+
return "null";
|
|
10
|
+
}
|
|
11
|
+
if (value === null || typeof value !== "object") {
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
16
|
+
}
|
|
17
|
+
const record = value;
|
|
18
|
+
const keys = Object.keys(record).toSorted(compareKeys);
|
|
19
|
+
const parts = [];
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const raw = record[key];
|
|
22
|
+
if (raw === void 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
26
|
+
}
|
|
27
|
+
return `{${parts.join(",")}}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { stableStringify };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const ROW_ID_FIELD = "_id";
|
|
2
|
+
const DELTA_FALLBACK_TABLE = "__lunora__";
|
|
3
|
+
const readRowId = (row) => {
|
|
4
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
5
|
+
return void 0;
|
|
6
|
+
}
|
|
7
|
+
const id = row[ROW_ID_FIELD];
|
|
8
|
+
return typeof id === "string" ? id : void 0;
|
|
9
|
+
};
|
|
10
|
+
const indexRowsById = (rows) => {
|
|
11
|
+
const byId = /* @__PURE__ */ new Map();
|
|
12
|
+
const order = [];
|
|
13
|
+
for (const row of rows) {
|
|
14
|
+
const id = readRowId(row);
|
|
15
|
+
if (id === void 0 || byId.has(id)) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
byId.set(id, row);
|
|
19
|
+
order.push(id);
|
|
20
|
+
}
|
|
21
|
+
return { byId, order };
|
|
22
|
+
};
|
|
23
|
+
const survivorsKeepOrder = (previous, next) => {
|
|
24
|
+
const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
|
|
25
|
+
const survivingNext = next.order.filter((id) => previous.byId.has(id));
|
|
26
|
+
if (survivingPrevious.length !== survivingNext.length) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return survivingPrevious.every((id, index) => survivingNext[index] === id);
|
|
30
|
+
};
|
|
31
|
+
const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const id of previous.order) {
|
|
34
|
+
if (!next.byId.has(id)) {
|
|
35
|
+
out.push({
|
|
36
|
+
delta: { key: id, op: "delete", table: deltaTable },
|
|
37
|
+
frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const id of next.order) {
|
|
46
|
+
const nextRow = next.byId.get(id);
|
|
47
|
+
const previousRow = previous.byId.get(id);
|
|
48
|
+
const nextFingerprint = JSON.stringify(nextRow);
|
|
49
|
+
const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
|
|
50
|
+
if (previousFingerprint === nextFingerprint) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const op = previousFingerprint === void 0 ? "insert" : "update";
|
|
54
|
+
out.push({
|
|
55
|
+
delta: { key: id, op, row: nextRow, table: deltaTable },
|
|
56
|
+
frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(previousJson);
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
const previous = indexRowsById(parsed);
|
|
72
|
+
const next = indexRowsById(nextResult);
|
|
73
|
+
if (previous === void 0 || next === void 0) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
if (!survivorsKeepOrder(previous, next)) {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
|
|
80
|
+
const tableJson = JSON.stringify(deltaTable);
|
|
81
|
+
const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
|
|
82
|
+
if (framed.length > next.order.length) {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
if (frames !== void 0) {
|
|
86
|
+
for (const { frame } of framed) {
|
|
87
|
+
frames.push(frame);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return framed.map(({ delta }) => delta);
|
|
91
|
+
};
|
|
92
|
+
const trySendFrame = (ws, frame) => {
|
|
93
|
+
try {
|
|
94
|
+
ws.send(frame);
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
|
|
101
|
+
const idJson = JSON.stringify(subId);
|
|
102
|
+
let delivered = true;
|
|
103
|
+
for (const deltaBody of deltaFrames) {
|
|
104
|
+
if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
|
|
105
|
+
delivered = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return delivered;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { sendDeltaFrames, subscriptionListDeltas, trySendFrame };
|
package/package.json
CHANGED
|
@@ -1,31 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/do",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cloudflare",
|
|
7
|
+
"durable-objects",
|
|
8
|
+
"hibernation",
|
|
9
|
+
"lunora",
|
|
10
|
+
"shard",
|
|
11
|
+
"sqlite",
|
|
12
|
+
"websocket",
|
|
13
|
+
"workers"
|
|
14
|
+
],
|
|
6
15
|
"homepage": "https://lunora.sh",
|
|
16
|
+
"bugs": "https://github.com/anolilab/lunora/issues",
|
|
17
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Daniel Bannert",
|
|
20
|
+
"email": "d.bannert@anolilab.de"
|
|
21
|
+
},
|
|
7
22
|
"repository": {
|
|
8
23
|
"type": "git",
|
|
9
24
|
"url": "git+https://github.com/anolilab/lunora.git",
|
|
10
25
|
"directory": "packages/do"
|
|
11
26
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"cloudflare",
|
|
18
|
-
"workers",
|
|
19
|
-
"durable-objects",
|
|
20
|
-
"sqlite",
|
|
21
|
-
"websocket",
|
|
22
|
-
"hibernation",
|
|
23
|
-
"shard"
|
|
27
|
+
"files": [
|
|
28
|
+
"./dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE.md",
|
|
31
|
+
"__assets__"
|
|
24
32
|
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"main": "./dist/index.mjs",
|
|
36
|
+
"module": "./dist/index.mjs",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
25
45
|
"publishConfig": {
|
|
26
46
|
"access": "public"
|
|
27
47
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@visulima/redact": "3.0.0-alpha.14",
|
|
50
|
+
"drizzle-orm": "^0.45.2"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": "^22.15.0 || >=24.11.0"
|
|
54
|
+
}
|
|
31
55
|
}
|