@graffiti-garden/implementation-decentralized 0.1.3 → 0.1.5

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 (46) hide show
  1. package/dist/1-services/4-inboxes/cache.d.ts +34 -0
  2. package/dist/1-services/4-inboxes/cache.d.ts.map +1 -0
  3. package/dist/1-services/4-inboxes/messages.d.ts +70 -0
  4. package/dist/1-services/4-inboxes/messages.d.ts.map +1 -0
  5. package/dist/1-services/4-inboxes.d.ts +28 -67
  6. package/dist/1-services/4-inboxes.d.ts.map +1 -1
  7. package/dist/3-protocol/4-graffiti.d.ts +1 -0
  8. package/dist/3-protocol/4-graffiti.d.ts.map +1 -1
  9. package/dist/browser/index.js +7 -7
  10. package/dist/browser/index.js.map +4 -4
  11. package/dist/cjs/1-services/1-authorization.js +1 -1
  12. package/dist/cjs/1-services/1-authorization.js.map +2 -2
  13. package/dist/cjs/1-services/4-inboxes/cache.js +186 -0
  14. package/dist/cjs/1-services/4-inboxes/cache.js.map +7 -0
  15. package/dist/cjs/1-services/4-inboxes/messages.js +69 -0
  16. package/dist/cjs/1-services/4-inboxes/messages.js.map +7 -0
  17. package/dist/cjs/1-services/4-inboxes-tests.js +339 -9
  18. package/dist/cjs/1-services/4-inboxes-tests.js.map +3 -3
  19. package/dist/cjs/1-services/4-inboxes.js +210 -399
  20. package/dist/cjs/1-services/4-inboxes.js.map +3 -3
  21. package/dist/cjs/3-protocol/4-graffiti.js +19 -10
  22. package/dist/cjs/3-protocol/4-graffiti.js.map +2 -2
  23. package/dist/cjs/index.spec.js +1 -0
  24. package/dist/cjs/index.spec.js.map +2 -2
  25. package/dist/esm/1-services/1-authorization.js +1 -1
  26. package/dist/esm/1-services/1-authorization.js.map +2 -2
  27. package/dist/esm/1-services/4-inboxes/cache.js +156 -0
  28. package/dist/esm/1-services/4-inboxes/cache.js.map +7 -0
  29. package/dist/esm/1-services/4-inboxes/messages.js +60 -0
  30. package/dist/esm/1-services/4-inboxes/messages.js.map +7 -0
  31. package/dist/esm/1-services/4-inboxes-tests.js +340 -10
  32. package/dist/esm/1-services/4-inboxes-tests.js.map +3 -3
  33. package/dist/esm/1-services/4-inboxes.js +222 -383
  34. package/dist/esm/1-services/4-inboxes.js.map +3 -3
  35. package/dist/esm/3-protocol/4-graffiti.js +19 -10
  36. package/dist/esm/3-protocol/4-graffiti.js.map +2 -2
  37. package/dist/esm/index.spec.js +2 -1
  38. package/dist/esm/index.spec.js.map +2 -2
  39. package/package.json +1 -1
  40. package/src/1-services/1-authorization.ts +1 -1
  41. package/src/1-services/4-inboxes/cache.ts +227 -0
  42. package/src/1-services/4-inboxes/messages.ts +63 -0
  43. package/src/1-services/4-inboxes-tests.ts +387 -2
  44. package/src/1-services/4-inboxes.ts +297 -512
  45. package/src/3-protocol/4-graffiti.ts +26 -10
  46. package/src/index.spec.ts +3 -1
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,47 +15,44 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var inboxes_exports = {};
30
20
  __export(inboxes_exports, {
31
21
  Inboxes: () => Inboxes,
32
- LABELED_MESSAGE_ID_KEY: () => LABELED_MESSAGE_ID_KEY,
33
- LABELED_MESSAGE_LABEL_KEY: () => LABELED_MESSAGE_LABEL_KEY,
34
- LABELED_MESSAGE_MESSAGE_KEY: () => LABELED_MESSAGE_MESSAGE_KEY,
35
- LabeledMessageBaseSchema: () => LabeledMessageBaseSchema,
36
- MESSAGE_METADATA_KEY: () => MESSAGE_METADATA_KEY,
37
- MESSAGE_OBJECT_KEY: () => MESSAGE_OBJECT_KEY,
38
- MESSAGE_TAGS_KEY: () => MESSAGE_TAGS_KEY,
39
- MessageBaseSchema: () => MessageBaseSchema,
40
- TagsSchema: () => TagsSchema,
41
- Uint8ArraySchema: () => Uint8ArraySchema
22
+ LABELED_MESSAGE_ID_KEY: () => import_messages2.LABELED_MESSAGE_ID_KEY,
23
+ LABELED_MESSAGE_LABEL_KEY: () => import_messages2.LABELED_MESSAGE_LABEL_KEY,
24
+ LABELED_MESSAGE_MESSAGE_KEY: () => import_messages2.LABELED_MESSAGE_MESSAGE_KEY,
25
+ LabeledMessageBaseSchema: () => import_messages2.LabeledMessageBaseSchema,
26
+ MESSAGE_METADATA_KEY: () => import_messages2.MESSAGE_METADATA_KEY,
27
+ MESSAGE_OBJECT_KEY: () => import_messages2.MESSAGE_OBJECT_KEY,
28
+ MESSAGE_TAGS_KEY: () => import_messages2.MESSAGE_TAGS_KEY,
29
+ MessageBaseSchema: () => import_messages2.MessageBaseSchema,
30
+ TagsSchema: () => import_messages2.TagsSchema,
31
+ Uint8ArraySchema: () => import_messages2.Uint8ArraySchema
42
32
  });
