@braidlabs/core 0.1.1 → 0.2.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.
Files changed (77) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/adapter.d.ts +13 -0
  3. package/src/adapters/adapter.js.map +1 -1
  4. package/src/adapters/compat-adapter.d.ts +7 -0
  5. package/src/adapters/compat-adapter.js +24 -16
  6. package/src/adapters/compat-adapter.js.map +1 -1
  7. package/src/adapters/contract-adapter.d.ts +21 -0
  8. package/src/adapters/contract-adapter.js +71 -0
  9. package/src/adapters/contract-adapter.js.map +1 -0
  10. package/src/config.d.ts +29 -0
  11. package/src/config.js +16 -0
  12. package/src/config.js.map +1 -1
  13. package/src/context/context-bus.d.ts +63 -3
  14. package/src/context/context-bus.js +147 -61
  15. package/src/context/context-bus.js.map +1 -1
  16. package/src/elements/fragment-slot.d.ts +114 -1
  17. package/src/elements/fragment-slot.js +743 -60
  18. package/src/elements/fragment-slot.js.map +1 -1
  19. package/src/env/create-env.d.ts +20 -8
  20. package/src/env/create-env.js +48 -16
  21. package/src/env/create-env.js.map +1 -1
  22. package/src/env/define-fragment.d.ts +43 -0
  23. package/src/env/define-fragment.js +23 -0
  24. package/src/env/define-fragment.js.map +1 -0
  25. package/src/env/fragment-env.d.ts +20 -1
  26. package/src/env/fragment-env.js +0 -12
  27. package/src/env/fragment-env.js.map +1 -1
  28. package/src/errors.d.ts +1 -1
  29. package/src/errors.js.map +1 -1
  30. package/src/index.d.ts +14 -1
  31. package/src/index.js +7 -1
  32. package/src/index.js.map +1 -1
  33. package/src/protocol.d.ts +20 -4
  34. package/src/protocol.js +23 -7
  35. package/src/protocol.js.map +1 -1
  36. package/src/realm/realm-manager.d.ts +19 -2
  37. package/src/realm/realm-manager.js +32 -7
  38. package/src/realm/realm-manager.js.map +1 -1
  39. package/src/realm/sandbox-realm.d.ts +95 -0
  40. package/src/realm/sandbox-realm.js +194 -0
  41. package/src/realm/sandbox-realm.js.map +1 -0
  42. package/src/weave/capabilities.d.ts +71 -0
  43. package/src/weave/capabilities.js +61 -0
  44. package/src/weave/capabilities.js.map +1 -0
  45. package/src/weave/channel.d.ts +63 -0
  46. package/src/weave/channel.js +189 -0
  47. package/src/weave/channel.js.map +1 -0
  48. package/src/weave/closing.d.ts +99 -0
  49. package/src/weave/closing.js +130 -0
  50. package/src/weave/closing.js.map +1 -0
  51. package/src/weave/context-bridge.d.ts +81 -0
  52. package/src/weave/context-bridge.js +145 -0
  53. package/src/weave/context-bridge.js.map +1 -0
  54. package/src/weave/contract.d.ts +89 -0
  55. package/src/weave/contract.js +143 -0
  56. package/src/weave/contract.js.map +1 -0
  57. package/src/weave/envelope.d.ts +80 -0
  58. package/src/weave/envelope.js +49 -0
  59. package/src/weave/envelope.js.map +1 -0
  60. package/src/weave/guest.d.ts +59 -0
  61. package/src/weave/guest.js +120 -0
  62. package/src/weave/guest.js.map +1 -0
  63. package/src/weave/handshake.d.ts +124 -0
  64. package/src/weave/handshake.js +109 -0
  65. package/src/weave/handshake.js.map +1 -0
  66. package/src/weave/liveness.d.ts +90 -0
  67. package/src/weave/liveness.js +189 -0
  68. package/src/weave/liveness.js.map +1 -0
  69. package/src/weave/messages.d.ts +27 -0
  70. package/src/weave/messages.js +13 -0
  71. package/src/weave/messages.js.map +1 -0
  72. package/src/weave/port-backing.d.ts +19 -0
  73. package/src/weave/port-backing.js +54 -0
  74. package/src/weave/port-backing.js.map +1 -0
  75. package/src/weave/same-realm-backing.d.ts +21 -0
  76. package/src/weave/same-realm-backing.js +44 -0
  77. package/src/weave/same-realm-backing.js.map +1 -0
