@tier0/sdk-v2 0.9.10 → 0.10.1

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 (238) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/LICENSE +21 -21
  3. package/README.md +162 -24
  4. package/dist/cjs/client.d.ts +83 -3
  5. package/dist/cjs/client.js +4 -6
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/cloud-server.d.ts +33 -26
  8. package/dist/cjs/cloud-server.js +102 -100
  9. package/dist/cjs/cloud-server.js.map +1 -1
  10. package/dist/cjs/cloud-transport.d.ts +15 -20
  11. package/dist/cjs/cloud-transport.js +87 -68
  12. package/dist/cjs/cloud-transport.js.map +1 -1
  13. package/dist/cjs/data/bootstrap.js +3 -4
  14. package/dist/cjs/data/bootstrap.js.map +1 -1
  15. package/dist/cjs/data/call.d.ts +1 -3
  16. package/dist/cjs/data/call.js +8 -12
  17. package/dist/cjs/data/call.js.map +1 -1
  18. package/dist/cjs/data/client.d.ts +8 -7
  19. package/dist/cjs/data/client.js +24 -11
  20. package/dist/cjs/data/client.js.map +1 -1
  21. package/dist/cjs/data/collection.d.ts +3 -5
  22. package/dist/cjs/data/collection.js +3 -4
  23. package/dist/cjs/data/collection.js.map +1 -1
  24. package/dist/cjs/data/context.d.ts +5 -11
  25. package/dist/cjs/data/context.js +21 -36
  26. package/dist/cjs/data/context.js.map +1 -1
  27. package/dist/cjs/data/error.js +1 -1
  28. package/dist/cjs/data/error.js.map +1 -1
  29. package/dist/cjs/data/index.d.ts +5 -3
  30. package/dist/cjs/data/index.js +5 -5
  31. package/dist/cjs/data/index.js.map +1 -1
  32. package/dist/cjs/data/relay.d.ts +51 -0
  33. package/dist/cjs/data/relay.js +371 -0
  34. package/dist/cjs/data/relay.js.map +1 -0
  35. package/dist/cjs/data/subscribe.d.ts +22 -38
  36. package/dist/cjs/data/subscribe.js +22 -28
  37. package/dist/cjs/data/subscribe.js.map +1 -1
  38. package/dist/cjs/event-stream.d.ts +28 -0
  39. package/dist/cjs/event-stream.js +222 -0
  40. package/dist/cjs/event-stream.js.map +1 -0
  41. package/dist/cjs/mq/client.d.ts +32 -1
  42. package/dist/cjs/mq/client.js +404 -69
  43. package/dist/cjs/mq/client.js.map +1 -1
  44. package/dist/cjs/mq/index.d.ts +1 -0
  45. package/dist/cjs/mq/index.js +3 -1
  46. package/dist/cjs/mq/index.js.map +1 -1
  47. package/dist/cjs/mq/topic.d.ts +29 -0
  48. package/dist/cjs/mq/topic.js +48 -0
  49. package/dist/cjs/mq/topic.js.map +1 -0
  50. package/dist/cjs/mq/types.d.ts +21 -1
  51. package/dist/cjs/mq/types.js.map +1 -1
  52. package/dist/cjs/openapi/api.generated.d.ts +21 -0
  53. package/dist/cjs/openapi/api.generated.js +11 -0
  54. package/dist/cjs/openapi/api.generated.js.map +1 -1
  55. package/dist/cjs/openapi/client.d.ts +46 -0
  56. package/dist/cjs/openapi/client.js +158 -10
  57. package/dist/cjs/openapi/client.js.map +1 -1
  58. package/dist/cjs/openapi/codecs.generated.d.ts +75 -0
  59. package/dist/cjs/openapi/codecs.generated.js +1 -1
  60. package/dist/cjs/openapi/codecs.generated.js.map +1 -1
  61. package/dist/cjs/openapi/types.generated.d.ts +206 -1
  62. package/dist/cjs/openapi/types.generated.js.map +1 -1
  63. package/dist/cjs/page-runtime.d.ts +48 -0
  64. package/dist/cjs/page-runtime.js +74 -0
  65. package/dist/cjs/page-runtime.js.map +1 -0
  66. package/dist/cjs/platform-proxy.d.ts +17 -0
  67. package/dist/cjs/platform-proxy.js +52 -0
  68. package/dist/cjs/platform-proxy.js.map +1 -0
  69. package/dist/cjs/runtime-context.d.ts +2 -1
  70. package/dist/cjs/runtime-context.js +6 -2
  71. package/dist/cjs/runtime-context.js.map +1 -1
  72. package/dist/cjs/runtime-env.js.map +1 -1
  73. package/dist/cjs/source.d.ts +19 -1
  74. package/dist/cjs/source.js +9 -3
  75. package/dist/cjs/source.js.map +1 -1
  76. package/dist/cjs/stream.js +3 -2
  77. package/dist/cjs/stream.js.map +1 -1
  78. package/dist/cjs/streams/contract.generated.d.ts +7 -1
  79. package/dist/cjs/streams/contract.generated.js.map +1 -1
  80. package/dist/cjs/streams/messages.js +1 -10
  81. package/dist/cjs/streams/messages.js.map +1 -1
  82. package/dist/cjs/streams/requests.d.ts +3 -0
  83. package/dist/cjs/streams/requests.js +5 -2
  84. package/dist/cjs/streams/requests.js.map +1 -1
  85. package/dist/cjs/streams/transport.d.ts +1 -13
  86. package/dist/cjs/streams/transport.js +2 -18
  87. package/dist/cjs/streams/transport.js.map +1 -1
  88. package/dist/cjs/uns-v2/client.d.ts +1 -6
  89. package/dist/cjs/uns-v2/client.js +30 -6
  90. package/dist/cjs/uns-v2/client.js.map +1 -1
  91. package/dist/cjs/uns-v2/data-client.d.ts +5 -2
  92. package/dist/cjs/uns-v2/data-client.js +7 -4
  93. package/dist/cjs/uns-v2/data-client.js.map +1 -1
  94. package/dist/cjs/uns-v2/generated.d.ts +6783 -4655
  95. package/dist/cjs/uns-v2/generated.js +221 -65
  96. package/dist/cjs/uns-v2/generated.js.map +1 -1
  97. package/dist/cjs/uns-v2/sse.d.ts +7 -1
  98. package/dist/cjs/uns-v2/sse.js +12 -2
  99. package/dist/cjs/uns-v2/sse.js.map +1 -1
  100. package/dist/esm/client.d.ts +83 -3
  101. package/dist/esm/client.js +5 -7
  102. package/dist/esm/client.js.map +1 -1
  103. package/dist/esm/cloud-server.d.ts +33 -26
  104. package/dist/esm/cloud-server.js +99 -97
  105. package/dist/esm/cloud-server.js.map +1 -1
  106. package/dist/esm/cloud-transport.d.ts +15 -20
  107. package/dist/esm/cloud-transport.js +86 -67
  108. package/dist/esm/cloud-transport.js.map +1 -1
  109. package/dist/esm/data/bootstrap.js +3 -4
  110. package/dist/esm/data/bootstrap.js.map +1 -1
  111. package/dist/esm/data/call.d.ts +1 -3
  112. package/dist/esm/data/call.js +9 -13
  113. package/dist/esm/data/call.js.map +1 -1
  114. package/dist/esm/data/client.d.ts +8 -7
  115. package/dist/esm/data/client.js +22 -9
  116. package/dist/esm/data/client.js.map +1 -1
  117. package/dist/esm/data/collection.d.ts +3 -5
  118. package/dist/esm/data/collection.js +3 -4
  119. package/dist/esm/data/collection.js.map +1 -1
  120. package/dist/esm/data/context.d.ts +5 -11
  121. package/dist/esm/data/context.js +19 -33
  122. package/dist/esm/data/context.js.map +1 -1
  123. package/dist/esm/data/error.js +1 -1
  124. package/dist/esm/data/error.js.map +1 -1
  125. package/dist/esm/data/index.d.ts +5 -3
  126. package/dist/esm/data/index.js +3 -2
  127. package/dist/esm/data/index.js.map +1 -1
  128. package/dist/esm/data/relay.d.ts +51 -0
  129. package/dist/esm/data/relay.js +368 -0
  130. package/dist/esm/data/relay.js.map +1 -0
  131. package/dist/esm/data/subscribe.d.ts +22 -38
  132. package/dist/esm/data/subscribe.js +22 -27
  133. package/dist/esm/data/subscribe.js.map +1 -1
  134. package/dist/esm/event-stream.d.ts +28 -0
  135. package/dist/esm/event-stream.js +217 -0
  136. package/dist/esm/event-stream.js.map +1 -0
  137. package/dist/esm/mq/client.d.ts +32 -1
  138. package/dist/esm/mq/client.js +404 -69
  139. package/dist/esm/mq/client.js.map +1 -1
  140. package/dist/esm/mq/index.d.ts +1 -0
  141. package/dist/esm/mq/index.js +1 -0
  142. package/dist/esm/mq/index.js.map +1 -1
  143. package/dist/esm/mq/topic.d.ts +29 -0
  144. package/dist/esm/mq/topic.js +44 -0
  145. package/dist/esm/mq/topic.js.map +1 -0
  146. package/dist/esm/mq/types.d.ts +21 -1
  147. package/dist/esm/mq/types.js.map +1 -1
  148. package/dist/esm/openapi/api.generated.d.ts +21 -0
  149. package/dist/esm/openapi/api.generated.js +11 -0
  150. package/dist/esm/openapi/api.generated.js.map +1 -1
  151. package/dist/esm/openapi/client.d.ts +46 -0
  152. package/dist/esm/openapi/client.js +157 -10
  153. package/dist/esm/openapi/client.js.map +1 -1
  154. package/dist/esm/openapi/codecs.generated.d.ts +75 -0
  155. package/dist/esm/openapi/codecs.generated.js +1 -1
  156. package/dist/esm/openapi/codecs.generated.js.map +1 -1
  157. package/dist/esm/openapi/types.generated.d.ts +206 -1
  158. package/dist/esm/openapi/types.generated.js.map +1 -1
  159. package/dist/esm/page-runtime.d.ts +48 -0
  160. package/dist/esm/page-runtime.js +65 -0
  161. package/dist/esm/page-runtime.js.map +1 -0
  162. package/dist/esm/platform-proxy.d.ts +17 -0
  163. package/dist/esm/platform-proxy.js +47 -0
  164. package/dist/esm/platform-proxy.js.map +1 -0
  165. package/dist/esm/runtime-context.d.ts +2 -1
  166. package/dist/esm/runtime-context.js +6 -2
  167. package/dist/esm/runtime-context.js.map +1 -1
  168. package/dist/esm/runtime-env.js.map +1 -1
  169. package/dist/esm/source.d.ts +19 -1
  170. package/dist/esm/source.js +10 -4
  171. package/dist/esm/source.js.map +1 -1
  172. package/dist/esm/stream.js +4 -3
  173. package/dist/esm/stream.js.map +1 -1
  174. package/dist/esm/streams/contract.generated.d.ts +7 -1
  175. package/dist/esm/streams/contract.generated.js.map +1 -1
  176. package/dist/esm/streams/messages.js +1 -10
  177. package/dist/esm/streams/messages.js.map +1 -1
  178. package/dist/esm/streams/requests.d.ts +3 -0
  179. package/dist/esm/streams/requests.js +5 -2
  180. package/dist/esm/streams/requests.js.map +1 -1
  181. package/dist/esm/streams/transport.d.ts +1 -13
  182. package/dist/esm/streams/transport.js +1 -17
  183. package/dist/esm/streams/transport.js.map +1 -1
  184. package/dist/esm/uns-v2/client.d.ts +1 -6
  185. package/dist/esm/uns-v2/client.js +30 -6
  186. package/dist/esm/uns-v2/client.js.map +1 -1
  187. package/dist/esm/uns-v2/data-client.d.ts +5 -2
  188. package/dist/esm/uns-v2/data-client.js +8 -5
  189. package/dist/esm/uns-v2/data-client.js.map +1 -1
  190. package/dist/esm/uns-v2/generated.d.ts +6783 -4655
  191. package/dist/esm/uns-v2/generated.js +221 -65
  192. package/dist/esm/uns-v2/generated.js.map +1 -1
  193. package/dist/esm/uns-v2/sse.d.ts +7 -1
  194. package/dist/esm/uns-v2/sse.js +12 -2
  195. package/dist/esm/uns-v2/sse.js.map +1 -1
  196. package/package.json +25 -12
  197. package/skills/tier0-sdk-v2/SKILL.md +11 -5
  198. package/skills/tier0-sdk-v2/references/integration.md +14 -24
  199. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +11 -5
  200. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +6 -10
  201. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +20 -3
  202. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +46 -13
  203. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/SKILL.md +2 -2
  204. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/app-members.md +5 -1
  205. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/SKILL.md +25 -0
  206. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/references/quickstart.md +1 -1
  207. package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/SKILL.md +3 -3
  208. package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/references/send.md +6 -6
  209. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +4 -4
  210. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/changes.md +2 -2
  211. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +26 -36
  212. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +9 -23
  213. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/protocols.md +12 -29
  214. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/read.md +4 -5
  215. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +5 -5
  216. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +3 -3
  217. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +23 -9
  218. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/deploy.md +14 -12
  219. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/http-server.md +4 -4
  220. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +4 -3
  221. package/skills/tier0-sdk-v2/tier0-sdk-v2-streams/SKILL.md +14 -12
  222. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +3 -1
  223. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +2 -2
  224. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +22 -1
  225. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +5 -6
  226. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/update.md +11 -0
  227. package/dist/cjs/app-identity-contract.generated.d.ts +0 -22
  228. package/dist/cjs/app-identity-contract.generated.js +0 -51
  229. package/dist/cjs/app-identity-contract.generated.js.map +0 -1
  230. package/dist/cjs/runtime.d.ts +0 -13
  231. package/dist/cjs/runtime.js +0 -51
  232. package/dist/cjs/runtime.js.map +0 -1
  233. package/dist/esm/app-identity-contract.generated.d.ts +0 -22
  234. package/dist/esm/app-identity-contract.generated.js +0 -48
  235. package/dist/esm/app-identity-contract.generated.js.map +0 -1
  236. package/dist/esm/runtime.d.ts +0 -13
  237. package/dist/esm/runtime.js +0 -46
  238. package/dist/esm/runtime.js.map +0 -1