43
33
  module.exports = __toCommonJS(inboxes_exports);
44
34
  var import_utilities = require("./utilities");
45
35
  var import_api = require("@graffiti-garden/api");
46
36
  var import_dag_cbor = require("@ipld/dag-cbor");
47
37
  var import_mini = require("zod/mini");
38
+ var import_messages = require("./4-inboxes/messages");
39
+ var import_cache = require("./4-inboxes/cache");
40
+ var import_messages2 = require("./4-inboxes/messages");
41
+ const INBOX_REQUEST_TIMEOUT_MS = 3e4;
42
+ const INBOX_LOCK_WAIT_TIMEOUT_MS = 3e4;
48
43
  class Inboxes {
49
44
  getAuthorizationEndpoint = import_utilities.getAuthorizationEndpoint;
50
45
  cache_ = null;
51
46
  get cache() {
52
47
  if (!this.cache_) {
53
- this.cache_ = createCache();
48
+ this.cache_ = (0, import_cache.createCache)();
54
49
  }
55
50
  return this.cache_;
56
51
  }
57
52
  async send(inboxUrl, message) {
58
53
  (0, import_utilities.verifyHTTPSEndpoint)(inboxUrl);
59
- const url2 = `${inboxUrl}/send`;
60
- const response = await (0, import_utilities.fetchWithErrorHandling)(url2, {
54
+ const url = `${inboxUrl}/send`;
55
+ const response = await (0, import_utilities.fetchWithErrorHandling)(url, {
61
56
  method: "PUT",
62
57
  headers: {
63
58
  "Content-Type": "application/cbor"
@@ -66,18 +61,18 @@ class Inboxes {
66
61
  });
67
62
  const blob = await response.blob();
68
63
  const cbor = (0, import_dag_cbor.decode)(await blob.arrayBuffer());
69
- const parsed = SendResponseSchema.parse(cbor);
64
+ const parsed = import_messages.SendResponseSchema.parse(cbor);
70
65
  return parsed.id;
71
66
  }
72
67
  async get(inboxUrl, messageId, inboxToken) {
73
- const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);
68
+ const messageCacheKey = (0, import_cache.getMessageCacheKey)(inboxUrl, messageId);
74
69
  const cache = await this.cache;
75
70
  const cached = await cache.messages.get(messageCacheKey);
76
71
  if (cached !== void 0) return cached;
77
- const url2 = `${inboxUrl}/message/${messageId}`;
72
+ const url = `${inboxUrl}/message/${messageId}`;
78
73
  let response = null;
79
74
  try {
80
- response = await (0, import_utilities.fetchWithErrorHandling)(url2, {
75
+ response = await (0, import_utilities.fetchWithErrorHandling)(url, {
81
76
  method: "GET",
82
77
  headers: {
83
78
  ...inboxToken ? {
@@ -94,15 +89,15 @@ class Inboxes {
94
89
  }
95
90
  const blob = await response.blob();
96
91
  const cbor = (0, import_dag_cbor.decode)(await blob.arrayBuffer());
97
- const parsed = LabeledMessageBaseSchema.parse(cbor);
92
+ const parsed = import_messages.LabeledMessageBaseSchema.parse(cbor);
98
93
  await cache.messages.set(messageCacheKey, parsed);
99
94
  return parsed;
100
95
  }
101
96
  async label(inboxUrl, messageId, label, inboxToken) {
102
97
  (0, import_utilities.verifyHTTPSEndpoint)(inboxUrl);
103
98
  if (inboxToken) {
104
- const url2 = `${inboxUrl}/label/${messageId}`;
105
- await (0, import_utilities.fetchWithErrorHandling)(url2, {
99
+ const url = `${inboxUrl}/label/${messageId}`;
100
+ await (0, import_utilities.fetchWithErrorHandling)(url, {
106
101
  method: "PUT",
107
102
  headers: {
108
103
  "Content-Type": "application/cbor",
@@ -112,7 +107,7 @@ class Inboxes {
112
107
  });
113
108
  }
114
109
  const cache = await this.cache;
115
- const messageCacheKey = getMessageCacheKey(inboxUrl, messageId);
110
+ const messageCacheKey = (0, import_cache.getMessageCacheKey)(inboxUrl, messageId);
116
111
  const result = await cache.messages.get(messageCacheKey);
117
112
  if (result) {
118
113
  await cache.messages.set(messageCacheKey, {
@@ -122,6 +117,7 @@ class Inboxes {
122
117
  }
123
118
  }
124
119
  async fetchMessageBatch(inboxUrl, type, body, inboxToken, cursor, signal) {
120
+ const timeout = AbortSignal.timeout(INBOX_REQUEST_TIMEOUT_MS);
125
121
  const response = await (0, import_utilities.fetchWithErrorHandling)(
126
122
  `${inboxUrl}/${type}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
127
123
  {
@@ -133,7 +129,7 @@ class Inboxes {
133
129
  } : {}
134
130
  },
135
131
  body,
136
- signal
132
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout
137
133
  }
138
134
  );
139
135
  const retryAfterHeader = response.headers.get("Retry-After");
@@ -141,421 +137,236 @@ class Inboxes {
141
137
  const waitTil = retryAfter && Number.isFinite(retryAfter) ? Date.now() + retryAfter * 1e3 : void 0;
142
138
  return { response, waitTil };
143
139
  }
144
- async *yieldFromCache(cache, inboxUrl, messageIdsCacheKey, cachedMessageIds, cacheNumSeen = 0) {
145
- const messageIds = cachedMessageIds.messageIds.slice(cacheNumSeen);
146
- const messages = await Promise.all(
147
- messageIds.map(async (id) => {
148
- const message = await cache.messages.get(
149
- getMessageCacheKey(inboxUrl, id)
150
- );
151
- if (!message) {
152
- try {
153
- await cache.messageIds.del(messageIdsCacheKey);
154
- } catch {
155
- }
156
- throw new Error("Cache out of sync - perhaps clear browser storage");
157
- }
158
- return message;
159
- })
160
- );
161
- yield* messages;
162
- }
163
- async *lockedMessageStreamer(...args) {
164
- if (typeof window === "undefined" || !await canUseIDB()) {
165
- const streamer = this.messageStreamer(...args);
166
- while (true) {
167
- const next = await streamer.next();
168
- if (next.done) return next.value;
169
- yield next.value;
170
- }
171
- }
172
- const messageIdsCacheKey = await args[0];
173
- const lockKey = `graffiti:inbox:${messageIdsCacheKey}`;
174
- let releaseLock = () => {
175
- };
176
- let hasLock = false;
177
- await new Promise((resolvehasLock) => {
178
- window.navigator.locks.request(
179
- lockKey,
140
+ async withQueryLock(messageIdsCacheKey, useLock, work, signal) {
141
+ if (!useLock) return work();
142
+ const timeout = AbortSignal.timeout(INBOX_LOCK_WAIT_TIMEOUT_MS);
143
+ let acquired = false;
144
+ try {
145
+ return await window.navigator.locks.request(
146
+ `graffiti:inbox:${messageIdsCacheKey}`,
180
147
  {
181
148
  mode: "exclusive",
182
- ifAvailable: true
149
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout
183
150
  },
184
- async (lock) => {
185
- hasLock = !!lock;
186
- resolvehasLock();
187
- await new Promise((r) => releaseLock = r);
151
+ async () => {
152
+ acquired = true;
153
+ return work();
188
154
  }
189
155
  );
190
- });
191
- if (hasLock) {
192
- try {
193
- const streamer = this.messageStreamer(...args);
194
- while (true) {
195
- const next = await streamer.next();
196
- if (next.done) return next.value;
197
- yield next.value;
198
- }
199
- } finally {
200
- releaseLock();
156
+ } catch (error) {
157
+ if (!acquired && timeout.aborted) {
158
+ throw new Error("Timed out waiting for another tab's inbox query", {
159
+ cause: error
160
+ });
201
161
  }
162
+ throw error;
202
163
  }
203
- releaseLock();
204
- await window.navigator.locks.request(lockKey, () => {
205
- });
206
- const inboxUrl = args[1];
207
- const objectSchema = args[5] ?? {};
208
- const cacheVersion = args[6];
209
- const cacheNumSeen = args[7];
210
- const cache = await this.cache;
211
- const cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);
212
- if (!cachedMessageIds) {
213
- throw new Error("Cache not found");
214
- }
215
- if (cacheVersion !== void 0 && cacheVersion !== cachedMessageIds.version) {
216
- throw new import_api.GraffitiErrorCursorExpired("Cursor is stale");
217
- }
218
- const iterator = this.yieldFromCache(
219
- cache,
220
- inboxUrl,
221
- messageIdsCacheKey,
222
- cachedMessageIds,
223
- cacheNumSeen
224
- );
225
- for await (const m of iterator) yield m;
226
- const outputCursor = {
227
- numSeen: cachedMessageIds.messageIds.length,
228
- version: cachedMessageIds.version,
229
- messageIdsCacheKey,
230
- objectSchema
231
- };
232
- return JSON.stringify(outputCursor);
233
164
  }
234
- async *messageStreamer(messageIdsCacheKey_, inboxUrl, type, body, inboxToken, objectSchema = {}, cacheVersion, cacheNumSeen = 0) {
165
+ async *messageStreamer(source) {
166
+ const { inboxUrl, inboxToken } = source;
167
+ const type = source.kind === "export" ? "export" : "query";
168
+ const objectSchema = source.kind === "query" ? source.schema : source.kind === "continue" ? source.cursor.objectSchema : {};
235
169
  const validator = await (0, import_api.compileGraffitiObjectSchema)(objectSchema);
236
- const messageIdsCacheKey = await messageIdsCacheKey_;
170
+ const body = source.kind === "query" ? source.body : void 0;
171
+ const messageIdsCacheKey = source.kind === "continue" ? source.cursor.messageIdsCacheKey : await (0, import_cache.getMessageIdsCacheKey)(inboxUrl, type, body);
172
+ const cacheVersion = source.kind === "continue" ? source.cursor.version : void 0;
237
173
  const cache = await this.cache;
238
- let cachedMessageIds = await cache.messageIds.get(messageIdsCacheKey);
239
- if (cacheVersion !== void 0 && cacheVersion !== cachedMessageIds?.version) {
240
- throw new import_api.GraffitiErrorCursorExpired("Cursor is stale");
241
- }
242
- let waitTil = cachedMessageIds?.waitTil;
243
- await waitFor(waitTil);
244
- const cachedCursor = cachedMessageIds?.cursor;
245
- let firstResponse = void 0;
246
- try {
247
- const out = await this.fetchMessageBatch(
248
- inboxUrl,
249
- type,
250
- body,
251
- inboxToken,
252
- cachedCursor
253
- );
254
- firstResponse = out.response;
255
- waitTil = out.waitTil;
256
- } catch (e) {
257
- if (!(e instanceof import_api.GraffitiErrorCursorExpired && cachedCursor)) {
258
- console.error(
259
- "Unexpected error in stream, waiting 5 seconds before continuing..."
174
+ const useLock = typeof window !== "undefined" && await (0, import_cache.canUseIDB)();
175
+ const initialQuery = await cache.messageIds.get(messageIdsCacheKey);
176
+ let seenGeneration = initialQuery?.generation;
177
+ let seenVersion = initialQuery?.version;
178
+ let numSeen = source.kind === "continue" ? source.cursor.numSeen : 0;
179
+ let firstPage = true;
180
+ const fetchPage = async (signal) => {
181
+ while (true) {
182
+ signal?.throwIfAborted();
183
+ const step = await this.withQueryLock(
184
+ messageIdsCacheKey,
185
+ useLock,
186
+ async () => {
187
+ signal?.throwIfAborted();
188
+ let query = await cache.messageIds.get(messageIdsCacheKey);
189
+ if (cacheVersion !== void 0 && cacheVersion !== query?.version) {
190
+ throw new import_api.GraffitiErrorCursorExpired("Cursor is stale");
191
+ }
192
+ if (query?.version !== seenVersion) {
193
+ if (!firstPage) throw new import_api.GraffitiErrorCursorExpired("Cursor is stale");
194
+ numSeen = 0;
195
+ }
196
+ if (query?.generation && query.generation !== seenGeneration) {
197
+ return { query };
198
+ }
199
+ if (query?.waitTil !== void 0 && query.waitTil > Date.now()) {
200
+ return { waitTil: query.waitTil };
201
+ }
202
+ let batch;
203
+ try {
204
+ batch = await this.fetchMessageBatch(
205
+ inboxUrl,
206
+ type,
207
+ firstPage ? body : void 0,
208
+ // Later pages send no body.
209
+ inboxToken,
210
+ query?.cursor,
211
+ signal
212
+ );
213
+ } catch (error) {
214
+ if (!(error instanceof import_api.GraffitiErrorCursorExpired && query?.cursor)) {
215
+ if (firstPage && !signal?.aborted) {
216
+ return { initialFetchError: error };
217
+ }
218
+ throw error;
219
+ }
220
+ if (!firstPage) throw error;
221
+ await cache.messageIds.del(messageIdsCacheKey);
222
+ if (cacheVersion !== void 0) throw error;
223
+ query = void 0;
224
+ numSeen = 0;
225
+ batch = await this.fetchMessageBatch(
226
+ inboxUrl,
227
+ type,
228
+ body,
229
+ inboxToken,
230
+ void 0,
231
+ signal
232
+ );
233
+ }
234
+ const decoded = (0, import_dag_cbor.decode)(
235
+ new Uint8Array(await batch.response.arrayBuffer())
236
+ );
237
+ const { results, hasMore, cursor } = import_messages.MessageResultSchema.parse(decoded);
238
+ const labeledMessages = results.map(
239
+ (result) => {
240
+ const object = result[import_messages.LABELED_MESSAGE_MESSAGE_KEY][import_messages.MESSAGE_OBJECT_KEY];
241
+ if (!validator(object)) {
242
+ throw new Error("Server returned data that does not match schema");
243
+ }
244
+ return {
245
+ ...result,
246
+ [import_messages.LABELED_MESSAGE_MESSAGE_KEY]: {
247
+ ...result[import_messages.LABELED_MESSAGE_MESSAGE_KEY],
248
+ [import_messages.MESSAGE_OBJECT_KEY]: object
249
+ }
250
+ };
251
+ }
252
+ );
253
+ query = await (0, import_cache.storeQueryPage)(
254
+ cache,
255
+ inboxUrl,
256
+ messageIdsCacheKey,
257
+ query,
258
+ {
259
+ messages: labeledMessages,
260
+ cursor,
261
+ hasMore,
262
+ waitTil: batch.waitTil
263
+ }
264
+ );
265
+ return { query };
266
+ },
267
+ signal
260
268
  );
261
- await new Promise((resolve) => setTimeout(resolve, 5e3));
262
- throw e;
263
- }
264
- await cache.messageIds.del(messageIdsCacheKey);
265
- if (cacheVersion === void 0) {
266
- cachedMessageIds = void 0;
267
- } else {
268
- throw e;
269
+ if ("initialFetchError" in step) {
270
+ console.error(
271
+ "Unexpected error in stream, waiting 5 seconds before continuing..."
272
+ );
273
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
274
+ throw step.initialFetchError;
275
+ }
276
+ if ("waitTil" in step) {
277
+ await waitFor(step.waitTil);
278
+ continue;
279
+ }
280
+ return step.query;
269
281
  }
270
- }
271
- if (firstResponse !== void 0 && cachedMessageIds) {
272
- const iterator = this.yieldFromCache(
282
+ };
283
+ let nextPage;
284
+ while (true) {
285
+ const result = nextPage ? await nextPage : await fetchPage().then(
286
+ (query2) => ({ query: query2 }),
287
+ (error) => ({ error })
288
+ );
289
+ nextPage = void 0;
290
+ if ("error" in result) throw result.error;
291
+ const query = result.query;
292
+ const messages = await (0, import_cache.replayQuery)(
273
293
  cache,
274
294
  inboxUrl,
275
295
  messageIdsCacheKey,
276
- cachedMessageIds,
277
- cacheNumSeen
296
+ query,
297
+ numSeen
278
298
  );
279
- for await (const m of iterator) yield m;
280
- }
281
- if (firstResponse === void 0) {
282
- const out = await this.fetchMessageBatch(
283
- inboxUrl,
284
- type,
285
- body,
286
- inboxToken
287
- );
288
- firstResponse = out.response;
289
- waitTil = out.waitTil;
290
- }
291
- let response = firstResponse;
292
- let cursor;
293
- const version = cachedMessageIds?.version ?? crypto.randomUUID();
294
- let messageIds = cachedMessageIds?.messageIds ?? [];
295
- while (true) {
296
- const blob = await response.blob();
297
- const decoded = (0, import_dag_cbor.decode)(await blob.arrayBuffer());
298
- const {
299
- results,
300
- hasMore,
301
- cursor: nextCursor
302
- } = MessageResultSchema.parse(decoded);
303
- cursor = nextCursor;
304
- const labeledMessages = results.map(
305
- (result) => {
306
- const object = result[LABELED_MESSAGE_MESSAGE_KEY][MESSAGE_OBJECT_KEY];
307
- if (!validator(object)) {
308
- throw new Error("Server returned data that does not match schema");
309
- }
310
- return {
311
- ...result,
312
- [LABELED_MESSAGE_MESSAGE_KEY]: {
313
- ...result[LABELED_MESSAGE_MESSAGE_KEY],
314
- [MESSAGE_OBJECT_KEY]: object
315
- }
316
- };
317
- }
318
- );
319
- await cache.messages.setMany(
320
- labeledMessages.map((m) => [
321
- getMessageCacheKey(inboxUrl, m[LABELED_MESSAGE_ID_KEY]),
322
- m
323
- ])
324
- );
325
- messageIds = [
326
- ...messageIds,
327
- ...labeledMessages.map(
328
- (m) => m[LABELED_MESSAGE_ID_KEY]
329
- )
330
- ];
331
- await cache.messageIds.set(messageIdsCacheKey, {
332
- cursor,
333
- version,
334
- messageIds,
335
- waitTil
336
- });
337
- cacheNumSeen += labeledMessages.length;
299
+ numSeen = query.messageIds.length;
300
+ seenGeneration = query.generation;
301
+ seenVersion = query.version;
302
+ firstPage = false;
338
303
  const prefetchController = new AbortController();
339
- const nextPage = hasMore ? waitFor(waitTil).then(() => this.fetchMessageBatch(
340
- inboxUrl,
341
- type,
342
- void 0,
343
- // Body is never past the first time
344
- inboxToken,
345
- cursor,
346
- prefetchController.signal
347
- )).then((value) => ({ value }), (error) => ({ error })) : null;
304
+ if (query.hasMore) {
305
+ nextPage = fetchPage(prefetchController.signal).then(
306
+ (query2) => ({ query: query2 }),
307
+ (error) => ({ error })
308
+ );
309
+ }
348
310
  let consumedPage = false;
349
311
  try {
350
- for (const m of labeledMessages) yield m;
312
+ for (const message of messages) {
313
+ yield message;
314
+ }
351
315
  consumedPage = true;
352
316
  } finally {
353
317
  if (!consumedPage) prefetchController.abort();
354
318
  }
355
- if (!hasMore) break;
356
- const out = await nextPage;
357
- if ("error" in out) throw out.error;
358
- response = out.value.response;
359
- waitTil = out.value.waitTil;
319
+ if (!query.hasMore) {
320
+ const outputCursor = {
321
+ numSeen,
322
+ version: query.version,
323
+ messageIdsCacheKey,
324
+ objectSchema
325
+ };
326
+ return JSON.stringify(outputCursor);
327
+ }
360
328
  }
361
- const outputCursor = {
362
- numSeen: cacheNumSeen,
363
- version,
364
- messageIdsCacheKey,
365
- objectSchema
366
- };
367
- return JSON.stringify(outputCursor);
368
329
  }
369
330
  query(inboxUrl, tags, objectSchema, inboxToken) {
370
331
  (0, import_utilities.verifyHTTPSEndpoint)(inboxUrl);
371
- const body = (0, import_dag_cbor.encode)({
332
+ const body = new Uint8Array((0, import_dag_cbor.encode)({
372
333
  tags,
373
334
  schema: objectSchema
374
- });
375
- const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, "query", body);
376
- return this.lockedMessageStreamer(
377
- messageIdsCacheKey,
335
+ }));
336
+ return this.messageStreamer({
337
+ kind: "query",
378
338
  inboxUrl,
379
- "query",
380
- new Uint8Array(body),
381
- inboxToken,
382
- objectSchema
383
- );
339
+ body,
340
+ schema: objectSchema,
341
+ inboxToken
342
+ });
384
343
  }
385
344
  continueQuery(inboxUrl, cursor, inboxToken) {
386
345
  (0, import_utilities.verifyHTTPSEndpoint)(inboxUrl);
387
346
  const decodedCursor = JSON.parse(cursor);
388
- const { messageIdsCacheKey, numSeen, objectSchema, version } = CursorSchema.parse(decodedCursor);
389
- return this.lockedMessageStreamer(
390
- Promise.resolve(messageIdsCacheKey),
347
+ const parsedCursor = CursorSchema.parse(decodedCursor);
348
+ return this.messageStreamer({
349
+ kind: "continue",
391
350
  inboxUrl,
392
- "query",
393
- void 0,
394
- inboxToken,
395
- objectSchema,
396
- version,
397
- numSeen
398
- );
351
+ cursor: parsedCursor,
352
+ inboxToken
353
+ });
399
354
  }
400
355
  export(inboxUrl, inboxToken) {
401
356
  (0, import_utilities.verifyHTTPSEndpoint)(inboxUrl);
402
- const messageIdsCacheKey = getMessageIdsCacheKey(inboxUrl, "export");
403
- return this.lockedMessageStreamer(
404
- messageIdsCacheKey,
357
+ return this.messageStreamer({
358
+ kind: "export",
405
359
  inboxUrl,
406
- "export",
407
- void 0,
408
360
  inboxToken
409
- );
361
+ });
410
362
  }
411
363
  }
412
- const GraffitiObjectSchema = (0, import_mini.strictObject)({
413
- value: (0, import_mini.looseObject)({}),
414
- channels: (0, import_mini.array)((0, import_mini.string)()),
415
- allowed: (0, import_mini.optional)((0, import_mini.nullable)((0, import_mini.array)((0, import_mini.url)()))),
416
- url: (0, import_mini.url)(),
417
- actor: (0, import_mini.url)()
418
- });
419
- const Uint8ArraySchema = (0, import_mini.custom)(
420
- (v) => v instanceof Uint8Array
421
- );
422
- const TagsSchema = (0, import_mini.array)(Uint8ArraySchema);
423
- const MESSAGE_TAGS_KEY = "t";
424
- const MESSAGE_OBJECT_KEY = "o";
425
- const MESSAGE_METADATA_KEY = "m";
426
- const MessageBaseSchema = (0, import_mini.strictObject)({
427
- [MESSAGE_TAGS_KEY]: TagsSchema,
428
- [MESSAGE_OBJECT_KEY]: GraffitiObjectSchema,
429
- [MESSAGE_METADATA_KEY]: Uint8ArraySchema
430
- });
431
- const LABELED_MESSAGE_ID_KEY = "id";
432
- const LABELED_MESSAGE_MESSAGE_KEY = "m";
433
- const LABELED_MESSAGE_LABEL_KEY = "l";
434
- const LabeledMessageBaseSchema = (0, import_mini.strictObject)({
435
- [LABELED_MESSAGE_ID_KEY]: (0, import_mini.string)(),
436
- [LABELED_MESSAGE_MESSAGE_KEY]: MessageBaseSchema,
437
- [LABELED_MESSAGE_LABEL_KEY]: (0, import_mini.number)()
438
- });
439
- const SendResponseSchema = (0, import_mini.strictObject)({ id: (0, import_mini.string)() });
440
- const MessageResultSchema = (0, import_mini.strictObject)({
441
- results: (0, import_mini.array)(LabeledMessageBaseSchema),
442
- hasMore: (0, import_mini.boolean)(),
443
- cursor: (0, import_mini.string)()
444
- });
445
364
  const CursorSchema = (0, import_mini.strictObject)({
446
365
  messageIdsCacheKey: (0, import_mini.string)(),
447
366
  version: (0, import_mini.string)(),
448
367
  numSeen: (0, import_mini.int)().check((0, import_mini.nonnegative)()),
449
368
  objectSchema: (0, import_mini.union)([(0, import_mini.looseObject)({}), (0, import_mini.boolean)()])
450
369
  });
451
- async function canUseIDB() {
452
- try {
453
- if (!globalThis.indexedDB) return false;
454
- await new Promise((resolve, reject) => {
455
- const req = indexedDB.open("__idb_probe__", 1);
456
- req.onupgradeneeded = () => req.result.createObjectStore("k");
457
- req.onsuccess = () => {
458
- req.result.close();
459
- resolve();
460
- };
461
- req.onerror = () => reject(req.error);
462
- });
463
- return true;
464
- } catch {
465
- return false;
466
- }
467
- }
468
- function getMessageCacheKey(inboxUrl, messageId) {
469
- return `${encodeURIComponent(inboxUrl)}:${encodeURIComponent(messageId)}`;
470
- }
471
- async function getMessageIdsCacheKey(inboxUrl, type, body) {
472
- const cacheIdData = (0, import_dag_cbor.encode)({
473
- inboxUrl,
474
- type,
475
- body: body ?? null
476
- });
477
- return crypto.subtle.digest("SHA-256", new Uint8Array(cacheIdData)).then(
478
- (bytes) => Array.from(new Uint8Array(bytes)).map((byte) => byte.toString(16).padStart(2, "0")).join("")
479
- );
480
- }
481
- async function resetCacheDB() {
482
- await new Promise((resolve) => {
483
- const req = indexedDB.deleteDatabase("graffiti-inbox-cache");
484
- req.onsuccess = () => resolve();
485
- req.onerror = () => resolve();
486
- req.onblocked = () => resolve();
487
- });
488
- }
489
- async function createCache() {
490
- if (await canUseIDB()) {
491
- const { openDB } = await import("idb");
492
- const db = await openDB("graffiti-inbox-cache", 1, {
493
- upgrade(db2) {
494
- if (!db2.objectStoreNames.contains("m")) db2.createObjectStore("m");
495
- if (!db2.objectStoreNames.contains("q")) db2.createObjectStore("q");
496
- }
497
- });
498
- return {
499
- messages: {
500
- get: async (k) => {
501
- try {
502
- return db.get("m", k);
503
- } catch (error) {
504
- console.error("Error getting message from cache:", error);
505
- console.error("resetting cache...");
506
- await resetCacheDB();
507
- return void 0;
508
- }
509
- },
510
- set: async (k, v) => {
511
- await db.put("m", v, k);
512
- },
513
- setMany: async (entries) => {
514
- if (entries.length === 0) return;
515
- const tx = db.transaction("m", "readwrite");
516
- await Promise.all([
517
- ...entries.map(([k, v]) => tx.store.put(v, k)),
518
- tx.done
519
- ]);
520
- },
521
- del: (k) => db.delete("m", k)
522
- },
523
- messageIds: {
524
- get: async (k) => {
525
- try {
526
- return await db.get("q", k);
527
- } catch (error) {
528
- console.error("Error getting message IDs from cache:", error);
529
- console.error("resetting cache...");
530
- await resetCacheDB();
531
- return void 0;
532
- }
533
- },
534
- set: async (k, v) => {
535
- await db.put("q", v, k);
536
- },
537
- del: (k) => db.delete("q", k)
538
- }
539
- };
540
- }
541
- const m = /* @__PURE__ */ new Map();
542
- const q = /* @__PURE__ */ new Map();
543
- return {
544
- messages: {
545
- get: async (k) => m.get(k),
546
- set: async (k, v) => void m.set(k, v),
547
- setMany: async (entries) => {
548
- for (const [k, v] of entries) m.set(k, v);
549
- },
550
- del: async (k) => void m.delete(k)
551
- },
552
- messageIds: {
553
- get: async (k) => q.get(k),
554
- set: async (k, v) => void q.set(k, v),
555
- del: async (k) => void q.delete(k)
556
- }
557
- };
558
- }
559
370
  async function waitFor(waitTil) {
560
371
  if (waitTil !== void 0) {
561
372
  const waitFor2 = waitTil - Date.now();