@@ -1,8 +1,34 @@
1
1
  import { BraidError } from '../errors.js';
2
- const values = new Map();
3
- const subscriptions = new Map();
4
- const schemas = new Map();
5
- export const braidContext = {
2
+ /**
3
+ * A context bus.
4
+ *
5
+ * State moved from module scope to instance fields so that a page can hold more than one — see
6
+ * `createContextBus`. Every method body below is unchanged by that move; the semantics that make
7
+ * this a bus rather than an event emitter are the versioning ones, and they are per-bus by nature.
8
+ */
9
+ class ContextBus {
10
+ #values = new Map();
11
+ #subscriptions = new Map();
12
+ #schemas = new Map();
13
+ /**
14
+ * Firehose listeners: notified of *which* key changed, never of the value.
15
+ *
16
+ * This exists for the boundary router, which mirrors the whole bus into a fragment realm and
17
+ * therefore has to learn about keys that did not exist when it attached. It carries no value
18
+ * because every observer needs the value projected to a different version, so handing one out here
19
+ * would be handing out the wrong one.
20
+ */
21
+ #observers = new Set();
22
+ #notifyObservers(key) {
23
+ for (const observer of [...this.#observers]) {
24
+ try {
25
+ observer(key);
26
+ }
27
+ catch (error) {
28
+ console.error(`braid: a context observer threw for "${key}"`, error);
29
+ }
30
+ }
31
+ }
6
32
  /**
7
33
  * Declares the contract a context key carries.
8
34
  *
@@ -11,12 +37,17 @@ export const braidContext = {
11
37
  * real page.
12
38
  */
13
39
  register(key, schema) {
14
- schemas.set(key, schema);
15
- },
40
+ this.#schemas.set(key, schema);
41
+ // A schema registered after a value was published changes how that value projects, and a
42
+ // mirror holding the pre-registration shape would stay wrong until the next `set` — which for
43
+ // a slow-moving key like the signed-in user may be never.
44
+ if (this.#values.has(key))
45
+ this.#notifyObservers(key);
46
+ }
16
47
  /** The schema registered for a key, if any. */
17
48
  schemaFor(key) {
18
- return schemas.get(key);
19
- },
49
+ return this.#schemas.get(key);
50
+ }
20
51
  /**
21
52
  * Publishes a context value to every fragment (and host code) subscribed to the key.
22
53
  *
@@ -26,10 +57,10 @@ export const braidContext = {
26
57
  * failing to update, which is the least debuggable shape a bug can take on a composed page.
27
58
  */
28
59
  set(key, value) {
29
- values.set(key, value);
30
- for (const subscription of [...(subscriptions.get(key) ?? [])]) {
60
+ this.#values.set(key, value);
61
+ for (const subscription of [...(this.#subscriptions.get(key) ?? [])]) {
31
62
  try {
32
- subscription.listener(project(key, value, subscription.as));
63
+ subscription.listener(this.#project(key, value, subscription.as));
33
64
  }
34
65
  catch (error) {
35
66
  // Reported rather than swallowed: a subscriber that never hears anything again is worth
@@ -37,23 +68,24 @@ export const braidContext = {
37
68
  console.error(`braid: a "${key}" context subscriber threw; other subscribers were unaffected`, error);
38
69
  }
39
70
  }
40
- },
71
+ this.#notifyObservers(key);
72
+ }
41
73
  get(key, options) {
42
- const value = values.get(key);
74
+ const value = this.#values.get(key);
43
75
  if (value === undefined)
44
76
  return undefined;
45
77
  // A read at an unreachable version is the same error as a subscription at one, and is raised
46
78
  // rather than quietly answered at the publisher's version: handing a fragment a shape it cannot
47
79
  // parse is how a context bus produces a stack trace in someone else's code.
48
- assertReachable(key, options?.as, options?.fragmentId);
49
- return project(key, value, options?.as);
50
- },
80
+ this.#assertReachable(key, options?.as, options?.fragmentId);
81
+ return this.#project(key, value, options?.as);
82
+ }
51
83
  subscribe(key, listener, options) {
52
- assertReachable(key, options?.as, options?.fragmentId);
53
- let keySubscriptions = subscriptions.get(key);
84
+ this.#assertReachable(key, options?.as, options?.fragmentId);
85
+ let keySubscriptions = this.#subscriptions.get(key);
54
86
  if (!keySubscriptions) {
55
87
  keySubscriptions = new Set();
56
- subscriptions.set(key, keySubscriptions);
88
+ this.#subscriptions.set(key, keySubscriptions);
57
89
  }
58
90
  const subscription = {
59
91
  listener,
@@ -63,55 +95,109 @@ export const braidContext = {
63
95
  const unsubscribe = () => void keySubscriptions.delete(subscription);
64
96
  options?.signal?.addEventListener('abort', unsubscribe, { once: true });
65
97
  return unsubscribe;
66
- },
98
+ }
99
+ /** Every key that currently holds a value. Used to seed a fragment's mirror at handshake. */
100
+ keys() {
101
+ return [...this.#values.keys()];
102
+ }
103
+ /**
104
+ * Observes *which* keys change, including keys that do not exist yet.
105
+ *
106
+ * `subscribe` cannot serve this: it binds to one key by name, so a mirror built from it would
107
+ * silently miss every key first published after the fragment mounted.
108
+ */
109
+ observe(listener) {
110
+ this.#observers.add(listener);
111
+ return () => void this.#observers.delete(listener);
112
+ }
113
+ /**
114
+ * Raises if `as` cannot be projected to from the published version.
115
+ *
116
+ * The check `subscribe` runs, exposed so it can be run *earlier* — at the boundary handshake,
117
+ * before a fragment has been handed any state. Decided from the declared chain rather than from
118
+ * a stored value, so a fragment that mounts before the first broadcast gets the same answer as
119
+ * one that mounts after it.
120
+ */
121
+ assertReachable(key, as, fragmentId) {
122
+ this.#assertReachable(key, as, fragmentId);
123
+ }
67
124
  /** Exposed for tests. */
68
125
  clear() {
69
- values.clear();
70
- subscriptions.clear();
71
- schemas.clear();
72
- },
73
- };
126
+ this.#values.clear();
127
+ this.#subscriptions.clear();
128
+ this.#schemas.clear();
129
+ this.#observers.clear();
130
+ }
131
+ /**
132
+ * Projects a published value to one subscriber's version.
133
+ *
134
+ * `write({ as })` down-migrates and returns the older envelope — the polite move when the reader is
135
+ * known to be older than the publisher. Subscribers receive the payload rather than the envelope:
136
+ * the version they asked for is the version they get, so handing them a wrapper to unwrap would be
137
+ * ceremony without information.
138
+ */
139
+ #project(key, value, as) {
140
+ const schema = this.#schemas.get(key);
141
+ if (!schema)
142
+ return structuredClone(value);
143
+ const envelope = as === undefined || as === schema.version ? schema.write(value) : schema.write(value, { as });
144
+ return structuredClone(envelope.payload);
145
+ }
146
+ /**
147
+ * Refuses a subscriber whose version cannot be reached from the publisher's.
148
+ *
149
+ * Decided from the declared chain rather than by attempting a migration, so the answer does not
150
+ * depend on a value happening to have been published yet — a fragment that mounts before the first
151
+ * broadcast deserves the same error as one that mounts after it.
152
+ */
153
+ #assertReachable(key, as, fragmentId) {
154
+ const schema = this.#schemas.get(key);
155
+ if (!schema || as === undefined || as === schema.version)
156
+ return;
157
+ const raise = (message, fixHint) => {
158
+ throw new BraidError(message, { fragmentId: fragmentId ?? key, stage: 'context-version', fixHint });
159
+ };
160
+ if (as > schema.version) {
161
+ raise(`context "${key}" is published at v${schema.version}, and this subscriber asked for v${as}`, 'the publisher is older than the subscriber — deploy the host, or subscribe at a version it can produce');
162
+ }
163
+ if (as < 1)
164
+ raise(`context "${key}" has no v${as}`, 'contract versions start at 1');
165
+ // Every step between the subscriber's version and the publisher's has to walk back down.
166
+ const missing = schema.steps
167
+ .filter((step) => step.to > as && step.to <= schema.version && !step.down)
168
+ .map((step) => `v${step.to} (${step.description})`);
169
+ if (missing.length > 0) {
170
+ raise(`context "${key}" cannot be projected from v${schema.version} down to v${as}: ` +
171
+ `${missing.join(', ')} ${missing.length === 1 ? 'declares' : 'declare'} no down migration`, `add a down migration to the step that introduced ${missing[0]}, or subscribe at v${schema.version}`);
172
+ }
173
+ }
174
+ }
74
175
  /**
75
- * Projects a published value to one subscriber's version.
176
+ * The page-level context bus: what `<fragment-slot>` routes to unless a fragment is scoped elsewhere.
76
177
  *
77
- * `write({ as })` down-migrates and returns the older envelope — the polite move when the reader is
78
- * known to be older than the publisher. Subscribers receive the payload rather than the envelope:
79
- * the version they asked for is the version they get, so handing them a wrapper to unwrap would be
80
- * ceremony without information.
178
+ * Still a singleton, still the default, and every existing import of it behaves exactly as before.
179
+ * What changed is that it is now *an instance of something*, which is what makes a second bus
180
+ * possible at all.
81
181
  */
82
- function project(key, value, as) {
83
- const schema = schemas.get(key);
84
- if (!schema)
85
- return structuredClone(value);
86
- const envelope = as === undefined || as === schema.version ? schema.write(value) : schema.write(value, { as });
87
- return structuredClone(envelope.payload);
88
- }
182
+ export const braidContext = new ContextBus();
89
183
  /**
90
- * Refuses a subscriber whose version cannot be reached from the publisher's.
184
+ * Creates a bus a fragment owns, rather than one it merely subscribes to.
185
+ *
186
+ * Keys published here never reach the page bus and are never visible to any other fragment — not
187
+ * their values, not their names, not the timing of their changes. Two uses, and they are the two
188
+ * readings the plan left open:
189
+ *
190
+ * - **A private namespace.** A fragment that wants durable, versioned, cross-realm state that is
191
+ * nobody else's business gets it without inventing a second storage mechanism, and without the
192
+ * host having to grant or withhold anything.
193
+ * - **A subtree bus**, once nested fragments exist: a parent fragment hands its own bus to the
194
+ * fragments it composes, and they coordinate below the host's notice.
91
195
  *
92
- * Decided from the declared chain rather than by attempting a migration, so the answer does not
93
- * depend on a value happening to have been published yet — a fragment that mounts before the first
94
- * broadcast deserves the same error as one that mounts after it.
196
+ * The first works today. The second needs nested fragments to be a first-class concept, which is a
197
+ * larger commitment and is not made here — but nothing about this signature has to change when it
198
+ * is, because the routing already addresses buses by `scope` rather than by position.
95
199
  */
96
- function assertReachable(key, as, fragmentId) {
97
- const schema = schemas.get(key);
98
- if (!schema || as === undefined || as === schema.version)
99
- return;
100
- const raise = (message, fixHint) => {
101
- throw new BraidError(message, { fragmentId: fragmentId ?? key, stage: 'context-version', fixHint });
102
- };
103
- if (as > schema.version) {
104
- raise(`context "${key}" is published at v${schema.version}, and this subscriber asked for v${as}`, 'the publisher is older than the subscriber — deploy the host, or subscribe at a version it can produce');
105
- }
106
- if (as < 1)
107
- raise(`context "${key}" has no v${as}`, 'contract versions start at 1');
108
- // Every step between the subscriber's version and the publisher's has to walk back down.
109
- const missing = schema.steps
110
- .filter((step) => step.to > as && step.to <= schema.version && !step.down)
111
- .map((step) => `v${step.to} (${step.description})`);
112
- if (missing.length > 0) {
113
- raise(`context "${key}" cannot be projected from v${schema.version} down to v${as}: ` +
114
- `${missing.join(', ')} ${missing.length === 1 ? 'declares' : 'declare'} no down migration`, `add a down migration to the step that introduced ${missing[0]}, or subscribe at v${schema.version}`);
115
- }
200
+ export function createContextBus() {
201
+ return new ContextBus();
116
202
  }
117
203
  //# sourceMappingURL=context-bus.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-bus.js","sourceRoot":"","sources":["../../../../../libs/core/src/context/context-bus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-bus.js","sourceRoot":"","sources":["../../../../../libs/core/src/context/context-bus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAmD1C;;;;;;GAMG;AACH,MAAM,UAAU;IACL,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IACrC,cAAc,GAAG,IAAI,GAAG,EAA6B,CAAC;IACtD,QAAQ,GAAG,IAAI,GAAG,EAAoC,CAAC;IAEhE;;;;;;;OAOG;IACM,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEvD,gBAAgB,CAAC,GAAW;QAC1B,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC;gBACH,QAAQ,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAI,GAAW,EAAE,MAA0B;QACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAkC,CAAC,CAAC;QAC3D,yFAAyF;QACzF,8FAA8F;QAC9F,0DAA0D;QAC1D,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,+CAA+C;IAC/C,SAAS,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;;OAOG;IACH,GAAG,CAAC,GAAW,EAAE,KAAc;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE7B,KAAK,MAAM,YAAY,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACrE,IAAI,CAAC;gBACH,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,wFAAwF;gBACxF,oEAAoE;gBACpE,OAAO,CAAC,KAAK,CAAC,aAAa,GAAG,+DAA+D,EAAE,KAAK,CAAC,CAAC;YACxG,CAAC;QACH,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,OAA4B;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAE1C,6FAA6F;QAC7F,gGAAgG;QAChG,4EAA4E;QAC5E,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,QAAyB,EAAE,OAAiC;QACjF,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAE7D,IAAI,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;YAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,YAAY,GAAiB;YACjC,QAAQ;YACR,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;SACzD,CAAC;QACF,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAEnC,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACrE,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,6FAA6F;IAC7F,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,QAA+B;QACrC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,GAAW,EAAE,EAAW,EAAE,UAAmB;QAC3D,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;IAC7C,CAAC;IAED,yBAAyB;IACzB,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAEH;;;;;;;OAOG;IACD,QAAQ,CAAC,GAAW,EAAE,KAAc,EAAE,EAAsB;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM;YAAE,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;QAE3C,MAAM,QAAQ,GAAG,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/G,OAAO,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAEH;;;;;;OAMG;IACD,gBAAgB,CAAC,GAAW,EAAE,EAAsB,EAAE,UAA8B;QAClF,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,MAAM,CAAC,OAAO;YAAE,OAAO;QAEjE,MAAM,KAAK,GAAG,CAAC,OAAe,EAAE,OAAe,EAAS,EAAE;YACxD,MAAM,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,UAAU,IAAI,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC;QACtG,CAAC,CAAC;QAEF,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,KAAK,CACH,YAAY,GAAG,sBAAsB,MAAM,CAAC,OAAO,oCAAoC,EAAE,EAAE,EAC3F,wGAAwG,CACzG,CAAC;QACJ,CAAC;QAED,IAAI,EAAE,GAAG,CAAC;YAAE,KAAK,CAAC,YAAY,GAAG,aAAa,EAAE,EAAE,EAAE,8BAA8B,CAAC,CAAC;QAEpF,yFAAyF;QACzF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK;aACzB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;aACzE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QAEtD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CACH,YAAY,GAAG,+BAA+B,MAAM,CAAC,OAAO,aAAa,EAAE,IAAI;gBAC7E,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,oBAAoB,EAC5F,oDAAoD,OAAO,CAAC,CAAC,CAAC,sBAAsB,MAAM,CAAC,OAAO,EAAE,CACrG,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,UAAU,EAAE,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,UAAU,EAAE,CAAC;AAC1B,CAAC"}
@@ -1,4 +1,22 @@
1
- export type FragmentSlotState = 'idle' | 'loading' | 'ready' | 'error';
1
+ import { BraidError } from '../errors.js';
2
+ import { LivenessState } from '../weave/liveness.js';
3
+ /**
4
+ * What the slot reports about its fragment.
5
+ *
6
+ * The original four are all still here and still mean what they meant — `ready` in particular is
7
+ * kept as the name for "mounted, first beat not yet observed", which the liveness machine calls
8
+ * `connecting`. Renaming it would have broken every host checking `slot.state === 'ready'` in
9
+ * exchange for a word.
10
+ *
11
+ * The new three are the ones `ready` used to have to cover on its own:
12
+ *
13
+ * - `healthy` — beating.
14
+ * - `unobservable` — the tab is hidden, so the host has stopped being able to tell. Not a fault.
15
+ * - `suspect` → `gone` — beats stopped while the fragment should have been running.
16
+ */
17
+ export type FragmentSlotState = 'idle' | 'loading' | 'ready' | 'error' | 'closing' | 'healthy' | 'unobservable' | 'suspect' | 'gone';
18
+ /** `connecting` is reported as `ready`; every other liveness state carries its own name. */
19
+ export declare function slotStateFor(liveness: LivenessState): FragmentSlotState;
2
20
  /**
3
21
  * Finds server-rendered fragment content the gateway pierced into a slot.
4
22
  *
@@ -8,6 +26,28 @@ export type FragmentSlotState = 'idle' | 'loading' | 'ready' | 'error';
8
26
  * and replaces identical content — so it is worth a named helper and a test.
9
27
  */
10
28
  export declare function findPiercedContentRoot(shadowRoot: ShadowRoot | null): HTMLElement | null;
29
+ /**
30
+ * The slot's own styles, including the fallback rules.
31
+ *
32
+ * `gone` and `error` hide the fragment's content and reveal whatever the host put in the fallback
33
+ * slot. Note what is *not* hidden: `suspect`. A fragment that is merely slow keeps its rendered
34
+ * content on screen, because replacing a working-if-sluggish UI with an apology is a worse outcome
35
+ * than a moment of unresponsiveness — and suspicion is designed to be recoverable.
36
+ */
37
+ export declare const SLOT_STYLES: string;
38
+ /**
39
+ * Ensures the shadow root can present host-provided fallback content:
40
+ *
41
+ * ```html
42
+ * <fragment-slot name="checkout"><p slot="fallback">Checkout is unavailable.</p></fragment-slot>
43
+ * ```
44
+ *
45
+ * Added separately from the styles because pierced fragments arrive with a shadow root the gateway
46
+ * wrote, which has content but no fallback slot — and a fallback that only worked for
47
+ * client-rendered fragments would be missing from exactly the pages that server-render because
48
+ * their first paint matters.
49
+ */
50
+ export declare function ensureFallbackSlot(shadowRoot: ShadowRoot): void;
11
51
  /**
12
52
  * `<fragment-slot>` — the custom element a host renders to mount a fragment.
13
53
  *
@@ -30,6 +70,19 @@ export declare class FragmentSlot extends HTMLElement {
30
70
  static get observedAttributes(): string[];
31
71
  get state(): FragmentSlotState;
32
72
  get name(): string;
73
+ /**
74
+ * The fragment's unsaved-work declaration, or null.
75
+ *
76
+ * Readable synchronously because the only place a host can act on it — a `beforeunload`
77
+ * handler — cannot await anything. Braid never installs that handler itself:
78
+ *
79
+ * ```ts
80
+ * addEventListener('beforeunload', (event) => {
81
+ * if (slot.dirty) event.preventDefault();
82
+ * });
83
+ * ```
84
+ */
85
+ get dirty(): string | null;
33
86
  get props(): Record<string, unknown>;
34
87
  set props(value: Record<string, unknown>);
35
88
  attributeChangedCallback(attribute: string, oldValue: string | null, newValue: string | null): void;
@@ -37,3 +90,63 @@ export declare class FragmentSlot extends HTMLElement {
37
90
  disconnectedCallback(): void;
38
91
  reload(): Promise<void>;
39
92
  }
93
+ /**
94
+ * Fetches the fragment's document from the gateway's document namespace.
95
+ *
96
+ * Redirects are refused rather than followed. Followed, a cross-origin one (an auth redirect to an
97
+ * identity provider, say) dies as an opaque CORS error that hides the status that caused it, and a
98
+ * relative one resolves against the gateway's origin — outside the namespace — so the shell's or a
99
+ * login page's html would be injected as the fragment's document without any error at all.
100
+ *
101
+ * A browser hands back an `opaqueredirect` for `redirect: 'manual'` (status 0, no readable
102
+ * `Location`); other runtimes expose the real 3xx, so the target is named when it can be read.
103
+ *
104
+ * A fragment that opted in to `redirect: 'navigate'` is the exception: the gateway answers with a
105
+ * `409` carrying the target, which becomes a {@link FragmentRedirectError} for the slot to follow.
106
+ *
107
+ * Resolves with the document and whether it arrived as HTML — which, through the gateway, is the one
108
+ * kind of document it prepares (scripts inert, handlers stripped). Anything else is not safe to put
109
+ * on screen ahead of the adapter.
110
+ */
111
+ export declare function fetchFragmentHtml(fragmentId: string, routeSrcUrl: URL, signal: AbortSignal): Promise<{
112
+ html: string;
113
+ isHtml: boolean;
114
+ }>;
115
+ /**
116
+ * The gateway relayed a redirect from the fragment and vouched for its target. Carries the full
117
+ * target; the message does not, because a login's query string is where its tokens live and an
118
+ * error message ends up in the console and in the `braid:error` event.
119
+ */
120
+ export declare class FragmentRedirectError extends BraidError {
121
+ readonly target: string;
122
+ constructor(fragmentId: string, target: string, documentUrl: string);
123
+ }
124
+ /** How long a second redirect from the same fragment is treated as a loop rather than a new one. */
125
+ export declare const REDIRECT_LOOP_WINDOW_MS = 30000;
126
+ export interface RedirectDeps {
127
+ /** Navigates the top-level page. */
128
+ assign(url: string): void;
129
+ /** `sessionStorage`, as a thunk: merely reaching for it throws in some privacy modes. */
130
+ storage(): Pick<Storage, 'getItem' | 'setItem'>;
131
+ now(): number;
132
+ /** What a relative target is relative to. */
133
+ baseHref: string;
134
+ /**
135
+ * Fragments whose redirect this page is already following. Two slots for one fragment hear the
136
+ * same redirect in the same tick; the second is not a loop, it is the same navigation.
137
+ */
138
+ inFlight: Set<string>;
139
+ }
140
+ /**
141
+ * Sends the page where the fragment's redirect asked, once.
142
+ *
143
+ * @returns `null` when the page is navigating, or the error to report when it is not.
144
+ *
145
+ * Two things stop it. The scheme must be http(s) — the gateway already checks, and this is the one
146
+ * line between a `javascript:` URL and `location.assign`. And a fragment that redirects again
147
+ * within {@link REDIRECT_LOOP_WINDOW_MS} is a loop: login succeeded, the user came back, and the
148
+ * fragment still cannot tell who they are. A page reload loses all in-memory state, so the only
149
+ * thing that can remember the last redirect is `sessionStorage`; if that is unavailable, the answer
150
+ * is no — an unbounded loop is worse than an error.
151
+ */
152
+ export declare function followFragmentRedirect(error: FragmentRedirectError, deps: RedirectDeps): BraidError | null;