@@ -23,9 +23,11 @@
23
23
  * `delete` always passes, a late one too, and forgets the record's
24
24
  * revision, since a record written again under the same ID may start
25
25
  * over at revision 1.
26
- * - Changes in collections with employee data rules (`restricted`) come as
27
- * collection-level notices unless `restricted: "record"` is asked for.
28
26
  * - Aborting `signal` or leaving the loop ends the iteration without error.
27
+ * - The stream runs as the App: it sees every change of the collections
28
+ * granted to the App. A server opens one per process and fans it out to
29
+ * browsers with `createChangeRelay`, filtering per browser by what the App
30
+ * lets that visitor see.
29
31
  *
30
32
  * Events carry identities only, never record data; read the record to show it.
31
33
  */
@@ -43,18 +45,6 @@ const RESET_PAUSE_MS = 1_000;
43
45
  const HEALTHY_CONNECTION_MS = 30_000;
44
46
  /** Records whose last revision is remembered; beyond this the oldest is forgotten (a stale change may then pass, which only costs a reread). */
45
47
  const SUBSCRIBE_SEEN_RECORDS_MAX = 10_000;
46
- /**
47
- * The event as an employee's browser may see it: a change in a collection
48
- * with employee data rules becomes a collection-level notice without record
49
- * ID or revision, so it does not reveal records the employee may not read.
50
- * Other events pass unchanged.
51
- */
52
- export function redactRestricted(event) {
53
- if (event.type !== 'change' || !event.restricted || event.change === 'collection')
54
- return event;
55
- const { eventId, collection, collectionId, occurredAt, restricted } = event;
56
- return { type: 'change', eventId, collection, collectionId, occurredAt, restricted, change: 'collection' };
57
- }
58
48
  function reconnectDelay(failures, afterMs) {
59
49
  if (afterMs !== undefined)
60
50
  return Math.min(afterMs, RECONNECT_MAX_MS);
@@ -106,7 +96,7 @@ function pause(ms, signal) {
106
96
  });
107
97
  }
