@haikit/postgres 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,10 +52,13 @@ With your own migration tool, apply the new `ALTER TABLE` from `schema` before
52
52
  deploying. Rows written before the column existed are held to the window your
53
53
  code declares.
54
54
 
55
- **Upgrading to 0.12:** `migrate()` adds `haikit_notices`, and `notice_seq` and
55
+ **Upgrading to 0.13:** `migrate()` adds `haikit_notices`, and `notice_seq` and
56
56
  `noticed_through` to `haikit_conversations`, for `hai.notify`. As before, if
57
57
  you call `migrate()` at startup there is nothing to do.
58
58
 
59
+ **Upgrading to 0.14:** `migrate()` adds `kind` to `haikit_notices`, and
60
+ `wakes` and `woke_through` to `haikit_conversations`, for wake notices.
61
+
59
62
  Handles are numbered per conversation, so every conversation's digests start at
60
63
  `ui_01`.
61
64
 
@@ -89,6 +92,27 @@ const store = pgStore(pool, {
89
92
  A notice belongs to its conversation and goes when it does: deleting a
90
93
  conversation deletes its notices. `sweepOrphans` never touches them.
91
94
 
95
+ ## Wake turns across servers
96
+
97
+ With several app servers, the same `listen` connection carries a wake turn
98
+ from the server running it to the others, so a browser connected to any of
99
+ them sees the reply. `publish` sends each of the turn's frames as `NOTIFY`
100
+ on `haikit_turns`. A frame longer than one `NOTIFY` can hold (just under
101
+ 8,000 bytes) goes as several, sent in one statement, and is put back
102
+ together on arrival. Frames are sent one at a time per conversation, each
103
+ committed before the next, so they arrive in order. Every store can publish;
104
+ only one given `listen` hears. A message past `MAX_PUBLISH_BYTES` (about
105
+ 52 MB, far beyond any frame) is refused, not sent.
106
+
107
+ **Upgrading:** `migrate()` adds `wake_turns` to `haikit_conversations`, each
108
+ conversation's count of wake turns, which numbers them so a late frame from
109
+ an older turn can't take over from a newer one.
110
+
111
+ It is best effort, as `NOTIFY` is: a server whose listening connection is
112
+ down misses those frames, and a tab there sees the reply when it next
113
+ reconnects to a server that has it, or not at all. The frames themselves
114
+ are never stored, so there is nothing of them to clean up.
115
+
92
116
  ## Cleaning up
93
117
 
94
118
  A turn that is overtaken mid-flight can leave payload rows that the surviving
package/dist/index.d.ts CHANGED
@@ -49,6 +49,10 @@ export interface Listener {
49
49
  }
50
50
  /** The channel `putNotice` notifies on, with the conversation id as payload. */
51
51
  export declare const NOTICE_CHANNEL = "haikit_notices";
52
+ /** The channel `publish` carries messages on, in pieces small enough for NOTIFY. */
53
+ export declare const TURN_CHANNEL = "haikit_turns";
54
+ /** The longest message `publish` takes, in UTF-8 bytes: what MAX_PIECES pieces of base64 hold. */
55
+ export declare const MAX_PUBLISH_BYTES: number;
52
56
  /**
53
57
  * The schema, one statement per entry. `migrate()` runs these; export them to
54
58
  * your own migration tool instead if you have one.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAID,gFAAgF;AAChF,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAsDnC,CAAC;AAEF,8FAA8F;AAC9F,wBAAsB,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1D;AA8DD,wBAAgB,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,YAAY,CAkPjF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAYrG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAID,gFAAgF;AAChF,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,oFAAoF;AACpF,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAc3C,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,QAA2C,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAiEnC,CAAC;AAEF,8FAA8F;AAC9F,wBAAsB,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1D;AAmED,wBAAgB,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,YAAY,CAoWjF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAYrG"}
package/dist/index.js CHANGED
@@ -14,6 +14,19 @@ import { ConversationBusy, StaleLease, } from "@haikit/core";
14
14
  const LEASE_MS = 120_000;
15
15
  /** The channel `putNotice` notifies on, with the conversation id as payload. */
16
16
  export const NOTICE_CHANNEL = "haikit_notices";
17
+ /** The channel `publish` carries messages on, in pieces small enough for NOTIFY. */
18
+ export const TURN_CHANNEL = "haikit_turns";
19
+ /**
20
+ * The most of a message one NOTIFY carries. Postgres refuses payloads of 8000
21
+ * bytes or more; a piece is ASCII, so this leaves room for its header.
22
+ */
23
+ const PIECE = 7_000;
24
+ /** How long half a message waits for the rest before it is given up on. */
25
+ const PIECE_TIMEOUT_MS = 30_000;
26
+ /** The most pieces one message may claim to have: about 52 MB of message, far past any frame. */
27
+ const MAX_PIECES = 10_000;
28
+ /** The longest message `publish` takes, in UTF-8 bytes: what MAX_PIECES pieces of base64 hold. */
29
+ export const MAX_PUBLISH_BYTES = Math.floor((PIECE * MAX_PIECES * 3) / 4);
17
30
  /**
18
31
  * The schema, one statement per entry. `migrate()` runs these; export them to
19
32
  * your own migration tool instead if you have one.
@@ -32,11 +45,19 @@ export const schema = [
32
45
  -- the last notice number given out, and the last one its history took in
33
46
  notice_seq integer NOT NULL DEFAULT 0,
34
47
  noticed_through integer NOT NULL DEFAULT 0,
48
+ -- when recent wake turns started, and the last notice that may no longer start one
49
+ wakes jsonb NOT NULL DEFAULT '[]',
50
+ woke_through integer NOT NULL DEFAULT 0,
51
+ -- how many wake turns it has started, to tell an older turn's frames from a newer's
52
+ wake_turns integer NOT NULL DEFAULT 0,
35
53
  updated_at timestamptz NOT NULL DEFAULT now()
36
54
  )`,
37
55
  // a table created before notices existed gains the columns
38
56
  `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS notice_seq integer NOT NULL DEFAULT 0`,
39
57
  `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS noticed_through integer NOT NULL DEFAULT 0`,
58
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS wakes jsonb NOT NULL DEFAULT '[]'`,
59
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS woke_through integer NOT NULL DEFAULT 0`,
60
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS wake_turns integer NOT NULL DEFAULT 0`,
40
61
  // Append-only, and never an orphan: a notice belongs to a conversation that
41
62
  // exists, and goes when it does.
42
63
  `CREATE TABLE IF NOT EXISTS haikit_notices (
@@ -48,8 +69,11 @@ export const schema = [
48
69
  payload jsonb NOT NULL,
49
70
  model text,
50
71
  handle text,
72
+ -- 'wake', or NULL for a passive notice
73
+ kind text,
51
74
  PRIMARY KEY (conversation_id, seq)
52
75
  )`,
76
+ `ALTER TABLE haikit_notices ADD COLUMN IF NOT EXISTS kind text`,
53
77
  // Write-once. Nothing about a payload changes after insert — everything that
54
78
  // does lives on the fenced conversation row (see Conversation.frozen).
55
79
  `CREATE TABLE IF NOT EXISTS haikit_payloads (
@@ -87,9 +111,10 @@ export async function migrate(db) {
87
111
  const CONVERSATION_COLUMNS = `
88
112
  id, status, messages::text AS messages, handles::text AS handles,
89
113
  frozen::text AS frozen, pending::text AS pending, lease_token, noticed_through,
114
+ wakes::text AS wakes, woke_through, wake_turns,
90
115
  (extract(epoch FROM lease_until) * 1000)::float8 AS lease_until_ms`;
91
116
  const NOTICE_COLUMNS = `
92
- conversation_id, seq, name, version, payload::text AS payload, model, handle,
117
+ conversation_id, seq, name, version, payload::text AS payload, model, handle, kind,
93
118
  (extract(epoch FROM created_at) * 1000)::float8 AS created_at_ms`;
94
119
  // The window is read as text too: 'Infinity' is how "never" is stored, and
95
120
  // drivers need not agree on turning it into a number.
@@ -108,6 +133,9 @@ const toConversation = (row) => ({
108
133
  leaseUntil: row.lease_until_ms == null ? null : Number(row.lease_until_ms),
109
134
  leaseToken: row.lease_token,
110
135
  noticedThrough: Number(row.noticed_through ?? 0),
136
+ wakes: row.wakes == null ? [] : json(row.wakes),
137
+ wokeThrough: Number(row.woke_through ?? 0),
138
+ wakeTurns: Number(row.wake_turns ?? 0),
111
139
  });
112
140
  const toNotice = (row) => ({
113
141
  conversationId: row.conversation_id,
@@ -118,6 +146,7 @@ const toNotice = (row) => ({
118
146
  payload: json(row.payload),
119
147
  model: row.model,
120
148
  ...(row.handle == null ? {} : { handle: row.handle }),
149
+ ...(row.kind === "wake" ? { kind: "wake" } : {}),
121
150
  });
122
151
  const toPayload = (row) => ({
123
152
  handle: row.handle,
@@ -140,20 +169,118 @@ export function pgStore(db, options = {}) {
140
169
  if (!Number.isFinite(leaseMs) || leaseMs <= 0) {
141
170
  throw new RangeError(`leaseMs must be a positive, finite number of milliseconds (got ${leaseMs})`);
142
171
  }
143
- // One LISTEN for the whole store, taken when something first watches, and
144
- // each notification handed to whoever is watching that conversation.
172
+ // One LISTEN per channel for the whole store, taken when something first
173
+ // needs it, and each notification handed to whoever wants that conversation.
145
174
  const waiting = new Map();
146
- let listening = null;
147
- const listen = (listener) => (listening ??= listener
148
- .listen(NOTICE_CHANNEL, (id) => {
175
+ const listening = new Map();
176
+ const listenOn = (listener, channel, onNotify) => {
177
+ let started = listening.get(channel);
178
+ if (!started) {
179
+ started = listener.listen(channel, onNotify).catch((err) => {
180
+ // try again next time, rather than never
181
+ listening.delete(channel);
182
+ throw err;
183
+ });
184
+ listening.set(channel, started);
185
+ }
186
+ return started;
187
+ };
188
+ const listen = (listener) => listenOn(listener, NOTICE_CHANNEL, (id) => {
149
189
  for (const wake of waiting.get(id) ?? [])
150
190
  wake();
151
- })
152
- .catch((err) => {
153
- // try again on the next watch, rather than never
154
- listening = null;
155
- throw err;
156
- }));
191
+ });
192
+ // ── publish / subscribe, over NOTIFY ───────────────────────────────────
193
+ // A message goes out as pieces of at most PIECE base64 characters, each
194
+ // `<conversation> <message id> <index> <count> <piece>`, all in one
195
+ // statement, and is put back together on the way in. Messages from this
196
+ // store are sent one at a time per conversation, each committed before the
197
+ // next starts, so they arrive in the order they were published.
198
+ const hearing = new Map();
199
+ const partial = new Map();
200
+ const sending = new Map();
201
+ const onTurn = (payload) => {
202
+ const [conversationId, id, index, count, piece] = String(payload).split(" ");
203
+ if (!hearing.has(conversationId))
204
+ return;
205
+ // The channel is shared, and anything may NOTIFY on it: a piece that
206
+ // doesn't hold together is dropped, never let throw out of the listener.
207
+ const n = Number(count);
208
+ const i = Number(index);
209
+ if (!id || !Number.isSafeInteger(n) || n < 1 || n > MAX_PIECES || !Number.isSafeInteger(i) || i < 0 || i >= n)
210
+ return;
211
+ const known = partial.get(id);
212
+ if (known && known.pieces.length !== n)
213
+ return;
214
+ let entry = known;
215
+ if (!entry) {
216
+ // A message whose other pieces never come (a listening connection that
217
+ // dropped part way) is given up on after a while, whatever else arrives.
218
+ const expire = setTimeout(() => partial.delete(id), PIECE_TIMEOUT_MS);
219
+ // and is no reason to keep the process alive
220
+ expire.unref?.();
221
+ entry = { pieces: new Array(n), got: 0, expire };
222
+ }
223
+ if (entry.pieces[i] === undefined)
224
+ entry.got++;
225
+ entry.pieces[i] = piece ?? "";
226
+ if (entry.got < n)
227
+ return void partial.set(id, entry);
228
+ clearTimeout(entry.expire);
229
+ partial.delete(id);
230
+ let message;
231
+ try {
232
+ message = new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(entry.pieces.join("")), (c) => c.charCodeAt(0)));
233
+ }
234
+ catch {
235
+ return; // not base64, or not UTF-8 once decoded: not one of ours
236
+ }
237
+ for (const hear of [...(hearing.get(conversationId) ?? [])])
238
+ hear(message);
239
+ };
240
+ async function publish(conversationId, message) {
241
+ const bytes = new TextEncoder().encode(String(message));
242
+ // Refused here rather than sent and dropped by every listener, and before
243
+ // any of the work of encoding it.
244
+ if (bytes.length > MAX_PUBLISH_BYTES) {
245
+ throw new RangeError(`message too long to publish: ${bytes.length} bytes, over the ${MAX_PUBLISH_BYTES}-byte maximum`);
246
+ }
247
+ let binary = "";
248
+ for (const b of bytes)
249
+ binary += String.fromCharCode(b);
250
+ const encoded = btoa(binary);
251
+ const id = newToken().replaceAll("-", "");
252
+ const count = Math.max(1, Math.ceil(encoded.length / PIECE));
253
+ const pieces = Array.from({ length: count }, (_, i) => `${conversationId} ${id} ${i} ${count} ${encoded.slice(i * PIECE, (i + 1) * PIECE)}`);
254
+ const before = sending.get(conversationId) ?? Promise.resolve();
255
+ const sent = before.then(() => db.query(`SELECT pg_notify('${TURN_CHANNEL}', piece) FROM jsonb_array_elements_text($1::jsonb) AS piece`, [
256
+ JSON.stringify(pieces),
257
+ ]));
258
+ const settled = sent.then(() => { }, () => { });
259
+ sending.set(conversationId, settled);
260
+ try {
261
+ await sent;
262
+ }
263
+ finally {
264
+ if (sending.get(conversationId) === settled)
265
+ sending.delete(conversationId);
266
+ }
267
+ }
268
+ async function subscribe(conversationId, onMessage, signal) {
269
+ if (signal.aborted)
270
+ return;
271
+ const set = hearing.get(conversationId) ?? new Set();
272
+ set.add(onMessage);
273
+ hearing.set(conversationId, set);
274
+ signal.addEventListener("abort", () => {
275
+ set.delete(onMessage);
276
+ if (!set.size && hearing.get(conversationId) === set)
277
+ hearing.delete(conversationId);
278
+ }, { once: true });
279
+ // Resolves once LISTEN has taken hold, so nothing published after this
280
+ // resolves is missed. Best effort, as the contract says: a failed LISTEN
281
+ // is tried again next time, and this resolves all the same.
282
+ await listenOn(options.listen, TURN_CHANNEL, onTurn).catch(() => { });
283
+ }
157
284
  async function* watch(conversationId, signal) {
158
285
  // One wake-up stands for any number of notices: a step resolves once
159
286
  // something has landed since the last, and the caller reads them all.
@@ -236,6 +363,9 @@ export function pgStore(db, options = {}) {
236
363
  pending = $7::jsonb,
237
364
  lease_until = to_timestamp($8::float8 / 1000),
238
365
  noticed_through = $9::integer,
366
+ wakes = $10::jsonb,
367
+ woke_through = $11::integer,
368
+ wake_turns = $12::integer,
239
369
  updated_at = now()
240
370
  WHERE id = $1 AND lease_token = $2
241
371
  RETURNING id`, [
@@ -249,6 +379,9 @@ export function pgStore(db, options = {}) {
249
379
  conversation.pending == null ? null : JSON.stringify(conversation.pending),
250
380
  conversation.leaseUntil,
251
381
  conversation.noticedThrough ?? 0,
382
+ JSON.stringify(conversation.wakes ?? []),
383
+ conversation.wokeThrough ?? 0,
384
+ conversation.wakeTurns ?? 0,
252
385
  ]);
253
386
  if (!rows.length)
254
387
  await stale(conversation.id);
@@ -318,8 +451,8 @@ export function pgStore(db, options = {}) {
318
451
  WHERE id = $1::text
319
452
  RETURNING notice_seq
320
453
  ), stored AS (
321
- INSERT INTO haikit_notices (conversation_id, seq, name, version, payload, model, handle)
322
- SELECT $1::text, notice_seq, $2::text, $3::integer, $4::jsonb, $5::text, $6::text FROM next
454
+ INSERT INTO haikit_notices (conversation_id, seq, name, version, payload, model, handle, kind)
455
+ SELECT $1::text, notice_seq, $2::text, $3::integer, $4::jsonb, $5::text, $6::text, $7::text FROM next
323
456
  RETURNING ${NOTICE_COLUMNS}
324
457
  )
325
458
  SELECT stored.*, pg_notify('${NOTICE_CHANNEL}', $1::text) FROM stored`, [
@@ -329,6 +462,7 @@ export function pgStore(db, options = {}) {
329
462
  JSON.stringify(record.payload ?? null),
330
463
  record.model,
331
464
  record.handle ?? null,
465
+ record.kind === "wake" ? "wake" : null,
332
466
  ]);
333
467
  if (!rows.length)
334
468
  throw new Error(`conversation ${record.conversationId} does not exist`);
@@ -345,7 +479,9 @@ export function pgStore(db, options = {}) {
345
479
  const exists = await db.query(`SELECT 1 FROM haikit_conversations WHERE id = $1`, [conversationId]);
346
480
  return exists.rows.length ? [] : null;
347
481
  },
348
- ...(options.listen ? { watch } : {}),
482
+ // Anyone can publish; hearing it takes a LISTEN connection.
483
+ publish,
484
+ ...(options.listen ? { watch, subscribe } : {}),
349
485
  };
350
486
  }
351
487
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,gBAAgB,EAChB,UAAU,GAKX,MAAM,cAAc,CAAC;AAuCtB,MAAM,QAAQ,GAAG,OAAO,CAAC;AAEzB,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,oFAAoF;AACpF,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C;;;GAGG;AACH,MAAM,KAAK,GAAG,KAAK,CAAC;AAEpB,2EAA2E;AAC3E,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC,iGAAiG;AACjG,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,kGAAkG;AAClG,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1E;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAsB;IACvC;;;;;;;;;;;;;;;;;;;KAmBG;IACH,2DAA2D;IAC3D,iGAAiG;IACjG,sGAAsG;IACtG,6FAA6F;IAC7F,mGAAmG;IACnG,iGAAiG;IACjG,4EAA4E;IAC5E,iCAAiC;IACjC;;;;;;;;;;;;KAYG;IACH,+DAA+D;IAC/D,6EAA6E;IAC7E,uEAAuE;IACvE;;;;;;;;;;;;;;;KAeG;IACH,yEAAyE;IACzE,kEAAkE;IAClE,sFAAsF;IACtF,uFAAuF;CACxF,CAAC;AAEF,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAa;IACzC,0EAA0E;IAC1E,8CAA8C;IAC9C,KAAK,MAAM,SAAS,IAAI,MAAM;QAAE,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAC5D,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,kEAAkE;AAClE,MAAM,oBAAoB,GAAG;;;;qEAIwC,CAAC;AAEtE,MAAM,cAAc,GAAG;;mEAE4C,CAAC;AAEpE,2EAA2E;AAC3E,sDAAsD;AACtD,MAAM,eAAe,GAAG;;;yCAGiB,CAAC;AAE1C,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAEhD,MAAM,cAAc,GAAG,CAAC,GAAQ,EAAgB,EAAE,CAAC,CAAC;IAClD,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC5B,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1B,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACxB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IACvD,UAAU,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1E,UAAU,EAAE,GAAG,CAAC,WAAW;IAC3B,cAAc,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,CAAC;IAChD,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;IAC/C,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,CAAC,GAAQ,EAAgB,EAAE,CAAC,CAAC;IAC5C,cAAc,EAAE,GAAG,CAAC,eAAe;IACnC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;IACpC,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;IAC5B,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CAC1D,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,GAAQ,EAAiB,EAAE,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,cAAc,EAAE,GAAG,CAAC,eAAe;IACnC,SAAS,EAAE,GAAG,CAAC,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;IAC5B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;IACtB,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;IACpC,YAAY,EACV,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;CAC/G,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;AACtD,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,QAAQ,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;AAEjF,MAAM,UAAU,OAAO,CAAC,EAAa,EAAE,UAA0B,EAAE;IACjE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5C,6EAA6E;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,iEAAiE;IACjE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,kEAAkE,OAAO,GAAG,CAAC,CAAC;IACrG,CAAC;IAED,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAC;IACtD,MAAM,QAAQ,GAAG,CAAC,QAAkB,EAAE,OAAe,EAAE,QAAmC,EAAE,EAAE;QAC5F,IAAI,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACzD,yCAAyC;gBACzC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC1B,MAAM,GAAG,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,QAAkB,EAAE,EAAE,CACpC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE,EAAE;QACxC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;YAAE,IAAI,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEL,0EAA0E;IAC1E,wEAAwE;IACxE,oEAAoE;IACpE,wEAAwE;IACxE,2EAA2E;IAC3E,gEAAgE;IAChE,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0C,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoF,CAAC;IAC5G,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEjD,MAAM,MAAM,GAAG,CAAC,OAAe,EAAE,EAAE;QACjC,MAAM,CAAC,cAAc,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,OAAO;QACzC,qEAAqE;QACrE,yEAAyE;QACzE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QACtH,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/C,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,uEAAuE;YACvE,yEAAyE;YACzE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC;YACtE,6CAA6C;YAC5C,MAAiC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC7C,KAAK,GAAG,EAAE,MAAM,EAAE,IAAI,KAAK,CAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3D,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,EAAE,CAAC;QAC/C,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC;YAAE,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACtD,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnB,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CACxD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CACrE,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,yDAAyD;QACnE,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7E,CAAC,CAAC;IAEF,KAAK,UAAU,OAAO,CAAC,cAAsB,EAAE,OAAe;QAC5D,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,0EAA0E;QAC1E,kCAAkC;QAClC,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACrC,MAAM,IAAI,UAAU,CAClB,gCAAgC,KAAK,CAAC,MAAM,oBAAoB,iBAAiB,eAAe,CACjG,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACpD,GAAG,cAAc,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CACrF,CAAC;QACF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAC5B,EAAE,CAAC,KAAK,CAAC,qBAAqB,YAAY,8DAA8D,EAAE;YACxG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SACvB,CAAC,CACH,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CACvB,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC;QACb,CAAC;gBAAS,CAAC;YACT,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,OAAO;gBAAE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,KAAK,UAAU,SAAS,CAAC,cAAsB,EAAE,SAAoC,EAAE,MAAmB;QACxG,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO;QAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,gBAAgB,CACrB,OAAO,EACP,GAAG,EAAE;YACH,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACvF,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAC;QACF,uEAAuE;QACvE,yEAAyE;QACzE,4DAA4D;QAC5D,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,SAAS,CAAC,CAAC,KAAK,CAAC,cAAsB,EAAE,MAAmB;QAC/D,qEAAqE;QACrE,sEAAsE;QACtE,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,IAAI,GAAwB,IAAI,CAAC;QACrC,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,EAAE,EAAE,CAAC;QACX,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,MAAO,CAAC,CAAC;YAC9B,qEAAqE;YACrE,6DAA6D;YAC7D,MAAM,GAAG,IAAI,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACvB,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;gBACxD,IAAI,GAAG,IAAI,CAAC;gBACZ,IAAI,MAAM,CAAC,OAAO;oBAAE,OAAO;gBAC3B,MAAM,GAAG,KAAK,CAAC;gBACf,KAAK,CAAC;YACR,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,IAAI;gBAAE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,KAAK,UAAU,KAAK,CAAC,cAAsB;QACzC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,kDAAkD,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;QACtG,MAAM,IAAI,CAAC,MAAM;YACf,CAAC,CAAC,IAAI,UAAU,CAAC,cAAc,CAAC;YAChC,CAAC,CAAC,IAAI,UAAU,CAAC,cAAc,EAAE,kDAAkD,CAAC,CAAC;IACzF,CAAC;IAED,OAAO;QACL,KAAK,CAAC,gBAAgB,CAAC,EAAE;YACvB,IAAI,EAAE,EAAE,CAAC;gBACP,uEAAuE;gBACvE,uEAAuE;gBACvE,oEAAoE;gBACpE,yEAAyE;gBACzE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;;wBAIc,oBAAoB,EAAE,EACpC,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAC1B,CAAC;gBACF,IAAI,IAAI,CAAC,CAAC,CAAC;oBAAE,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE5C,uEAAuE;gBACvE,mEAAmE;gBACnE,qCAAqC;gBACrC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,kDAAkD,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;gBACvD,kEAAkE;YACpE,CAAC;YAED,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;qBAEa,oBAAoB,EAAE,EACnC,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAC/B,CAAC;YACF,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,YAAY;YACjC,yEAAyE;YACzE,yEAAyE;YACzE,sEAAsE;YACtE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;;;;;;;;;;;uBAae,EACf;gBACE,YAAY,CAAC,EAAE;gBACf,YAAY,CAAC,UAAU;gBACvB,YAAY,CAAC,MAAM;gBACnB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC;gBACrC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC;gBACpC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC;gBACnC,uEAAuE;gBACvE,YAAY,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC;gBAC1E,YAAY,CAAC,UAAU;gBACvB,YAAY,CAAC,cAAc,IAAI,CAAC;gBAChC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC;gBACxC,YAAY,CAAC,WAAW,IAAI,CAAC;gBAC7B,YAAY,CAAC,SAAS,IAAI,CAAC;aAC5B,CACF,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,MAAM,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU;YACjC,0EAA0E;YAC1E,oEAAoE;YACpE,sEAAsE;YACtE,kEAAkE;YAClE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;;;;;;;;;;;0BAakB,EAClB;gBACE,MAAM,CAAC,cAAc;gBACrB,UAAU;gBACV,MAAM,CAAC,SAAS;gBAChB,MAAM,CAAC,OAAO;gBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC5B,MAAM,CAAC,IAAI;gBACX,MAAM,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC;aAC7E,CACF,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,MAAM,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACxB,CAAC;QAED,uEAAuE;QACvE,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc;YACrC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B,UAAU,eAAe,kEAAkE,EAC3F,CAAC,cAAc,EAAE,MAAM,CAAC,CACzB,CAAC;YACF,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7C,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc;YACvC,IAAI,CAAC,OAAO,CAAC,MAAM;gBAAE,OAAO,EAAE,CAAC;YAC/B,wEAAwE;YACxE,yCAAyC;YACzC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B,UAAU,eAAe;;wEAEuC,EAChE,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAC1C,CAAC;YACF,mEAAmE;YACnE,uEAAuE;YACvE,uEAAuE;YACvE,mEAAmE;YACnE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAU,CAAC,CAAC,CAAC;YACnF,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC/B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,MAAM;YACpB,wEAAwE;YACxE,kEAAkE;YAClE,yEAAyE;YACzE,mEAAmE;YACnE,iEAAiE;YACjE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;;;;;uBAOe,cAAc;;uCAEE,cAAc,0BAA0B,EACvE;gBACE,MAAM,CAAC,cAAc;gBACrB,MAAM,CAAC,IAAI;gBACX,MAAM,CAAC,OAAO;gBACd,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC;gBACtC,MAAM,CAAC,KAAK;gBACZ,MAAM,CAAC,MAAM,IAAI,IAAI;gBACrB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;aACvC,CACF,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,MAAM,CAAC,cAAc,iBAAiB,CAAC,CAAC;YAC1F,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,KAAK,EAAE,KAAK;YAC3C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B,UAAU,cAAc;;;4BAGJ,EACpB,CAAC,cAAc,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC,CACvC,CAAC;YACF,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC3C,0DAA0D;YAC1D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,kDAAkD,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;YACpG,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACxC,CAAC;QAED,4DAA4D;QAC5D,OAAO;QACP,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC;AAWD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAa,EAAE,OAAqB;IACrE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAC7B;;;;;;yBAMqB,EACrB,CAAC,OAAO,CAAC,WAAW,CAAC,CACtB,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;AAClC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haikit/postgres",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Postgres store adapter for haikit: durable conversations, fenced turn leases, write-once payloads. Bring your own driver.",
5
5
  "keywords": [
6
6
  "haikit",
@@ -34,7 +34,7 @@
34
34
  "prepack": "npm --prefix ../.. run build"
35
35
  },
36
36
  "dependencies": {
37
- "@haikit/core": "0.13.0"
37
+ "@haikit/core": "0.15.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
package/src/index.ts CHANGED
@@ -62,6 +62,24 @@ const LEASE_MS = 120_000;
62
62
  /** The channel `putNotice` notifies on, with the conversation id as payload. */
63
63
  export const NOTICE_CHANNEL = "haikit_notices";
64
64
 
65
+ /** The channel `publish` carries messages on, in pieces small enough for NOTIFY. */
66
+ export const TURN_CHANNEL = "haikit_turns";
67
+
68
+ /**
69
+ * The most of a message one NOTIFY carries. Postgres refuses payloads of 8000
70
+ * bytes or more; a piece is ASCII, so this leaves room for its header.
71
+ */
72
+ const PIECE = 7_000;
73
+
74
+ /** How long half a message waits for the rest before it is given up on. */
75
+ const PIECE_TIMEOUT_MS = 30_000;
76
+
77
+ /** The most pieces one message may claim to have: about 52 MB of message, far past any frame. */
78
+ const MAX_PIECES = 10_000;
79
+
80
+ /** The longest message `publish` takes, in UTF-8 bytes: what MAX_PIECES pieces of base64 hold. */
81
+ export const MAX_PUBLISH_BYTES = Math.floor((PIECE * MAX_PIECES * 3) / 4);
82
+
65
83
  /**
66
84
  * The schema, one statement per entry. `migrate()` runs these; export them to
67
85
  * your own migration tool instead if you have one.
@@ -80,11 +98,19 @@ export const schema: readonly string[] = [
80
98
  -- the last notice number given out, and the last one its history took in
81
99
  notice_seq integer NOT NULL DEFAULT 0,
82
100
  noticed_through integer NOT NULL DEFAULT 0,
101
+ -- when recent wake turns started, and the last notice that may no longer start one
102
+ wakes jsonb NOT NULL DEFAULT '[]',
103
+ woke_through integer NOT NULL DEFAULT 0,
104
+ -- how many wake turns it has started, to tell an older turn's frames from a newer's
105
+ wake_turns integer NOT NULL DEFAULT 0,
83
106
  updated_at timestamptz NOT NULL DEFAULT now()
84
107
  )`,
85
108
  // a table created before notices existed gains the columns
86
109
  `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS notice_seq integer NOT NULL DEFAULT 0`,
87
110
  `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS noticed_through integer NOT NULL DEFAULT 0`,
111
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS wakes jsonb NOT NULL DEFAULT '[]'`,
112
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS woke_through integer NOT NULL DEFAULT 0`,
113
+ `ALTER TABLE haikit_conversations ADD COLUMN IF NOT EXISTS wake_turns integer NOT NULL DEFAULT 0`,
88
114
  // Append-only, and never an orphan: a notice belongs to a conversation that
89
115
  // exists, and goes when it does.
90
116
  `CREATE TABLE IF NOT EXISTS haikit_notices (
@@ -96,8 +122,11 @@ export const schema: readonly string[] = [
96
122
  payload jsonb NOT NULL,
97
123
  model text,
98
124
  handle text,
125
+ -- 'wake', or NULL for a passive notice
126
+ kind text,
99
127
  PRIMARY KEY (conversation_id, seq)
100
128
  )`,
129
+ `ALTER TABLE haikit_notices ADD COLUMN IF NOT EXISTS kind text`,
101
130
  // Write-once. Nothing about a payload changes after insert — everything that
102
131
  // does lives on the fenced conversation row (see Conversation.frozen).
103
132
  `CREATE TABLE IF NOT EXISTS haikit_payloads (
@@ -136,10 +165,11 @@ export async function migrate(db: Queryable): Promise<void> {
136
165
  const CONVERSATION_COLUMNS = `
137
166
  id, status, messages::text AS messages, handles::text AS handles,
138
167
  frozen::text AS frozen, pending::text AS pending, lease_token, noticed_through,
168
+ wakes::text AS wakes, woke_through, wake_turns,
139
169
  (extract(epoch FROM lease_until) * 1000)::float8 AS lease_until_ms`;
140
170
 
141
171
  const NOTICE_COLUMNS = `
142
- conversation_id, seq, name, version, payload::text AS payload, model, handle,
172
+ conversation_id, seq, name, version, payload::text AS payload, model, handle, kind,
143
173
  (extract(epoch FROM created_at) * 1000)::float8 AS created_at_ms`;
144
174
 
145
175
  // The window is read as text too: 'Infinity' is how "never" is stored, and
@@ -161,6 +191,9 @@ const toConversation = (row: any): Conversation => ({
161
191
  leaseUntil: row.lease_until_ms == null ? null : Number(row.lease_until_ms),
162
192
  leaseToken: row.lease_token,
163
193
  noticedThrough: Number(row.noticed_through ?? 0),
194
+ wakes: row.wakes == null ? [] : json(row.wakes),
195
+ wokeThrough: Number(row.woke_through ?? 0),
196
+ wakeTurns: Number(row.wake_turns ?? 0),
164
197
  });
165
198
 
166
199
  const toNotice = (row: any): NoticeRecord => ({
@@ -172,6 +205,7 @@ const toNotice = (row: any): NoticeRecord => ({
172
205
  payload: json(row.payload),
173
206
  model: row.model,
174
207
  ...(row.handle == null ? {} : { handle: row.handle }),
208
+ ...(row.kind === "wake" ? { kind: "wake" as const } : {}),
175
209
  });
176
210
 
177
211
  const toPayload = (row: any): PayloadRecord => ({
@@ -199,20 +233,125 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
199
233
  throw new RangeError(`leaseMs must be a positive, finite number of milliseconds (got ${leaseMs})`);
200
234
  }
201
235
 
202
- // One LISTEN for the whole store, taken when something first watches, and
203
- // each notification handed to whoever is watching that conversation.
236
+ // One LISTEN per channel for the whole store, taken when something first
237
+ // needs it, and each notification handed to whoever wants that conversation.
204
238
  const waiting = new Map<string, Set<() => void>>();
205
- let listening: Promise<unknown> | null = null;
206
- const listen = (listener: Listener) =>
207
- (listening ??= listener
208
- .listen(NOTICE_CHANNEL, (id) => {
209
- for (const wake of waiting.get(id) ?? []) wake();
210
- })
211
- .catch((err) => {
212
- // try again on the next watch, rather than never
213
- listening = null;
239
+ const listening = new Map<string, Promise<unknown>>();
240
+ const listenOn = (listener: Listener, channel: string, onNotify: (payload: string) => void) => {
241
+ let started = listening.get(channel);
242
+ if (!started) {
243
+ started = listener.listen(channel, onNotify).catch((err) => {
244
+ // try again next time, rather than never
245
+ listening.delete(channel);
214
246
  throw err;
215
- }));
247
+ });
248
+ listening.set(channel, started);
249
+ }
250
+ return started;
251
+ };
252
+ const listen = (listener: Listener) =>
253
+ listenOn(listener, NOTICE_CHANNEL, (id) => {
254
+ for (const wake of waiting.get(id) ?? []) wake();
255
+ });
256
+
257
+ // ── publish / subscribe, over NOTIFY ───────────────────────────────────
258
+ // A message goes out as pieces of at most PIECE base64 characters, each
259
+ // `<conversation> <message id> <index> <count> <piece>`, all in one
260
+ // statement, and is put back together on the way in. Messages from this
261
+ // store are sent one at a time per conversation, each committed before the
262
+ // next starts, so they arrive in the order they were published.
263
+ const hearing = new Map<string, Set<(message: string) => void>>();
264
+ const partial = new Map<string, { pieces: string[]; got: number; expire: ReturnType<typeof setTimeout> }>();
265
+ const sending = new Map<string, Promise<void>>();
266
+
267
+ const onTurn = (payload: string) => {
268
+ const [conversationId, id, index, count, piece] = String(payload).split(" ");
269
+ if (!hearing.has(conversationId)) return;
270
+ // The channel is shared, and anything may NOTIFY on it: a piece that
271
+ // doesn't hold together is dropped, never let throw out of the listener.
272
+ const n = Number(count);
273
+ const i = Number(index);
274
+ if (!id || !Number.isSafeInteger(n) || n < 1 || n > MAX_PIECES || !Number.isSafeInteger(i) || i < 0 || i >= n) return;
275
+ const known = partial.get(id);
276
+ if (known && known.pieces.length !== n) return;
277
+ let entry = known;
278
+ if (!entry) {
279
+ // A message whose other pieces never come (a listening connection that
280
+ // dropped part way) is given up on after a while, whatever else arrives.
281
+ const expire = setTimeout(() => partial.delete(id), PIECE_TIMEOUT_MS);
282
+ // and is no reason to keep the process alive
283
+ (expire as { unref?: () => void }).unref?.();
284
+ entry = { pieces: new Array<string>(n), got: 0, expire };
285
+ }
286
+ if (entry.pieces[i] === undefined) entry.got++;
287
+ entry.pieces[i] = piece ?? "";
288
+ if (entry.got < n) return void partial.set(id, entry);
289
+ clearTimeout(entry.expire);
290
+ partial.delete(id);
291
+ let message: string;
292
+ try {
293
+ message = new TextDecoder("utf-8", { fatal: true }).decode(
294
+ Uint8Array.from(atob(entry.pieces.join("")), (c) => c.charCodeAt(0)),
295
+ );
296
+ } catch {
297
+ return; // not base64, or not UTF-8 once decoded: not one of ours
298
+ }
299
+ for (const hear of [...(hearing.get(conversationId) ?? [])]) hear(message);
300
+ };
301
+
302
+ async function publish(conversationId: string, message: string) {
303
+ const bytes = new TextEncoder().encode(String(message));
304
+ // Refused here rather than sent and dropped by every listener, and before
305
+ // any of the work of encoding it.
306
+ if (bytes.length > MAX_PUBLISH_BYTES) {
307
+ throw new RangeError(
308
+ `message too long to publish: ${bytes.length} bytes, over the ${MAX_PUBLISH_BYTES}-byte maximum`,
309
+ );
310
+ }
311
+ let binary = "";
312
+ for (const b of bytes) binary += String.fromCharCode(b);
313
+ const encoded = btoa(binary);
314
+ const id = newToken().replaceAll("-", "");
315
+ const count = Math.max(1, Math.ceil(encoded.length / PIECE));
316
+ const pieces = Array.from({ length: count }, (_, i) =>
317
+ `${conversationId} ${id} ${i} ${count} ${encoded.slice(i * PIECE, (i + 1) * PIECE)}`,
318
+ );
319
+ const before = sending.get(conversationId) ?? Promise.resolve();
320
+ const sent = before.then(() =>
321
+ db.query(`SELECT pg_notify('${TURN_CHANNEL}', piece) FROM jsonb_array_elements_text($1::jsonb) AS piece`, [
322
+ JSON.stringify(pieces),
323
+ ]),
324
+ );
325
+ const settled = sent.then(
326
+ () => {},
327
+ () => {},
328
+ );
329
+ sending.set(conversationId, settled);
330
+ try {
331
+ await sent;
332
+ } finally {
333
+ if (sending.get(conversationId) === settled) sending.delete(conversationId);
334
+ }
335
+ }
336
+
337
+ async function subscribe(conversationId: string, onMessage: (message: string) => void, signal: AbortSignal) {
338
+ if (signal.aborted) return;
339
+ const set = hearing.get(conversationId) ?? new Set();
340
+ set.add(onMessage);
341
+ hearing.set(conversationId, set);
342
+ signal.addEventListener(
343
+ "abort",
344
+ () => {
345
+ set.delete(onMessage);
346
+ if (!set.size && hearing.get(conversationId) === set) hearing.delete(conversationId);
347
+ },
348
+ { once: true },
349
+ );
350
+ // Resolves once LISTEN has taken hold, so nothing published after this
351
+ // resolves is missed. Best effort, as the contract says: a failed LISTEN
352
+ // is tried again next time, and this resolves all the same.
353
+ await listenOn(options.listen!, TURN_CHANNEL, onTurn).catch(() => {});
354
+ }
216
355
 
217
356
  async function* watch(conversationId: string, signal: AbortSignal): AsyncIterable<void> {
218
357
  // One wake-up stands for any number of notices: a step resolves once
@@ -302,6 +441,9 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
302
441
  pending = $7::jsonb,
303
442
  lease_until = to_timestamp($8::float8 / 1000),
304
443
  noticed_through = $9::integer,
444
+ wakes = $10::jsonb,
445
+ woke_through = $11::integer,
446
+ wake_turns = $12::integer,
305
447
  updated_at = now()
306
448
  WHERE id = $1 AND lease_token = $2
307
449
  RETURNING id`,
@@ -316,6 +458,9 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
316
458
  conversation.pending == null ? null : JSON.stringify(conversation.pending),
317
459
  conversation.leaseUntil,
318
460
  conversation.noticedThrough ?? 0,
461
+ JSON.stringify(conversation.wakes ?? []),
462
+ conversation.wokeThrough ?? 0,
463
+ conversation.wakeTurns ?? 0,
319
464
  ],
320
465
  );
321
466
  if (!rows.length) await stale(conversation.id);
@@ -397,8 +542,8 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
397
542
  WHERE id = $1::text
398
543
  RETURNING notice_seq
399
544
  ), stored AS (
400
- INSERT INTO haikit_notices (conversation_id, seq, name, version, payload, model, handle)
401
- SELECT $1::text, notice_seq, $2::text, $3::integer, $4::jsonb, $5::text, $6::text FROM next
545
+ INSERT INTO haikit_notices (conversation_id, seq, name, version, payload, model, handle, kind)
546
+ SELECT $1::text, notice_seq, $2::text, $3::integer, $4::jsonb, $5::text, $6::text, $7::text FROM next
402
547
  RETURNING ${NOTICE_COLUMNS}
403
548
  )
404
549
  SELECT stored.*, pg_notify('${NOTICE_CHANNEL}', $1::text) FROM stored`,
@@ -409,6 +554,7 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
409
554
  JSON.stringify(record.payload ?? null),
410
555
  record.model,
411
556
  record.handle ?? null,
557
+ record.kind === "wake" ? "wake" : null,
412
558
  ],
413
559
  );
414
560
  if (!rows.length) throw new Error(`conversation ${record.conversationId} does not exist`);
@@ -429,7 +575,9 @@ export function pgStore(db: Queryable, options: PgStoreOptions = {}): StoreAdapt
429
575
  return exists.rows.length ? [] : null;
430
576
  },
431
577
 
432
- ...(options.listen ? { watch } : {}),
578
+ // Anyone can publish; hearing it takes a LISTEN connection.
579
+ publish,
580
+ ...(options.listen ? { watch, subscribe } : {}),
433
581
  };
434
582
  }
435
583