@raybase/record-mount 0.0.2 → 0.3.3

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.
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The declarable client binding: an app names its record-door service
3
+ * binding and gets a typed client against the door's wire contract.
4
+ *
5
+ * Two refusals live here and they land at different moments, deliberately.
6
+ *
7
+ * **The address is checked at construction.** A binding name that is
8
+ * absent from `env`, or present but not door-shaped, is a deploy
9
+ * misconfiguration and there is nothing to wait for: `openRecordDoor`
10
+ * throws immediately. So does a malformed declared namespace.
11
+ *
12
+ * **The contract version is checked at the first verb call**, memoized per
13
+ * door stub — which is per isolate, because a Worker's `env` and its
14
+ * binding stubs are built once per isolate and handed to every request.
15
+ * The check cannot honestly happen at construction: a Worker constructs
16
+ * its handle on every request, and an RPC round trip there would price the
17
+ * check per request. `assertContract()` is the eager form for an app that
18
+ * genuinely has a boot hook and wants to pay once. Either way no verb
19
+ * result reaches the app before the pin has been checked, a mismatch is
20
+ * terminal and stays memoized, and a failure to reach the door at all
21
+ * clears the memo so a flap does not poison an isolate.
22
+ *
23
+ * ## What a Cloudflare RPC stub does to property checks
24
+ *
25
+ * A real service-binding stub answers EVERY property access with a callable.
26
+ * `typeof stub.append === "function"` is therefore true against a read door,
27
+ * true against a service that is not a record door at all, and true for a
28
+ * property nobody ever defined. Property presence carries no information
29
+ * there, and code that reads it is reading its own assumption back.
30
+ *
31
+ * So this module detects the stub's KIND by behavior — probing a sentinel
32
+ * property no door will ever serve — and takes a different route for each:
33
+ *
34
+ * - **A plain object** (a test double, an in-process fake): presence is real.
35
+ * The construction-time "not a record door" refusal and the read-door
36
+ * `writable` answer both mean what they say.
37
+ * - **An RPC stub**: presence means nothing, so the missing-verb refusal at
38
+ * construction is skipped rather than pretended, and writability comes from
39
+ * the door's own `version().writes` declaration. Until that reply lands,
40
+ * `writable` is `"unknown"` — an honest third state, not a guess — and a
41
+ * `TypeError` from a write verb on an undeclared door is TRANSLATED into
42
+ * this package's typed refusal so a consumer switches on one taxonomy
43
+ * rather than sniffing runtime messages.
44
+ */
45
+ import type { RecordAppendResult, RecordCountGroup, RecordCountResult, RecordDoorVersion, RecordGetResult, RecordId, RecordListQuery, RecordListResult, RecordMaterializeEntry, RecordQuery, RecordQueryResult, RecordReverseRefsResult, RecordTransitionEvent, RecordTransitionResult } from "./contract.js";
46
+ export type OpenRecordDoorOptions<Env> = {
47
+ /** The Worker's environment — where the service binding lives. */
48
+ env: Env;
49
+ /**
50
+ * The binding name declared in `wrangler.jsonc`, e.g. `"STORE_DOOR"`.
51
+ * Named rather than passed as a stub so the refusal can say which line
52
+ * of configuration is wrong, and so the contract check has a stable
53
+ * identity to memoize against.
54
+ */
55
+ binding: string;
56
+ /**
57
+ * The namespace this mount believes its binding carries.
58
+ *
59
+ * **This is a declaration, not a verification.** The namespace is a
60
+ * deploy-seam fact: it rides the binding's `props` beside the actor,
61
+ * where only a chair-signed `wrangler` config can write it, and no verb
62
+ * takes or returns a namespace — that one-wayness is what makes the
63
+ * address unforgeable. The client can read `version()`; it cannot read
64
+ * props. So what this value buys is: exactly one namespace per mount
65
+ * (asserted structurally — a mount holds one store), a legible name in
66
+ * refusals and telemetry, and a written-down claim a reviewer can diff
67
+ * against the deployed config. What it cannot do is catch a config that
68
+ * says `prod` here and something else in the props.
69
+ */
70
+ namespace: string;
71
+ /**
72
+ * Pin a contract version other than the one this build compiles
73
+ * against. Present for a consumer that deliberately lags a release;
74
+ * absent, the client expects its own.
75
+ */
76
+ expect?: string;
77
+ };
78
+ /**
79
+ * The typed client. Every verb the door serves, the taxonomy rehydrated
80
+ * into `RecordDoorError`, and `materialize` paging under the covers so a
81
+ * caller sees the stream the substrate's own signature promises.
82
+ */
83
+ export type RecordDoorClient = {
84
+ /** The binding name this client was opened against. */
85
+ readonly binding: string;
86
+ /** The declared namespace. See `OpenRecordDoorOptions.namespace`. */
87
+ readonly namespace: string;
88
+ /** The contract version this client pins. */
89
+ readonly contract: string;
90
+ /**
91
+ * Whether the bound door carries the write verbs — and `"unknown"` when
92
+ * nothing observable says yet.
93
+ *
94
+ * This is a getter, and its answer sharpens as the client learns:
95
+ *
96
+ * - **Plain binding** (a double, an in-process fake): the methods are
97
+ * really there or really not, so this is a boolean from the first read
98
+ * and never changes.
99
+ * - **RPC stub**: `"unknown"` at construction — property presence is
100
+ * meaningless across the seam — and then the door's declared
101
+ * `version().writes` once the contract check has resolved one. A door
102
+ * that declares nothing (one built before the field existed) leaves this
103
+ * `"unknown"` forever, which is the honest answer rather than a guess.
104
+ *
105
+ * For a definite answer, `await client.assertContract()` first and then
106
+ * read it. `"unknown"` never blocks a write: the call goes out, and a
107
+ * `TypeError` from a door that turns out to lack the verb comes back as
108
+ * this package's typed `forbidden` refusal.
109
+ */
110
+ readonly writable: boolean | "unknown";
111
+ /** The door's identity, unchecked — the one call that must work before
112
+ * the contract is known. */
113
+ version(): Promise<RecordDoorVersion>;
114
+ /** Check the pin now rather than at the first verb. Idempotent. */
115
+ assertContract(): Promise<void>;
116
+ get(id: RecordId): Promise<RecordGetResult>;
117
+ query(q: RecordQuery): Promise<RecordQueryResult>;
118
+ materialize(prefix: string): Promise<AsyncIterable<RecordMaterializeEntry>>;
119
+ list(q: RecordListQuery): Promise<RecordListResult>;
120
+ count(args: {
121
+ family?: string;
122
+ groupBy: RecordCountGroup;
123
+ }): Promise<RecordCountResult>;
124
+ reverseRefs(id: RecordId, opts?: {
125
+ limit?: number;
126
+ order?: "asc" | "desc";
127
+ }): Promise<RecordReverseRefsResult>;
128
+ append(body: string | Uint8Array): Promise<RecordAppendResult>;
129
+ transition(id: RecordId, expected: string, next: string, event: RecordTransitionEvent): Promise<RecordTransitionResult>;
130
+ };
131
+ export declare function openRecordDoor<Env>(options: OpenRecordDoorOptions<Env>): RecordDoorClient;
132
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAEjB,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAwBvB,MAAM,MAAM,qBAAqB,CAAC,GAAG,IAAI;IACvC,kEAAkE;IAClE,GAAG,EAAE,GAAG,CAAC;IACT;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;OAaG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAEvC;iCAC6B;IAC7B,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACtC,mEAAmE;IACnE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC,GAAG,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5C,KAAK,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC;IAC5E,IAAI,CAAC,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACpD,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxF,WAAW,CACT,EAAE,EAAE,QAAQ,EACZ,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAChD,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/D,UAAU,CACR,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACpC,CAAC;AAoBF,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAiVzF"}
package/dist/client.js ADDED
@@ -0,0 +1,376 @@
1
+ /**
2
+ * The declarable client binding: an app names its record-door service
3
+ * binding and gets a typed client against the door's wire contract.
4
+ *
5
+ * Two refusals live here and they land at different moments, deliberately.
6
+ *
7
+ * **The address is checked at construction.** A binding name that is
8
+ * absent from `env`, or present but not door-shaped, is a deploy
9
+ * misconfiguration and there is nothing to wait for: `openRecordDoor`
10
+ * throws immediately. So does a malformed declared namespace.
11
+ *
12
+ * **The contract version is checked at the first verb call**, memoized per
13
+ * door stub — which is per isolate, because a Worker's `env` and its
14
+ * binding stubs are built once per isolate and handed to every request.
15
+ * The check cannot honestly happen at construction: a Worker constructs
16
+ * its handle on every request, and an RPC round trip there would price the
17
+ * check per request. `assertContract()` is the eager form for an app that
18
+ * genuinely has a boot hook and wants to pay once. Either way no verb
19
+ * result reaches the app before the pin has been checked, a mismatch is
20
+ * terminal and stays memoized, and a failure to reach the door at all
21
+ * clears the memo so a flap does not poison an isolate.
22
+ *
23
+ * ## What a Cloudflare RPC stub does to property checks
24
+ *
25
+ * A real service-binding stub answers EVERY property access with a callable.
26
+ * `typeof stub.append === "function"` is therefore true against a read door,
27
+ * true against a service that is not a record door at all, and true for a
28
+ * property nobody ever defined. Property presence carries no information
29
+ * there, and code that reads it is reading its own assumption back.
30
+ *
31
+ * So this module detects the stub's KIND by behavior — probing a sentinel
32
+ * property no door will ever serve — and takes a different route for each:
33
+ *
34
+ * - **A plain object** (a test double, an in-process fake): presence is real.
35
+ * The construction-time "not a record door" refusal and the read-door
36
+ * `writable` answer both mean what they say.
37
+ * - **An RPC stub**: presence means nothing, so the missing-verb refusal at
38
+ * construction is skipped rather than pretended, and writability comes from
39
+ * the door's own `version().writes` declaration. Until that reply lands,
40
+ * `writable` is `"unknown"` — an honest third state, not a guess — and a
41
+ * `TypeError` from a write verb on an undeclared door is TRANSLATED into
42
+ * this package's typed refusal so a consumer switches on one taxonomy
43
+ * rather than sniffing runtime messages.
44
+ */
45
+ import { RECORD_DOOR_CONTRACT_VERSION } from "./contract.js";
46
+ import { recordDoorError, rehydrateRecordResult, RecordDoorError } from "./errors.js";
47
+ /** The namespace rule the record substrate itself enforces. */
48
+ const NAMESPACE_RE = /^[a-z0-9-]+$/;
49
+ /**
50
+ * A property no record door will ever serve. If reading it off the binding
51
+ * yields a callable, the binding answers every property access with one — it
52
+ * is an RPC stub, and nothing about it can be learned by looking.
53
+ */
54
+ const RPC_PROBE = "__raybaseRecordDoorRpcProbe__";
55
+ /** The verbs every door carries, whichever entrypoint it is. */
56
+ const REQUIRED_VERBS = [
57
+ "version",
58
+ "get",
59
+ "query",
60
+ "materialize",
61
+ "list",
62
+ "count",
63
+ "reverseRefs"
64
+ ];
65
+ /**
66
+ * The per-isolate contract memo.
67
+ *
68
+ * Keyed by the door stub itself, then by the pinned version. A Worker's
69
+ * binding stubs are built with `env` once per isolate, so a WeakMap on the
70
+ * stub memoizes for exactly the isolate's life — and two clients against
71
+ * two different doors cannot share an answer even when their bindings are
72
+ * spelled the same, which a name-keyed cache would let happen.
73
+ *
74
+ * It memoizes the SERVED VERSION, not merely "the check passed". A Worker
75
+ * builds a fresh client per request over one isolate-lifetime stub, so the
76
+ * second client and every one after it must be able to read the door's
77
+ * declared capabilities out of the memo. Memoizing a bare `Promise<void>`
78
+ * meant only the isolate's first client ever saw `version().writes`, and
79
+ * every later request read `"unknown"` forever.
80
+ */
81
+ const contractChecks = new WeakMap();
82
+ export function openRecordDoor(options) {
83
+ const { env, binding, namespace } = options;
84
+ const expect = options.expect ?? RECORD_DOOR_CONTRACT_VERSION;
85
+ if (typeof binding !== "string" || binding.length === 0) {
86
+ throw new Error("openRecordDoor requires a binding name, e.g. binding: \"STORE_DOOR\"");
87
+ }
88
+ if (typeof namespace !== "string" || !NAMESPACE_RE.test(namespace)) {
89
+ throw new Error(`openRecordDoor: namespace ${JSON.stringify(namespace)} is not a legal record namespace ` +
90
+ "(lowercase letters, digits and hyphens). The namespace a mount declares must match the " +
91
+ "one its binding's props carry; nothing at runtime can check that for you.");
92
+ }
93
+ const bag = env;
94
+ const candidate = bag === null || bag === undefined ? undefined : bag[binding];
95
+ if (candidate === undefined || candidate === null || typeof candidate !== "object") {
96
+ throw new Error(`openRecordDoor: no service binding named ${JSON.stringify(binding)} on this Worker's env. ` +
97
+ "Declare it in wrangler.jsonc with the door's entrypoint and its {actor, namespace} props.");
98
+ }
99
+ const stub = candidate;
100
+ /**
101
+ * Does this binding answer every property access with a callable?
102
+ *
103
+ * A Cloudflare RPC service-binding stub does, so the probe below is a
104
+ * function on a real stub and `undefined` on any ordinary object. This one
105
+ * fact decides which of the two checks that follow are meaningful.
106
+ */
107
+ const isRpcStub = typeof stub[RPC_PROBE] === "function";
108
+ if (!isRpcStub) {
109
+ // On a plain binding, absence is absence: a missing verb is a genuinely
110
+ // wrong object and saying so at construction beats a TypeError at the
111
+ // first call. On an RPC stub this check is skipped rather than performed
112
+ // and believed — every verb would "be there", including on a service
113
+ // that has never heard of a record door. What catches THAT case is the
114
+ // translation in `notARecordDoor` below, at the first call that goes out.
115
+ const missing = REQUIRED_VERBS.filter((verb) => typeof stub[verb] !== "function");
116
+ if (missing.length > 0) {
117
+ throw new Error(`openRecordDoor: the binding ${JSON.stringify(binding)} is not a record door — it carries no ` +
118
+ `${missing.join(", ")}. Check the binding's "entrypoint" in wrangler.jsonc.`);
119
+ }
120
+ }
121
+ /**
122
+ * Writability, as far as this client honestly knows it right now.
123
+ *
124
+ * A plain binding settles it at construction and never moves. An RPC stub
125
+ * starts `"unknown"` and moves exactly once, when a `version()` reply
126
+ * declares `writes`.
127
+ */
128
+ let writable = isRpcStub
129
+ ? "unknown"
130
+ : typeof stub.append === "function" && typeof stub.transition === "function";
131
+ /** Record what a `version()` reply declared, if it declared anything. */
132
+ const learnFrom = (served) => {
133
+ if (isRpcStub && typeof served.writes === "boolean")
134
+ writable = served.writes;
135
+ };
136
+ /**
137
+ * The runtime's method-missing signal, and only for this verb's own name.
138
+ *
139
+ * workerd refuses a method the bound entrypoint does not carry with a
140
+ * TypeError whose message names the method — the observed shape is
141
+ * `The RPC receiver does not implement the method "append"`. Only that
142
+ * exact situation may be read as "the door does not carry this verb":
143
+ * any other TypeError — a broken door, a serialization failure, a
144
+ * mis-invocation like the extracted-`.call` form that filed
145
+ * fnd-2026-08-31-mount-write-call-invocation — propagates raw, because
146
+ * loud beats mislabeled. If workerd's wording ever shifts, this
147
+ * translation stops firing and consumers see the raw TypeError:
148
+ * fail-open to noise, never to a wrong diagnosis.
149
+ */
150
+ const isVerbMissing = (ex, verb) => ex instanceof TypeError &&
151
+ ex.message.includes("does not implement") &&
152
+ ex.message.includes(`"${verb}"`);
153
+ /**
154
+ * The bound service does not speak this contract at all.
155
+ *
156
+ * Only reachable on an RPC stub: a plain binding without `version()` was
157
+ * refused at construction. There, `version`'s own method-missing TypeError
158
+ * (see `isVerbMissing`) is the runtime saying "no such method", which is
159
+ * the only signal the seam gives that the binding points at the wrong
160
+ * service — so it is translated into the taxonomy's own `invalid_binding`
161
+ * rather than propagating as a bare runtime error a consumer would have to
162
+ * sniff. Any other TypeError is a different defect and is left alone.
163
+ */
164
+ const notARecordDoor = (verb, cause) => recordDoorError("invalid_binding", {
165
+ binding,
166
+ verb,
167
+ cause: cause instanceof Error ? cause.message : String(cause)
168
+ }, `invalid_binding: the service bound at '${binding}' does not speak the record-door contract — ` +
169
+ `calling '${verb}' on it found no such method. Check the binding's "service" and ` +
170
+ '"entrypoint" in wrangler.jsonc.');
171
+ /**
172
+ * `version()` against the stub, with the not-a-door translation. Kept
173
+ * separate from `rehydrateRecordResult` deliberately: only the CALL is
174
+ * guarded, so a typed refusal the door itself answers with propagates
175
+ * untouched instead of being swallowed by a catch meant for the seam.
176
+ */
177
+ const askVersion = async () => {
178
+ let reply;
179
+ try {
180
+ reply = await stub.version();
181
+ }
182
+ catch (ex) {
183
+ // Same razor as the write verbs: only the method-missing shape that
184
+ // names `version` itself means "not a record door" — any other
185
+ // TypeError is a different defect and propagates raw.
186
+ if (isRpcStub && isVerbMissing(ex, "version"))
187
+ throw notARecordDoor("version", ex);
188
+ throw ex;
189
+ }
190
+ const served = rehydrateRecordResult(reply);
191
+ learnFrom(served);
192
+ return served;
193
+ };
194
+ /** The memoized contract check, resolving to the version the door served. */
195
+ const checked = () => {
196
+ let byVersion = contractChecks.get(stub);
197
+ if (byVersion === undefined) {
198
+ byVersion = new Map();
199
+ contractChecks.set(stub, byVersion);
200
+ }
201
+ const memo = byVersion.get(expect);
202
+ if (memo !== undefined)
203
+ return memo;
204
+ const check = (async () => {
205
+ const served = await askVersion();
206
+ if (served.contract !== expect) {
207
+ throw recordDoorError("contract_mismatch", { expected: expect, served: served.contract }, `contract_mismatch: this client pins record-door contract '${expect}' and the door bound at ` +
208
+ `'${binding}' serves '${served.contract}' (deployment '${served.deployment}') — refusing ` +
209
+ "rather than guessing which shape is live");
210
+ }
211
+ return served;
212
+ })();
213
+ byVersion.set(expect, check);
214
+ check.catch((ex) => {
215
+ // Two codes are terminal and stay memoized, because both are deploy
216
+ // configuration rather than weather: `contract_mismatch` (this door
217
+ // serves another shape) and `invalid_binding` (this binding points at
218
+ // a service that is not a record door). Neither resolves by being
219
+ // asked again inside one isolate, and re-asking would price a round
220
+ // trip on every verb of a permanently misconfigured Worker.
221
+ //
222
+ // Anything else — the door unreachable, the seam flapping — is the
223
+ // world's unreliability and must not poison the isolate.
224
+ const terminal = ex instanceof RecordDoorError &&
225
+ (ex.code === "contract_mismatch" || ex.code === "invalid_binding");
226
+ if (!terminal)
227
+ byVersion.delete(expect);
228
+ });
229
+ return check;
230
+ };
231
+ /**
232
+ * The gate every verb passes: the pin is checked, and this client has read
233
+ * whatever the door declared about itself.
234
+ *
235
+ * The `learnFrom` is here rather than only inside the check because the
236
+ * check is memoized per STUB and this client may be the tenth one built
237
+ * over it. A client that never ran the check itself still learns from the
238
+ * reply the check kept.
239
+ */
240
+ const ready = async () => {
241
+ learnFrom(await checked());
242
+ };
243
+ /**
244
+ * The typed read-door refusal, with the reason it was reached.
245
+ *
246
+ * All three forms name the same fact — this binding cannot write — and
247
+ * differ in how the client learned it, because that is what an operator
248
+ * needs to tell "I bound the read entrypoint" from "the door is older than
249
+ * the declaration" from "the door says it writes and does not".
250
+ */
251
+ const noWriteVerb = (verb, how, cause) => {
252
+ const why = how === "declared"
253
+ ? `its version() reply declares writes: false, so it carries no '${verb}' verb`
254
+ : how === "runtime"
255
+ ? `calling '${verb}' on it found no such method, and it declares no writability of its own`
256
+ : `it carries no '${verb}' verb at all`;
257
+ const detail = {
258
+ actor: "(read binding)",
259
+ verb,
260
+ family: "(any)",
261
+ id: "(none)"
262
+ };
263
+ if (cause !== undefined) {
264
+ detail.cause = cause instanceof Error ? cause.message : String(cause);
265
+ }
266
+ return recordDoorError("forbidden", detail, `forbidden: the binding '${binding}' is a read door — ${why}. ` +
267
+ "Which door a Worker holds is deploy configuration, not something code can change.");
268
+ };
269
+ return {
270
+ binding,
271
+ namespace,
272
+ contract: expect,
273
+ get writable() {
274
+ return writable;
275
+ },
276
+ async version() {
277
+ return askVersion();
278
+ },
279
+ async assertContract() {
280
+ await ready();
281
+ },
282
+ async get(id) {
283
+ await ready();
284
+ return rehydrateRecordResult(await stub.get(id));
285
+ },
286
+ async query(q) {
287
+ await ready();
288
+ return rehydrateRecordResult(await stub.query(q));
289
+ },
290
+ async list(q) {
291
+ await ready();
292
+ return rehydrateRecordResult(await stub.list(q));
293
+ },
294
+ async count(args) {
295
+ await ready();
296
+ return rehydrateRecordResult(await stub.count(args));
297
+ },
298
+ async reverseRefs(id, opts) {
299
+ await ready();
300
+ return rehydrateRecordResult(await stub.reverseRefs(id, opts));
301
+ },
302
+ async materialize(prefix) {
303
+ await ready();
304
+ // The wire pages; the caller does not. A page always moves — the
305
+ // door emits an oversized single entry alone rather than stalling —
306
+ // so this walk terminates on any corpus the door will serve.
307
+ //
308
+ // A paged materialize is NOT a snapshot: the cursor is a position in
309
+ // a deterministic order, not a point in time, so a multi-page walk
310
+ // taken while writes land under the same prefix can repeat an entry
311
+ // or miss one. Walk a quiesced namespace when that matters.
312
+ async function* pages() {
313
+ let cursor;
314
+ do {
315
+ const page = rehydrateRecordResult(await stub.materialize(prefix, cursor));
316
+ yield* page.entries;
317
+ cursor = page.cursor;
318
+ } while (cursor !== undefined);
319
+ }
320
+ return pages();
321
+ },
322
+ // The two write verbs share one shape, spelled out at each so neither
323
+ // drifts. Refuse BEFORE calling when writability is known false; call
324
+ // when it is true or unknown; and translate the runtime's method-missing
325
+ // TypeError into the typed refusal only while writability is undeclared
326
+ // AND the error names this verb's own method. A door that declared
327
+ // `writes: true` and still throws is either lying or broken, and any
328
+ // TypeError that is not this verb's method-missing is a real defect —
329
+ // translating either would hide it behind a refusal that reads as
330
+ // configuration (fnd-2026-08-31-mount-write-call-invocation was masked
331
+ // exactly that way for an evening).
332
+ //
333
+ // Each verb is invoked DIRECTLY on the stub, never extracted and
334
+ // re-invoked via Function.prototype.call: workerd's RPC proxy answers
335
+ // the `.call` property access as an RPC method named "call", so the
336
+ // extracted form throws `does not implement the method "call"` on
337
+ // every real binding.
338
+ async append(body) {
339
+ await ready();
340
+ // The second clause is the optional type's, not a second policy: a
341
+ // plain binding missing the verb already read `false` above.
342
+ if (writable === false || stub.append === undefined) {
343
+ throw noWriteVerb("append", isRpcStub ? "declared" : "absent");
344
+ }
345
+ let reply;
346
+ try {
347
+ reply = await stub.append(body);
348
+ }
349
+ catch (ex) {
350
+ if (writable !== true && isVerbMissing(ex, "append")) {
351
+ throw noWriteVerb("append", "runtime", ex);
352
+ }
353
+ throw ex;
354
+ }
355
+ return rehydrateRecordResult(reply);
356
+ },
357
+ async transition(id, expected, next, event) {
358
+ await ready();
359
+ if (writable === false || stub.transition === undefined) {
360
+ throw noWriteVerb("transition", isRpcStub ? "declared" : "absent");
361
+ }
362
+ let reply;
363
+ try {
364
+ reply = await stub.transition(id, expected, next, event);
365
+ }
366
+ catch (ex) {
367
+ if (writable !== true && isVerbMissing(ex, "transition")) {
368
+ throw noWriteVerb("transition", "runtime", ex);
369
+ }
370
+ throw ex;
371
+ }
372
+ return rehydrateRecordResult(reply);
373
+ }
374
+ };
375
+ }
376
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}