108
98
  export function createSubscribe(runtime) {
109
- return async function* subscribe(options = {}) {
99
+ return async function* subscribe(options = {}, observer = {}) {
110
100
  const definitions = [...new Set((options.collections ?? runtime.declared).map(runtime.lookup))];
111
101
  if (definitions.length < 1 || definitions.length > UNS_V2_SUBSCRIBE_MAX_COLLECTIONS) {
112
102
  throw new TypeError(`data.subscribe watches 1–${UNS_V2_SUBSCRIBE_MAX_COLLECTIONS} collections; got ${definitions.length}.`);
@@ -117,10 +107,7 @@ export function createSubscribe(runtime) {
117
107
  }
118
108
  if (options.lastEventId !== undefined)
119
109
  assertEventId(options.lastEventId);
120
- const redact = (options.restricted ?? 'collection') === 'collection';
121
110
  const idleMs = options.idleTimeoutMs ?? SUBSCRIBE_IDLE_TIMEOUT_MS;
122
- // The subject of the request that subscribed; an App-level subscription carries none.
123
- const call = { dataSubject: runtime.subject() };
124
111
  const stop = new AbortController();
125
112
  const onAbort = () => stop.abort();
126
113
  options.signal?.addEventListener('abort', onAbort, { once: true });
@@ -130,7 +117,9 @@ export function createSubscribe(runtime) {
130
117
  let failures = 0;
131
118
  const seen = new SeenRevisions(SUBSCRIBE_SEEN_RECORDS_MAX);
132
119
  /** Waits, longer each time, before the next attempt. */
133
- const backOff = async (afterMs) => {
120
+ const backOff = async (error) => {
121
+ observer.retrying?.(error);
122
+ const afterMs = error.retry.afterMs;
134
123
  failures += 1;
135
124
  await pause(reconnectDelay(failures, afterMs), stop.signal);
136
125
  };
@@ -144,7 +133,7 @@ export function createSubscribe(runtime) {
144
133
  // A freshly imported App answers APP_NOT_READY for a while: keep waiting for it.
145
134
  if (!(error instanceof DataError) || !error.retry.safe || stop.signal.aborted)
146
135
  throw error;
147
- await backOff(error.retry.afterMs);
136
+ await backOff(error);
148
137
  continue;
149
138
  }
150
139
  const aliases = new Map();
@@ -163,17 +152,24 @@ export function createSubscribe(runtime) {
163
152
  const openedAt = Date.now();
164
153
  let healthy = false;
165
154
  let reset = false;
155
+ let streaming = false;
166
156
  try {
167
157
  arm();
168
- const stream = runtime.openStream({ collectionIds: [...aliases.keys()], ...(recordIds ? { recordIds } : {}) }, { ...call, lastEventId, signal: connection.signal });
158
+ const stream = runtime.openStream({ collectionIds: [...aliases.keys()], ...(recordIds ? { recordIds } : {}) }, { lastEventId, signal: connection.signal });
169
159
  for await (const event of stream) {
170
160
  arm();
171
161
  if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS)
172
162
  healthy = true;
163
+ if (!streaming) {
164
+ streaming = true;
165
+ observer.connected?.();
166
+ }
173
167
  if (event.type === 'heartbeat') {
174
168
  // The connect heartbeat names the current position: resuming from it loses nothing.
175
- if (event.eventId !== undefined)
169
+ if (event.eventId !== undefined) {
176
170
  lastEventId = event.eventId;
171
+ observer.position?.(event.eventId);
172
+ }
177
173
  continue;
178
174
  }
179
175
  if (event.type === 'reset') {
@@ -187,20 +183,19 @@ export function createSubscribe(runtime) {
187
183
  healthy = true;
188
184
  // A dropped change is still a resume position.
189
185
  lastEventId = event.eventId;
186
+ observer.position?.(event.eventId);
190
187
  if (!seen.admit(event.collectionId, event.recordId, event.revision, event.change))
191
188
  continue;
192
- const change = {
189
+ yield {
193
190
  type: 'change',
194
191
  eventId: event.eventId,
195
192
  collection: aliases.get(event.collectionId) ?? event.collectionId,
196
193
  collectionId: event.collectionId,
197
194
  occurredAt: event.occurredAt,
198
- restricted: event.restricted,
199
195
  change: event.change,
200
196
  recordId: event.recordId,
201
197
  revision: event.revision,
202
198
  };
203
- yield redact ? redactRestricted(change) : change;
204
199
  }
205
200
  }
206
201
  catch (error) {
@@ -216,7 +211,7 @@ export function createSubscribe(runtime) {
216
211
  catch (cause) {
217
212
  if (!(cause instanceof DataError) || !cause.retry.safe)
218
213
  throw cause;
219
- await backOff(cause.retry.afterMs);
214
+ await backOff(cause);
220
215
  continue;
221
216
  }
222
217
  if (!changed)
@@ -229,7 +224,7 @@ export function createSubscribe(runtime) {
229
224
  }
230
225
  if (!error.retry.safe)
231
226
  throw error;
232
- await backOff(error.retry.afterMs);
227
+ await backOff(error);
233
228
  continue;
234
229
  }
235
230
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../../src/data/subscribe.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `data.subscribe`: committed record changes of declared collections over\n * UNS `records:subscribe` (server-sent events), as one async iterable that\n * outlives single connections.\n *\n * - Reconnects with `Last-Event-ID` when the stream ends, breaks, or stays\n * silent for `idleTimeoutMs` (UNS sends a heartbeat every 15 s). A stream\n * that dies young without a change backs off, so a server dropping every\n * connection is not hammered.\n * - Failures marked `retry.safe` (429 over the subscription limit, 503,\n * `APP_NOT_READY` from ensure …) are retried after `retry.afterMs` or a\n * backoff of up to 30 s, without end; any other failure ends the\n * iteration with that DataError.\n * - `reset` is passed on: `CURSOR_EXPIRED` (the position was cleaned up after\n * 24 h, cannot be decoded after a key rotation, or the tenant database\n * moved) or `SCOPE_CHANGED` (access changed, e.g. a collection is no longer\n * readable). The consumer reloads what it shows; the next connection\n * starts from now, without a resume position. A collection recreated\n * under a new ID (see bootstrap self-healing) yields `COLLECTION_RECREATED`.\n * - UNS delivers changes in write-transaction order, not commit order, so\n * revisions of one record may arrive out of order. A non-delete change at\n * or below the revision already passed on for its record is dropped; a\n * `delete` always passes, a late one too, and forgets the record's\n * revision, since a record written again under the same ID may start\n * over at revision 1.\n * - Changes in collections with employee data rules (`restricted`) come as\n * collection-level notices unless `restricted: \"record\"` is asked for.\n * - Aborting `signal` or leaving the loop ends the iteration without error.\n *\n * Events carry identities only, never record data; read the record to show it.\n */\nimport { isStaleCollectionError, type EnsureState } from './bootstrap.js';\nimport type { CallOptions, DataOpenStream } from './call.js';\nimport { assertEventId, UNS_V2_SUBSCRIBE_MAX_COLLECTIONS, UNS_V2_SUBSCRIBE_MAX_RECORD_IDS } from '../uns-v2/client.js';\nimport { collectionLabel, type CollectionTarget } from './define.js';\nimport { DataError } from './error.js';\n\n/** Three missed 15 s heartbeats. */\nexport const SUBSCRIBE_IDLE_TIMEOUT_MS = 45_000;\nconst RECONNECT_BASE_MS = 500;\nconst RECONNECT_MAX_MS = 30_000;\n/** After a reset, the next connection waits at least this long. */\nconst RESET_PAUSE_MS = 1_000;\n/** A connection alive this long counts as healthy even without changes. */\nconst HEALTHY_CONNECTION_MS = 30_000;\n/** Records whose last revision is remembered; beyond this the oldest is forgotten (a stale change may then pass, which only costs a reread). */\nconst SUBSCRIBE_SEEN_RECORDS_MAX = 10_000;\n\nexport interface SubscribeOptions {\n /** Declarations, aliases, or existing-ID references (at most 64); defaults to every declared collection. */\n collections?: readonly (CollectionTarget | string)[];\n /** Only changes of these record IDs (1–100, duplicates ignored), when exactly one collection is watched. */\n recordIds?: readonly string[];\n /**\n * How changes in collections with employee data rules arrive.\n * `collection` (default): as a collection-level notice without record ID\n * or revision, safe to forward to any employee. `record`: the full event,\n * for server-side consumers that do not forward it to browsers, or that\n * redact per recipient with `redactRestricted`.\n */\n restricted?: 'collection' | 'record';\n /** Resume after this event (e.g. a browser's `Last-Event-ID`). */\n lastEventId?: string;\n signal?: AbortSignal;\n /** Reconnect when nothing, not even a heartbeat, arrives for this long. */\n idleTimeoutMs?: number;\n}\n\ninterface ChangeBase {\n type: 'change';\n eventId: string;\n /** Alias of a declared collection, or the ID of an existing collection reference. */\n collection: string;\n collectionId: string;\n occurredAt: number;\n /**\n * The collection has employee data rules: whether an employee may see this\n * record depends on who they are. Such changes arrive as `CollectionChange`\n * unless the subscription asked for `restricted: \"record\"`.\n */\n restricted: boolean;\n}\n\n/**\n * One record committed (`record`) or deleted (`delete`) at `revision`. A\n * `record` change is never at or below a revision already passed on for the\n * same record since its last delete; a `delete` may be.\n */\nexport interface RecordChange extends ChangeBase {\n change: 'record' | 'delete';\n recordId: string;\n revision: number;\n}\n\n/** Something in the collection changed; which record is not disclosed. Reload through the data rules. */\nexport interface CollectionChange extends ChangeBase {\n change: 'collection';\n}\n\nexport type ChangeEvent = RecordChange | CollectionChange;\n/** `reason`: `CURSOR_EXPIRED`, `SCOPE_CHANGED` (from UNS) or `COLLECTION_RECREATED`. */\nexport type SubscriptionEvent = ChangeEvent | { type: 'reset'; reason: string; message?: string };\n\n/**\n * The event as an employee's browser may see it: a change in a collection\n * with employee data rules becomes a collection-level notice without record\n * ID or revision, so it does not reveal records the employee may not read.\n * Other events pass unchanged.\n */\nexport function redactRestricted<E extends SubscriptionEvent>(event: E): E | CollectionChange {\n if (event.type !== 'change' || !event.restricted || event.change === 'collection') return event;\n const { eventId, collection, collectionId, occurredAt, restricted } = event;\n return { type: 'change', eventId, collection, collectionId, occurredAt, restricted, change: 'collection' };\n}\n\nexport interface SubscribeRuntime {\n openStream: DataOpenStream;\n ensure(targets: readonly CollectionTarget[]): Promise<EnsureState>;\n rebuild(before: EnsureState, cause: DataError, targets: readonly CollectionTarget[]): Promise<boolean>;\n resolve(target: CollectionTarget): Promise<string>;\n lookup(target: CollectionTarget | string): CollectionTarget;\n declared: readonly CollectionTarget[];\n subject(): string | undefined;\n}\n\nfunction reconnectDelay(failures: number, afterMs: number | undefined): number {\n if (afterMs !== undefined) return Math.min(afterMs, RECONNECT_MAX_MS);\n const ceiling = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** Math.max(0, failures - 1));\n return ceiling / 2 + Math.random() * (ceiling / 2);\n}\n\n/**\n * The highest revision passed on per record, so changes delivered out of\n * order are dropped (UNS orders by write transaction, not by commit).\n */\nclass SeenRevisions {\n private readonly revisions = new Map<string, number>();\n\n constructor(private readonly max: number) {}\n\n /** Whether the change is new; remembers its revision, or forgets the record on delete. */\n admit(collectionId: string, recordId: string, revision: number, change: 'record' | 'delete'): boolean {\n const key = `${collectionId}\\u0000${recordId}`;\n const seen = this.revisions.get(key);\n // A delete always passes, a late one too: the record may be written again under the same ID, from revision 1.\n if (change === 'delete') {\n this.revisions.delete(key);\n return true;\n }\n if (seen !== undefined && revision <= seen) return false;\n this.revisions.delete(key);\n this.revisions.set(key, revision);\n if (this.revisions.size > this.max) this.revisions.delete(this.revisions.keys().next().value!);\n return true;\n }\n\n clear() {\n this.revisions.clear();\n }\n}\n\nfunction pause(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) return resolve();\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n signal.addEventListener('abort', done, { once: true });\n });\n}\n\nexport function createSubscribe(runtime: SubscribeRuntime) {\n return async function* subscribe(options: SubscribeOptions = {}): AsyncGenerator<SubscriptionEvent, void, undefined> {\n const definitions = [...new Set((options.collections ?? runtime.declared).map(runtime.lookup))];\n if (definitions.length < 1 || definitions.length > UNS_V2_SUBSCRIBE_MAX_COLLECTIONS) {\n throw new TypeError(`data.subscribe watches 1–${UNS_V2_SUBSCRIBE_MAX_COLLECTIONS} collections; got ${definitions.length}.`);\n }\n const recordIds = options.recordIds === undefined ? undefined : [...new Set(options.recordIds)];\n if (recordIds && (recordIds.length < 1 || recordIds.length > UNS_V2_SUBSCRIBE_MAX_RECORD_IDS || definitions.length !== 1)) {\n throw new TypeError(`data.subscribe takes 1–${UNS_V2_SUBSCRIBE_MAX_RECORD_IDS} recordIds, of exactly one collection.`);\n }\n if (options.lastEventId !== undefined) assertEventId(options.lastEventId);\n const redact = (options.restricted ?? 'collection') === 'collection';\n const idleMs = options.idleTimeoutMs ?? SUBSCRIBE_IDLE_TIMEOUT_MS;\n // The subject of the request that subscribed; an App-level subscription carries none.\n const call: CallOptions = { dataSubject: runtime.subject() };\n const stop = new AbortController();\n const onAbort = () => stop.abort();\n options.signal?.addEventListener('abort', onAbort, { once: true });\n if (options.signal?.aborted) stop.abort();\n let lastEventId = options.lastEventId;\n let failures = 0;\n const seen = new SeenRevisions(SUBSCRIBE_SEEN_RECORDS_MAX);\n /** Waits, longer each time, before the next attempt. */\n const backOff = async (afterMs?: number) => {\n failures += 1;\n await pause(reconnectDelay(failures, afterMs), stop.signal);\n };\n try {\n while (!stop.signal.aborted) {\n let state: EnsureState;\n try {\n state = await runtime.ensure(definitions);\n } catch (error) {\n // A freshly imported App answers APP_NOT_READY for a while: keep waiting for it.\n if (!(error instanceof DataError) || !error.retry.safe || stop.signal.aborted) throw error;\n await backOff(error.retry.afterMs);\n continue;\n }\n const aliases = new Map<string, string>();\n for (const definition of definitions) {\n const id = await runtime.resolve(definition);\n aliases.set(id, collectionLabel(definition));\n }\n\n const connection = new AbortController();\n const closeConnection = () => connection.abort();\n stop.signal.addEventListener('abort', closeConnection, { once: true });\n let idle: ReturnType<typeof setTimeout> | undefined;\n const arm = () => {\n clearTimeout(idle);\n idle = setTimeout(closeConnection, idleMs);\n };\n const openedAt = Date.now();\n let healthy = false;\n let reset = false;\n try {\n arm();\n const stream = runtime.openStream(\n { collectionIds: [...aliases.keys()], ...(recordIds ? { recordIds } : {}) },\n { ...call, lastEventId, signal: connection.signal },\n );\n for await (const event of stream) {\n arm();\n if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) healthy = true;\n if (event.type === 'heartbeat') {\n // The connect heartbeat names the current position: resuming from it loses nothing.\n if (event.eventId !== undefined) lastEventId = event.eventId;\n continue;\n }\n if (event.type === 'reset') {\n lastEventId = undefined;\n reset = true;\n // The consumer rereads everything; revisions seen before mean nothing for what follows.\n seen.clear();\n yield event.message === undefined ? { type: 'reset', reason: event.reason } : { type: 'reset', reason: event.reason, message: event.message };\n continue;\n }\n healthy = true;\n // A dropped change is still a resume position.\n lastEventId = event.eventId;\n if (!seen.admit(event.collectionId, event.recordId, event.revision, event.change)) continue;\n const change: RecordChange = {\n type: 'change',\n eventId: event.eventId,\n collection: aliases.get(event.collectionId) ?? event.collectionId,\n collectionId: event.collectionId,\n occurredAt: event.occurredAt,\n restricted: event.restricted,\n change: event.change,\n recordId: event.recordId,\n revision: event.revision,\n };\n yield redact ? redactRestricted(change) : change;\n }\n } catch (error) {\n if (stop.signal.aborted) return;\n if (!(error instanceof DataError)) throw error;\n if (isStaleCollectionError(error)) {\n let changed: boolean;\n try {\n changed = await runtime.rebuild(state, error, definitions);\n } catch (cause) {\n if (!(cause instanceof DataError) || !cause.retry.safe) throw cause;\n await backOff(cause.retry.afterMs);\n continue;\n }\n if (!changed) throw error;\n // Recreated collections have new IDs; their old cursor means nothing.\n lastEventId = undefined;\n seen.clear();\n yield { type: 'reset', reason: 'COLLECTION_RECREATED' };\n continue;\n }\n if (!error.retry.safe) throw error;\n await backOff(error.retry.afterMs);\n continue;\n } finally {\n clearTimeout(idle);\n connection.abort();\n stop.signal.removeEventListener('abort', closeConnection);\n }\n // The stream ended or went silent. Reconnect at once after a healthy connection, else back off.\n if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) healthy = true;\n if (healthy) failures = 0;\n else failures += 1;\n // A reset always waits a moment: a server resetting in a loop is not reconnected to in a loop.\n const wait = Math.max(reset ? RESET_PAUSE_MS : 0, healthy ? 0 : reconnectDelay(failures, undefined));\n if (wait > 0) await pause(wait, stop.signal);\n }\n } finally {\n stop.abort();\n options.signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n"]}
1
+ {"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../../src/data/subscribe.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `data.subscribe`: committed record changes of declared collections over\n * UNS `records:subscribe` (server-sent events), as one async iterable that\n * outlives single connections.\n *\n * - Reconnects with `Last-Event-ID` when the stream ends, breaks, or stays\n * silent for `idleTimeoutMs` (UNS sends a heartbeat every 15 s). A stream\n * that dies young without a change backs off, so a server dropping every\n * connection is not hammered.\n * - Failures marked `retry.safe` (429 over the subscription limit, 503,\n * `APP_NOT_READY` from ensure …) are retried after `retry.afterMs` or a\n * backoff of up to 30 s, without end; any other failure ends the\n * iteration with that DataError.\n * - `reset` is passed on: `CURSOR_EXPIRED` (the position was cleaned up after\n * 24 h, cannot be decoded after a key rotation, or the tenant database\n * moved) or `SCOPE_CHANGED` (access changed, e.g. a collection is no longer\n * readable). The consumer reloads what it shows; the next connection\n * starts from now, without a resume position. A collection recreated\n * under a new ID (see bootstrap self-healing) yields `COLLECTION_RECREATED`.\n * - UNS delivers changes in write-transaction order, not commit order, so\n * revisions of one record may arrive out of order. A non-delete change at\n * or below the revision already passed on for its record is dropped; a\n * `delete` always passes, a late one too, and forgets the record's\n * revision, since a record written again under the same ID may start\n * over at revision 1.\n * - Aborting `signal` or leaving the loop ends the iteration without error.\n * - The stream runs as the App: it sees every change of the collections\n * granted to the App. A server opens one per process and fans it out to\n * browsers with `createChangeRelay`, filtering per browser by what the App\n * lets that visitor see.\n *\n * Events carry identities only, never record data; read the record to show it.\n */\nimport { isStaleCollectionError, type EnsureState } from './bootstrap.js';\nimport type { DataOpenStream } from './call.js';\nimport { assertEventId, UNS_V2_SUBSCRIBE_MAX_COLLECTIONS, UNS_V2_SUBSCRIBE_MAX_RECORD_IDS } from '../uns-v2/client.js';\nimport { collectionLabel, type CollectionTarget } from './define.js';\nimport { DataError } from './error.js';\n\n/** Three missed 15 s heartbeats. */\nexport const SUBSCRIBE_IDLE_TIMEOUT_MS = 45_000;\nconst RECONNECT_BASE_MS = 500;\nconst RECONNECT_MAX_MS = 30_000;\n/** After a reset, the next connection waits at least this long. */\nconst RESET_PAUSE_MS = 1_000;\n/** A connection alive this long counts as healthy even without changes. */\nconst HEALTHY_CONNECTION_MS = 30_000;\n/** Records whose last revision is remembered; beyond this the oldest is forgotten (a stale change may then pass, which only costs a reread). */\nconst SUBSCRIBE_SEEN_RECORDS_MAX = 10_000;\n\nexport interface SubscribeOptions {\n /** Declarations, aliases, or existing-ID references (at most 64); defaults to every declared collection. */\n collections?: readonly (CollectionTarget | string)[];\n /** Only changes of these record IDs (1–100, duplicates ignored), when exactly one collection is watched. */\n recordIds?: readonly string[];\n /** Resume after this event (e.g. a browser's `Last-Event-ID`). */\n lastEventId?: string;\n signal?: AbortSignal;\n /** Reconnect when nothing, not even a heartbeat, arrives for this long. */\n idleTimeoutMs?: number;\n}\n\n/**\n * One record committed (`record`) or deleted (`delete`) at `revision`. A\n * `record` change is never at or below a revision already passed on for the\n * same record since its last delete; a `delete` may be.\n */\nexport interface RecordChange {\n type: 'change';\n eventId: string;\n /** Alias of a declared collection, or the ID of an existing collection reference. */\n collection: string;\n collectionId: string;\n occurredAt: number;\n change: 'record' | 'delete';\n recordId: string;\n revision: number;\n}\n\n/** `reason`: `CURSOR_EXPIRED`, `SCOPE_CHANGED` (from UNS) or `COLLECTION_RECREATED`. */\nexport type SubscriptionEvent = RecordChange | { type: 'reset'; reason: string; message?: string };\n\n/**\n * What `data.subscribe` tells a server-side consumer about the upstream\n * (`createChangeRelay` uses it; not part of the public API).\n */\nexport interface SubscribeObserver {\n /** A connection is streaming: its first frame (the connect heartbeat) arrived. */\n connected?(): void;\n /** The resume position moved: a heartbeat position or a change, dropped or not. */\n position?(eventId: string): void;\n /** An attempt failed and will be retried after a wait (e.g. 429 over the subscription limit, `APP_NOT_READY`). */\n retrying?(error: DataError): void;\n}\n\nexport interface SubscribeRuntime {\n openStream: DataOpenStream;\n ensure(targets: readonly CollectionTarget[]): Promise<EnsureState>;\n rebuild(before: EnsureState, cause: DataError, targets: readonly CollectionTarget[]): Promise<boolean>;\n resolve(target: CollectionTarget): Promise<string>;\n lookup(target: CollectionTarget | string): CollectionTarget;\n declared: readonly CollectionTarget[];\n}\n\nfunction reconnectDelay(failures: number, afterMs: number | undefined): number {\n if (afterMs !== undefined) return Math.min(afterMs, RECONNECT_MAX_MS);\n const ceiling = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** Math.max(0, failures - 1));\n return ceiling / 2 + Math.random() * (ceiling / 2);\n}\n\n/**\n * The highest revision passed on per record, so changes delivered out of\n * order are dropped (UNS orders by write transaction, not by commit).\n */\nclass SeenRevisions {\n private readonly revisions = new Map<string, number>();\n\n constructor(private readonly max: number) {}\n\n /** Whether the change is new; remembers its revision, or forgets the record on delete. */\n admit(collectionId: string, recordId: string, revision: number, change: 'record' | 'delete'): boolean {\n const key = `${collectionId}\\u0000${recordId}`;\n const seen = this.revisions.get(key);\n // A delete always passes, a late one too: the record may be written again under the same ID, from revision 1.\n if (change === 'delete') {\n this.revisions.delete(key);\n return true;\n }\n if (seen !== undefined && revision <= seen) return false;\n this.revisions.delete(key);\n this.revisions.set(key, revision);\n if (this.revisions.size > this.max) this.revisions.delete(this.revisions.keys().next().value!);\n return true;\n }\n\n clear() {\n this.revisions.clear();\n }\n}\n\nfunction pause(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) return resolve();\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n signal.addEventListener('abort', done, { once: true });\n });\n}\n\nexport function createSubscribe(runtime: SubscribeRuntime) {\n return async function* subscribe(options: SubscribeOptions = {}, observer: SubscribeObserver = {}): AsyncGenerator<SubscriptionEvent, void, undefined> {\n const definitions = [...new Set((options.collections ?? runtime.declared).map(runtime.lookup))];\n if (definitions.length < 1 || definitions.length > UNS_V2_SUBSCRIBE_MAX_COLLECTIONS) {\n throw new TypeError(`data.subscribe watches 1–${UNS_V2_SUBSCRIBE_MAX_COLLECTIONS} collections; got ${definitions.length}.`);\n }\n const recordIds = options.recordIds === undefined ? undefined : [...new Set(options.recordIds)];\n if (recordIds && (recordIds.length < 1 || recordIds.length > UNS_V2_SUBSCRIBE_MAX_RECORD_IDS || definitions.length !== 1)) {\n throw new TypeError(`data.subscribe takes 1–${UNS_V2_SUBSCRIBE_MAX_RECORD_IDS} recordIds, of exactly one collection.`);\n }\n if (options.lastEventId !== undefined) assertEventId(options.lastEventId);\n const idleMs = options.idleTimeoutMs ?? SUBSCRIBE_IDLE_TIMEOUT_MS;\n const stop = new AbortController();\n const onAbort = () => stop.abort();\n options.signal?.addEventListener('abort', onAbort, { once: true });\n if (options.signal?.aborted) stop.abort();\n let lastEventId = options.lastEventId;\n let failures = 0;\n const seen = new SeenRevisions(SUBSCRIBE_SEEN_RECORDS_MAX);\n /** Waits, longer each time, before the next attempt. */\n const backOff = async (error: DataError) => {\n observer.retrying?.(error);\n const afterMs = error.retry.afterMs;\n failures += 1;\n await pause(reconnectDelay(failures, afterMs), stop.signal);\n };\n try {\n while (!stop.signal.aborted) {\n let state: EnsureState;\n try {\n state = await runtime.ensure(definitions);\n } catch (error) {\n // A freshly imported App answers APP_NOT_READY for a while: keep waiting for it.\n if (!(error instanceof DataError) || !error.retry.safe || stop.signal.aborted) throw error;\n await backOff(error);\n continue;\n }\n const aliases = new Map<string, string>();\n for (const definition of definitions) {\n const id = await runtime.resolve(definition);\n aliases.set(id, collectionLabel(definition));\n }\n\n const connection = new AbortController();\n const closeConnection = () => connection.abort();\n stop.signal.addEventListener('abort', closeConnection, { once: true });\n let idle: ReturnType<typeof setTimeout> | undefined;\n const arm = () => {\n clearTimeout(idle);\n idle = setTimeout(closeConnection, idleMs);\n };\n const openedAt = Date.now();\n let healthy = false;\n let reset = false;\n let streaming = false;\n try {\n arm();\n const stream = runtime.openStream(\n { collectionIds: [...aliases.keys()], ...(recordIds ? { recordIds } : {}) },\n { lastEventId, signal: connection.signal },\n );\n for await (const event of stream) {\n arm();\n if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) healthy = true;\n if (!streaming) {\n streaming = true;\n observer.connected?.();\n }\n if (event.type === 'heartbeat') {\n // The connect heartbeat names the current position: resuming from it loses nothing.\n if (event.eventId !== undefined) {\n lastEventId = event.eventId;\n observer.position?.(event.eventId);\n }\n continue;\n }\n if (event.type === 'reset') {\n lastEventId = undefined;\n reset = true;\n // The consumer rereads everything; revisions seen before mean nothing for what follows.\n seen.clear();\n yield event.message === undefined ? { type: 'reset', reason: event.reason } : { type: 'reset', reason: event.reason, message: event.message };\n continue;\n }\n healthy = true;\n // A dropped change is still a resume position.\n lastEventId = event.eventId;\n observer.position?.(event.eventId);\n if (!seen.admit(event.collectionId, event.recordId, event.revision, event.change)) continue;\n yield {\n type: 'change',\n eventId: event.eventId,\n collection: aliases.get(event.collectionId) ?? event.collectionId,\n collectionId: event.collectionId,\n occurredAt: event.occurredAt,\n change: event.change,\n recordId: event.recordId,\n revision: event.revision,\n };\n }\n } catch (error) {\n if (stop.signal.aborted) return;\n if (!(error instanceof DataError)) throw error;\n if (isStaleCollectionError(error)) {\n let changed: boolean;\n try {\n changed = await runtime.rebuild(state, error, definitions);\n } catch (cause) {\n if (!(cause instanceof DataError) || !cause.retry.safe) throw cause;\n await backOff(cause);\n continue;\n }\n if (!changed) throw error;\n // Recreated collections have new IDs; their old cursor means nothing.\n lastEventId = undefined;\n seen.clear();\n yield { type: 'reset', reason: 'COLLECTION_RECREATED' };\n continue;\n }\n if (!error.retry.safe) throw error;\n await backOff(error);\n continue;\n } finally {\n clearTimeout(idle);\n connection.abort();\n stop.signal.removeEventListener('abort', closeConnection);\n }\n // The stream ended or went silent. Reconnect at once after a healthy connection, else back off.\n if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) healthy = true;\n if (healthy) failures = 0;\n else failures += 1;\n // A reset always waits a moment: a server resetting in a loop is not reconnected to in a loop.\n const wait = Math.max(reset ? RESET_PAUSE_MS : 0, healthy ? 0 : reconnectDelay(failures, undefined));\n if (wait > 0) await pause(wait, stop.signal);\n }\n } finally {\n stop.abort();\n options.signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { type ServerSentEvent } from './uns-v2/sse.js';
2
+ export interface EventStreamOptions {
3
+ url: string;
4
+ headers?: Record<string, string>;
5
+ lastEventId?: string;
6
+ fetch?: typeof globalThis.fetch;
7
+ onEvent(event: ServerSentEvent): void;
8
+ /**
9
+ * The stream is back after a pause for the App's sleep or an authorization
10
+ * failure. Events may have been missed, resume position or not.
11
+ */
12
+ onResume?(): void;
13
+ /** The stream ended for good; it does not reconnect. Not called after `close()`. */
14
+ onClose?(error: EventStreamError): void;
15
+ /** Reconnect when nothing, not even a heartbeat, arrives for this long. Off by default. */
16
+ idleTimeoutMs?: number;
17
+ /** Close the connection while the page is hidden and resume from the last event id when it is visible. Default true. */
18
+ pauseWhenHidden?: boolean;
19
+ }
20
+ export interface EventStream {
21
+ close(): void;
22
+ }
23
+ export declare class EventStreamError extends Error {
24
+ readonly status: number;
25
+ readonly code: string | undefined;
26
+ constructor(status: number, code: string | undefined);
27
+ }
28
+ export declare function openEventStream(options: EventStreamOptions): EventStream;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * A server-sent event stream over `fetch`, for browsers. `EventSource` gives up
3
+ * for good on any HTTP error, so a 503 from a sleeping App or a restarting node
4
+ * agent (design 21.1) would end it. This one reads the error and acts on it:
5
+ *
6
+ * - `sbx_sleeping`, `sbx_starting`: stop, and reconnect on the runtime's
7
+ * `resumed`. Without the runtime `sbx_sleeping` ends the stream and
8
+ * `sbx_starting` waits `retry_after_ms`.
9
+ * - an authorization failure: reported to the runtime, then as above. A
10
+ * successful reauthorization also fires `resumed`.
11
+ * - `node_restarting`, `upstream_error` and any other retryable failure, and a
12
+ * broken connection: reconnect by itself after `retry_after_ms` or a backoff,
13
+ * resuming from `Last-Event-ID`.
14
+ * - a non-retryable failure: ends the stream.
15
+ *
16
+ * While the runtime says `sleeping` nothing is sent: those requests would not
17
+ * wake the App anyway.
18
+ *
19
+ * While the page is hidden (`document.visibilityState`) the connection is
20
+ * closed, so a background tab does not hold a long connection to the App;
21
+ * when the page is visible again it reconnects at once from the last event
22
+ * id (`Last-Event-ID`). The server replays what was missed or answers with
23
+ * its own reset; `onResume` is not called for it. `pauseWhenHidden: false`
24
+ * keeps the connection open in the background.
25
+ */
26
+ import { getPageRuntime, onPageRuntime, isAuthErrorCode, isAppSleeping, readPlatformError, reportAuthError } from './page-runtime.js';
27
+ import { parseServerSentEvents } from './uns-v2/sse.js';
28
+ const RECONNECT_BASE_MS = 1_000;
29
+ const RECONNECT_MAX_MS = 30_000;
30
+ /** A connection alive this long resets the backoff. */
31
+ const HEALTHY_CONNECTION_MS = 30_000;
32
+ /**
33
+ * A paused stream looks again this often while the runtime still says
34
+ * `running`: a routing race can answer `sbx_sleeping` for a running App, and
35
+ * then no `resumed` ever comes. Such a request cannot wake anything.
36
+ */
37
+ const PAUSE_RECHECK_MS = 30_000;
38
+ export class EventStreamError extends Error {
39
+ status;
40
+ code;
41
+ constructor(status, code) {
42
+ super(`Event stream refused with HTTP ${status}${code ? ` (${code})` : ''}.`);
43
+ this.status = status;
44
+ this.code = code;
45
+ this.name = 'EventStreamError';
46
+ }
47
+ }
48
+ function classify(status, failure) {
49
+ const code = failure?.code;
50
+ const runtime = getPageRuntime() !== undefined;
51
+ if (isAuthErrorCode(code)) {
52
+ reportAuthError(code);
53
+ return runtime ? { next: 'pause' } : { next: 'end', error: new EventStreamError(status, code) };
54
+ }
55
+ if (code === 'sbx_sleeping' || code === 'sbx_starting' || code === 'sbx_start_failed') {
56
+ if (runtime)
57
+ return { next: 'pause' };
58
+ if (code !== 'sbx_starting')
59
+ return { next: 'end', error: new EventStreamError(status, code) };
60
+ }
61
+ if (failure?.retryable)
62
+ return { next: 'reconnect', afterMs: failure.retryAfterMs };
63
+ // Not ours to classify: an edge proxy's 5xx passes, a route's 4xx is final.
64
+ if (!failure && status >= 500)
65
+ return { next: 'reconnect' };
66
+ return { next: 'end', error: new EventStreamError(status, code) };
67
+ }
68
+ export function openEventStream(options) {
69
+ const { url, onEvent, onResume, onClose, idleTimeoutMs } = options;
70
+ let lastEventId = options.lastEventId;
71
+ let closed = false;
72
+ let paused = isAppSleeping();
73
+ let resumedFromPause = false;
74
+ let failures = 0;
75
+ let resumes = 0;
76
+ let connection;
77
+ let wake;
78
+ const page = (options.pauseWhenHidden ?? true) && typeof document !== 'undefined' ? document : undefined;
79
+ let hidden = page?.visibilityState === 'hidden';
80
+ const onVisibility = () => {
81
+ hidden = page.visibilityState === 'hidden';
82
+ if (hidden)
83
+ connection?.abort();
84
+ wake?.();
85
+ };
86
+ page?.addEventListener('visibilitychange', onVisibility);
87
+ const sleep = (ms) => new Promise((resolve) => {
88
+ const timer = ms === undefined ? undefined : setTimeout(() => done(), ms);
89
+ const done = () => {
90
+ clearTimeout(timer);
91
+ wake = undefined;
92
+ resolve();
93
+ };
94
+ wake = done;
95
+ });
96
+ const offSleeping = onPageRuntime('sleeping', () => {
97
+ paused = true;
98
+ connection?.abort();
99
+ wake?.();
100
+ });
101
+ const offResumed = onPageRuntime('resumed', () => {
102
+ if (paused)
103
+ resumedFromPause = true;
104
+ paused = false;
105
+ resumes += 1;
106
+ wake?.();
107
+ });
108
+ function backoff() {
109
+ failures += 1;
110
+ const ceiling = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** (failures - 1));
111
+ return ceiling / 2 + Math.random() * (ceiling / 2);
112
+ }
113
+ async function consume(body, controller) {
114
+ const openedAt = Date.now();
115
+ let idle;
116
+ const arm = () => {
117
+ if (idleTimeoutMs === undefined)
118
+ return;
119
+ clearTimeout(idle);
120
+ idle = setTimeout(() => controller.abort(), idleTimeoutMs);
121
+ };
122
+ arm();
123
+ try {
124
+ for await (const event of parseServerSentEvents(body)) {
125
+ arm();
126
+ if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS)
127
+ failures = 0;
128
+ if ('comment' in event)
129
+ continue;
130
+ if (event.id !== undefined)
131
+ lastEventId = event.id;
132
+ if ('event' in event)
133
+ onEvent(event);
134
+ }
135
+ }
136
+ finally {
137
+ clearTimeout(idle);
138
+ }
139
+ }
140
+ async function connect() {
141
+ const controller = new AbortController();
142
+ connection = controller;
143
+ const headers = { ...options.headers, Accept: 'text/event-stream' };
144
+ if (lastEventId !== undefined)
145
+ headers['Last-Event-ID'] = lastEventId;
146
+ try {
147
+ const response = await (options.fetch ?? globalThis.fetch)(url, { headers, cache: 'no-store', signal: controller.signal });
148
+ const type = response.headers.get('content-type') ?? '';
149
+ if (response.ok && /^text\/event-stream\b/i.test(type) && response.body) {
150
+ if (resumedFromPause) {
151
+ resumedFromPause = false;
152
+ onResume?.();
153
+ }
154
+ await consume(response.body, controller);
155
+ return { next: 'reconnect' };
156
+ }
157
+ const failure = await readPlatformError(response);
158
+ await response.body?.cancel().catch(() => undefined);
159
+ return classify(response.status, failure);
160
+ }
161
+ catch {
162
+ return { next: 'reconnect' };
163
+ }
164
+ finally {
165
+ connection = undefined;
166
+ }
167
+ }
168
+ async function run() {
169
+ while (!closed) {
170
+ if (hidden) {
171
+ await sleep();
172
+ continue;
173
+ }
174
+ if (paused) {
175
+ await sleep(getPageRuntime()?.getState() === 'running' ? PAUSE_RECHECK_MS : undefined);
176
+ if (getPageRuntime()?.getState() === 'running' && !closed) {
177
+ resumedFromPause = true;
178
+ paused = false;
179
+ }
180
+ continue;
181
+ }
182
+ const seenResumes = resumes;
183
+ const outcome = await connect();
184
+ if (closed)
185
+ return;
186
+ if (outcome.next === 'end') {
187
+ close();
188
+ onClose?.(outcome.error);
189
+ return;
190
+ }
191
+ if (outcome.next === 'pause') {
192
+ // A `resumed` that arrived while the refusal was on its way already happened.
193
+ if (resumes === seenResumes)
194
+ paused = true;
195
+ else
196
+ resumedFromPause = true;
197
+ continue;
198
+ }
199
+ // Closed for a hidden page: wait for it to be visible, not for a backoff.
200
+ if (hidden)
201
+ continue;
202
+ if (!paused)
203
+ await sleep(outcome.afterMs ?? backoff());
204
+ }
205
+ }
206
+ function close() {
207
+ closed = true;
208
+ offSleeping();
209
+ offResumed();
210
+ page?.removeEventListener('visibilitychange', onVisibility);
211
+ connection?.abort();
212
+ wake?.();
213
+ }
214
+ void run();
215
+ return { close };
216
+ }
217
+ //# sourceMappingURL=event-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-stream.js","sourceRoot":"","sources":["../../src/event-stream.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A server-sent event stream over `fetch`, for browsers. `EventSource` gives up\n * for good on any HTTP error, so a 503 from a sleeping App or a restarting node\n * agent (design 21.1) would end it. This one reads the error and acts on it:\n *\n * - `sbx_sleeping`, `sbx_starting`: stop, and reconnect on the runtime's\n * `resumed`. Without the runtime `sbx_sleeping` ends the stream and\n * `sbx_starting` waits `retry_after_ms`.\n * - an authorization failure: reported to the runtime, then as above. A\n * successful reauthorization also fires `resumed`.\n * - `node_restarting`, `upstream_error` and any other retryable failure, and a\n * broken connection: reconnect by itself after `retry_after_ms` or a backoff,\n * resuming from `Last-Event-ID`.\n * - a non-retryable failure: ends the stream.\n *\n * While the runtime says `sleeping` nothing is sent: those requests would not\n * wake the App anyway.\n *\n * While the page is hidden (`document.visibilityState`) the connection is\n * closed, so a background tab does not hold a long connection to the App;\n * when the page is visible again it reconnects at once from the last event\n * id (`Last-Event-ID`). The server replays what was missed or answers with\n * its own reset; `onResume` is not called for it. `pauseWhenHidden: false`\n * keeps the connection open in the background.\n */\nimport { getPageRuntime, onPageRuntime, isAuthErrorCode, isAppSleeping, readPlatformError, reportAuthError, type PlatformError } from './page-runtime.js';\nimport { parseServerSentEvents, type ServerSentEvent } from './uns-v2/sse.js';\n\nconst RECONNECT_BASE_MS = 1_000;\nconst RECONNECT_MAX_MS = 30_000;\n/** A connection alive this long resets the backoff. */\nconst HEALTHY_CONNECTION_MS = 30_000;\n/**\n * A paused stream looks again this often while the runtime still says\n * `running`: a routing race can answer `sbx_sleeping` for a running App, and\n * then no `resumed` ever comes. Such a request cannot wake anything.\n */\nconst PAUSE_RECHECK_MS = 30_000;\n\nexport interface EventStreamOptions {\n url: string;\n headers?: Record<string, string>;\n lastEventId?: string;\n fetch?: typeof globalThis.fetch;\n onEvent(event: ServerSentEvent): void;\n /**\n * The stream is back after a pause for the App's sleep or an authorization\n * failure. Events may have been missed, resume position or not.\n */\n onResume?(): void;\n /** The stream ended for good; it does not reconnect. Not called after `close()`. */\n onClose?(error: EventStreamError): void;\n /** Reconnect when nothing, not even a heartbeat, arrives for this long. Off by default. */\n idleTimeoutMs?: number;\n /** Close the connection while the page is hidden and resume from the last event id when it is visible. Default true. */\n pauseWhenHidden?: boolean;\n}\n\nexport interface EventStream {\n close(): void;\n}\n\nexport class EventStreamError extends Error {\n constructor(readonly status: number, readonly code: string | undefined) {\n super(`Event stream refused with HTTP ${status}${code ? ` (${code})` : ''}.`);\n this.name = 'EventStreamError';\n }\n}\n\ntype Outcome =\n | { next: 'reconnect'; afterMs?: number }\n | { next: 'pause' }\n | { next: 'end'; error: EventStreamError };\n\nfunction classify(status: number, failure: PlatformError | undefined): Outcome {\n const code = failure?.code;\n const runtime = getPageRuntime() !== undefined;\n if (isAuthErrorCode(code)) {\n reportAuthError(code);\n return runtime ? { next: 'pause' } : { next: 'end', error: new EventStreamError(status, code) };\n }\n if (code === 'sbx_sleeping' || code === 'sbx_starting' || code === 'sbx_start_failed') {\n if (runtime) return { next: 'pause' };\n if (code !== 'sbx_starting') return { next: 'end', error: new EventStreamError(status, code) };\n }\n if (failure?.retryable) return { next: 'reconnect', afterMs: failure.retryAfterMs };\n // Not ours to classify: an edge proxy's 5xx passes, a route's 4xx is final.\n if (!failure && status >= 500) return { next: 'reconnect' };\n return { next: 'end', error: new EventStreamError(status, code) };\n}\n\nexport function openEventStream(options: EventStreamOptions): EventStream {\n const { url, onEvent, onResume, onClose, idleTimeoutMs } = options;\n let lastEventId = options.lastEventId;\n let closed = false;\n let paused = isAppSleeping();\n let resumedFromPause = false;\n let failures = 0;\n let resumes = 0;\n let connection: AbortController | undefined;\n let wake: (() => void) | undefined;\n const page = (options.pauseWhenHidden ?? true) && typeof document !== 'undefined' ? document : undefined;\n let hidden = page?.visibilityState === 'hidden';\n const onVisibility = () => {\n hidden = page!.visibilityState === 'hidden';\n if (hidden) connection?.abort();\n wake?.();\n };\n page?.addEventListener('visibilitychange', onVisibility);\n\n const sleep = (ms?: number) => new Promise<void>((resolve) => {\n const timer = ms === undefined ? undefined : setTimeout(() => done(), ms);\n const done = () => {\n clearTimeout(timer);\n wake = undefined;\n resolve();\n };\n wake = done;\n });\n\n const offSleeping = onPageRuntime('sleeping', () => {\n paused = true;\n connection?.abort();\n wake?.();\n });\n const offResumed = onPageRuntime('resumed', () => {\n if (paused) resumedFromPause = true;\n paused = false;\n resumes += 1;\n wake?.();\n });\n\n function backoff(): number {\n failures += 1;\n const ceiling = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** (failures - 1));\n return ceiling / 2 + Math.random() * (ceiling / 2);\n }\n\n async function consume(body: ReadableStream<Uint8Array>, controller: AbortController): Promise<void> {\n const openedAt = Date.now();\n let idle: ReturnType<typeof setTimeout> | undefined;\n const arm = () => {\n if (idleTimeoutMs === undefined) return;\n clearTimeout(idle);\n idle = setTimeout(() => controller.abort(), idleTimeoutMs);\n };\n arm();\n try {\n for await (const event of parseServerSentEvents(body)) {\n arm();\n if (Date.now() - openedAt >= HEALTHY_CONNECTION_MS) failures = 0;\n if ('comment' in event) continue;\n if (event.id !== undefined) lastEventId = event.id;\n if ('event' in event) onEvent(event);\n }\n } finally {\n clearTimeout(idle);\n }\n }\n\n async function connect(): Promise<Outcome> {\n const controller = new AbortController();\n connection = controller;\n const headers: Record<string, string> = { ...options.headers, Accept: 'text/event-stream' };\n if (lastEventId !== undefined) headers['Last-Event-ID'] = lastEventId;\n try {\n const response = await (options.fetch ?? globalThis.fetch)(url, { headers, cache: 'no-store', signal: controller.signal });\n const type = response.headers.get('content-type') ?? '';\n if (response.ok && /^text\\/event-stream\\b/i.test(type) && response.body) {\n if (resumedFromPause) {\n resumedFromPause = false;\n onResume?.();\n }\n await consume(response.body, controller);\n return { next: 'reconnect' };\n }\n const failure = await readPlatformError(response);\n await response.body?.cancel().catch(() => undefined);\n return classify(response.status, failure);\n } catch {\n return { next: 'reconnect' };\n } finally {\n connection = undefined;\n }\n }\n\n async function run(): Promise<void> {\n while (!closed) {\n if (hidden) {\n await sleep();\n continue;\n }\n if (paused) {\n await sleep(getPageRuntime()?.getState() === 'running' ? PAUSE_RECHECK_MS : undefined);\n if (getPageRuntime()?.getState() === 'running' && !closed) {\n resumedFromPause = true;\n paused = false;\n }\n continue;\n }\n const seenResumes = resumes;\n const outcome = await connect();\n if (closed) return;\n if (outcome.next === 'end') {\n close();\n onClose?.(outcome.error);\n return;\n }\n if (outcome.next === 'pause') {\n // A `resumed` that arrived while the refusal was on its way already happened.\n if (resumes === seenResumes) paused = true;\n else resumedFromPause = true;\n continue;\n }\n // Closed for a hidden page: wait for it to be visible, not for a backoff.\n if (hidden) continue;\n if (!paused) await sleep(outcome.afterMs ?? backoff());\n }\n }\n\n function close(): void {\n closed = true;\n offSleeping();\n offResumed();\n page?.removeEventListener('visibilitychange', onVisibility);\n connection?.abort();\n wake?.();\n }\n\n void run();\n return { close };\n}\n"]}
@@ -13,11 +13,40 @@ export declare class Tier0MQClient {
13
13
  private explicitUsername;
14
14
  private listeners;
15
15
  private subscriptions;
16
+ private topicSubscriptions;
17
+ private pendingTopicPackets;
18
+ private sentTopicPackets;
16
19
  private connectingPromise;
20
+ private rejectPendingConnect;
17
21
  private _connected;
22
+ /** Bumped by disconnect(); callbacks of an older generation are ignored. */
23
+ private generation;
24
+ /** Random clientId segment; a persistent session is found again through it. */
25
+ private readonly clientSuffix;
26
+ private reconnectTimer;
27
+ private reconnectAttempts;
28
+ private connectWaiters;
18
29
  constructor(config?: MQTTConfig);
19
- private get mqttUrl();
30
+ private mqttUrlFor;
31
+ /**
32
+ * Where the credential comes from; undefined when a fixed password is used.
33
+ * `proxy`: platform proxy mode, the node proxy puts in the App's key and
34
+ * the SDK sends none.
35
+ */
36
+ private tokenSource;
37
+ /** Local preflight before a transport exists: address and credentials must be usable. */
38
+ private validateConnectionConfig;
39
+ /**
40
+ * What the next connection presents. Through the node proxy the same
41
+ * identity reconnects, and the proxy puts in the App's current key.
42
+ */
43
+ private connectionParams;
20
44
  private ensureConnected;
45
+ private clearReconnect;
46
+ private reconnectDelay;
47
+ private scheduleReconnect;
48
+ /** The same client: mqtt.js keeps its session and offline queue. */
49
+ private reconnect;
21
50
  private doConnect;
22
51
  connect(config?: MQTTConfig): Promise<void>;
23
52
  subscribe(topic: string, handler: TopicHandler): void;
@@ -31,6 +60,8 @@ export declare class Tier0MQClient {
31
60
  off<K extends keyof MQTTEventMap>(event: K, handler: MQTTEventMap[K]): void;
32
61
  private emit;
33
62
  private resubscribeAll;
63
+ private clearTopicState;
64
+ private subscribeTopic;
34
65
  private topicMatch;
35
66
  get isConnected(): boolean;
36
67
  get subscribedTopics(): string[];