iterate 0.3.0 → 0.4.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/README.md +164 -82
- package/dist/{next/api.d.ts → api.d.ts} +197 -33
- package/dist/{next/app-server.d.ts → app-server.d.ts} +7 -0
- package/dist/{next/app-server.mjs → app-server.mjs} +17 -15
- package/dist/app-server.mjs.map +1 -0
- package/dist/{next/app-session.mjs → app-session.mjs} +12 -15
- package/dist/app-session.mjs.map +1 -0
- package/dist/{next/app.mjs → app.mjs} +53 -14
- package/dist/app.mjs.map +1 -0
- package/dist/{next/client → client}/live-state.d.ts +11 -11
- package/dist/client/oauth.d.ts +17 -0
- package/dist/{next/client → client}/react.d.ts +10 -42
- package/dist/{next/client → client}/socket.d.ts +1 -0
- package/dist/client.mjs +156 -4
- package/dist/client.mjs.map +1 -0
- package/dist/{next/expression.d.ts → expression.d.ts} +11 -69
- package/dist/{next/expression.mjs → expression.mjs} +11 -109
- package/dist/expression.mjs.map +1 -0
- package/dist/lib-BWr-5mFO.mjs +36 -0
- package/dist/lib-BWr-5mFO.mjs.map +1 -0
- package/dist/{next/lib.d.ts → lib.d.ts} +16 -2
- package/dist/{next/lib.mjs → lib.mjs} +32 -3
- package/dist/lib.mjs.map +1 -0
- package/dist/node.d.ts +15 -3
- package/dist/node.mjs +36 -174
- package/dist/node.mjs.map +1 -1
- package/dist/{next/oauth-scopes.mjs → oauth-scopes.mjs} +1 -1
- package/dist/oauth-scopes.mjs.map +1 -0
- package/dist/{next/oauth.mjs → oauth.mjs} +14 -2
- package/dist/oauth.mjs.map +1 -0
- package/dist/principal.d.ts +8 -0
- package/dist/principal.mjs +8 -0
- package/dist/principal.mjs.map +1 -0
- package/dist/project-ingress.d.ts +58 -0
- package/dist/project-ingress.mjs +104 -0
- package/dist/project-ingress.mjs.map +1 -0
- package/dist/{next/react.mjs → react.mjs} +12 -12
- package/dist/react.mjs.map +1 -0
- package/dist/sdk/auth.d.ts +25 -0
- package/dist/sdk/index.d.ts +155 -0
- package/dist/sdk/record-pipelined-steps.d.ts +19 -0
- package/dist/sdk.mjs +245 -2
- package/dist/sdk.mjs.map +1 -0
- package/dist/{next/stream → stream}/processor.d.ts +28 -23
- package/dist/{next/stream → stream}/processor.mjs +48 -25
- package/dist/stream/processor.mjs.map +1 -0
- package/dist/{next/stream → stream}/run.d.ts +9 -6
- package/dist/{next/stream → stream}/run.mjs +13 -8
- package/dist/stream/run.mjs.map +1 -0
- package/dist/stream/test-support.d.ts +45 -0
- package/dist/stream/test-support.mjs +196 -0
- package/dist/stream/test-support.mjs.map +1 -0
- package/package.json +65 -219
- package/THIRD_PARTY_NOTICES.md +0 -55
- package/bin/iterate.js +0 -94
- package/dist/api-url-B6404M82.mjs +0 -17
- package/dist/api-url-B6404M82.mjs.map +0 -1
- package/dist/app-ref-BipL0feU.mjs +0 -35
- package/dist/app-ref-BipL0feU.mjs.map +0 -1
- package/dist/app-ref-C1CrgXqX.mjs +0 -7
- package/dist/app-ref-C1CrgXqX.mjs.map +0 -1
- package/dist/app-ref-DYai_om1.mjs +0 -7
- package/dist/app-ref-DYai_om1.mjs.map +0 -1
- package/dist/cli-D0c-pDL_.mjs +0 -1010
- package/dist/cli-D0c-pDL_.mjs.map +0 -1
- package/dist/client.d.ts +0 -3
- package/dist/cloudflare-BTm90gQ4.mjs +0 -951
- package/dist/cloudflare-BTm90gQ4.mjs.map +0 -1
- package/dist/contract-s4FW4eES.mjs +0 -309
- package/dist/contract-s4FW4eES.mjs.map +0 -1
- package/dist/document-review/index.d.ts +0 -5
- package/dist/document-review/types.d.ts +0 -107
- package/dist/document-review.mjs +0 -7015
- package/dist/document-review.mjs.map +0 -1
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +0 -205
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +0 -1
- package/dist/idempotency-DleloJNt.mjs +0 -28
- package/dist/idempotency-DleloJNt.mjs.map +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -8
- package/dist/index.mjs.map +0 -1
- package/dist/itx/api-url.d.ts +0 -6
- package/dist/itx/itx-node-client.d.ts +0 -65
- package/dist/itx/itx-session.d.ts +0 -215
- package/dist/itx/owned-rpc-session.d.ts +0 -14
- package/dist/itx/query-client.d.ts +0 -10
- package/dist/itx-api.generated.d.ts +0 -6195
- package/dist/itx-session-sjud8GiT.mjs +0 -534
- package/dist/itx-session-sjud8GiT.mjs.map +0 -1
- package/dist/live-state-BJNqOwFw.mjs +0 -299
- package/dist/live-state-BJNqOwFw.mjs.map +0 -1
- package/dist/next/app-server.mjs.map +0 -1
- package/dist/next/app-session.mjs.map +0 -1
- package/dist/next/app.mjs.map +0 -1
- package/dist/next/client/oauth.d.ts +0 -12
- package/dist/next/client.mjs +0 -156
- package/dist/next/client.mjs.map +0 -1
- package/dist/next/expression.mjs.map +0 -1
- package/dist/next/lib.mjs.map +0 -1
- package/dist/next/oauth-scopes.mjs.map +0 -1
- package/dist/next/oauth.mjs.map +0 -1
- package/dist/next/principal.d.ts +0 -64
- package/dist/next/principal.mjs +0 -98
- package/dist/next/principal.mjs.map +0 -1
- package/dist/next/project-ingress.d.ts +0 -37
- package/dist/next/project-ingress.mjs +0 -75
- package/dist/next/project-ingress.mjs.map +0 -1
- package/dist/next/react.mjs.map +0 -1
- package/dist/next/sdk/auth.d.ts +0 -5
- package/dist/next/sdk/index.d.ts +0 -112
- package/dist/next/sdk.mjs +0 -139
- package/dist/next/sdk.mjs.map +0 -1
- package/dist/next/stream/processor.mjs.map +0 -1
- package/dist/next/stream/run.mjs.map +0 -1
- package/dist/next-node.d.ts +0 -15
- package/dist/next-node.mjs +0 -51
- package/dist/next-node.mjs.map +0 -1
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +0 -56
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +0 -1
- package/dist/processors/cloudflare.d.ts +0 -3
- package/dist/processors/durable-object-processor-durability.d.ts +0 -79
- package/dist/processors/event-consumption-metrics.d.ts +0 -82
- package/dist/processors/idempotency.d.ts +0 -13
- package/dist/processors/index.d.ts +0 -12
- package/dist/processors/processor-contracts.d.ts +0 -342
- package/dist/processors/processor-facet.d.ts +0 -186
- package/dist/processors/processor-host-capabilities.d.ts +0 -60
- package/dist/processors/prompt-sections.d.ts +0 -17
- package/dist/processors/rpc-types.d.ts +0 -515
- package/dist/processors/schemas.d.ts +0 -102
- package/dist/processors/stream-handle.d.ts +0 -45
- package/dist/processors/stream-processor-keepalive.d.ts +0 -95
- package/dist/processors/stream-processor-registry.d.ts +0 -233
- package/dist/processors/stream-processor-runner.d.ts +0 -289
- package/dist/processors/stream-processor.d.ts +0 -339
- package/dist/processors/stream-runtime-metrics.d.ts +0 -107
- package/dist/processors/testing.d.ts +0 -302
- package/dist/processors-BoNyeBfQ.mjs +0 -10
- package/dist/processors-BoNyeBfQ.mjs.map +0 -1
- package/dist/processors-cloudflare.mjs +0 -3
- package/dist/processors-testing.mjs +0 -435
- package/dist/processors-testing.mjs.map +0 -1
- package/dist/processors.mjs +0 -52
- package/dist/processors.mjs.map +0 -1
- package/dist/protocol-DnK_f2m6.mjs +0 -251
- package/dist/protocol-DnK_f2m6.mjs.map +0 -1
- package/dist/sdk/capnweb/index.d.ts +0 -2
- package/dist/sdk/capnweb/live-state/compact.d.ts +0 -5
- package/dist/sdk/capnweb/live-state/diff.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/engine.d.ts +0 -44
- package/dist/sdk/capnweb/live-state/index.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/protocol.d.ts +0 -87
- package/dist/sdk/capnweb/live-state/retain.d.ts +0 -23
- package/dist/sdk/capnweb/live-state/store.d.ts +0 -20
- package/dist/sdk/capnweb/live-state/types.d.ts +0 -11
- package/dist/sdk/capnweb/react.d.ts +0 -45
- package/dist/sdk/capnweb/react.mjs +0 -316
- package/dist/sdk/capnweb/react.mjs.map +0 -1
- package/dist/sdk/capnweb.mjs +0 -4
- package/dist/sdk/itx/react.d.ts +0 -191
- package/dist/sdk/itx/react.mjs +0 -383
- package/dist/sdk/itx/react.mjs.map +0 -1
- package/dist/sdk-DMB-IM11.mjs +0 -933
- package/dist/sdk-DMB-IM11.mjs.map +0 -1
- package/dist/sdk.d.ts +0 -339
- package/dist/serve-itx.d.ts +0 -46
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +0 -31
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +0 -1055
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/contract.d.ts +0 -4839
- package/dist/starter-apps/flake-dashboard/contract.mjs +0 -2
- package/dist/starter-apps/flake-dashboard/index.d.ts +0 -17
- package/dist/starter-apps/flake-dashboard/index.mjs +0 -56
- package/dist/starter-apps/flake-dashboard/index.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/worker.d.ts +0 -4607
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +0 -8914
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +0 -17987
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/contract.d.ts +0 -9193
- package/dist/starter-apps/github-ai-linter/index.d.ts +0 -10
- package/dist/starter-apps/github-ai-linter/index.mjs +0 -36
- package/dist/starter-apps/github-ai-linter/index.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +0 -13
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +0 -808
- package/dist/starter-apps/github-ai-linter/rules.d.ts +0 -34
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.mjs +0 -947
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/app-ref.d.ts +0 -27
- package/dist/starter-apps/guestbook/client.d.ts +0 -7
- package/dist/starter-apps/guestbook/client.mjs +0 -59
- package/dist/starter-apps/guestbook/configured-worker.mjs +0 -205
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/index.d.ts +0 -9
- package/dist/starter-apps/guestbook/index.mjs +0 -31
- package/dist/starter-apps/guestbook/index.mjs.map +0 -1
- package/dist/starter-apps/guestbook/processor.d.ts +0 -2267
- package/dist/starter-apps/guestbook/worker.d.ts +0 -26
- package/dist/starter-apps/guestbook/worker.mjs +0 -191
- package/dist/starter-apps/guestbook/worker.mjs.map +0 -1
- package/dist/starter-apps/media/configured-worker.mjs +0 -577
- package/dist/starter-apps/media/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/media/index.mjs +0 -36
- package/dist/starter-apps/media/index.mjs.map +0 -1
- package/dist/starter-apps/media/ref.mjs +0 -20
- package/dist/starter-apps/media/ref.mjs.map +0 -1
- package/dist/starter-apps/media/worker.mjs +0 -579
- package/dist/starter-apps/media/worker.mjs.map +0 -1
- package/dist/starter-apps/notes/configured-worker.mjs +0 -6134
- package/dist/starter-apps/notes/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/notes/index.mjs +0 -23
- package/dist/starter-apps/notes/index.mjs.map +0 -1
- package/dist/starter-apps/notes/ref.mjs +0 -21
- package/dist/starter-apps/notes/ref.mjs.map +0 -1
- package/dist/starter-apps/notes/worker.mjs +0 -427
- package/dist/starter-apps/notes/worker.mjs.map +0 -1
- package/dist/starter-apps/todo/client.mjs +0 -59
- package/dist/starter-apps/todo/configured-worker.mjs +0 -2864
- package/dist/starter-apps/todo/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/todo/index.d.ts +0 -8
- package/dist/starter-apps/todo/index.mjs +0 -29
- package/dist/starter-apps/todo/index.mjs.map +0 -1
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +0 -2082
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +0 -1
- package/dist/usingCtx-mZx5nsAW.mjs +0 -11800
- package/dist/usingCtx-mZx5nsAW.mjs.map +0 -1
- package/dist/worker-ref-DZxPDmb_.mjs +0 -390
- package/dist/worker-ref-DZxPDmb_.mjs.map +0 -1
- package/dist/worker.d.mts +0 -33
- package/dist/worker.mjs +0 -18
- package/dist/worker.mjs.map +0 -1
- package/menubar/Iterate.entitlements +0 -12
- package/menubar/Iterate.swift +0 -914
- package/menubar/IterateIcon.swift +0 -145
- package/menubar/README.md +0 -28
- package/menubar/build-menubar-app.sh +0 -59
- /package/dist/{next/api.mjs → api.mjs} +0 -0
- /package/dist/{next/app-session.d.ts → app-session.d.ts} +0 -0
- /package/dist/{next/app.d.ts → app.d.ts} +0 -0
- /package/dist/{next/oauth-scopes.d.ts → oauth-scopes.d.ts} +0 -0
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import { a as compactPatch, s as diff } from "./protocol-DnK_f2m6.mjs";
|
|
2
|
-
import { RpcTarget } from "@iterate-com/capnweb";
|
|
3
|
-
//#region src/sdk/capnweb/live-state/retain.ts
|
|
4
|
-
/**
|
|
5
|
-
* Retain a single-argument RPC callback so it survives past the call that
|
|
6
|
-
* carried it. `dup()` duplicates the underlying remote stub (that dup is what
|
|
7
|
-
* `[Symbol.dispose]` releases); `onRpcBroken` is forwarded defensively — Cap'n
|
|
8
|
-
* Web stubs expose no own property descriptors and a Workers RPC property access
|
|
9
|
-
* can fabricate a pipelined method that rejects at call time, so we wire whatever
|
|
10
|
-
* the stub claims and swallow registration failures. `onRpcBroken` is only a
|
|
11
|
-
* prompt hint; callers that need a terminal verdict must model one explicitly
|
|
12
|
-
* (for example, the durable wake lane's independent settlement capability).
|
|
13
|
-
*/
|
|
14
|
-
function retainCallback(callback) {
|
|
15
|
-
const retainable = callback;
|
|
16
|
-
const retained = retainable.dup?.() ?? retainable;
|
|
17
|
-
const dispose = retained[Symbol.dispose]?.bind(retained);
|
|
18
|
-
let disposed = false;
|
|
19
|
-
const wrapped = Object.assign((arg) => retained(arg), { [Symbol.dispose]() {
|
|
20
|
-
if (disposed) return;
|
|
21
|
-
disposed = true;
|
|
22
|
-
dispose?.();
|
|
23
|
-
} });
|
|
24
|
-
const onRpcBroken = retained.onRpcBroken;
|
|
25
|
-
if (typeof onRpcBroken === "function") wrapped.onRpcBroken = (handler) => {
|
|
26
|
-
try {
|
|
27
|
-
const result = onRpcBroken.call(retained, handler);
|
|
28
|
-
if (isThenable(result)) Promise.resolve(result).catch(() => {});
|
|
29
|
-
} catch {}
|
|
30
|
-
};
|
|
31
|
-
return wrapped;
|
|
32
|
-
}
|
|
33
|
-
/** Thenable probe: RPC stubs and their call results are thenable-shaped. */
|
|
34
|
-
function isThenable(value) {
|
|
35
|
-
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Dispose the result of an RPC call whose value the caller ignores. Reading a
|
|
39
|
-
* Cap'n Web / Workers RPC method yields a disposable stub even when unused, and
|
|
40
|
-
* dropping it without disposal leaks the remote reference.
|
|
41
|
-
*/
|
|
42
|
-
function disposeIgnoredRpcResult(result) {
|
|
43
|
-
if (result !== null && (typeof result === "object" || typeof result === "function") && Symbol.dispose in result) result[Symbol.dispose]();
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/sdk/capnweb/live-state/engine.ts
|
|
47
|
-
const DEFAULT_DEBOUNCE_MS = 100;
|
|
48
|
-
const ACK_TIMEOUT_MS = 1e4;
|
|
49
|
-
/**
|
|
50
|
-
* A source-agnostic live store: hold a state value, and when it changes push the
|
|
51
|
-
* minimal diff to every subscriber. It does not know or care where the state
|
|
52
|
-
* comes from — a Durable Object folds events into it, a stateless RpcTarget polls
|
|
53
|
-
* a third-party API into it — so ANY RpcTarget can expose live state by holding
|
|
54
|
-
* one and returning a read-only wrapper from a `.live` getter.
|
|
55
|
-
*
|
|
56
|
-
* Two rules make it cheap and correct:
|
|
57
|
-
* - Updates are IMMUTABLE (`setState`/`assign` build a new value, never mutate),
|
|
58
|
-
* so the diff short-circuits unchanged branches by identity — O(changed), not
|
|
59
|
-
* O(size). See `diff.ts`.
|
|
60
|
-
* - Push work runs only while a subscriber exists. Transient readers call
|
|
61
|
-
* `readSince` explicitly; a dormant engine schedules nothing.
|
|
62
|
-
*
|
|
63
|
-
* All subscribers share one revision line. Fast subscribers share a patch;
|
|
64
|
-
* slow subscribers receive a coalesced patch from their acknowledged state.
|
|
65
|
-
* A revision mismatch makes the client resync instead of applying stale data.
|
|
66
|
-
*/
|
|
67
|
-
var LiveState = class {
|
|
68
|
-
#current;
|
|
69
|
-
/** Latest flushed state — the shared diff baseline. */
|
|
70
|
-
#broadcast;
|
|
71
|
-
#revision = 0;
|
|
72
|
-
#epoch = crypto.randomUUID();
|
|
73
|
-
#lastUpdate;
|
|
74
|
-
#lastCompactUpdate;
|
|
75
|
-
#subscribers = /* @__PURE__ */ new Map();
|
|
76
|
-
#debounceMs;
|
|
77
|
-
#flushTimer;
|
|
78
|
-
constructor(initial, options = {}) {
|
|
79
|
-
this.#current = initial;
|
|
80
|
-
this.#broadcast = initial;
|
|
81
|
-
this.#debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
82
|
-
}
|
|
83
|
-
/** The current state — reflects every `setState`/`assign`, even while dormant. */
|
|
84
|
-
getState() {
|
|
85
|
-
return this.#current;
|
|
86
|
-
}
|
|
87
|
-
/** One retained delta bounds history while a single parent can pull incrementally.
|
|
88
|
-
* A late reader or a new incarnation receives an explicit snapshot instead. */
|
|
89
|
-
readSince(cursor, options = {}) {
|
|
90
|
-
if (this.#flushTimer !== void 0) clearTimeout(this.#flushTimer);
|
|
91
|
-
this.#flushTimer = void 0;
|
|
92
|
-
this.#flush();
|
|
93
|
-
if (cursor?.epoch === this.#epoch) {
|
|
94
|
-
if (cursor.revision === this.#revision) return {
|
|
95
|
-
epoch: this.#epoch,
|
|
96
|
-
update: null
|
|
97
|
-
};
|
|
98
|
-
if (cursor.revision === this.#lastUpdate?.from) return {
|
|
99
|
-
epoch: this.#epoch,
|
|
100
|
-
update: options.patchVersion === 3 ? this.#lastCompactUpdate : this.#lastUpdate
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
epoch: this.#epoch,
|
|
105
|
-
update: options.patchVersion === 3 ? { s: [this.#revision, this.#broadcast] } : {
|
|
106
|
-
type: "snapshot",
|
|
107
|
-
revision: this.#revision,
|
|
108
|
-
state: this.#broadcast
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/** True while at least one live subscriber makes projection work observable. */
|
|
113
|
-
get observed() {
|
|
114
|
-
return this.#subscribers.size > 0;
|
|
115
|
-
}
|
|
116
|
-
/** Replace the state. Build a NEW value (don't mutate) so the diff stays cheap. */
|
|
117
|
-
setState(next) {
|
|
118
|
-
this.#current = typeof next === "function" ? next(this.#current) : next;
|
|
119
|
-
if (this.#subscribers.size > 0) this.#scheduleFlush();
|
|
120
|
-
}
|
|
121
|
-
/** Shallow-merge a partial into the state — sugar for `setState(p => ({ ...p, ...partial }))`. */
|
|
122
|
-
assign(partial) {
|
|
123
|
-
this.setState((prev) => ({
|
|
124
|
-
...prev,
|
|
125
|
-
...partial
|
|
126
|
-
}));
|
|
127
|
-
}
|
|
128
|
-
subscribe(sink, options = {}) {
|
|
129
|
-
if (this.#subscribers.size === 0) this.#flush();
|
|
130
|
-
const retained = retainCallback(sink);
|
|
131
|
-
this.#subscribers.set(retained, {
|
|
132
|
-
version: options.patchVersion ?? 1,
|
|
133
|
-
state: this.#broadcast,
|
|
134
|
-
revision: this.#revision,
|
|
135
|
-
busy: false
|
|
136
|
-
});
|
|
137
|
-
retained.onRpcBroken?.(() => this.#drop(retained));
|
|
138
|
-
this.#deliver(retained, options.patchVersion === 3 ? { s: [this.#revision, this.#broadcast] } : {
|
|
139
|
-
type: "snapshot",
|
|
140
|
-
revision: this.#revision,
|
|
141
|
-
state: this.#broadcast
|
|
142
|
-
});
|
|
143
|
-
return {
|
|
144
|
-
ping: () => this.#subscribers.has(retained),
|
|
145
|
-
unsubscribe: () => this.#drop(retained),
|
|
146
|
-
[Symbol.dispose]: () => this.#drop(retained)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
#scheduleFlush() {
|
|
150
|
-
this.#flushTimer ??= setTimeout(() => {
|
|
151
|
-
this.#flushTimer = void 0;
|
|
152
|
-
this.#flush();
|
|
153
|
-
}, this.#debounceMs);
|
|
154
|
-
}
|
|
155
|
-
#flush() {
|
|
156
|
-
const previous = this.#broadcast;
|
|
157
|
-
const patch = diff(previous, this.#current);
|
|
158
|
-
if (patch === void 0) return;
|
|
159
|
-
const from = this.#revision;
|
|
160
|
-
const to = this.#revision += 1;
|
|
161
|
-
this.#broadcast = this.#current;
|
|
162
|
-
const update = {
|
|
163
|
-
type: "patch",
|
|
164
|
-
from,
|
|
165
|
-
to,
|
|
166
|
-
patch
|
|
167
|
-
};
|
|
168
|
-
this.#lastUpdate = update;
|
|
169
|
-
this.#lastCompactUpdate = { p: [
|
|
170
|
-
from,
|
|
171
|
-
to,
|
|
172
|
-
compactPatch(previous, patch)
|
|
173
|
-
] };
|
|
174
|
-
for (const subscriber of this.#subscribers.keys()) this.#sendLatest(subscriber);
|
|
175
|
-
}
|
|
176
|
-
/** Slow subscribers hold one call and one baseline, never a queue of token updates. */
|
|
177
|
-
#sendLatest(subscriber) {
|
|
178
|
-
const held = this.#subscribers.get(subscriber);
|
|
179
|
-
if (!held || held.busy || held.revision === this.#revision) return;
|
|
180
|
-
if (held.version === 3) {
|
|
181
|
-
if (held.revision === this.#lastUpdate?.from) this.#deliver(subscriber, this.#lastCompactUpdate);
|
|
182
|
-
else {
|
|
183
|
-
const patch = diff(held.state, this.#broadcast);
|
|
184
|
-
this.#deliver(subscriber, { p: [
|
|
185
|
-
held.revision,
|
|
186
|
-
this.#revision,
|
|
187
|
-
patch ? compactPatch(held.state, patch) : {}
|
|
188
|
-
] });
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const patch = held.version === 2 && held.revision === this.#lastUpdate?.from ? this.#lastUpdate.patch : diff(held.state, this.#broadcast, { arrays: held.version === 2 });
|
|
193
|
-
this.#deliver(subscriber, {
|
|
194
|
-
type: "patch",
|
|
195
|
-
from: held.revision,
|
|
196
|
-
to: this.#revision,
|
|
197
|
-
patch: patch ?? {}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
/** Only acknowledgement releases the next coalesced update; a dead sink is dropped. */
|
|
201
|
-
#deliver(subscriber, update) {
|
|
202
|
-
const held = this.#subscribers.get(subscriber);
|
|
203
|
-
if (!held) return;
|
|
204
|
-
held.state = this.#broadcast;
|
|
205
|
-
held.revision = this.#revision;
|
|
206
|
-
held.busy = true;
|
|
207
|
-
let result;
|
|
208
|
-
try {
|
|
209
|
-
result = subscriber(update);
|
|
210
|
-
} catch {
|
|
211
|
-
this.#drop(subscriber);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (isThenable(result)) {
|
|
215
|
-
held.timeout = setTimeout(() => {
|
|
216
|
-
console.warn("Live-state subscriber dropped: acknowledgement timed out", {
|
|
217
|
-
revision: held.revision,
|
|
218
|
-
timeoutMs: ACK_TIMEOUT_MS
|
|
219
|
-
});
|
|
220
|
-
this.#drop(subscriber);
|
|
221
|
-
}, ACK_TIMEOUT_MS);
|
|
222
|
-
Promise.resolve(result).then(() => {
|
|
223
|
-
if (held.timeout !== void 0) clearTimeout(held.timeout);
|
|
224
|
-
held.busy = false;
|
|
225
|
-
this.#sendLatest(subscriber);
|
|
226
|
-
}, () => this.#drop(subscriber)).finally(() => disposeIgnoredRpcResult(result));
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
held.busy = false;
|
|
230
|
-
disposeIgnoredRpcResult(result);
|
|
231
|
-
}
|
|
232
|
-
#drop(subscriber) {
|
|
233
|
-
const timeout = this.#subscribers.get(subscriber)?.timeout;
|
|
234
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
235
|
-
if (!this.#subscribers.delete(subscriber)) return;
|
|
236
|
-
subscriber[Symbol.dispose]();
|
|
237
|
-
if (this.#subscribers.size === 0 && this.#flushTimer !== void 0) {
|
|
238
|
-
clearTimeout(this.#flushTimer);
|
|
239
|
-
this.#flushTimer = void 0;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
//#endregion
|
|
244
|
-
//#region src/sdk/capnweb/live-state/index.ts
|
|
245
|
-
/**
|
|
246
|
-
* Expose mutable server state as a read-only Cap'n Web capability.
|
|
247
|
-
* Pass an in-memory `LiveState`, or a stream-processor registry whose engine
|
|
248
|
-
* must hydrate before its first snapshot.
|
|
249
|
-
*/
|
|
250
|
-
var LiveStateRpcTarget = class extends RpcTarget {
|
|
251
|
-
#live;
|
|
252
|
-
#beforeRead;
|
|
253
|
-
constructor(source) {
|
|
254
|
-
super();
|
|
255
|
-
if ("loadAndRefreshLive" in source) {
|
|
256
|
-
this.#live = source.live;
|
|
257
|
-
this.#beforeRead = () => source.loadAndRefreshLive();
|
|
258
|
-
} else {
|
|
259
|
-
this.#live = source;
|
|
260
|
-
this.#beforeRead = void 0;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
async get() {
|
|
264
|
-
const loading = this.#beforeRead?.();
|
|
265
|
-
if (isThenable(loading)) await loading;
|
|
266
|
-
return this.#live.getState();
|
|
267
|
-
}
|
|
268
|
-
async subscribe(onUpdate, options) {
|
|
269
|
-
const loading = this.#beforeRead?.();
|
|
270
|
-
if (isThenable(loading)) await loading;
|
|
271
|
-
return new LiveStateSubscriptionRpcTarget(this.#live.subscribe(onUpdate, options));
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
/**
|
|
275
|
-
* The wire handle for one subscription — `ping`/`unsubscribe` over RPC.
|
|
276
|
-
* Exported for relays that build their subscription OUTSIDE a `LiveState`
|
|
277
|
-
* engine (the worker-local liveState-socket relay) but must hand back the
|
|
278
|
-
* same handle shape `LiveStateRpcTarget.subscribe` does.
|
|
279
|
-
*/
|
|
280
|
-
var LiveStateSubscriptionRpcTarget = class extends RpcTarget {
|
|
281
|
-
#subscription;
|
|
282
|
-
constructor(subscription) {
|
|
283
|
-
super();
|
|
284
|
-
this.#subscription = subscription;
|
|
285
|
-
}
|
|
286
|
-
ping() {
|
|
287
|
-
return this.#subscription.ping();
|
|
288
|
-
}
|
|
289
|
-
unsubscribe() {
|
|
290
|
-
this.#subscription.unsubscribe();
|
|
291
|
-
}
|
|
292
|
-
[Symbol.dispose]() {
|
|
293
|
-
this.#subscription.unsubscribe();
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
//#endregion
|
|
297
|
-
export { isThenable as a, disposeIgnoredRpcResult as i, LiveStateSubscriptionRpcTarget as n, retainCallback as o, LiveState as r, LiveStateRpcTarget as t };
|
|
298
|
-
|
|
299
|
-
//# sourceMappingURL=live-state-BJNqOwFw.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-state-BJNqOwFw.mjs","names":["#epoch","#subscribers","#debounceMs","#current","#broadcast","#flushTimer","#flush","#revision","#lastUpdate","#lastCompactUpdate","#scheduleFlush","#drop","#deliver","#sendLatest","#live","#beforeRead","#subscription"],"sources":["../src/sdk/capnweb/live-state/retain.ts","../src/sdk/capnweb/live-state/engine.ts","../src/sdk/capnweb/live-state/index.ts"],"sourcesContent":["// Canonical retention for RPC callback stubs — the one place that knows a sink\n// might be a remote Cap'n Web / Workers RPC stub rather than a local function.\n//\n// A \"sink\" is a callback the server keeps calling AFTER the RPC method that\n// carried it returned (a live subscription's push). Retaining it means: dup the\n// underlying stub while the transport supports it, and dispose the dup on close.\n// Stream event delivery and the live-state engine both build on these primitives\n// instead of re-deriving the transport dance.\n\n/** An RPC callback after retention: still callable, plus disposable and (best-effort) broken-transport aware. */\nexport type RetainedCallback<Arg> = ((arg: Arg) => unknown) &\n Disposable & { onRpcBroken?(handler: (error: unknown) => void): void };\n\n/**\n * Retain a single-argument RPC callback so it survives past the call that\n * carried it. `dup()` duplicates the underlying remote stub (that dup is what\n * `[Symbol.dispose]` releases); `onRpcBroken` is forwarded defensively — Cap'n\n * Web stubs expose no own property descriptors and a Workers RPC property access\n * can fabricate a pipelined method that rejects at call time, so we wire whatever\n * the stub claims and swallow registration failures. `onRpcBroken` is only a\n * prompt hint; callers that need a terminal verdict must model one explicitly\n * (for example, the durable wake lane's independent settlement capability).\n */\nexport function retainCallback<Arg>(callback: (arg: Arg) => unknown): RetainedCallback<Arg> {\n const retainable = callback as ((arg: Arg) => unknown) &\n Partial<Disposable> & {\n dup?(): (arg: Arg) => unknown;\n onRpcBroken?(handler: (error: unknown) => void): void;\n };\n const retained = (retainable.dup?.() ?? retainable) as typeof retainable;\n const dispose = retained[Symbol.dispose]?.bind(retained);\n // Idempotent by contract: owners often dispose from several paths (engine\n // drop + connection teardown), and a double release of the underlying dup\n // would free someone else's reference.\n let disposed = false;\n const wrapped = Object.assign((arg: Arg) => retained(arg), {\n [Symbol.dispose]() {\n if (disposed) return;\n disposed = true;\n dispose?.();\n },\n }) as RetainedCallback<Arg>;\n const onRpcBroken = retained.onRpcBroken;\n if (typeof onRpcBroken === \"function\") {\n wrapped.onRpcBroken = (handler: (error: unknown) => void) => {\n try {\n const result = onRpcBroken.call(retained, handler);\n if (isThenable(result)) void Promise.resolve(result).catch(() => {});\n } catch {\n // Registration is best-effort.\n }\n };\n }\n return wrapped;\n}\n\n/** Thenable probe: RPC stubs and their call results are thenable-shaped. */\nexport function isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value !== null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\n/**\n * Dispose the result of an RPC call whose value the caller ignores. Reading a\n * Cap'n Web / Workers RPC method yields a disposable stub even when unused, and\n * dropping it without disposal leaks the remote reference.\n */\nexport function disposeIgnoredRpcResult(result: unknown): void {\n if (\n result !== null &&\n (typeof result === \"object\" || typeof result === \"function\") &&\n Symbol.dispose in result\n ) {\n (result as Disposable)[Symbol.dispose]();\n }\n}\n","import {\n disposeIgnoredRpcResult,\n isThenable,\n retainCallback,\n type RetainedCallback,\n} from \"./retain.ts\";\nimport { diff } from \"./diff.ts\";\nimport { compactPatch } from \"./compact.ts\";\nimport type {\n LiveStateCursor,\n LiveStateRead,\n LiveStateSubscriptionOptions,\n LiveUpdate,\n} from \"./protocol.ts\";\n\n/** Handle returned by `LiveState.subscribe` — the ownership + liveness surface for one subscriber. */\nexport type LiveStateSubscription = {\n /** Still registered on a live engine? A dead DO incarnation also makes the call reject. */\n ping(): boolean;\n unsubscribe(): void;\n [Symbol.dispose](): void;\n};\n\nconst DEFAULT_DEBOUNCE_MS = 100;\nconst ACK_TIMEOUT_MS = 10_000;\n\ntype Subscriber<State> = {\n version: 1 | 2 | 3;\n state: State;\n revision: number;\n busy: boolean;\n timeout?: ReturnType<typeof setTimeout>;\n};\n\n/**\n * A source-agnostic live store: hold a state value, and when it changes push the\n * minimal diff to every subscriber. It does not know or care where the state\n * comes from — a Durable Object folds events into it, a stateless RpcTarget polls\n * a third-party API into it — so ANY RpcTarget can expose live state by holding\n * one and returning a read-only wrapper from a `.live` getter.\n *\n * Two rules make it cheap and correct:\n * - Updates are IMMUTABLE (`setState`/`assign` build a new value, never mutate),\n * so the diff short-circuits unchanged branches by identity — O(changed), not\n * O(size). See `diff.ts`.\n * - Push work runs only while a subscriber exists. Transient readers call\n * `readSince` explicitly; a dormant engine schedules nothing.\n *\n * All subscribers share one revision line. Fast subscribers share a patch;\n * slow subscribers receive a coalesced patch from their acknowledged state.\n * A revision mismatch makes the client resync instead of applying stale data.\n */\nexport class LiveState<State extends object> {\n #current: State;\n /** Latest flushed state — the shared diff baseline. */\n #broadcast: State;\n #revision = 0;\n readonly #epoch = crypto.randomUUID();\n #lastUpdate: Extract<LiveUpdate<State>, { type: \"patch\" }> | undefined;\n #lastCompactUpdate: Extract<LiveUpdate<State>, { p: unknown }> | undefined;\n readonly #subscribers = new Map<RetainedCallback<LiveUpdate<State>>, Subscriber<State>>();\n readonly #debounceMs: number;\n #flushTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(initial: State, options: { debounceMs?: number } = {}) {\n this.#current = initial;\n this.#broadcast = initial;\n this.#debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n }\n\n /** The current state — reflects every `setState`/`assign`, even while dormant. */\n getState(): State {\n return this.#current;\n }\n\n /** One retained delta bounds history while a single parent can pull incrementally.\n * A late reader or a new incarnation receives an explicit snapshot instead. */\n readSince(\n cursor?: LiveStateCursor,\n options: LiveStateSubscriptionOptions = {},\n ): LiveStateRead<State> {\n if (this.#flushTimer !== undefined) clearTimeout(this.#flushTimer);\n this.#flushTimer = undefined;\n this.#flush();\n if (cursor?.epoch === this.#epoch) {\n if (cursor.revision === this.#revision) return { epoch: this.#epoch, update: null };\n if (cursor.revision === this.#lastUpdate?.from) {\n return {\n epoch: this.#epoch,\n update: options.patchVersion === 3 ? this.#lastCompactUpdate! : this.#lastUpdate,\n };\n }\n }\n return {\n epoch: this.#epoch,\n update:\n options.patchVersion === 3\n ? { s: [this.#revision, this.#broadcast] }\n : { type: \"snapshot\", revision: this.#revision, state: this.#broadcast },\n };\n }\n\n /** True while at least one live subscriber makes projection work observable. */\n get observed(): boolean {\n return this.#subscribers.size > 0;\n }\n\n /** Replace the state. Build a NEW value (don't mutate) so the diff stays cheap. */\n setState(next: State | ((prev: State) => State)): void {\n this.#current = typeof next === \"function\" ? next(this.#current) : next;\n if (this.#subscribers.size > 0) this.#scheduleFlush();\n }\n\n /** Shallow-merge a partial into the state — sugar for `setState(p => ({ ...p, ...partial }))`. */\n assign(partial: Partial<State>): void {\n this.setState((prev) => ({ ...prev, ...partial }));\n }\n\n subscribe(\n sink: (update: LiveUpdate<State>) => unknown,\n options: LiveStateSubscriptionOptions = {},\n ): LiveStateSubscription {\n // First subscriber after dormancy: adopt the latest state as the shared\n // baseline. While subscribers exist, `#broadcast` only advances inside a\n // flush (with a patch to everyone), so it always matches what they've seen.\n if (this.#subscribers.size === 0) {\n this.#flush();\n }\n\n const retained = retainCallback(sink);\n this.#subscribers.set(retained, {\n version: options.patchVersion ?? 1,\n state: this.#broadcast,\n revision: this.#revision,\n busy: false,\n });\n retained.onRpcBroken?.(() => this.#drop(retained));\n // The initial snapshot is the first paint — delivered now, never debounced.\n // A synchronously-throwing sink is dropped here and never becomes live.\n this.#deliver(\n retained,\n options.patchVersion === 3\n ? { s: [this.#revision, this.#broadcast] }\n : { type: \"snapshot\", revision: this.#revision, state: this.#broadcast },\n );\n\n return {\n ping: () => this.#subscribers.has(retained),\n unsubscribe: () => this.#drop(retained),\n [Symbol.dispose]: () => this.#drop(retained),\n };\n }\n\n #scheduleFlush(): void {\n this.#flushTimer ??= setTimeout(() => {\n this.#flushTimer = undefined;\n this.#flush();\n }, this.#debounceMs);\n }\n\n #flush(): void {\n const previous = this.#broadcast;\n const patch = diff(previous, this.#current);\n if (patch === undefined) return;\n const from = this.#revision;\n const to = (this.#revision += 1);\n this.#broadcast = this.#current;\n const update = { type: \"patch\", from, to, patch } satisfies LiveUpdate<State>;\n this.#lastUpdate = update;\n this.#lastCompactUpdate = { p: [from, to, compactPatch(previous, patch)] };\n for (const subscriber of this.#subscribers.keys()) {\n this.#sendLatest(subscriber);\n }\n }\n\n /** Slow subscribers hold one call and one baseline, never a queue of token updates. */\n #sendLatest(subscriber: RetainedCallback<LiveUpdate<State>>): void {\n const held = this.#subscribers.get(subscriber);\n if (!held || held.busy || held.revision === this.#revision) return;\n if (held.version === 3) {\n if (held.revision === this.#lastUpdate?.from) {\n this.#deliver(subscriber, this.#lastCompactUpdate!);\n } else {\n const patch = diff(held.state, this.#broadcast);\n this.#deliver(subscriber, {\n p: [held.revision, this.#revision, patch ? compactPatch(held.state, patch) : {}],\n });\n }\n return;\n }\n const patch =\n held.version === 2 && held.revision === this.#lastUpdate?.from\n ? this.#lastUpdate.patch\n : diff(held.state, this.#broadcast, { arrays: held.version === 2 });\n // A coalesced sequence may return to the same value. An empty object patch\n // still advances its revision so the following shared update applies.\n this.#deliver(subscriber, {\n type: \"patch\",\n from: held.revision,\n to: this.#revision,\n patch: patch ?? {},\n });\n }\n\n /** Only acknowledgement releases the next coalesced update; a dead sink is dropped. */\n #deliver(subscriber: RetainedCallback<LiveUpdate<State>>, update: LiveUpdate<State>): void {\n const held = this.#subscribers.get(subscriber);\n if (!held) return;\n held.state = this.#broadcast;\n held.revision = this.#revision;\n held.busy = true;\n let result: unknown;\n try {\n result = subscriber(update);\n } catch {\n this.#drop(subscriber);\n return;\n }\n if (isThenable(result)) {\n held.timeout = setTimeout(() => {\n console.warn(\"Live-state subscriber dropped: acknowledgement timed out\", {\n revision: held.revision,\n timeoutMs: ACK_TIMEOUT_MS,\n });\n this.#drop(subscriber);\n }, ACK_TIMEOUT_MS);\n void Promise.resolve(result)\n .then(\n () => {\n if (held.timeout !== undefined) clearTimeout(held.timeout);\n held.busy = false;\n this.#sendLatest(subscriber);\n },\n () => this.#drop(subscriber),\n )\n .finally(() => disposeIgnoredRpcResult(result));\n return;\n }\n held.busy = false;\n disposeIgnoredRpcResult(result);\n }\n\n #drop(subscriber: RetainedCallback<LiveUpdate<State>>): void {\n const timeout = this.#subscribers.get(subscriber)?.timeout;\n if (timeout !== undefined) clearTimeout(timeout);\n if (!this.#subscribers.delete(subscriber)) return;\n subscriber[Symbol.dispose]();\n if (this.#subscribers.size === 0 && this.#flushTimer !== undefined) {\n clearTimeout(this.#flushTimer); // dormant again: no work until the next subscriber\n this.#flushTimer = undefined;\n }\n }\n}\n","// `iterate/sdk/capnweb` — live state end to end: the snapshot+patch wire\n// protocol, the structural diff that produces patches, the client store that\n// reassembles them, and the SERVER engine (`LiveState`) that holds a value\n// and pushes minimal diffs to retained RPC subscribers. No socket, no keeper\n// — a Worker or Durable Object can import it without dragging the browser\n// session machinery into its bundle.\nimport { RpcTarget } from \"@iterate-com/capnweb\";\nimport type { LiveState as LiveStateEngine, LiveStateSubscription } from \"./engine.ts\";\nimport type { LiveStateSubscriptionOptions, LiveUpdate } from \"./protocol.ts\";\nimport { isThenable } from \"./retain.ts\";\nimport type { LiveStateRpc, LiveStateSubscriptionHandle } from \"./types.ts\";\n\ntype LiveStateSource<State extends object> = Pick<LiveStateEngine<State>, \"getState\" | \"subscribe\">;\n\ntype RefreshingLiveStateSource<State extends object> = {\n readonly live: LiveStateSource<State>;\n loadAndRefreshLive(): void | PromiseLike<void>;\n};\n\nexport { createLiveStateStore, type LiveStateStore } from \"./store.ts\";\nexport { applyPatch, diff } from \"./diff.ts\";\nexport { compactPatch, applyCompactPatch } from \"./compact.ts\";\nexport { isLiveStateSnapshot, liveStateRevision } from \"./protocol.ts\";\nexport type {\n CompactLiveStatePatch,\n LiveStateCursor,\n LiveStateRead,\n LiveStatePatch,\n LiveStateSubscriptionOptions,\n LiveUpdate,\n} from \"./protocol.ts\";\nexport { LiveState, type LiveStateSubscription } from \"./engine.ts\";\nexport type { LiveStateRpc, LiveStateSubscriptionHandle } from \"./types.ts\";\nexport {\n disposeIgnoredRpcResult,\n isThenable,\n retainCallback,\n type RetainedCallback,\n} from \"./retain.ts\";\n\n/**\n * Expose mutable server state as a read-only Cap'n Web capability.\n * Pass an in-memory `LiveState`, or a stream-processor registry whose engine\n * must hydrate before its first snapshot.\n */\nexport class LiveStateRpcTarget<State extends object>\n extends RpcTarget\n implements LiveStateRpc<State>\n{\n readonly #live: LiveStateSource<State>;\n readonly #beforeRead: (() => void | PromiseLike<void>) | undefined;\n\n constructor(source: LiveStateSource<State> | RefreshingLiveStateSource<State>) {\n super();\n if (\"loadAndRefreshLive\" in source) {\n this.#live = source.live;\n this.#beforeRead = () => source.loadAndRefreshLive();\n } else {\n this.#live = source;\n this.#beforeRead = undefined;\n }\n }\n\n async get() {\n const loading = this.#beforeRead?.();\n if (isThenable(loading)) await loading;\n return this.#live.getState();\n }\n\n async subscribe(\n onUpdate: (update: LiveUpdate<State>) => unknown,\n options?: LiveStateSubscriptionOptions,\n ) {\n const loading = this.#beforeRead?.();\n if (isThenable(loading)) await loading;\n return new LiveStateSubscriptionRpcTarget(this.#live.subscribe(onUpdate, options));\n }\n}\n\n/**\n * The wire handle for one subscription — `ping`/`unsubscribe` over RPC.\n * Exported for relays that build their subscription OUTSIDE a `LiveState`\n * engine (the worker-local liveState-socket relay) but must hand back the\n * same handle shape `LiveStateRpcTarget.subscribe` does.\n */\nexport class LiveStateSubscriptionRpcTarget\n extends RpcTarget\n implements LiveStateSubscriptionHandle\n{\n readonly #subscription: LiveStateSubscription;\n\n constructor(subscription: LiveStateSubscription) {\n super();\n this.#subscription = subscription;\n }\n\n ping() {\n return this.#subscription.ping();\n }\n\n unsubscribe() {\n this.#subscription.unsubscribe();\n }\n\n [Symbol.dispose]() {\n this.#subscription.unsubscribe();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app-server.mjs","names":[],"sources":["../../src/next/app-server.ts"],"sourcesContent":["// app-server.ts — THE BROWSER-AUTH GATE every app worker runs first: the OAuth client (one CIMD\n// client id per app origin, PKCE, the `__Host-` cookie), the `/api` proxy, and the few pages the gate\n// serves itself. A browser session is an ordinary OAuth client of ONE issuer — by default the\n// deployment's own (`config.issuer`), or any os-next issuer a person CONNECTS the app to on purpose (a\n// self-hosted platform). Connecting is deliberate: a same-origin POST from `/.auth/connect`, a page\n// that names the issuer and asks — never a link, because a link would be a forced login: it could\n// bind a browser to an issuer of the linker's choosing, and the app would then show that issuer's\n// world as the person's. Once a session exists, its record is the ONLY source of where the credential\n// goes (`BrowserSession.host()`): the login probe, the `/api` proxy and logout all read it there.\n// eslint-disable-next-line iterate/no-capnweb-http-batch -- One bounded read for the Sign-in-again page, no live capabilities.\nimport { newHttpBatchRpcSession } from \"capnweb\";\nimport type { BrowserHost, BrowserSession } from \"./app-session.ts\";\nimport type { IterateApi } from \"./api.ts\";\nimport { cookieValueOf } from \"./principal.ts\";\nimport { isSameOriginBrowserRequest, sameOriginPath } from \"./lib.ts\";\nimport { OAuthScopes } from \"./oauth-scopes.ts\";\n\n/** The port separates local apps sharing localhost's cookie jar. */\nfunction sessionCookieName(url: URL) {\n return `__Host-itx-session${url.port ? `-${url.port}` : \"\"}`;\n}\nexport function appSession(namespace: DurableObjectNamespace<BrowserSession>, request: Request) {\n const url = new URL(request.url);\n const id = cookieValueOf(request.headers.get(\"cookie\"), sessionCookieName(url));\n return id && /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(id)\n ? namespace.getByName(`${url.origin}:${id}`)\n : null;\n}\n\n/** Start one ordinary app session; the caller publishes its cookie after its own\n * sign-in step succeeds. The issuer uses this same code/PKCE flow internally. */\nexport async function startAppSession(\n sessions: DurableObjectNamespace<BrowserSession>,\n host: BrowserHost,\n next: string,\n) {\n const id = crypto.randomUUID();\n const session = sessions.getByName(`${host.origin}:${id}`);\n const location = await session.begin(host, next);\n const setCookie = `${sessionCookieName(new URL(host.origin))}=${id}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000`;\n return { session, location, setCookie };\n}\n\n/** What an issuer a person names must be: an https ORIGIN (a path or query is dropped, not refused),\n * not an IP literal, not loopback, and not under one of this deployment's own zones (`denyZones` —\n * a project host on our wildcard is userspace and could serve a look-alike issuer) — unless it IS\n * the deployment's default issuer, which is always allowed as it is. Pure: the discovery check that\n * the origin really answers as that issuer is `issuerAnswersAt`. */\nexport function issuerOriginOf(\n candidate: string,\n options: { defaultIssuer: string; denyZones?: readonly string[] },\n): { origin: string } | { error: string } {\n let url: URL;\n try {\n url = new URL(candidate.trim());\n } catch {\n return { error: \"Enter the issuer's address, like https://iterate.example.workers.dev.\" };\n }\n const origin = url.origin;\n if (origin === options.defaultIssuer) return { origin };\n if (url.protocol !== \"https:\") return { error: \"The issuer must be served over https.\" };\n const host = url.hostname;\n if (\n /^\\d{1,3}(?:\\.\\d{1,3}){3}$/.test(host) ||\n host.startsWith(\"[\") ||\n host === \"localhost\" ||\n host.endsWith(\".localhost\")\n )\n return { error: \"The issuer must have a public hostname.\" };\n for (const zone of options.denyZones || []) {\n const denied = zone.toLowerCase().replace(/^\\.+/, \"\");\n if (denied && (host === denied || host.endsWith(`.${denied}`)))\n return { error: `${host} is not an issuer this app can be connected to.` };\n }\n return { origin };\n}\n\n/** The issuer must say it is the issuer: its discovery document's `issuer` equals the origin\n * exactly. NOTHING else is read from the document — the endpoints stay hand-built from the origin\n * (app-session.ts: `/oauth2/token`, and `/api` as the resource), so a document can steer nothing.\n * Null when it answers as expected, else the reason. */\nasync function issuerAnswersAt(origin: string): Promise<string | null> {\n try {\n const response = await fetch(`${origin}/.well-known/oauth-authorization-server`, {\n headers: { accept: \"application/json\" },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) return `${new URL(origin).host} does not answer as an iterate platform.`;\n const document: unknown = await response.json();\n const issuer =\n typeof document === \"object\" && document ? (document as { issuer?: unknown }).issuer : null;\n if (issuer !== origin)\n return `${new URL(origin).host} does not answer as an iterate platform (its issuer is ${typeof issuer === \"string\" ? issuer : \"missing\"}).`;\n return null;\n } catch {\n return `${new URL(origin).host} could not be reached.`;\n }\n}\n\n/** `next` as a same-origin path with any `issuer` query parameter removed — an issuer rides in\n * through `/.auth/connect` and nowhere else, so a `next` can never carry one back into a login\n * (the Sign-in-again form returns to the very login URL that showed it). Untouched when there is\n * none to remove, so what a page sent stays byte for byte. */\nfunction nextPathOf(next: string | null, origin: string): string {\n const path = sameOriginPath(next || \"/\", origin);\n const url = new URL(path, origin);\n if (!url.searchParams.has(\"issuer\")) return path;\n url.searchParams.delete(\"issuer\");\n return url.pathname + url.search;\n}\n\n/** What the app's own sign-in currently is, for the Sign-in-again page to show: who it is signed in\n * as, and the projects it reaches — its grant, the thing that turned out not to include the project\n * the page named. One bounded HTTP-batch read of the platform (the logout path's shape), best\n * effort: a slow or failed read simply leaves those lines off, the page still works. */\nasync function grantSummary(resource: string, bearer: string) {\n // eslint-disable-next-line iterate/no-capnweb-http-batch -- One bounded read, no live capabilities.\n using api = newHttpBatchRpcSession<IterateApi>(\n new Request(resource, {\n headers: { Authorization: `Bearer ${bearer}` },\n signal: AbortSignal.timeout(5_000),\n }),\n );\n using session = api.authenticate({ type: \"from-server-cookie\" });\n const info = session.info();\n const projects = session.projects.list();\n const [{ principal }, list] = await Promise.all([info, projects]);\n return { email: principal.email, projects: list.map((project) => project.slug) };\n}\n\n/** What each scope is, to the person, in one or two words — the Sign-in-again page's permissions\n * line. The consent page (public/authorize.js) spells the same three at sentence length. */\nconst scopeLabels: Record<string, string> = {\n iterate: \"your projects\",\n account: \"your account\",\n \"organizations:write\": \"your organizations\",\n};\n\nconst text = (value: string) => value.replace(/[&<>\"]/g, (c) => `&#${c.charCodeAt(0)};`);\n\n/** The gate's own pages, dressed without asking anyone: the issuer's stylesheet is loaded only when\n * the issuer is the deployment's own (a stylesheet is a document's dress, and an issuer a person\n * typed does not get to dress a page on this origin). */\nconst inlinePageCss = `body{margin:0;min-height:100svh;display:grid;place-items:center;font:15px/1.5 ui-sans-serif,system-ui,sans-serif;color:#18181b;background:#fafafa}main{width:min(28rem,calc(100% - 2rem));padding:2rem;background:#fff;border:1px solid #e4e4e7;border-radius:12px}h1{font-size:1.25rem;margin:0 0 .75rem}p{margin:0 0 .75rem}.muted{color:#71717a}code{font:.9em ui-monospace,SFMono-Regular,Menlo,monospace}button,.button{display:inline-block;padding:.55rem 1rem;border:0;border-radius:8px;background:#18181b;color:#fff;font:inherit;cursor:pointer;text-decoration:none}a.quiet{color:#71717a;margin-left:1rem}`;\n\n/** A page of the gate's own: `head` is what the head carries beyond the title, `body` the card. */\nfunction gatePage(input: {\n title: string;\n head: string;\n body: string;\n csp: string;\n status?: number;\n}): Response {\n return new Response(\n `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>${text(input.title)}</title>${input.head}</head><body><main class=\"issuer-card\">${input.body}</main></body></html>`,\n {\n status: input.status ?? 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n \"Content-Security-Policy\": input.csp,\n \"X-Frame-Options\": \"DENY\",\n },\n },\n );\n}\n\n/** The head and the CSP of a gate page: the deployment's own issuer dresses it (its stylesheet, its\n * logo); any other issuer gets the inline dress and nothing of its own on this origin. */\nfunction dressOf(\n issuer: string,\n defaultIssuer: string,\n): { head: string; csp: string; mark: string } {\n if (issuer === defaultIssuer)\n return {\n head: `<link rel=\"stylesheet\" href=\"${issuer}/issuer.css\"><link rel=\"icon\" href=\"${issuer}/iterate-logo.svg\" type=\"image/svg+xml\">`,\n csp: `default-src 'none'; style-src ${issuer}; img-src ${issuer}; form-action 'self'; frame-ancestors 'none'`,\n mark: `<img class=\"issuer-mark\" src=\"${issuer}/iterate-logo.svg\" alt=\"\" width=\"56\" height=\"56\">`,\n };\n return {\n head: `<style>${inlinePageCss}</style>`,\n csp: \"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'\",\n mark: \"\",\n };\n}\n\n/** The sign-in this browser holds does not cover what the page needs: a project the page named\n * (`?project=`, the page's own word — its `/projects/<ref>` was absent from the session's list; the\n * page is not verified, it only offers a sign-out the person must click) or a permission the app\n * asks for. One page for both, on the app's origin because ending its session is a same-origin\n * POST. It shows who the app is signed in as, through which issuer, and the projects that sign-in\n * reaches (its grant), then one button that ends this app's session and comes straight back to\n * `/.auth/login`, which starts a fresh one — the issuer still knows the person, so they land on\n * consent: the project to tick, or Switch account — and returns to the page that sent them. */\nasync function signInAgainPage(input: {\n url: URL;\n issuer: string;\n resource: string;\n defaultIssuer: string;\n bearer: string;\n scopes: string[];\n project: string | null;\n}) {\n const { url, issuer, resource, defaultIssuer, bearer, scopes, project } = input;\n const lacks = project\n ? `does not include a project called <code>${text(project)}</code>`\n : \"does not include a permission this app asks for\";\n const grant = await grantSummary(resource, bearer).catch(() => null);\n const reaches = grant\n ? grant.projects.length\n ? `It reaches ${grant.projects\n .slice(0, 12)\n .map((slug) => `<code>${text(slug)}</code>`)\n .join(\n \", \",\n )}${grant.projects.length > 12 ? ` and ${grant.projects.length - 12} more` : \"\"}.`\n : \"It reaches no projects yet.\"\n : \"\";\n const permissions = `Permissions: ${scopes.map((scope) => scopeLabels[scope] || scope).join(\", \")}.`;\n const signedIn = [\n grant?.email ? `Signed in as <strong>${text(grant.email)}</strong>` : \"Signed in\",\n ` through <strong>${text(new URL(issuer).host)}</strong>.`,\n reaches,\n permissions,\n ]\n .filter(Boolean)\n .join(\" \")\n .replace(\"</strong> through\", \"</strong> through\");\n const dress = dressOf(issuer, defaultIssuer);\n const back = nextPathOf(url.pathname + url.search, url.origin);\n return gatePage({\n title: \"Sign in again\",\n head: dress.head,\n csp: dress.csp,\n body: `${dress.mark}<h1>Sign in again</h1><p>Your sign-in to <strong>${text(url.host)}</strong> ${lacks}.</p><p class=\"muted\">${signedIn}</p><p class=\"muted\">Signing in again brings you back here. At consent, tick the project — or switch account.</p><form method=\"post\" action=\"/.auth/logout?next=${encodeURIComponent(back)}\"><button class=\"primary\" type=\"submit\">Sign in again</button></form>`,\n });\n}\n\n/** `/.auth/connect` — the one page that may bind this browser to an issuer other than the\n * deployment's own: it names the issuer's host in plain text, says what it will end, and asks. The\n * Continue button is a same-origin POST; the page loads nothing from the issuer it names. */\nfunction connectPage(input: {\n url: URL;\n origin: string;\n next: string;\n scope: string | null;\n held: { issuer: string } | null;\n defaultIssuer: string;\n}): Response {\n const { url, origin, next, scope, held, defaultIssuer } = input;\n const host = new URL(origin).host;\n const ends =\n held && held.issuer !== origin\n ? `<p class=\"muted\">This ends your current sign-in through <strong>${text(new URL(held.issuer).host)}</strong>.</p>`\n : \"\";\n const dress = dressOf(defaultIssuer, defaultIssuer);\n return gatePage({\n title: `Connect to ${host}`,\n head: dress.head,\n csp: dress.csp,\n body: `${dress.mark}<h1>Connect ${text(url.host)} to <code>${text(host)}</code>?</h1><p>This app will sign you in through <strong>${text(host)}</strong> and show what that platform holds. Continue only if that is your own iterate platform.</p>${ends}<form method=\"post\" action=\"/.auth/connect\"><input type=\"hidden\" name=\"issuer\" value=\"${text(origin)}\"><input type=\"hidden\" name=\"next\" value=\"${text(next)}\">${scope ? `<input type=\"hidden\" name=\"scope\" value=\"${text(scope)}\">` : \"\"}<button class=\"primary\" type=\"submit\">Continue</button><a class=\"quiet\" href=\"/\">Cancel</a></form>`,\n });\n}\n\n/** A refusal of the gate's own, as a page the person can read. */\nfunction refusalPage(defaultIssuer: string, message: string, status = 400): Response {\n const dress = dressOf(defaultIssuer, defaultIssuer);\n return gatePage({\n title: \"Cannot connect\",\n head: dress.head,\n csp: dress.csp,\n status,\n body: `${dress.mark}<h1>Cannot connect</h1><p>${text(message)}</p><p><a class=\"button\" href=\"/\">Back</a></p>`,\n });\n}\n\ntype AppAuth = {\n /** Public app branding; relative logo paths resolve against this app's origin. */\n client?: { name: string; logoUri: string };\n sessions: DurableObjectNamespace<BrowserSession>;\n /** The deployment's own issuer — where a browser with no session signs in. */\n issuer: string;\n /** Its `/api`. */\n resource: string;\n /** Platform dispatches in process to avoid /api recursion; other apps pass fetch. */\n api: (request: Request) => Promise<Response> | Response;\n /** The issuer proves identity before establishing its own ordinary app session. */\n loginPage?: string;\n /** Zones no connectable issuer may live under (`issuerOriginOf`): this deployment's own, whose\n * project hosts are userspace. The default issuer is exempt. */\n denyZones?: readonly string[];\n};\n\n/** The same OAuth client and /api proxy on a platform host or a separate app worker. */\nexport async function appAuth(request: Request, config: AppAuth): Promise<Response | null> {\n const url = new URL(request.url);\n const { issuer, resource, sessions } = config;\n const client = config.client && {\n name: config.client.name,\n logoUri: new URL(config.client.logoUri, url.origin).href,\n };\n const session = appSession(sessions, request);\n const clearCookie = `${sessionCookieName(url)}=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0`;\n /** Where THIS browser's credential goes: its session's record, else the deployment's own. A record\n * bound to a CONNECTED issuer whose resource is not that issuer's `/api` is not one this code\n * wrote — it is dropped (the deployment's own issuer may name its resource as it likes). */\n const held = async (): Promise<{ issuer: string; resource: string } | null> => {\n const host = await session?.host();\n if (!host) return null;\n if (host.issuer !== issuer && host.resource !== `${host.issuer}/api`) {\n await session!.discard();\n return null;\n }\n return host;\n };\n if (url.pathname === \"/.auth/client.json\")\n return Response.json(\n {\n client_id: `${url.origin}/.auth/client.json`,\n client_name: client?.name || url.host,\n client_uri: url.origin,\n logo_uri: client?.logoUri,\n redirect_uris: [`${url.origin}/.auth/callback`],\n token_endpoint_auth_method: \"none\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n },\n { headers: { \"Cache-Control\": \"public, max-age=300\" } },\n );\n\n if (url.pathname === \"/.auth/session.json\") {\n // which issuer this browser is connected to — the app's shell says so when it is not the\n // deployment's own; no credential, no identity, nothing a page on another origin could use\n if (request.method !== \"GET\") return new Response(\"Method not allowed\", { status: 405 });\n const host = await held();\n return Response.json(\n { issuer: host?.issuer ?? null, defaultIssuer: issuer },\n { headers: { \"Cache-Control\": \"no-store\" } },\n );\n }\n\n if (url.pathname === \"/.auth/connect\") {\n if (config.loginPage) return new Response(\"Not found\", { status: 404 });\n if (request.method === \"GET\") {\n const named = issuerOriginOf(url.searchParams.get(\"issuer\") ?? \"\", {\n defaultIssuer: issuer,\n denyZones: config.denyZones,\n });\n if (\"error\" in named) return refusalPage(issuer, named.error);\n const next = nextPathOf(url.searchParams.get(\"next\"), url.origin);\n const scope = url.searchParams.get(\"scope\");\n const host = await held();\n // the deployment's own issuer needs no asking when this browser is not bound elsewhere\n if (named.origin === issuer && (!host || host.issuer === issuer))\n return new Response(null, {\n status: 303,\n headers: {\n Location: `/.auth/login?${new URLSearchParams({ next, scope: scope || \"\" })}`,\n \"Cache-Control\": \"no-store\",\n },\n });\n return connectPage({\n url,\n origin: named.origin,\n next,\n scope,\n held: host,\n defaultIssuer: issuer,\n });\n }\n if (request.method !== \"POST\") return new Response(\"Method not allowed\", { status: 405 });\n if (!isSameOriginBrowserRequest(request))\n return new Response(\"Cross-site request refused\", { status: 403 });\n const form = await request.formData();\n const named = issuerOriginOf(String(form.get(\"issuer\") ?? \"\"), {\n defaultIssuer: issuer,\n denyZones: config.denyZones,\n });\n if (\"error\" in named) return refusalPage(issuer, named.error);\n const next = nextPathOf(String(form.get(\"next\") ?? \"/\"), url.origin);\n const parsed = OAuthScopes.safeParse(\n String(form.get(\"scope\") ?? \"\")\n .split(\" \")\n .filter(Boolean),\n );\n if (!parsed.success) return refusalPage(issuer, \"Unsupported permission.\");\n if (named.origin !== issuer) {\n const refused = await issuerAnswersAt(named.origin);\n if (refused) return refusalPage(issuer, refused);\n }\n const host = await held();\n if (host?.issuer === named.origin)\n return new Response(null, {\n status: 303,\n headers: {\n Location: `/.auth/login?${new URLSearchParams({ next, scope: parsed.data.join(\" \") })}`,\n \"Cache-Control\": \"no-store\",\n },\n });\n if (host) {\n // a deliberate switch: end the sign-in at the issuer it was with (best effort — that issuer\n // may be gone), then forget it here either way\n try {\n await session!.end();\n } catch {\n await session!.discard();\n }\n }\n const { location, setCookie } = await startAppSession(\n sessions,\n {\n origin: url.origin,\n client,\n issuer: named.origin,\n resource: `${named.origin}/api`,\n scopes: parsed.data,\n },\n next,\n );\n return new Response(null, {\n status: 302,\n headers: {\n Location: location,\n \"Set-Cookie\": setCookie,\n \"Cache-Control\": \"no-store\",\n \"Referrer-Policy\": \"no-referrer\",\n },\n });\n }\n\n if (url.pathname === \"/.auth/login\") {\n if (request.method !== \"GET\") return new Response(\"Method not allowed\", { status: 405 });\n const next = nextPathOf(url.searchParams.get(\"next\"), url.origin);\n const scope = url.searchParams.get(\"scope\");\n const parsed = OAuthScopes.safeParse(scope ? scope.split(\" \").filter(Boolean) : []);\n if (!parsed.success) return new Response(\"Unsupported permission\", { status: 400 });\n const scopes = parsed.data;\n // A link may NAME an issuer; only the connect page's POST may bind one. So a login link that\n // names one lands on that page (which also says no to a bad one) — a click is always required.\n const requested = url.searchParams.get(\"issuer\");\n if (requested && !config.loginPage) {\n const named = issuerOriginOf(requested, {\n defaultIssuer: issuer,\n denyZones: config.denyZones,\n });\n if (\"error\" in named || named.origin !== issuer)\n return new Response(null, {\n status: 303,\n headers: {\n Location: `/.auth/connect?${new URLSearchParams({ issuer: requested, next, scope: scope || \"\" })}`,\n \"Cache-Control\": \"no-store\",\n },\n });\n }\n const host = await held();\n const target = host || { issuer, resource };\n const bearer = await session?.bearer();\n if (bearer) {\n const probe = await config.api(\n new Request(target.resource, {\n method: \"POST\",\n body: \"\",\n headers: { Authorization: `Bearer ${bearer}` },\n }),\n );\n await probe.body?.cancel();\n if (probe.status !== 401) {\n if (!probe.ok) throw new Error(`iterate API check failed (${probe.status})`);\n const heldScopes = await session!.scopes();\n const project = url.searchParams.get(\"project\");\n if (!project && scopes.every((scope) => heldScopes.includes(scope)))\n return new Response(null, {\n status: 303,\n headers: { Location: next, \"Cache-Control\": \"no-store\" },\n });\n // Only a deliberate POST can replace a valid grant. GET never signs out.\n return signInAgainPage({\n url,\n issuer: target.issuer,\n resource: target.resource,\n defaultIssuer: issuer,\n bearer,\n scopes: heldScopes,\n project,\n });\n }\n await session!.discard();\n }\n if (config.loginPage)\n return new Response(null, {\n status: 303,\n headers: {\n Location: `${config.loginPage}?${new URLSearchParams({ next })}`,\n \"Cache-Control\": \"no-store\",\n },\n });\n // The fresh session goes where this browser was CONNECTED (an expired grant at a self-host\n // signs in again at that self-host, not at the deployment's own issuer); a browser with no\n // record signs in at the default.\n const { location, setCookie } = await startAppSession(\n sessions,\n { origin: url.origin, issuer: target.issuer, resource: target.resource, scopes, client },\n next,\n );\n return new Response(null, {\n status: 302,\n headers: {\n Location: location,\n \"Set-Cookie\": setCookie,\n \"Cache-Control\": \"no-store\",\n \"Referrer-Policy\": \"no-referrer\",\n },\n });\n }\n if (url.pathname === \"/.auth/callback\") {\n if (request.method !== \"GET\") return new Response(\"Method not allowed\", { status: 405 });\n const result = await session?.complete(url.search);\n if (!result || result.error)\n return new Response(result?.error || \"Sign-in does not match this browser.\", {\n status: 400,\n headers: { \"Cache-Control\": \"no-store\", \"Referrer-Policy\": \"no-referrer\" },\n });\n return new Response(null, {\n status: 303,\n headers: {\n Location: result.next!,\n \"Cache-Control\": \"no-store\",\n \"Referrer-Policy\": \"no-referrer\",\n },\n });\n }\n if (url.pathname === \"/.auth/logout\") {\n if (request.method !== \"POST\") return new Response(\"Method not allowed\", { status: 405 });\n if (!isSameOriginBrowserRequest(request))\n return new Response(\"Cross-site request refused\", { status: 403 });\n // Where the ended session was bound: a sign-out from a CONNECTED issuer returns through the\n // connect page for that issuer (one click, the host named), so \"Sign in again\" at a self-host\n // stays at the self-host instead of silently binding the browser back to the default.\n const ended = await held();\n try {\n await session?.end();\n } catch {\n console.error(\"oauth.app_logout_failed\", { origin: url.origin });\n return new Response(\n \"Sign-out could not complete. Your session is still present; go back and try again.\",\n { status: 503 },\n );\n }\n const next = nextPathOf(url.searchParams.get(\"next\"), url.origin);\n // The scopes the next sign-in must hold ride along: the Sign-in-again form's `next` IS the login\n // URL that asked for them (`/.auth/login?…&scope=…`), and the connect page starts the grant from\n // its own `scope` — without this the reconnected grant would hold `iterate` alone and the login\n // would send the person straight back to Sign in again.\n const nextUrl = new URL(next, url.origin);\n const scope =\n nextUrl.pathname === \"/.auth/login\" ? nextUrl.searchParams.get(\"scope\") || \"\" : \"\";\n const headers = new Headers({\n Location:\n ended && ended.issuer !== issuer && !config.loginPage\n ? `/.auth/connect?${new URLSearchParams({ issuer: ended.issuer, next, scope })}`\n : next,\n \"Set-Cookie\": clearCookie,\n \"Cache-Control\": \"no-store\",\n });\n return new Response(null, { status: 303, headers });\n }\n if (url.pathname === \"/api\") {\n let token = request.headers.get(\"authorization\");\n let cookieAuth = false;\n if (!token && request.method !== \"OPTIONS\") {\n // THE COOKIE'S AUTHORITY IS SAME-ORIGIN ONLY (CSRF): a page on another origin that happens to\n // carry this app's cookie gets nothing from it — its request goes on BARE, and a WebSocket then\n // authenticates in-band with its own token, `authenticate({ type: \"bearer\", token })` (os-next\n // api.ts), or holds no session at all.\n const bearer =\n session && request.headers.get(\"origin\") === url.origin ? await session.bearer() : null;\n if (bearer) {\n token = `Bearer ${bearer}`;\n cookieAuth = true;\n }\n }\n // where the credential goes is the session's record, never anything on the request\n const target = (await held()) ?? { issuer, resource };\n const headers = new Headers(request.headers);\n headers.delete(\"cookie\");\n // The same-origin gate above already ran. Provider CORS reconstruction loses 101.\n if (request.headers.get(\"upgrade\")?.toLowerCase() === \"websocket\") headers.delete(\"origin\");\n if (token) headers.set(\"authorization\", token);\n const response = await config.api(\n new Request(target.resource, new Request(request, { headers })),\n );\n if (cookieAuth && response.status === 401) {\n await session!.discard();\n const denied = new Response(response.body, response);\n denied.headers.append(\"Set-Cookie\", clearCookie);\n return denied;\n }\n return response;\n }\n if (url.pathname.startsWith(\"/.auth/\")) return new Response(\"Not found\", { status: 404 });\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app-session.mjs","names":["#serial","#clear","#tokenOptions","#endOnDeadGrant","#activate","#bearer"],"sources":["../../src/next/app-session.ts"],"sourcesContent":["import { DurableObject } from \"cloudflare:workers\";\n// eslint-disable-next-line iterate/no-capnweb-http-batch -- One bounded logout request, no live capabilities.\nimport { newHttpBatchRpcSession } from \"capnweb\";\nimport * as oauth from \"oauth4webapi\";\nimport { z } from \"zod\";\nimport { authorizationCodeRequest } from \"./client/oauth.ts\";\nimport { OAuthScopes } from \"./oauth-scopes.ts\";\nimport { isLocalOrigin } from \"./lib.ts\";\nimport type { IterateApi } from \"./api.ts\";\n\nexport type BrowserHost = {\n origin: string;\n issuer: string;\n resource: string;\n scopes: string[];\n client?: { id?: string; name: string; logoUri: string };\n};\ntype Base = BrowserHost & { clientId: string; next: string; until: number };\ntype Pending = Base & { phase: \"pending\"; state: string; verifier: string };\ntype Active = Base & {\n phase: \"active\";\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n};\ntype StoredSession = Pending | Active;\n\n/** Each app binds this same class. Token exchange and logout use the issuer's\n * public protocol, so separately deployed apps need no platform bindings.\n * The input gate serializes refresh and logout across tabs. */\nexport class BrowserSession extends DurableObject {\n begin(host: BrowserHost, next: string) {\n return this.#serial(async () => {\n if (await this.ctx.storage.get(\"session\")) throw new Error(\"Browser session already exists\");\n const origin = new URL(host.origin);\n const local = isLocalOrigin(host.origin);\n if (origin.protocol !== \"https:\" && !local) throw new Error(\"Browser login requires HTTPS\");\n let clientId = host.client?.id || `${host.origin}/.auth/client.json`;\n if (local) {\n const response = await fetch(`${host.issuer}/oauth2/register`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n client_name: host.client?.name || origin.host,\n client_uri: host.origin,\n logo_uri: host.client?.logoUri,\n redirect_uris: [`${host.origin}/.auth/callback`],\n token_endpoint_auth_method: \"none\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n }),\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new Error(`Local client registration failed (${response.status})`);\n clientId = z\n .object({ client_id: z.string().min(1) })\n .parse(await response.json()).client_id;\n }\n const { url, state, verifier } = await authorizationCodeRequest({\n issuer: host.issuer,\n clientId,\n redirectUri: `${host.origin}/.auth/callback`,\n resources: [host.resource],\n scopes: host.scopes,\n });\n const data: Pending = {\n ...host,\n next,\n clientId,\n phase: \"pending\",\n state,\n verifier,\n until: Date.now() + 10 * 60_000,\n };\n await this.ctx.storage.put(\"session\", data);\n await this.ctx.storage.setAlarm(data.until);\n return url.href;\n });\n }\n\n /** The issuer's redirect back to `/.auth/callback`, its query string. oauth4webapi validates it\n * (the state matches this browser's pending flow, the `iss` matches the issuer, no `error`), then\n * exchanges the code for tokens. */\n complete(callbackQuery: string) {\n return this.#serial(async () => {\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n if (!data || data.phase !== \"pending\" || data.until <= Date.now())\n return {\n error: \"This sign-in expired or does not match this browser. Start sign-in again.\",\n };\n const as: oauth.AuthorizationServer = {\n issuer: data.issuer,\n token_endpoint: `${data.issuer}/oauth2/token`,\n authorization_response_iss_parameter_supported: true,\n };\n const client: oauth.Client = { client_id: data.clientId };\n let callback: URLSearchParams;\n try {\n callback = oauth.validateAuthResponse(\n as,\n client,\n new URLSearchParams(callbackQuery),\n data.state,\n );\n } catch (error) {\n // A provider `error=` (the person declined) ends the flow; a state/iss mismatch leaves the\n // pending flow to be retried from a genuine browser.\n if (error instanceof oauth.AuthorizationResponseError) {\n await this.#clear();\n return { error: \"Authorization was declined.\" };\n }\n return {\n error: \"This sign-in expired or does not match this browser. Start sign-in again.\",\n };\n }\n const started = Date.now();\n let tokens: oauth.TokenEndpointResponse;\n try {\n const response = await oauth.authorizationCodeGrantRequest(\n as,\n client,\n oauth.None(),\n callback,\n `${data.origin}/.auth/callback`,\n data.verifier,\n this.#tokenOptions(data),\n );\n tokens = await oauth.processAuthorizationCodeResponse(as, client, response);\n } catch (error) {\n await this.#endOnDeadGrant(error);\n return { error: \"Sign-in could not complete. Start sign-in again.\" };\n }\n await this.#activate(data, tokens, started);\n return { next: data.next };\n });\n }\n\n bearer() {\n return this.#serial(() => this.#bearer());\n }\n /** THE ISSUER THIS SESSION IS BOUND TO and the resource its tokens are for — written once at\n * `begin`, never steered by a request: the app's `/api` proxy, its login probe and its logout read\n * them from here, so a browser connected to one issuer can only ever spend its credential there.\n * Null when no session was begun. */\n async host(): Promise<{ issuer: string; resource: string } | null> {\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n return data ? { issuer: data.issuer, resource: data.resource } : null;\n }\n async scopes() {\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n return data?.phase === \"active\" ? data.scopes : [];\n }\n /** Public client metadata chosen by the app before consent; never a browser-supplied claim. */\n async client() {\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n return data?.phase === \"active\" ? data.client : undefined;\n }\n /** A verified 401 means this local credential no longer grants access. */\n discard() {\n return this.#serial(() => this.#clear());\n }\n\n end() {\n return this.#serial(async () => {\n const token = await this.#bearer();\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n if (token && data) {\n // Probe classifies a revoked/expired credential without interpreting RPC error text.\n const probe = await fetch(\n new Request(data.resource, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${token}` },\n body: \"\",\n signal: AbortSignal.timeout(10_000),\n }),\n );\n await probe.body?.cancel();\n if (probe.status !== 401) {\n if (!probe.ok)\n throw new Error(`Sign-out could not reach Iterate (${probe.status}). Try again.`);\n // eslint-disable-next-line iterate/no-capnweb-http-batch -- A bounded logout command returns no live capabilities.\n using api = newHttpBatchRpcSession<IterateApi>(\n new Request(data.resource, {\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(10_000),\n }),\n );\n // `authenticate(...)` returns a SessionRpcTarget STUB — its own RPC result to dispose (the\n // capnweb README's `using authedApi = api.authenticate(...)`); disposing only `api` leaves\n // it dangling and workerd warns the result was never disposed.\n using session = api.authenticate({ type: \"from-server-cookie\" });\n await session.logout();\n }\n }\n // Network failures preserve the session and are shown to the caller.\n // Successful logout has written the issuer's durable revocation marker.\n await this.#clear();\n });\n }\n\n async alarm() {\n // Pending flows expire in ten minutes; active grants have the same absolute\n // thirty-day lifetime at the issuer. No refresh token survives this bound.\n await this.#clear();\n }\n async #bearer() {\n const data = await this.ctx.storage.get<StoredSession>(\"session\");\n if (!data || data.phase !== \"active\") return null;\n if (data.until <= Date.now()) {\n await this.#clear();\n return null;\n }\n if (data.expiresAt > Date.now() + 30_000) return data.accessToken;\n const as: oauth.AuthorizationServer = {\n issuer: data.issuer,\n token_endpoint: `${data.issuer}/oauth2/token`,\n };\n const client: oauth.Client = { client_id: data.clientId };\n const started = Date.now();\n let tokens: oauth.TokenEndpointResponse;\n try {\n const response = await oauth.refreshTokenGrantRequest(\n as,\n client,\n oauth.None(),\n data.refreshToken,\n this.#tokenOptions(data),\n );\n tokens = await oauth.processRefreshTokenResponse(as, client, response);\n } catch (error) {\n await this.#endOnDeadGrant(error);\n return null;\n }\n return (await this.#activate(data, tokens, started)).accessToken;\n }\n\n /** The token request's shared options: the audience (RFC 8707 resource), a bounded timeout, and —\n * only for a LOCAL APP (`begin` already admits http there) — oauth4webapi's opt-out of its\n * HTTPS-only default. Keyed on the app's origin, never the issuer's: an issuer a person typed can\n * not talk a deployed app into sending its codes in the clear. */\n #tokenOptions(data: StoredSession): oauth.TokenEndpointRequestOptions {\n const options: oauth.TokenEndpointRequestOptions = {\n additionalParameters: { resource: data.resource },\n signal: AbortSignal.timeout(10_000),\n };\n if (isLocalOrigin(data.origin)) options[oauth.allowInsecureRequests] = true;\n return options;\n }\n\n /** Write the active session from a token response. A refresh reuses the prior refresh token when\n * the issuer does not rotate it. */\n async #activate(\n data: StoredSession,\n tokens: oauth.TokenEndpointResponse,\n started: number,\n ): Promise<Active> {\n const refreshToken =\n tokens.refresh_token || (data.phase === \"active\" ? data.refreshToken : undefined);\n if (!refreshToken) throw new Error(\"Iterate returned no refresh token.\");\n const { origin, issuer, resource, clientId, next } = data;\n const stored: Active = {\n origin,\n issuer,\n resource,\n clientId,\n client: data.client,\n next,\n phase: \"active\",\n // An omitted (or empty) `scope` means unchanged (RFC 6749 §5.1) — keep what the grant already\n // holds rather than silently narrow to `iterate`.\n scopes: tokens.scope\n ? OAuthScopes.parse(tokens.scope.split(\" \").filter(Boolean))\n : data.scopes,\n accessToken: tokens.access_token,\n refreshToken,\n expiresAt: started + (tokens.expires_in ?? 0) * 1000,\n until: data.phase === \"pending\" ? started + 30 * 24 * 3600_000 : data.until,\n };\n await this.ctx.storage.put(\"session\", stored);\n await this.ctx.storage.setAlarm(stored.until);\n return stored;\n }\n\n /** A dead code or refresh token (`invalid_grant`) ends the session; other failures are transient. */\n async #endOnDeadGrant(error: unknown): Promise<void> {\n if (error instanceof oauth.ResponseBodyError && error.error === \"invalid_grant\") {\n await this.#clear();\n return;\n }\n throw error instanceof oauth.ResponseBodyError\n ? new Error(`Iterate token exchange failed (${error.status}). Try again.`)\n : error;\n }\n /** An operation failure must not reset the DO or fail other tabs' requests. */\n #serial<T>(work: () => Promise<T>): Promise<T> {\n return this.ctx\n .blockConcurrencyWhile(() =>\n work().then(\n (value) => ({ value }),\n (error: unknown) => ({ error }),\n ),\n )\n .then((result) => {\n if (\"error\" in result) throw result.error;\n return result.value;\n });\n }\n\n async #clear() {\n await this.ctx.storage.deleteAll();\n await this.ctx.storage.deleteAlarm();\n }\n}\n"],"mappings":"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"}
|
package/dist/next/app.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app.mjs","names":[],"sources":["../../src/next/client/socket.ts","../../src/next/app.ts"],"sourcesContent":["/** Open a WebSocket, trying again for a while when the connection fails: a phone waking up, a\n * tunnel flapping, a cold edge — the things a first attempt trips over. Resolves with the socket\n * once it is OPEN; rejects with the last failure after the last attempt. The delays are the\n * waits BETWEEN attempts (the first is immediate). */\nconst RETRY_DELAYS_MS: readonly number[] = [250, 500, 1_000, 2_000, 4_000, 8_000];\n\nexport function openSocketWithRetry(\n url: string | URL,\n options: {\n delaysMs?: readonly number[];\n /** the constructor to use — a test's fake, `WebSocket` otherwise */\n WebSocket?: typeof WebSocket;\n sleep?: (ms: number) => Promise<void>;\n } = {},\n): Promise<WebSocket> {\n const delays = options.delaysMs || RETRY_DELAYS_MS;\n const Socket = options.WebSocket || WebSocket;\n const sleep = options.sleep || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n const once = () =>\n new Promise<WebSocket>((resolve, reject) => {\n const socket = new Socket(url);\n const opened = () => {\n socket.removeEventListener(\"close\", failed);\n resolve(socket);\n };\n const failed = () => {\n socket.removeEventListener(\"open\", opened);\n reject(new Error(\"WebSocket connection failed.\"));\n };\n socket.addEventListener(\"open\", opened, { once: true });\n socket.addEventListener(\"close\", failed, { once: true });\n });\n return (async () => {\n let attempt = 0;\n for (;;) {\n try {\n return await once();\n } catch (error) {\n if (attempt >= delays.length) throw error;\n await sleep(delays[attempt]!);\n attempt += 1;\n }\n }\n })();\n}\n","import { newWebSocketRpcSession, type RpcStub } from \"capnweb\";\nimport type { IterateApi, IterateSessionApi } from \"./api.ts\";\nimport { openSocketWithRetry } from \"./client/socket.ts\";\nimport { OAuthScopes } from \"./oauth-scopes.ts\";\n\n/** What `authenticate` resolves to: the session as a capnweb stub (pipelined; disposable) and the\n * bootstrap info it answered with. Declared, so the package's declarations stay serializable. */\nexport type AuthenticatedApp = {\n api: RpcStub<IterateSessionApi>;\n info: ReturnType<IterateSessionApi[\"info\"]>;\n /** The page names a project this sign-in does not include — its `/projects/<ref>` is absent from\n * `api.projects.list()`: leave for `/.auth/login`, whose page offers to sign in again (the\n * project ticked at consent, or another account) and returns to this very URL. Never settles,\n * like `authenticate`'s own leave for login. */\n signInFor(project: string): Promise<never>;\n};\nexport type IterateClient = { authenticate(next?: string): Promise<AuthenticatedApp> };\n\n/** The browser leaves for the issuer's login (a document navigation) and this never settles — no\n * framework in the loop: a TanStack `beforeLoad` awaiting it ends the way a thrown\n * `redirect({ reloadDocument: true })` did, a plain page simply navigates. */\nfunction leaveForLogin(login: string): Promise<never> {\n window.location.assign(login);\n return new Promise<never>(() => {});\n}\n\n/** One socket to `/api` with the session the OAuth gate resolved from the cookie. The `api` is\n * capnweb's pipelined `authenticate(...)` answer: usable before the socket has even opened, calls\n * queue until it has. */\ntype Connection = { api: RpcStub<IterateSessionApi>; socket: WebSocket; dispose(): void };\nfunction connectionOn(socket: WebSocket): Connection {\n const iterate = newWebSocketRpcSession<IterateApi>(socket);\n const api = iterate.authenticate({\n type: \"from-server-cookie\",\n }) as unknown as RpcStub<IterateSessionApi>;\n return { api, socket, dispose: () => iterate[Symbol.dispose]() };\n}\n\n/** Create once per app — a TanStack route's client-only `beforeLoad`, or a plain page's entry.\n * Every loader and action of the page shares the returned public RPC session.\n *\n * Connecting tries for a while (client/socket.ts: ≈16 s of attempts) before an error reaches the\n * page — a phone waking up or a flapping tunnel is not a reason to show \"connection failed\". And\n * the `api` the page holds is a proxy to the CURRENT connection: when the socket closes, the next\n * call opens a fresh one and pipelines onto it, so a dropped connection costs a reconnect, not the\n * page. A reconnect the platform refuses (the session ended elsewhere) rejects that call; the\n * page's retry runs `authenticate` again — a fresh one, the socket's close forgot the last — whose\n * probe sends the browser to log in. */\nexport function createIterateClient(options: { scopes?: string[] } = {}): IterateClient {\n const scopes = OAuthScopes.parse(options.scopes || []);\n let live: Connection | null = null;\n let connecting: Promise<AuthenticatedApp> | undefined;\n const socketUrl = () => {\n const url = new URL(\"/api\", window.location.href);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url;\n };\n /** Adopt a connection as the live one until its socket closes. A close also forgets the settled\n * `authenticate` — the next one probes `/api` again, so a session that ended elsewhere sends the\n * browser to log in instead of a retry that can only fail. */\n function adopt(socket: WebSocket): Connection {\n const connection = connectionOn(socket);\n live = connection;\n const forget = () => {\n if (live !== connection) return;\n live = null;\n connecting = undefined;\n };\n const dispose = () => {\n forget();\n connection.dispose();\n };\n socket.addEventListener(\n \"close\",\n () => {\n forget();\n window.removeEventListener(\"pagehide\", dispose);\n },\n { once: true },\n );\n window.addEventListener(\"pagehide\", dispose, { once: true });\n return connection;\n }\n // Every property read goes to the live connection — or to a fresh one when the last socket\n // closed (a new WebSocket, no wait: capnweb queues the call until it opens). What comes back is\n // capnweb's own stub for that property — a method stub carries its path, so it is returned as\n // is, never bound or otherwise touched (a capnweb stub answers `.bind` with another stub).\n const api = new Proxy({} as RpcStub<IterateSessionApi>, {\n get(_target, property) {\n const connection = live || adopt(new WebSocket(socketUrl()));\n return Reflect.get(connection.api as object, property);\n },\n });\n const loginUrl = (params: Record<string, string>) =>\n `/.auth/login?${new URLSearchParams({ scope: scopes.join(\" \"), ...params })}`;\n async function connect(next: string): Promise<AuthenticatedApp> {\n const login = loginUrl({ next });\n // HTTP distinguishes 401 from outages; a failed WebSocket is not evidence\n // that the visitor needs to log in. Errors reach the route's error boundary.\n const probe = await fetch(\"/api\", {\n method: \"POST\",\n body: \"\",\n signal: AbortSignal.timeout(10_000),\n });\n await probe.body?.cancel();\n if (probe.status === 401) return leaveForLogin(login);\n if (!probe.ok) throw new Error(`iterate is unavailable (${probe.status}). Please retry.`);\n if (!live) adopt(await openSocketWithRetry(socketUrl()));\n // Consent is task-based: the person may have granted fewer scopes than the app asked for\n // (every scope but `iterate` is optional on the consent page). The granted set is\n // `info.scopes` — an app reads it and offers a step-up link (`/.auth/login?scope=…`) for what\n // it lacks; it is never bounced back to consent for a permission the person declined.\n const info = await api.info();\n return {\n api,\n info,\n signInFor: (project) => leaveForLogin(loginUrl({ next: window.location.href, project })),\n };\n }\n return {\n authenticate(next = \"/\") {\n connecting ||= connect(next).catch((error) => {\n connecting = undefined;\n throw error;\n });\n return connecting;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** The same code/PKCE parameters for browser login and a console-minted token. */
|
|
2
|
-
export declare function authorizationCodeRequest(input: {
|
|
3
|
-
issuer: string;
|
|
4
|
-
clientId: string;
|
|
5
|
-
redirectUri: string;
|
|
6
|
-
resources: string[];
|
|
7
|
-
scopes?: string[];
|
|
8
|
-
}): Promise<{
|
|
9
|
-
url: URL;
|
|
10
|
-
state: string;
|
|
11
|
-
verifier: string;
|
|
12
|
-
}>;
|