@rindle/room 0.5.0
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/build.sh +53 -0
- package/dist/authority.d.ts +57 -0
- package/dist/authority.d.ts.map +1 -0
- package/dist/authority.js +73 -0
- package/dist/authority.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +64 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +53 -0
- package/dist/journal.js.map +1 -0
- package/dist/mutation-tx.d.ts +79 -0
- package/dist/mutation-tx.d.ts.map +1 -0
- package/dist/mutation-tx.js +207 -0
- package/dist/mutation-tx.js.map +1 -0
- package/dist/shell.d.ts +134 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +1589 -0
- package/dist/shell.js.map +1 -0
- package/dist/token.d.ts +89 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +158 -0
- package/dist/token.js.map +1 -0
- package/dist/wasm.d.ts +7 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +29 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +71 -0
- package/pkg/rindle_room.d.ts +345 -0
- package/pkg/rindle_room.js +924 -0
- package/pkg/rindle_room_bg.wasm +0 -0
- package/pkg/rindle_room_bg.wasm.d.ts +45 -0
- package/src/authority.ts +127 -0
- package/src/index.ts +49 -0
- package/src/journal.ts +110 -0
- package/src/mutation-tx.ts +275 -0
- package/src/shell.ts +1859 -0
- package/src/token.ts +219 -0
- package/src/wasm.ts +32 -0
package/build.sh
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build the rindle-room-wasm crate into this package as a portable ESM artifact by default
|
|
3
|
+
# (--target web): works in browsers/Workers (await init()) and Node (await init(bytes)) — see
|
|
4
|
+
# src/wasm.ts. The same artifact the Durable Object shell will run; the Node test shell
|
|
5
|
+
# drives it unmodified (RINDLE-REALTIME-DESIGN.md §11).
|
|
6
|
+
#
|
|
7
|
+
# ./build.sh [profile] # wasm-release (default) | release | dev
|
|
8
|
+
#
|
|
9
|
+
# Produces packages/room/pkg/{rindle_room.js, rindle_room_bg.wasm, rindle_room.d.ts}.
|
|
10
|
+
# Advanced wrappers may override:
|
|
11
|
+
# RINDLE_WASM_OUT output directory
|
|
12
|
+
# RINDLE_WASM_BINDGEN_TARGET wasm-bindgen target (default: web)
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
PROFILE="${1:-wasm-release}"
|
|
16
|
+
TARGET=wasm32-unknown-unknown
|
|
17
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
18
|
+
# The Cargo workspace lives under rust/; the wasm artifact ships into this JS package's pkg/ (which
|
|
19
|
+
# stays at the repo root). Build from rust/ so `-p rindle-room-wasm` and target/ resolve; emit into packages/room/pkg.
|
|
20
|
+
RUST_ROOT="$ROOT/rust"
|
|
21
|
+
OUT="${RINDLE_WASM_OUT:-$ROOT/packages/room/pkg}"
|
|
22
|
+
BINDGEN_TARGET="${RINDLE_WASM_BINDGEN_TARGET:-web}"
|
|
23
|
+
export PATH="$HOME/.cargo/bin:$PATH"
|
|
24
|
+
|
|
25
|
+
cd "$RUST_ROOT"
|
|
26
|
+
|
|
27
|
+
# `cargo rustc … --crate-type cdylib`: the lib's default crate-type is `rlib` (native builds
|
|
28
|
+
# must not emit a colliding cdylib, cargo#6313); the `.wasm` artifact needs `cdylib`, requested
|
|
29
|
+
# here just for this wasm32 build. Produces `target/$TARGET/<profile>/rindle_room_wasm.wasm`.
|
|
30
|
+
case "$PROFILE" in
|
|
31
|
+
dev) cargo rustc -p rindle-room-wasm --target "$TARGET" --crate-type cdylib; WASM="$RUST_ROOT/target/$TARGET/debug/rindle_room_wasm.wasm" ;;
|
|
32
|
+
release) cargo rustc -p rindle-room-wasm --release --target "$TARGET" --crate-type cdylib; WASM="$RUST_ROOT/target/$TARGET/release/rindle_room_wasm.wasm" ;;
|
|
33
|
+
*) cargo rustc -p rindle-room-wasm --profile "$PROFILE" --target "$TARGET" --crate-type cdylib; WASM="$RUST_ROOT/target/$TARGET/$PROFILE/rindle_room_wasm.wasm" ;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
wasm-bindgen --target "$BINDGEN_TARGET" --out-dir "$OUT" --out-name rindle_room "$WASM"
|
|
37
|
+
|
|
38
|
+
if command -v wasm-opt >/dev/null 2>&1 && [ "$PROFILE" != "dev" ]; then
|
|
39
|
+
# Same binaryen version guard as packages/wasm/build.sh (multi-table export bug < 110
|
|
40
|
+
# corrupts the reference-types externref table; wasm-bindgen#4528).
|
|
41
|
+
wo_ver="$(wasm-opt --version 2>/dev/null | grep -oE '[0-9]+' | head -1 || true)"
|
|
42
|
+
if [ -n "$wo_ver" ] && [ "$wo_ver" -lt 110 ]; then
|
|
43
|
+
echo "error: wasm-opt (binaryen) version $wo_ver is too old; need >= 110." >&2
|
|
44
|
+
echo " binaryen < 110 corrupts the reference-types externref table, so the wasm fails at" >&2
|
|
45
|
+
echo " runtime with 'WebAssembly.Table.grow(): failed to grow table by 4'." >&2
|
|
46
|
+
echo " Install a newer binaryen: https://github.com/WebAssembly/binaryen/releases" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
wasm-opt -Oz "$OUT/rindle_room_bg.wasm" -o "$OUT/rindle_room_bg.wasm"
|
|
50
|
+
echo "wasm-opt -Oz applied"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
echo "built $OUT (target=$BINDGEN_TARGET, profile=$PROFILE, $(wc -c < "$OUT/rindle_room_bg.wasm") bytes)"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** One CAS rejection in the authority's `409 conflict` body: the row's authoritative
|
|
2
|
+
* current image (`null` = absent), keyed by table + pk cells. */
|
|
3
|
+
export interface AuthorityConflict {
|
|
4
|
+
table: string;
|
|
5
|
+
pk: unknown[];
|
|
6
|
+
current: unknown[] | null;
|
|
7
|
+
}
|
|
8
|
+
/** The apply outcome. Anything else — network failure, a 5xx — is a **throw**: plain
|
|
9
|
+
* errors retry (same body, same id; dedup absorbs a landed retry), errors marked
|
|
10
|
+
* `fatal: true` (an identity mismatch — same id, different body — or a malformed
|
|
11
|
+
* refusal) kill the incarnation loudly instead. */
|
|
12
|
+
export type AuthorityApplyResult = {
|
|
13
|
+
kind: "ok";
|
|
14
|
+
applied: boolean;
|
|
15
|
+
cv?: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "conflict";
|
|
18
|
+
conflicts: AuthorityConflict[];
|
|
19
|
+
} | {
|
|
20
|
+
kind: "fenced";
|
|
21
|
+
currentEpoch?: number;
|
|
22
|
+
};
|
|
23
|
+
export interface RoomAuthority {
|
|
24
|
+
/** Claim the placement epoch for `doc` (§2.5) — once per shell process, at boot,
|
|
25
|
+
* AFTER resubmitting the previous incarnation's unconfirmed batches (their recorded
|
|
26
|
+
* epochs must still be current to land). */
|
|
27
|
+
claimEpoch(doc: string): Promise<number>;
|
|
28
|
+
/** The authority's ledger lmid per client (0 = none) — the boot probe that lets
|
|
29
|
+
* journal replay absorb already-durable mutations as dedup. */
|
|
30
|
+
lmids(doc: string, clients: string[]): Promise<Record<string, number>>;
|
|
31
|
+
/** Apply one batch: `body` is the exact journaled `/apply-row-change-txn` body
|
|
32
|
+
* string, sent verbatim. */
|
|
33
|
+
applyRowChangeTxn(body: string): Promise<AuthorityApplyResult>;
|
|
34
|
+
}
|
|
35
|
+
/** An apply error the shell must NOT retry (retrying cannot help; something is wrong
|
|
36
|
+
* with the batch or the room, not the network). */
|
|
37
|
+
export interface FatalAuthorityError extends Error {
|
|
38
|
+
fatal: true;
|
|
39
|
+
}
|
|
40
|
+
export declare function fatalAuthorityError(message: string): FatalAuthorityError;
|
|
41
|
+
export interface HttpAuthorityOptions {
|
|
42
|
+
/** The API server's apply endpoint (e.g. `http://…/api/rindle/apply-row-change-txn`). */
|
|
43
|
+
applyUrl: string;
|
|
44
|
+
/** The epoch-claim endpoint (e.g. `http://…/api/rindle/claim-room-epoch`). */
|
|
45
|
+
claimUrl: string;
|
|
46
|
+
/** The lmid-probe endpoint (e.g. `http://…/api/rindle/room-lmids`). */
|
|
47
|
+
lmidsUrl: string;
|
|
48
|
+
/** Extra headers on every call — the epoch-bound flush credential rides here. */
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
fetch?: typeof fetch;
|
|
51
|
+
}
|
|
52
|
+
/** The HTTP [`RoomAuthority`]: the API-server host as the room's sole counterpart
|
|
53
|
+
* (§5.3.1). Maps `409 {error:"fenced"}` / `409 {error:"conflict"}` to their results,
|
|
54
|
+
* a `500` mentioning a batch-identity mismatch to a fatal error, and anything else
|
|
55
|
+
* non-OK to a retryable throw. */
|
|
56
|
+
export declare function httpAuthority(opts: HttpAuthorityOptions): RoomAuthority;
|
|
57
|
+
//# sourceMappingURL=authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../src/authority.ts"],"names":[],"mappings":"AAcA;kEACkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC3B;AAED;;;oDAGoD;AACpD,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B;;iDAE6C;IAC7C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC;oEACgE;IAChE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvE;iCAC6B;IAC7B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAChE;AAED;oDACoD;AACpD,MAAM,WAAW,mBAAoB,SAAQ,KAAK;IAChD,KAAK,EAAE,IAAI,CAAC;CACb;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAIxE;AAED,MAAM,WAAW,oBAAoB;IACnC,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;mCAGmC;AACnC,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAsDvE"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The room's **write-authority seam** (RINDLE-REALTIME-DESIGN.md §5.3.1): the three
|
|
2
|
+
// calls a flushing room makes against the app's authority — claim a placement epoch at
|
|
3
|
+
// boot (§2.5), probe the durable lmids before journal replay (§3.3, T2's
|
|
4
|
+
// committed-before-crash row), and apply one journaled batch. In every deployment shape
|
|
5
|
+
// the counterpart is the API server's `/apply-row-change-txn` host ([`httpAuthority`]);
|
|
6
|
+
// the P3 gate drives the same interface against an in-process mock, which is what keeps
|
|
7
|
+
// the contract host-independent.
|
|
8
|
+
//
|
|
9
|
+
// The apply takes the **exact composed body string** the shell journaled — never a
|
|
10
|
+
// re-serialized object. One flush id names one immutable byte body forever (§5.3
|
|
11
|
+
// step 4): a retry, a crash-replay resubmission, and the first send all put identical
|
|
12
|
+
// bytes on the wire, which is what makes the authority's `batch_hash` identity check
|
|
13
|
+
// (§8.3, T6) meaningful.
|
|
14
|
+
export function fatalAuthorityError(message) {
|
|
15
|
+
const e = new Error(message);
|
|
16
|
+
e.fatal = true;
|
|
17
|
+
return e;
|
|
18
|
+
}
|
|
19
|
+
/** The HTTP [`RoomAuthority`]: the API-server host as the room's sole counterpart
|
|
20
|
+
* (§5.3.1). Maps `409 {error:"fenced"}` / `409 {error:"conflict"}` to their results,
|
|
21
|
+
* a `500` mentioning a batch-identity mismatch to a fatal error, and anything else
|
|
22
|
+
* non-OK to a retryable throw. */
|
|
23
|
+
export function httpAuthority(opts) {
|
|
24
|
+
const doFetch = opts.fetch ?? fetch;
|
|
25
|
+
const headers = { "content-type": "application/json", ...(opts.headers ?? {}) };
|
|
26
|
+
const post = async (url, body) => doFetch(url, { method: "POST", headers, body });
|
|
27
|
+
return {
|
|
28
|
+
async claimEpoch(doc) {
|
|
29
|
+
const res = await post(opts.claimUrl, JSON.stringify({ doc }));
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(`claim-room-epoch failed: ${res.status} ${await res.text()}`);
|
|
32
|
+
}
|
|
33
|
+
const out = (await res.json());
|
|
34
|
+
if (typeof out.epoch !== "number") {
|
|
35
|
+
throw fatalAuthorityError("claim-room-epoch returned no epoch");
|
|
36
|
+
}
|
|
37
|
+
return out.epoch;
|
|
38
|
+
},
|
|
39
|
+
async lmids(doc, clients) {
|
|
40
|
+
const res = await post(opts.lmidsUrl, JSON.stringify({ doc, clients }));
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`room-lmids failed: ${res.status} ${await res.text()}`);
|
|
43
|
+
}
|
|
44
|
+
const out = (await res.json());
|
|
45
|
+
return out.lmids ?? {};
|
|
46
|
+
},
|
|
47
|
+
async applyRowChangeTxn(body) {
|
|
48
|
+
const res = await post(opts.applyUrl, body);
|
|
49
|
+
if (res.status === 409) {
|
|
50
|
+
const out = (await res.json());
|
|
51
|
+
if (out.error === "fenced") {
|
|
52
|
+
return { kind: "fenced", currentEpoch: out.currentEpoch };
|
|
53
|
+
}
|
|
54
|
+
if (out.error === "conflict") {
|
|
55
|
+
return { kind: "conflict", conflicts: out.conflicts ?? [] };
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`apply-row-change-txn 409: ${JSON.stringify(out)}`);
|
|
58
|
+
}
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const text = await res.text();
|
|
61
|
+
// The §8.3 identity check: same flush id, different body — OUR bug, loud,
|
|
62
|
+
// never retried (a retry re-sends the same mismatched bytes forever).
|
|
63
|
+
if (res.status === 500 && text.includes("batch identity")) {
|
|
64
|
+
throw fatalAuthorityError(`apply-row-change-txn: ${text}`);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`apply-row-change-txn failed: ${res.status} ${text}`);
|
|
67
|
+
}
|
|
68
|
+
const out = (await res.json());
|
|
69
|
+
return { kind: "ok", applied: out.applied === true, cv: out.cv };
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=authority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority.js","sourceRoot":"","sources":["../src/authority.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,uFAAuF;AACvF,yEAAyE;AACzE,wFAAwF;AACxF,wFAAwF;AACxF,wFAAwF;AACxF,iCAAiC;AACjC,EAAE;AACF,mFAAmF;AACnF,iFAAiF;AACjF,sFAAsF;AACtF,qFAAqF;AACrF,yBAAyB;AAsCzB,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,CAAwB,CAAC;IACpD,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;IACf,OAAO,CAAC,CAAC;AACX,CAAC;AAcD;;;mCAGmC;AACnC,MAAM,UAAU,aAAa,CAAC,IAA0B;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;IACpC,MAAM,OAAO,GAAG,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAE,IAAY,EAAqB,EAAE,CAClE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,OAAO;QACL,KAAK,CAAC,UAAU,CAAC,GAAG;YAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YAC/D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChF,CAAC;YACD,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuB,CAAC;YACrD,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAClC,MAAM,mBAAmB,CAAC,oCAAoC,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,GAAG,CAAC,KAAK,CAAC;QACnB,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO;YACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YACxE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuC,CAAC;YACrE,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,iBAAiB,CAAC,IAAI;YAC1B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAI5B,CAAC;gBACF,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC3B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;oBAC7B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;gBAC9D,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtE,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,0EAA0E;gBAC1E,sEAAsE;gBACtE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;oBAC1D,MAAM,mBAAmB,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;gBAC7D,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;YACxE,CAAC;YACD,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuC,CAAC;YACrE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { initRoomWasm, WasmRoom } from "./wasm.ts";
|
|
2
|
+
export { createRoomShell } from "./shell.ts";
|
|
3
|
+
export type { DownstreamOptions, RoomScopeSpec, RoomShell, RoomShellOptions, UpstreamOptions, WritesOptions, } from "./shell.ts";
|
|
4
|
+
export { journalEntryOutcome, memoryJournal } from "./journal.ts";
|
|
5
|
+
export type { RoomFlushRecord, RoomJournal, RoomJournalEntry } from "./journal.ts";
|
|
6
|
+
export { fatalAuthorityError, httpAuthority } from "./authority.ts";
|
|
7
|
+
export type { AuthorityApplyResult, AuthorityConflict, HttpAuthorityOptions, RoomAuthority, } from "./authority.ts";
|
|
8
|
+
export type { KeyedRow, MutationTx, RoomMutator, RoomMutatorCtx, WireValue, } from "./mutation-tx.ts";
|
|
9
|
+
export { mintRoomToken, verifyRoomToken, RoomTokenError, scopeSpecsHash } from "./token.ts";
|
|
10
|
+
export type { MintRoomTokenOptions, RoomTokenPayload, VerifyRoomTokenOptions, } from "./token.ts";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5F,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @rindle/room — the Rindle Realtime room (P0 read-only follower + P1 lease-gated
|
|
2
|
+
// downstream + P2 shared optimistic writes).
|
|
3
|
+
//
|
|
4
|
+
// Four layers, deliberately separable:
|
|
5
|
+
// - `initRoomWasm` / `WasmRoom` (src/wasm.ts): the wasm room core — rindle-room-wasm
|
|
6
|
+
// compiled to `pkg/`, host-neutral (the Durable Object shell runs the SAME artifact).
|
|
7
|
+
// - `mintRoomToken` / `verifyRoomToken` (src/token.ts): the §10.1 self-authorizing
|
|
8
|
+
// signed lease token — minted by the API server (the authority), verified by any
|
|
9
|
+
// shell via WebCrypto (Node and Workers alike).
|
|
10
|
+
// - `MutationTx` / `RoomJournal` (src/mutation-tx.ts, src/journal.ts): the §5.1 write
|
|
11
|
+
// plane's two seams — the §4.2 mutator interface (a shared app registry drops in
|
|
12
|
+
// verbatim) and the pluggable durable sidecar an ack means (§8.1).
|
|
13
|
+
// - `createRoomShell` (src/shell.ts): the Node test shell — upstream ws subscriber of a
|
|
14
|
+
// live rindled, downstream ws server speaking the `@rindle/remote` protocol verbatim,
|
|
15
|
+
// gated by the tokens, with lease-TTL enforcement, the `/revoke` control plane, and
|
|
16
|
+
// the journaled write path.
|
|
17
|
+
export { initRoomWasm, WasmRoom } from "./wasm.js";
|
|
18
|
+
export { createRoomShell } from "./shell.js";
|
|
19
|
+
export { journalEntryOutcome, memoryJournal } from "./journal.js";
|
|
20
|
+
export { fatalAuthorityError, httpAuthority } from "./authority.js";
|
|
21
|
+
export { mintRoomToken, verifyRoomToken, RoomTokenError, scopeSpecsHash } from "./token.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,6CAA6C;AAC7C,EAAE;AACF,uCAAuC;AACvC,qFAAqF;AACrF,wFAAwF;AACxF,mFAAmF;AACnF,mFAAmF;AACnF,kDAAkD;AAClD,sFAAsF;AACtF,mFAAmF;AACnF,qEAAqE;AACrE,wFAAwF;AACxF,wFAAwF;AACxF,sFAAsF;AACtF,8BAA8B;AAE9B,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAS7C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAcpE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** One journaled mutation: the wire envelope plus its recorded outcome. Replay applies
|
|
2
|
+
* the OUTCOME — a non-applied (`rejected`/`deopt`) entry consumes its mid without
|
|
3
|
+
* running the mutator. */
|
|
4
|
+
export interface RoomJournalEntry {
|
|
5
|
+
clientID: string;
|
|
6
|
+
mid: number;
|
|
7
|
+
name: string;
|
|
8
|
+
args: unknown;
|
|
9
|
+
/** The connection's authenticated subject at push time (the lease token's `sub`,
|
|
10
|
+
* shell-stamped — managed-writes §3.3): recovery is re-invocation, so identity is
|
|
11
|
+
* an input that must survive the crash. `""` = an entry journaled before the
|
|
12
|
+
* identity plane existed (mutators see it as unauthenticated). */
|
|
13
|
+
sub: string;
|
|
14
|
+
/** The recorded verdict (H-iv-b): `"applied"` replays by RE-INVOKING the mutator
|
|
15
|
+
* (§3.3 — and against a moved base the re-invocation may legitimately reject or
|
|
16
|
+
* DEOPT; the journal record is never rewritten, the shell's recorded-outcome map
|
|
17
|
+
* reflects what the replaying incarnation produced); `"rejected"` (final — authz /
|
|
18
|
+
* validation / unknown mutator) and `"deopt"` (the §3.3 commit gate refused; the
|
|
19
|
+
* client was told to re-route the mutation) both replay as a consumed-mid-no-effect
|
|
20
|
+
* WITHOUT running anything — re-judging a deopt could invent effects the client
|
|
21
|
+
* already re-routed elsewhere. Absent = a legacy pre-H-iv-b entry: read through
|
|
22
|
+
* {@link journalEntryOutcome}. */
|
|
23
|
+
outcome?: "applied" | "rejected" | "deopt";
|
|
24
|
+
/** Legacy pre-H-iv-b flag, superseded by {@link outcome} but still WRITTEN (`true`)
|
|
25
|
+
* alongside BOTH non-applied outcomes: a legacy reader replays either kind as a
|
|
26
|
+
* consumed-mid-no-effect, which is exactly right. */
|
|
27
|
+
rejected?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** The ONE reading rule for an entry's verdict across journal generations: `outcome`
|
|
30
|
+
* when present, else the legacy `rejected` flag, else applied. */
|
|
31
|
+
export declare function journalEntryOutcome(entry: RoomJournalEntry): "applied" | "rejected" | "deopt";
|
|
32
|
+
/** One journaled flush batch: the room's flush-stream position, the placement epoch it
|
|
33
|
+
* was built under, and the EXACT `/apply-row-change-txn` body string — resubmitted
|
|
34
|
+
* verbatim, never rebuilt (§5.3 step 4). */
|
|
35
|
+
export interface RoomFlushRecord {
|
|
36
|
+
seq: number;
|
|
37
|
+
epoch: number;
|
|
38
|
+
body: string;
|
|
39
|
+
}
|
|
40
|
+
export interface RoomJournal {
|
|
41
|
+
/** Append `entries` durably, in order. Resolving is the ack gate (§8.1): the shell
|
|
42
|
+
* advances lmid rows only after this resolves. A rejection is fatal to the
|
|
43
|
+
* incarnation — an ack that might not survive must never be sent. */
|
|
44
|
+
append(entries: RoomJournalEntry[]): Promise<void>;
|
|
45
|
+
/** Every entry ever appended, in append order — the boot-time replay source. */
|
|
46
|
+
replay(): Promise<RoomJournalEntry[]>;
|
|
47
|
+
/** Journal one built flush batch, BEFORE its first send. Same durability contract
|
|
48
|
+
* as `append`: a rejection is fatal (an unjournaled batch must never reach the
|
|
49
|
+
* wire — a retry could otherwise rebuild different bytes under the same id). */
|
|
50
|
+
appendFlush(record: RoomFlushRecord): Promise<void>;
|
|
51
|
+
/** The authority settled flush `seq` (committed, deduped, or dead) — drop it. */
|
|
52
|
+
confirmFlush(seq: number): Promise<void>;
|
|
53
|
+
/** Unconfirmed flush records in seq order, plus the highest seq ever appended
|
|
54
|
+
* (0 = none) — the boot-time resubmission source and the seq seed. */
|
|
55
|
+
replayFlushes(): Promise<{
|
|
56
|
+
records: RoomFlushRecord[];
|
|
57
|
+
maxSeq: number;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
/** An in-process journal: survives incarnations within one shell process (and, handed
|
|
61
|
+
* to a second shell, a simulated process crash — the T2 harness). Not durable beyond
|
|
62
|
+
* the process, by definition. */
|
|
63
|
+
export declare function memoryJournal(): RoomJournal;
|
|
64
|
+
//# sourceMappingURL=journal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAkBA;;2BAE2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd;;;uEAGmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;uCAQmC;IACnC,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;IAC3C;;0DAEsD;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;mEACmE;AACnE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAE7F;AAED;;6CAE6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B;;0EAEsE;IACtE,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,gFAAgF;IAChF,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACtC;;qFAEiF;IACjF,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,iFAAiF;IACjF,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC;2EACuE;IACvE,aAAa,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E;AAED;;kCAEkC;AAClC,wBAAgB,aAAa,IAAI,WAAW,CA0B3C"}
|
package/dist/journal.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The room's pluggable journal (RINDLE-REALTIME-DESIGN.md §2.2, §5.1, §8.1): the
|
|
2
|
+
// durable sidecar an **ack** means. The shell appends mutation ENVELOPES under a group
|
|
3
|
+
// commit and advances the author's lmid row only after `append` resolves — so `acked`
|
|
4
|
+
// survives a room-process crash by construction, replayed by re-invoking the mutators
|
|
5
|
+
// against the freshly re-subscribed base (§3.3; recovery is re-invocation, never effect
|
|
6
|
+
// replay). Each host brings its own durability: the Durable Object shell backs this
|
|
7
|
+
// with per-object transactional storage (P4); the Node test shell defaults to
|
|
8
|
+
// `memoryJournal`, whose durability class is exactly "the shell process" — which is
|
|
9
|
+
// what makes T2's crash matrix a plain test: hand ONE journal to a second shell and
|
|
10
|
+
// the restart-with-journal failure class runs in-process.
|
|
11
|
+
//
|
|
12
|
+
// With P3's write-behind the journal ALSO holds the built flush batches (§5.3 step 4):
|
|
13
|
+
// the exact body bytes are appended BEFORE the first send and dropped once the
|
|
14
|
+
// authority confirms, so a crash-replay resubmits the same immutable body — one flush
|
|
15
|
+
// id names one byte body forever (the §8.3/T6 identity property). Mutation entries
|
|
16
|
+
// still grow without bound here; bounded retention (truncate ≤ the durable watermark)
|
|
17
|
+
// is a host-journal concern (P4's storage journal).
|
|
18
|
+
/** The ONE reading rule for an entry's verdict across journal generations: `outcome`
|
|
19
|
+
* when present, else the legacy `rejected` flag, else applied. */
|
|
20
|
+
export function journalEntryOutcome(entry) {
|
|
21
|
+
return entry.outcome ?? (entry.rejected === true ? "rejected" : "applied");
|
|
22
|
+
}
|
|
23
|
+
/** An in-process journal: survives incarnations within one shell process (and, handed
|
|
24
|
+
* to a second shell, a simulated process crash — the T2 harness). Not durable beyond
|
|
25
|
+
* the process, by definition. */
|
|
26
|
+
export function memoryJournal() {
|
|
27
|
+
const log = [];
|
|
28
|
+
const flushes = new Map();
|
|
29
|
+
let maxSeq = 0;
|
|
30
|
+
return {
|
|
31
|
+
append(entries) {
|
|
32
|
+
log.push(...entries);
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
},
|
|
35
|
+
replay() {
|
|
36
|
+
return Promise.resolve([...log]);
|
|
37
|
+
},
|
|
38
|
+
appendFlush(record) {
|
|
39
|
+
flushes.set(record.seq, record);
|
|
40
|
+
maxSeq = Math.max(maxSeq, record.seq);
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
},
|
|
43
|
+
confirmFlush(seq) {
|
|
44
|
+
flushes.delete(seq);
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
},
|
|
47
|
+
replayFlushes() {
|
|
48
|
+
const records = [...flushes.values()].sort((a, b) => a.seq - b.seq);
|
|
49
|
+
return Promise.resolve({ records, maxSeq });
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,uFAAuF;AACvF,sFAAsF;AACtF,sFAAsF;AACtF,wFAAwF;AACxF,oFAAoF;AACpF,8EAA8E;AAC9E,oFAAoF;AACpF,oFAAoF;AACpF,0DAA0D;AAC1D,EAAE;AACF,uFAAuF;AACvF,+EAA+E;AAC/E,sFAAsF;AACtF,mFAAmF;AACnF,sFAAsF;AACtF,oDAAoD;AA+BpD;mEACmE;AACnE,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,OAAO,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC7E,CAAC;AA6BD;;kCAEkC;AAClC,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACnD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO;QACL,MAAM,CAAC,OAAO;YACZ,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;YACrB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,MAAM;YACJ,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACnC,CAAC;QACD,WAAW,CAAC,MAAM;YAChB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAChC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,YAAY,CAAC,GAAG;YACd,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,aAAa;YACX,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACpE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { WasmRoom } from "./wasm.ts";
|
|
2
|
+
/** A bare wire cell (the client stack's `WireValue`). */
|
|
3
|
+
export type WireValue = number | string | boolean | null;
|
|
4
|
+
/** A row keyed by column name. */
|
|
5
|
+
export type KeyedRow = Record<string, WireValue>;
|
|
6
|
+
/** The §4.2 write handle. Keyed methods are schema-checked; positional methods are the
|
|
7
|
+
* raw wire shape (cells in schema column order, pk cells in `primaryKey` order).
|
|
8
|
+
* `query` is not available in room mutators yet (it throws) — typed so a client
|
|
9
|
+
* registry that uses it still registers, and fails loudly at run time. */
|
|
10
|
+
export interface MutationTx {
|
|
11
|
+
/** Read one row by primary key (e.g. `tx.row("issue", { id: 1 })`). */
|
|
12
|
+
row(table: string, pk: KeyedRow): KeyedRow | undefined;
|
|
13
|
+
/** Insert a FULL row (every column named; missing or unknown columns throw). */
|
|
14
|
+
insert(table: string, row: KeyedRow): void;
|
|
15
|
+
/** Update the row identified by the pk columns; only the named non-pk columns
|
|
16
|
+
* change. A missing row is a NO-OP (rebase-friendly). */
|
|
17
|
+
update(table: string, row: KeyedRow): void;
|
|
18
|
+
/** Insert, or fully replace when the pk already exists (a FULL row, like insert). */
|
|
19
|
+
upsert(table: string, row: KeyedRow): void;
|
|
20
|
+
/** Delete the row identified by the pk columns. A missing row is a NO-OP. */
|
|
21
|
+
delete(table: string, pk: KeyedRow): void;
|
|
22
|
+
/** NOT SUPPORTED in room mutators yet — throws. (Typed to accept the client
|
|
23
|
+
* builder's queries so shared registries typecheck.) */
|
|
24
|
+
query(query: {
|
|
25
|
+
ast(): unknown;
|
|
26
|
+
}): never;
|
|
27
|
+
get(table: string, pk: WireValue[]): WireValue[] | undefined;
|
|
28
|
+
add(table: string, row: WireValue[]): void;
|
|
29
|
+
remove(table: string, row: WireValue[]): void;
|
|
30
|
+
edit(table: string, oldRow: WireValue[], newRow: (WireValue | undefined)[]): void;
|
|
31
|
+
}
|
|
32
|
+
/** The ambient authorization context a room mutator runs under (managed-writes design
|
|
33
|
+
* §3.2). Shell-stamped from the connection's DO-verified lease token — NEVER
|
|
34
|
+
* client-supplied — so per-row/per-field rules checked against it are trustworthy.
|
|
35
|
+
* The shape deliberately matches the shared-generator drivers' `ctx.user`, so one
|
|
36
|
+
* registry body runs identically in all three homes (client / api-server / room). */
|
|
37
|
+
export interface RoomMutatorCtx {
|
|
38
|
+
/** The authenticated subject (the lease token's `sub`). `""` on replay of an entry
|
|
39
|
+
* journaled before the identity plane — treat as unauthenticated. */
|
|
40
|
+
user: string;
|
|
41
|
+
}
|
|
42
|
+
/** A room mutator: deterministic, replayable — re-invoked on journal replay against
|
|
43
|
+
* the freshly re-subscribed base (§3.3), so the same purity rules as a client
|
|
44
|
+
* mutator apply (no clock, no randomness, a pure function of `(base, args, ctx)`).
|
|
45
|
+
* An auth check against `ctx` re-runs on replay against the freshly rebuilt base —
|
|
46
|
+
* a mutation that passed before a crash can replay as rejected if the permission
|
|
47
|
+
* row changed in between; that is §3.3's intended rebase behavior. */
|
|
48
|
+
export type RoomMutator = (tx: MutationTx, args: never, ctx: RoomMutatorCtx) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Tag an error as an ENVIRONMENT shortfall (H-iv-b): the room lacks a capability the
|
|
51
|
+
* mutation needs (today: `tx.query`), which is a verdict about the ROOM, not the
|
|
52
|
+
* mutation — the shell classifies it as a DEOPT (the client re-routes the mutation to
|
|
53
|
+
* the daemon stream, where the capability exists) instead of a FINAL rejection (which
|
|
54
|
+
* would drop the mutation). Contrast a validation/authz throw: re-routing can't help
|
|
55
|
+
* those, so they stay `rejected`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function environmentShortfall(message: string): Error;
|
|
58
|
+
/** Whether `e` was tagged by {@link environmentShortfall}. */
|
|
59
|
+
export declare function isEnvironmentShortfall(e: unknown): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Guard against the two mutator shapes that would corrupt silently instead of failing loudly.
|
|
62
|
+
* A `shared(...)` GENERATOR registered verbatim returns an un-iterated generator — zero writes,
|
|
63
|
+
* acked as applied (data loss); an ASYNC mutator runs synchronously only to its first `await`,
|
|
64
|
+
* so later writes land OUTSIDE the committed transaction. Both shells call this on the
|
|
65
|
+
* mutator's return value inside their try/reject path, so either shape becomes an explicit
|
|
66
|
+
* rejection with a pointed message. (An adapter that DRIVES a shared generator registry against
|
|
67
|
+
* the room tx is future work — managed-writes design §8.)
|
|
68
|
+
*/
|
|
69
|
+
export declare function assertSyncMutatorReturn(returned: unknown, name: string): void;
|
|
70
|
+
/** One table's positional shape, parsed from the upstream hello. */
|
|
71
|
+
export interface TableShape {
|
|
72
|
+
columns: string[];
|
|
73
|
+
/** Indices into `columns`. */
|
|
74
|
+
primaryKey: number[];
|
|
75
|
+
}
|
|
76
|
+
/** Build the MutationTx for one open wasm transaction. Valid only while that
|
|
77
|
+
* transaction is open — the shell creates one per mutation and never retains it. */
|
|
78
|
+
export declare function mutationTx(room: WasmRoom, shapes: Map<string, TableShape>): MutationTx;
|
|
79
|
+
//# sourceMappingURL=mutation-tx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-tx.d.ts","sourceRoot":"","sources":["../src/mutation-tx.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AACzD,kCAAkC;AAClC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEjD;;;2EAG2E;AAC3E,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACvD,gFAAgF;IAChF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C;8DAC0D;IAC1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,qFAAqF;IACrF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1C;6DACyD;IACzD,KAAK,CAAC,KAAK,EAAE;QAAE,GAAG,IAAI,OAAO,CAAA;KAAE,GAAG,KAAK,CAAC;IAExC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,GAAG,SAAS,CAAC;IAC7D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;CACnF;AAED;;;;sFAIsF;AACtF,MAAM,WAAW,cAAc;IAC7B;0EACsE;IACtE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;uEAKuE;AACvE,MAAM,MAAM,WAAW,GAAG,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;AAErF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAI3D;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAI1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgB7E;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AA4BD;qFACqF;AACrF,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,UAAU,CA2HtF"}
|