@anuma/sdk 9.1.0 → 9.1.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.
@@ -9683,11 +9683,12 @@ function isRemoteWorkLeaseExpired(error) {
9683
9683
 
9684
9684
  // src/lib/memory/store/remotePersistence.ts
9685
9685
  var RemoteMemoryError = class extends Error {
9686
- constructor(message, status, code, retryAfterMs) {
9686
+ constructor(message, status, code, retryAfterMs, rateLimitBucket) {
9687
9687
  super(message);
9688
9688
  this.status = status;
9689
9689
  this.code = code;
9690
9690
  this.retryAfterMs = retryAfterMs;
9691
+ this.rateLimitBucket = rateLimitBucket;
9691
9692
  this.name = "RemoteMemoryError";
9692
9693
  }
9693
9694
  };
@@ -9782,6 +9783,14 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
9782
9783
  throw new Error("timeoutMs must be a positive number");
9783
9784
  const root = base.href.replace(/\/$/, "") + apiPath;
9784
9785
  const fetcher = options.fetch ?? globalThis.fetch;
9786
+ const cooldowns = /* @__PURE__ */ new Map();
9787
+ const memoryBudget = (path, method) => {
9788
+ if (method === "PUT")
9789
+ return path === "/topics" ? "private-memory-import" : "private-memory-write";
9790
+ if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
9791
+ return "private-memory-import";
9792
+ return "private-memory-read";
9793
+ };
9785
9794
  const request = async (path, init = {}, signal) => {
9786
9795
  const controller = new AbortController();
9787
9796
  let timedOut = false;
@@ -9805,6 +9814,25 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
9805
9814
  const token = await Promise.race([options.getToken(), aborted2]);
9806
9815
  if (controller.signal.aborted) await aborted2;
9807
9816
  if (!token) throw new RemoteMemoryError("Authentication required", 401);
9817
+ const pathname = path.split("?")[0];
9818
+ const route = `${init.method ?? "GET"} ${pathname}`;
9819
+ const budgets = ["*", route];
9820
+ if (apiPath === "/api/private-memories")
9821
+ budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
9822
+ for (const bucket of budgets) {
9823
+ const cooldown = cooldowns.get(bucket);
9824
+ if (!cooldown) continue;
9825
+ const remaining = cooldown.until - Date.now();
9826
+ if (remaining > 0)
9827
+ throw new RemoteMemoryError(
9828
+ cooldown.error.message,
9829
+ 429,
9830
+ cooldown.error.code,
9831
+ remaining,
9832
+ cooldown.error.rateLimitBucket
9833
+ );
9834
+ cooldowns.delete(bucket);
9835
+ }
9808
9836
  const response = await fetcher(root + path, {
9809
9837
  ...init,
9810
9838
  headers: {
@@ -9817,12 +9845,18 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
9817
9845
  if (!response.ok) {
9818
9846
  const body = await response.json().catch(() => null);
9819
9847
  const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
9820
- throw new RemoteMemoryError(
9848
+ const error = new RemoteMemoryError(
9821
9849
  record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
9822
9850
  response.status,
9823
9851
  record(body) && typeof body.code === "string" ? body.code : void 0,
9824
- retryAfter ?? void 0
9852
+ retryAfter ?? void 0,
9853
+ record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
9825
9854
  );
9855
+ if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
9856
+ const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
9857
+ cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
9858
+ }
9859
+ throw error;
9826
9860
  }
9827
9861
  return await response.json();
9828
9862
  } catch (error) {
@@ -10062,6 +10096,7 @@ async function createRemoteMemoryPersistence(options) {
10062
10096
  return {
10063
10097
  ...createRemoteTopicPersistence(request),
10064
10098
  ...createRemoteWorkPersistence(request),
10099
+ supportsRealtimeChanges: account.realtime_changes === true,
10065
10100
  counts: async () => {
10066
10101
  const result = await request("/counts", {});
10067
10102
  if (!record(result) || ["live", "archived", "quarantined"].some(
@@ -21345,6 +21380,9 @@ var RemoteMemoryPartialCreateError = class extends Error {
21345
21380
  function rejectsInput(error) {
21346
21381
  return error instanceof EmbeddingHttpError && error.status !== void 0 && error.status >= 400 && error.status < 500 && error.status !== 429 && !isFatalEmbeddingError(error);
21347
21382
  }
21383
+ function rejectsMemoryWrite(error) {
21384
+ return error instanceof RemoteMemoryError && (error.status === 400 || error.status === 422) && (error.code === void 0 || error.code === "unknown_field");
21385
+ }
21348
21386
  function warnFailed(failed) {
21349
21387
  if (failed.length)
21350
21388
  getLogger().warn(
@@ -21432,16 +21470,47 @@ function createRemoteMemoryStore(options) {
21432
21470
  const { persistence, embeddingOptions } = options;
21433
21471
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
21434
21472
  const pollIntervalMs = options.pollIntervalMs ?? 6e4;
21473
+ const activity = options.activity ?? {
21474
+ isActive: () => (typeof document === "undefined" || document.visibilityState !== "hidden") && (typeof navigator === "undefined" || navigator.onLine !== false),
21475
+ subscribe: (onChange) => {
21476
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
21477
+ };
21478
+ document.addEventListener("visibilitychange", onChange);
21479
+ window.addEventListener("online", onChange);
21480
+ window.addEventListener("offline", onChange);
21481
+ return () => {
21482
+ document.removeEventListener("visibilitychange", onChange);
21483
+ window.removeEventListener("online", onChange);
21484
+ window.removeEventListener("offline", onChange);
21485
+ };
21486
+ }
21487
+ };
21435
21488
  const pipeline = createRemoteMemoryPipeline({
21436
21489
  ...options,
21437
21490
  graphRanking: options.graphRanking ?? ((query, _traverse, recallOptions) => topicGraphRanking(query, recallOptions)),
21438
21491
  temporalRanking: options.temporalRanking ?? ((query, now) => eventTimeRanking(query, now))
21439
21492
  });
21440
21493
  const subscribers = /* @__PURE__ */ new Set();
21494
+ const retainTopics = () => pollIntervalMs > 0 && [...subscribers].some((subscriber) => subscriber.options.topics);
21441
21495
  const seen = /* @__PURE__ */ new Map();
21496
+ const reads = /* @__PURE__ */ new Map();
21497
+ const metadataReads = /* @__PURE__ */ new Map();
21498
+ let topicSnapshot;
21499
+ let topicCatalog;
21500
+ const invalidateReads = () => {
21501
+ reads.clear();
21502
+ metadataReads.clear();
21503
+ topicSnapshot = void 0;
21504
+ topicCatalog = void 0;
21505
+ };
21442
21506
  let timer;
21507
+ let scheduledAt;
21508
+ let stopActivity;
21509
+ let stopChanges;
21510
+ let changesConnected = false;
21443
21511
  const notify = (change, records = []) => {
21444
- if (timer)
21512
+ invalidateReads();
21513
+ if (subscribers.size)
21445
21514
  for (const record2 of records) {
21446
21515
  const previous = seen.get(record2.memory.memory_id);
21447
21516
  if ((previous?.version ?? 0) + 1 === record2.version)
@@ -21465,8 +21534,37 @@ function createRemoteMemoryStore(options) {
21465
21534
  } while (cursor2 && records.length < max);
21466
21535
  return records.slice(0, max);
21467
21536
  };
21468
- const decrypted = (options2) => persistence.query(options2);
21469
- const metadata3 = (options2) => persistence.queryMetadata(options2);
21537
+ const shareRead = (inflight, options2, read) => {
21538
+ if (options2.signal) return read();
21539
+ const key = JSON.stringify(Object.entries(options2).sort(([a], [b]) => a.localeCompare(b)));
21540
+ const active = inflight.get(key);
21541
+ if (active) return active;
21542
+ const pending2 = read();
21543
+ inflight.set(key, pending2);
21544
+ const clear = () => {
21545
+ if (inflight.get(key) === pending2) inflight.delete(key);
21546
+ };
21547
+ void pending2.then(clear, clear);
21548
+ return pending2;
21549
+ };
21550
+ const decrypted = (options2) => shareRead(reads, options2, () => persistence.query(options2));
21551
+ const metadata3 = (options2) => shareRead(
21552
+ metadataReads,
21553
+ options2,
21554
+ () => persistence.queryMetadata(options2)
21555
+ );
21556
+ const readTopics = () => {
21557
+ if (topicCatalog) return topicCatalog;
21558
+ const pending2 = persistence.topics();
21559
+ topicCatalog = pending2;
21560
+ const clear = () => {
21561
+ if (topicCatalog === pending2) topicCatalog = void 0;
21562
+ };
21563
+ void pending2.then(() => {
21564
+ if (!retainTopics()) clear();
21565
+ }, clear);
21566
+ return pending2;
21567
+ };
21470
21568
  const enumerate = (filters, max = Infinity) => pages(decrypted, filters, max);
21471
21569
  const memoriesByTopics = async (names) => {
21472
21570
  const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
@@ -21920,11 +22018,11 @@ function createRemoteMemoryStore(options) {
21920
22018
  };
21921
22019
  const changesSince = async (cursor2, pending2) => {
21922
22020
  const records = [];
21923
- const failed = /* @__PURE__ */ new Set();
22021
+ const failed = /* @__PURE__ */ new Map();
21924
22022
  const collect = (page) => {
21925
22023
  warnFailed(page.failed);
21926
22024
  records.push(...page.items);
21927
- for (const failure of page.failed) failed.add(failure.memory_id);
22025
+ for (const failure of page.failed) failed.set(failure.memory_id, failure.version);
21928
22026
  };
21929
22027
  let resume = cursor2;
21930
22028
  let next;
@@ -21958,25 +22056,72 @@ function createRemoteMemoryStore(options) {
21958
22056
  let ready = false;
21959
22057
  let cursor;
21960
22058
  let pending = /* @__PURE__ */ new Set();
22059
+ let failedVersions = /* @__PURE__ */ new Map();
21961
22060
  let topicFingerprint;
21962
22061
  let polling = false;
22062
+ let pollRequested = false;
22063
+ let failures = 0;
22064
+ let retryAt = 0;
22065
+ const backoff = (error) => {
22066
+ failures++;
22067
+ retryAt = Date.now() + Math.max(
22068
+ error instanceof RemoteMemoryError ? error.retryAfterMs ?? 0 : 0,
22069
+ Math.min(pollIntervalMs * 2 ** Math.min(failures - 1, 10), 15 * 6e4)
22070
+ );
22071
+ };
22072
+ const schedulePoll = (delay, keepEarlier = false) => {
22073
+ const at = Math.max(Date.now() + delay, retryAt);
22074
+ if (keepEarlier && timer && scheduledAt !== void 0 && scheduledAt <= at) return;
22075
+ if (timer) clearTimeout(timer);
22076
+ timer = void 0;
22077
+ scheduledAt = void 0;
22078
+ if (!subscribers.size || pollIntervalMs <= 0 || !activity.isActive()) return;
22079
+ scheduledAt = at;
22080
+ timer = setTimeout(poll, Math.max(0, at - Date.now()));
22081
+ };
21963
22082
  const poll = () => {
21964
- if (polling) return;
22083
+ if (timer) clearTimeout(timer);
22084
+ timer = void 0;
22085
+ scheduledAt = void 0;
22086
+ if (!subscribers.size || pollIntervalMs <= 0 || !activity.isActive()) return;
22087
+ if (Date.now() < retryAt) {
22088
+ schedulePoll(0);
22089
+ return;
22090
+ }
22091
+ if (polling) {
22092
+ pollRequested = true;
22093
+ return;
22094
+ }
21965
22095
  polling = true;
22096
+ pollRequested = false;
21966
22097
  const current = generation;
21967
22098
  changesSince(cursor, pending).then(async ({ records, resume, failed }) => {
21968
22099
  let topicState;
22100
+ let topicFailed = false;
21969
22101
  if ([...subscribers].some((s) => s.options.topics))
21970
22102
  try {
21971
- topicState = JSON.stringify(
21972
- (await persistence.topics()).map((t) => [t.name, t.version])
21973
- );
22103
+ topicCatalog = void 0;
22104
+ topicState = JSON.stringify((await readTopics()).map((t) => [t.name, t.version]));
21974
22105
  } catch (error) {
22106
+ topicFailed = true;
22107
+ if (current === generation) backoff(error);
21975
22108
  getLogger().warn("[memory/remote-store] Topic subscription poll failed:", error);
21976
22109
  }
21977
22110
  if (current !== generation) return;
22111
+ if (!topicFailed) {
22112
+ failures = 0;
22113
+ retryAt = 0;
22114
+ }
22115
+ const failedChanged = [...failed].some(
22116
+ ([id2, version]) => failedVersions.get(id2) !== version
22117
+ );
22118
+ if (records.length || failedChanged || topicState !== void 0 && topicState !== topicFingerprint) {
22119
+ reads.clear();
22120
+ metadataReads.clear();
22121
+ topicSnapshot = void 0;
22122
+ }
21978
22123
  const changed = /* @__PURE__ */ new Set();
21979
- if (topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
22124
+ if (ready && failedChanged || topicState !== void 0 && topicFingerprint !== void 0 && topicState !== topicFingerprint) {
21980
22125
  for (const subscriber of subscribers)
21981
22126
  if (subscriber.options.topics) changed.add(subscriber);
21982
22127
  }
@@ -21993,15 +22138,40 @@ function createRemoteMemoryStore(options) {
21993
22138
  }
21994
22139
  ready = true;
21995
22140
  cursor = resume;
21996
- pending = failed;
22141
+ pending = new Set(failed.keys());
22142
+ failedVersions = failed;
21997
22143
  for (const subscriber of changed)
21998
22144
  if (subscribers.has(subscriber)) safely(subscriber.onChange);
21999
22145
  }).catch((error) => {
22146
+ if (current === generation) backoff(error);
22000
22147
  getLogger().warn("[memory/remote-store] Subscription poll failed:", error);
22001
22148
  }).finally(() => {
22002
22149
  polling = false;
22150
+ schedulePoll(
22151
+ current !== generation ? 0 : pollRequested ? 200 : changesConnected ? Math.max(pollIntervalMs, 5 * 6e4) : pollIntervalMs
22152
+ );
22003
22153
  });
22004
22154
  };
22155
+ const refreshActivity = () => {
22156
+ if (timer) clearTimeout(timer);
22157
+ timer = void 0;
22158
+ if (!activity.isActive()) {
22159
+ stopChanges?.();
22160
+ stopChanges = void 0;
22161
+ changesConnected = false;
22162
+ return;
22163
+ }
22164
+ if (!stopChanges && persistence.supportsRealtimeChanges && options.subscribeChanges)
22165
+ stopChanges = options.subscribeChanges((connected) => {
22166
+ changesConnected = connected;
22167
+ if (polling) {
22168
+ pollRequested = true;
22169
+ return;
22170
+ }
22171
+ schedulePoll(connected ? 200 : 0, true);
22172
+ });
22173
+ poll();
22174
+ };
22005
22175
  const unrepairable = /* @__PURE__ */ new Set();
22006
22176
  let repairCursor;
22007
22177
  const repairEmbeddingsUnlocked = async ({
@@ -22057,40 +22227,78 @@ function createRemoteMemoryStore(options) {
22057
22227
  result.failed += targets.length;
22058
22228
  return finish();
22059
22229
  }
22060
- for (const [i, record2] of targets.entries()) {
22230
+ const writes = targets.flatMap((record2, i) => {
22061
22231
  const vector = vectors[i];
22062
22232
  if (!vector) {
22063
22233
  result.failed++;
22064
- continue;
22234
+ return [];
22065
22235
  }
22066
- await serialized(record2.memory.memory_id, async () => {
22067
- signal?.throwIfAborted();
22068
- try {
22069
- const [saved] = await persistence.putMany(
22070
- [
22071
- {
22072
- memory: {
22073
- ...record2.memory,
22074
- embedding: storedVector(vector),
22075
- embedding_model: model
22076
- },
22077
- expectedVersion: record2
22078
- }
22079
- ],
22080
- signal,
22081
- leaseToken
22082
- );
22083
- notify("embedding", [saved]);
22084
- repaired.add(record2.memory.memory_id);
22085
- result.reembedded++;
22086
- } catch (error) {
22236
+ try {
22237
+ return [
22238
+ {
22239
+ memory: {
22240
+ ...record2.memory,
22241
+ embedding: storedVector(vector),
22242
+ embedding_model: model
22243
+ },
22244
+ expectedVersion: record2
22245
+ }
22246
+ ];
22247
+ } catch {
22248
+ result.failed++;
22249
+ return [];
22250
+ }
22251
+ });
22252
+ for (const batch of chunks2(writes, MAX_BATCH)) {
22253
+ const continueRepair = await serializedAll(
22254
+ batch.map(({ memory }) => memory.memory_id),
22255
+ async () => {
22087
22256
  signal?.throwIfAborted();
22088
- if (isRemoteWorkLeaseExpired(error)) throw error;
22089
- if (error instanceof RemoteMemoryError && error.code === "version_conflict")
22090
- result.skippedConflict++;
22091
- else result.failed++;
22257
+ const commit = async (slice) => {
22258
+ const saved = await persistence.putMany(slice, signal, leaseToken);
22259
+ for (const record2 of saved) repaired.add(record2.memory.memory_id);
22260
+ result.reembedded += saved.length;
22261
+ notify("embedding", saved);
22262
+ };
22263
+ try {
22264
+ await commit(batch);
22265
+ } catch (error) {
22266
+ signal?.throwIfAborted();
22267
+ if (isRemoteWorkLeaseExpired(error)) throw error;
22268
+ if (!(error instanceof RemoteMemoryError && error.code === "version_conflict") && !rejectsMemoryWrite(error)) {
22269
+ result.failed += batch.length;
22270
+ return false;
22271
+ }
22272
+ if (batch.length === 1) {
22273
+ if (rejectsMemoryWrite(error)) {
22274
+ unrepairable.add(batch[0].memory.memory_id);
22275
+ result.failed++;
22276
+ } else result.skippedConflict++;
22277
+ return true;
22278
+ }
22279
+ for (const [index, write] of batch.entries()) {
22280
+ signal?.throwIfAborted();
22281
+ try {
22282
+ await commit([write]);
22283
+ } catch (error2) {
22284
+ signal?.throwIfAborted();
22285
+ if (isRemoteWorkLeaseExpired(error2)) throw error2;
22286
+ if (error2 instanceof RemoteMemoryError && error2.code === "version_conflict")
22287
+ result.skippedConflict++;
22288
+ else if (rejectsMemoryWrite(error2)) {
22289
+ unrepairable.add(write.memory.memory_id);
22290
+ result.failed++;
22291
+ } else {
22292
+ result.failed += batch.length - index;
22293
+ return false;
22294
+ }
22295
+ }
22296
+ }
22297
+ }
22298
+ return true;
22092
22299
  }
22093
- });
22300
+ );
22301
+ if (!continueRepair) break;
22094
22302
  }
22095
22303
  return finish();
22096
22304
  };
@@ -22130,7 +22338,7 @@ function createRemoteMemoryStore(options) {
22130
22338
  },
22131
22339
  importTopics: async (topics) => {
22132
22340
  if (!topics.length) return;
22133
- const existing = new Set((await persistence.topics()).map((topic2) => topic2.name));
22341
+ const existing = new Set((await readTopics()).map((topic2) => topic2.name));
22134
22342
  const missing = topics.flatMap((topic2) => {
22135
22343
  const name = normalizeEntityName(topic2.name);
22136
22344
  if (existing.has(name)) return [];
@@ -22139,6 +22347,7 @@ function createRemoteMemoryStore(options) {
22139
22347
  });
22140
22348
  if (!missing.length) return;
22141
22349
  await persistence.importTopics(missing);
22350
+ invalidateReads();
22142
22351
  for (const subscriber of [...subscribers])
22143
22352
  if (subscriber.options.topics) safely(subscriber.onChange);
22144
22353
  },
@@ -22152,40 +22361,51 @@ function createRemoteMemoryStore(options) {
22152
22361
  listProjections: async (listOptions = {}) => (await listRows(metadata3, listOptions, listFilters(listOptions), maxOf(listOptions))).map(
22153
22362
  projection
22154
22363
  ),
22155
- listTopics: async () => {
22156
- const topics = /* @__PURE__ */ new Map();
22157
- for (const { memory } of await pages(metadata3, {
22158
- include_archived: true,
22159
- include_quarantined: true,
22160
- include_superseded: true
22161
- })) {
22162
- for (const topic2 of parseTopics(memory.topics) ?? []) {
22163
- const name = normalizeEntityName(topic2.name);
22164
- if (!name) continue;
22165
- const entry = topics.get(name) ?? {
22166
- name,
22167
- kind: null,
22168
- memoryCount: 0,
22169
- members: /* @__PURE__ */ new Set()
22170
- };
22171
- entry.kind ?? (entry.kind = topic2.kind ?? null);
22172
- entry.members.add(memory.memory_id);
22173
- topics.set(name, entry);
22364
+ listTopics: () => {
22365
+ if (topicSnapshot) return topicSnapshot.then((topics) => structuredClone(topics));
22366
+ const snapshot = (async () => {
22367
+ const topics = /* @__PURE__ */ new Map();
22368
+ for (const { memory } of await pages(metadata3, {
22369
+ include_archived: true,
22370
+ include_quarantined: true,
22371
+ include_superseded: true
22372
+ })) {
22373
+ for (const topic2 of parseTopics(memory.topics) ?? []) {
22374
+ const name = normalizeEntityName(topic2.name);
22375
+ if (!name) continue;
22376
+ const entry = topics.get(name) ?? {
22377
+ name,
22378
+ kind: null,
22379
+ memoryCount: 0,
22380
+ members: /* @__PURE__ */ new Set()
22381
+ };
22382
+ entry.kind ?? (entry.kind = topic2.kind ?? null);
22383
+ entry.members.add(memory.memory_id);
22384
+ topics.set(name, entry);
22385
+ }
22174
22386
  }
22175
- }
22176
- for (const topic2 of await persistence.topics()) {
22177
- if (topic2.is_deleted) continue;
22178
- const current = topics.get(topic2.name);
22179
- if (current) current.kind ?? (current.kind = topic2.kind ?? null);
22180
- else
22181
- topics.set(topic2.name, {
22182
- name: topic2.name,
22183
- kind: topic2.kind ?? null,
22184
- memoryCount: 0,
22185
- members: /* @__PURE__ */ new Set()
22186
- });
22187
- }
22188
- return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
22387
+ for (const topic2 of await readTopics()) {
22388
+ if (topic2.is_deleted) continue;
22389
+ const current = topics.get(topic2.name);
22390
+ if (current) current.kind ?? (current.kind = topic2.kind ?? null);
22391
+ else
22392
+ topics.set(topic2.name, {
22393
+ name: topic2.name,
22394
+ kind: topic2.kind ?? null,
22395
+ memoryCount: 0,
22396
+ members: /* @__PURE__ */ new Set()
22397
+ });
22398
+ }
22399
+ return [...topics.values()].map(({ members, ...topic2 }) => ({ ...topic2, memoryCount: members.size })).sort((a, b) => a.name.localeCompare(b.name));
22400
+ })();
22401
+ topicSnapshot = snapshot;
22402
+ const clear = () => {
22403
+ if (topicSnapshot === snapshot) topicSnapshot = void 0;
22404
+ };
22405
+ void snapshot.then(() => {
22406
+ if (!retainTopics()) clear();
22407
+ }, clear);
22408
+ return snapshot.then((topics) => structuredClone(topics));
22189
22409
  },
22190
22410
  get: async (id2) => asStored(await live(id2)),
22191
22411
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
@@ -22366,20 +22586,23 @@ function createRemoteMemoryStore(options) {
22366
22586
  subscribe: (onChange, subscribeOptions = {}) => {
22367
22587
  const subscriber = { onChange, options: subscribeOptions };
22368
22588
  subscribers.add(subscriber);
22369
- if (pollIntervalMs > 0 && !timer) {
22370
- timer = setInterval(poll, pollIntervalMs);
22371
- poll();
22589
+ if (pollIntervalMs > 0 && subscribers.size === 1) {
22590
+ stopActivity = activity.subscribe(refreshActivity);
22591
+ refreshActivity();
22372
22592
  }
22373
22593
  return () => {
22374
22594
  subscribers.delete(subscriber);
22375
- if (subscribers.size || !timer) return;
22376
- clearInterval(timer);
22595
+ if (!retainTopics()) invalidateReads();
22596
+ if (subscribers.size) return;
22597
+ if (timer) clearTimeout(timer);
22377
22598
  timer = void 0;
22599
+ stopActivity?.();
22600
+ stopActivity = void 0;
22601
+ stopChanges?.();
22602
+ stopChanges = void 0;
22603
+ changesConnected = false;
22378
22604
  generation++;
22379
- seen.clear();
22380
- ready = false;
22381
- cursor = void 0;
22382
- pending = /* @__PURE__ */ new Set();
22605
+ invalidateReads();
22383
22606
  };
22384
22607
  }
22385
22608
  };
package/dist/index.cjs CHANGED
@@ -1826,11 +1826,12 @@ function parseRetryAfterMs(header) {
1826
1826
 
1827
1827
  // src/lib/memory/store/remotePersistence.ts
1828
1828
  var RemoteMemoryError = class extends Error {
1829
- constructor(message, status, code, retryAfterMs) {
1829
+ constructor(message, status, code, retryAfterMs, rateLimitBucket) {
1830
1830
  super(message);
1831
1831
  this.status = status;
1832
1832
  this.code = code;
1833
1833
  this.retryAfterMs = retryAfterMs;
1834
+ this.rateLimitBucket = rateLimitBucket;
1834
1835
  this.name = "RemoteMemoryError";
1835
1836
  }
1836
1837
  };
@@ -1849,6 +1850,14 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1849
1850
  throw new Error("timeoutMs must be a positive number");
1850
1851
  const root = base.href.replace(/\/$/, "") + apiPath;
1851
1852
  const fetcher = options.fetch ?? globalThis.fetch;
1853
+ const cooldowns = /* @__PURE__ */ new Map();
1854
+ const memoryBudget = (path, method) => {
1855
+ if (method === "PUT")
1856
+ return path === "/topics" ? "private-memory-import" : "private-memory-write";
1857
+ if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
1858
+ return "private-memory-import";
1859
+ return "private-memory-read";
1860
+ };
1852
1861
  const request = async (path, init = {}, signal) => {
1853
1862
  const controller = new AbortController();
1854
1863
  let timedOut = false;
@@ -1872,6 +1881,25 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1872
1881
  const token = await Promise.race([options.getToken(), aborted]);
1873
1882
  if (controller.signal.aborted) await aborted;
1874
1883
  if (!token) throw new RemoteMemoryError("Authentication required", 401);
1884
+ const pathname = path.split("?")[0];
1885
+ const route = `${init.method ?? "GET"} ${pathname}`;
1886
+ const budgets = ["*", route];
1887
+ if (apiPath === "/api/private-memories")
1888
+ budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
1889
+ for (const bucket of budgets) {
1890
+ const cooldown = cooldowns.get(bucket);
1891
+ if (!cooldown) continue;
1892
+ const remaining = cooldown.until - Date.now();
1893
+ if (remaining > 0)
1894
+ throw new RemoteMemoryError(
1895
+ cooldown.error.message,
1896
+ 429,
1897
+ cooldown.error.code,
1898
+ remaining,
1899
+ cooldown.error.rateLimitBucket
1900
+ );
1901
+ cooldowns.delete(bucket);
1902
+ }
1875
1903
  const response = await fetcher(root + path, {
1876
1904
  ...init,
1877
1905
  headers: {
@@ -1884,12 +1912,18 @@ function createNearbyRequest(options, apiPath = "/api/private-memories") {
1884
1912
  if (!response.ok) {
1885
1913
  const body = await response.json().catch(() => null);
1886
1914
  const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
1887
- throw new RemoteMemoryError(
1915
+ const error = new RemoteMemoryError(
1888
1916
  record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
1889
1917
  response.status,
1890
1918
  record(body) && typeof body.code === "string" ? body.code : void 0,
1891
- retryAfter ?? void 0
1919
+ retryAfter ?? void 0,
1920
+ record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
1892
1921
  );
1922
+ if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
1923
+ const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
1924
+ cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
1925
+ }
1926
+ throw error;
1893
1927
  }
1894
1928
  return await response.json();
1895
1929
  } catch (error) {