@affectively/aeon 1.3.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +15 -21
  2. package/README.md +422 -342
  3. package/dist/compression/index.cjs +20 -3
  4. package/dist/compression/index.cjs.map +1 -1
  5. package/dist/compression/index.js +20 -3
  6. package/dist/compression/index.js.map +1 -1
  7. package/dist/crypto/index.cjs +30 -0
  8. package/dist/crypto/index.cjs.map +1 -1
  9. package/dist/crypto/index.js +29 -1
  10. package/dist/crypto/index.js.map +1 -1
  11. package/dist/distributed/index.cjs +15 -8
  12. package/dist/distributed/index.cjs.map +1 -1
  13. package/dist/distributed/index.js +15 -8
  14. package/dist/distributed/index.js.map +1 -1
  15. package/dist/index.cjs +6686 -3118
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +6642 -3117
  18. package/dist/index.js.map +1 -1
  19. package/dist/offline/index.cjs.map +1 -1
  20. package/dist/offline/index.js.map +1 -1
  21. package/dist/optimization/index.cjs +6 -3
  22. package/dist/optimization/index.cjs.map +1 -1
  23. package/dist/optimization/index.js +6 -3
  24. package/dist/optimization/index.js.map +1 -1
  25. package/dist/persistence/index.cjs +91 -29
  26. package/dist/persistence/index.cjs.map +1 -1
  27. package/dist/persistence/index.js +91 -29
  28. package/dist/persistence/index.js.map +1 -1
  29. package/dist/presence/index.cjs.map +1 -1
  30. package/dist/presence/index.js.map +1 -1
  31. package/dist/utils/index.cjs.map +1 -1
  32. package/dist/utils/index.js.map +1 -1
  33. package/dist/versioning/index.cjs +4 -3
  34. package/dist/versioning/index.cjs.map +1 -1
  35. package/dist/versioning/index.js +4 -3
  36. package/dist/versioning/index.js.map +1 -1
  37. package/package.json +195 -196
  38. package/dist/compression/index.d.cts +0 -189
  39. package/dist/compression/index.d.ts +0 -189
  40. package/dist/core/index.d.cts +0 -216
  41. package/dist/core/index.d.ts +0 -216
  42. package/dist/crypto/index.d.cts +0 -446
  43. package/dist/crypto/index.d.ts +0 -446
  44. package/dist/distributed/index.d.cts +0 -1016
  45. package/dist/distributed/index.d.ts +0 -1016
  46. package/dist/index.d.cts +0 -57
  47. package/dist/index.d.ts +0 -57
  48. package/dist/offline/index.d.cts +0 -154
  49. package/dist/offline/index.d.ts +0 -154
  50. package/dist/optimization/index.d.cts +0 -347
  51. package/dist/optimization/index.d.ts +0 -347
  52. package/dist/persistence/index.d.cts +0 -63
  53. package/dist/persistence/index.d.ts +0 -63
  54. package/dist/presence/index.d.cts +0 -283
  55. package/dist/presence/index.d.ts +0 -283
  56. package/dist/types-B7CxsoLh.d.cts +0 -33
  57. package/dist/types-B7CxsoLh.d.ts +0 -33
  58. package/dist/utils/index.d.cts +0 -38
  59. package/dist/utils/index.d.ts +0 -38
  60. package/dist/versioning/index.d.cts +0 -537
  61. package/dist/versioning/index.d.ts +0 -537
@@ -1,12 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  // src/persistence/DashStorageAdapter.ts
4
+ var DEFAULT_RULE = {
5
+ urgency: "deferred",
6
+ debounce: 50,
7
+ maxBufferSize: 5e3,
8
+ readThrough: true
9
+ };
4
10
  var DashStorageAdapter = class {
5
11
  backend;
6
12
  syncClient;
7
- syncDebounceMs;
8
- maxPendingChanges;
9
- onSyncError;
13
+ rules;
14
+ hooks;
10
15
  pendingChanges = /* @__PURE__ */ new Map();
11
16
  syncTimer = null;
12
17
  syncInFlight = false;
@@ -14,11 +19,33 @@ var DashStorageAdapter = class {
14
19
  constructor(backend, options = {}) {
15
20
  this.backend = backend;
16
21
  this.syncClient = options.syncClient ?? null;
17
- this.syncDebounceMs = options.syncDebounceMs ?? 50;
18
- this.maxPendingChanges = options.maxPendingChanges ?? 5e3;
19
- this.onSyncError = options.onSyncError ?? null;
22
+ this.hooks = options.hooks ?? {};
23
+ const defaultRule = {
24
+ ...DEFAULT_RULE,
25
+ ...options.rules?.default ?? {}
26
+ };
27
+ if (options.syncDebounceMs !== void 0)
28
+ defaultRule.debounce = options.syncDebounceMs;
29
+ if (options.maxPendingChanges !== void 0)
30
+ defaultRule.maxBufferSize = options.maxPendingChanges;
31
+ if (options.onSyncError && !this.hooks.onSyncError)
32
+ this.hooks.onSyncError = options.onSyncError;
33
+ this.rules = {
34
+ default: defaultRule,
35
+ prefixes: options.rules?.prefixes ?? {}
36
+ };
20
37
  }
38
+ /**
39
+ * Get an item, checking the write pool (pending changes) first for consistency.
40
+ */
21
41
  async getItem(key) {
42
+ const rule = this.getRuleForKey(key);
43
+ if (rule.readThrough !== false) {
44
+ const pending = this.pendingChanges.get(key);
45
+ if (pending) {
46
+ return pending.operation === "delete" ? null : pending.value ?? null;
47
+ }
48
+ }
22
49
  return await this.backend.get(key);
23
50
  }
24
51
  async setItem(key, value) {
@@ -53,34 +80,45 @@ var DashStorageAdapter = class {
53
80
  }
54
81
  trackChange(change) {
55
82
  this.pendingChanges.set(change.key, change);
56
- this.enforcePendingLimit();
57
- this.scheduleSync();
58
- }
59
- enforcePendingLimit() {
60
- if (this.pendingChanges.size <= this.maxPendingChanges) {
83
+ const rule = this.getRuleForKey(change.key);
84
+ if (rule.urgency === "realtime") {
85
+ void this.performSync();
61
86
  return;
62
87
  }
63
- const sorted = Array.from(this.pendingChanges.values()).sort(
64
- (a, b) => a.timestamp - b.timestamp
65
- );
66
- const overflow = this.pendingChanges.size - this.maxPendingChanges;
67
- for (let i = 0; i < overflow; i++) {
68
- const toDrop = sorted[i];
69
- if (toDrop) {
70
- this.pendingChanges.delete(toDrop.key);
71
- }
88
+ const maxSize = rule.maxBufferSize ?? 5e3;
89
+ if (this.pendingChanges.size >= maxSize) {
90
+ this.hooks.onBufferOverflow?.(
91
+ this.getPrefixMatch(change.key) || "default",
92
+ this.pendingChanges.size,
93
+ maxSize
94
+ );
95
+ void this.performSync();
96
+ return;
72
97
  }
98
+ this.scheduleSync(rule);
73
99
  }
74
- scheduleSync() {
75
- if (!this.syncClient) {
76
- return;
100
+ getRuleForKey(key) {
101
+ const prefix = this.getPrefixMatch(key);
102
+ return (prefix ? this.rules.prefixes?.[prefix] : this.rules.default) ?? this.rules.default;
103
+ }
104
+ getPrefixMatch(key) {
105
+ if (!this.rules.prefixes) {
106
+ return null;
77
107
  }
78
- if (this.syncTimer) {
79
- clearTimeout(this.syncTimer);
108
+ const prefixes = Object.keys(this.rules.prefixes).sort(
109
+ (a, b) => b.length - a.length
110
+ );
111
+ return prefixes.find((p) => key.startsWith(p)) ?? null;
112
+ }
113
+ scheduleSync(rule) {
114
+ if (!this.syncClient || this.syncTimer) {
115
+ return;
80
116
  }
117
+ const debounceMs = typeof rule.debounce === "string" ? this.parseInterval(rule.debounce) : rule.debounce ?? 50;
81
118
  this.syncTimer = setTimeout(() => {
119
+ this.syncTimer = null;
82
120
  void this.performSync();
83
- }, this.syncDebounceMs);
121
+ }, debounceMs);
84
122
  }
85
123
  async performSync() {
86
124
  if (!this.syncClient) {
@@ -100,6 +138,8 @@ var DashStorageAdapter = class {
100
138
  this.syncInFlight = true;
101
139
  try {
102
140
  await this.syncClient.syncChanges(changes);
141
+ this.hooks.onSync?.(changes);
142
+ this.hooks.onFlush?.(changes.length);
103
143
  } catch (error) {
104
144
  for (const change of changes) {
105
145
  const current = this.pendingChanges.get(change.key);
@@ -107,19 +147,39 @@ var DashStorageAdapter = class {
107
147
  this.pendingChanges.set(change.key, change);
108
148
  }
109
149
  }
110
- if (this.onSyncError) {
150
+ if (this.hooks.onSyncError) {
111
151
  const normalizedError = error instanceof Error ? error : new Error(String(error));
112
- this.onSyncError(normalizedError, changes);
152
+ this.hooks.onSyncError(normalizedError, changes);
113
153
  }
114
154
  } finally {
115
155
  this.syncInFlight = false;
116
156
  const rerun = this.syncPending || this.pendingChanges.size > 0;
117
157
  this.syncPending = false;
118
158
  if (rerun) {
119
- this.scheduleSync();
159
+ this.scheduleSync(this.rules.default);
120
160
  }
121
161
  }
122
162
  }
163
+ parseInterval(input) {
164
+ const match = input.match(/^(\d+)(ms|s|m|h|d)$/);
165
+ if (!match) return 50;
166
+ const value = parseInt(match[1], 10);
167
+ const unit = match[2];
168
+ switch (unit) {
169
+ case "ms":
170
+ return value;
171
+ case "s":
172
+ return value * 1e3;
173
+ case "m":
174
+ return value * 60 * 1e3;
175
+ case "h":
176
+ return value * 60 * 60 * 1e3;
177
+ case "d":
178
+ return value * 24 * 60 * 60 * 1e3;
179
+ default:
180
+ return 50;
181
+ }
182
+ }
123
183
  };
124
184
 
125
185
  // src/persistence/InMemoryStorageAdapter.ts
@@ -134,6 +194,8 @@ var InMemoryStorageAdapter = class {
134
194
  removeItem(key) {
135
195
  this.store.delete(key);
136
196
  }
197
+ async flushSync() {
198
+ }
137
199
  clear() {
138
200
  this.store.clear();
139
201
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/persistence/DashStorageAdapter.ts","../../src/persistence/InMemoryStorageAdapter.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { StorageAdapter } from './types';\r\n\r\nexport interface DashStorageBackend {\r\n get(key: string): Promise<string | null> | string | null;\r\n set(key: string, value: string): Promise<void> | void;\r\n delete(key: string): Promise<void> | void;\r\n}\r\n\r\nexport interface DashStorageChange {\r\n key: string;\r\n operation: 'set' | 'delete';\r\n value?: string;\r\n timestamp: number;\r\n}\r\n\r\nexport interface DashSyncClient {\r\n syncChanges(changes: DashStorageChange[]): Promise<void>;\r\n}\r\n\r\nexport interface DashStorageAdapterOptions {\r\n syncClient?: DashSyncClient;\r\n syncDebounceMs?: number;\r\n maxPendingChanges?: number;\r\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\r\n}\r\n\r\n/**\r\n * Storage adapter boundary for dash-backed persistence.\r\n *\r\n * Writes are local-first through the provided backend and optionally synced\r\n * to D1/R2 via a sync client using debounced change batches.\r\n */\r\nexport class DashStorageAdapter implements StorageAdapter {\r\n private readonly backend: DashStorageBackend;\r\n private readonly syncClient: DashSyncClient | null;\r\n private readonly syncDebounceMs: number;\r\n private readonly maxPendingChanges: number;\r\n private readonly onSyncError:\r\n | ((error: Error, changes: DashStorageChange[]) => void)\r\n | null;\r\n private readonly pendingChanges = new Map<string, DashStorageChange>();\r\n private syncTimer: ReturnType<typeof setTimeout> | null = null;\r\n private syncInFlight = false;\r\n private syncPending = false;\r\n\r\n constructor(\r\n backend: DashStorageBackend,\r\n options: DashStorageAdapterOptions = {}\r\n ) {\r\n this.backend = backend;\r\n this.syncClient = options.syncClient ?? null;\r\n this.syncDebounceMs = options.syncDebounceMs ?? 50;\r\n this.maxPendingChanges = options.maxPendingChanges ?? 5000;\r\n this.onSyncError = options.onSyncError ?? null;\r\n }\r\n\r\n async getItem(key: string): Promise<string | null> {\r\n return await this.backend.get(key);\r\n }\r\n\r\n async setItem(key: string, value: string): Promise<void> {\r\n await this.backend.set(key, value);\r\n this.trackChange({\r\n key,\r\n operation: 'set',\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n async removeItem(key: string): Promise<void> {\r\n await this.backend.delete(key);\r\n this.trackChange({\r\n key,\r\n operation: 'delete',\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n getPendingSyncCount(): number {\r\n return this.pendingChanges.size;\r\n }\r\n\r\n async flushSync(): Promise<void> {\r\n if (!this.syncClient || this.pendingChanges.size === 0) {\r\n return;\r\n }\r\n if (this.syncTimer) {\r\n clearTimeout(this.syncTimer);\r\n this.syncTimer = null;\r\n }\r\n await this.performSync();\r\n }\r\n\r\n private trackChange(change: DashStorageChange): void {\r\n this.pendingChanges.set(change.key, change);\r\n this.enforcePendingLimit();\r\n this.scheduleSync();\r\n }\r\n\r\n private enforcePendingLimit(): void {\r\n if (this.pendingChanges.size <= this.maxPendingChanges) {\r\n return;\r\n }\r\n\r\n const sorted = Array.from(this.pendingChanges.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp\r\n );\r\n const overflow = this.pendingChanges.size - this.maxPendingChanges;\r\n for (let i = 0; i < overflow; i++) {\r\n const toDrop = sorted[i];\r\n if (toDrop) {\r\n this.pendingChanges.delete(toDrop.key);\r\n }\r\n }\r\n }\r\n\r\n private scheduleSync(): void {\r\n if (!this.syncClient) {\r\n return;\r\n }\r\n\r\n if (this.syncTimer) {\r\n clearTimeout(this.syncTimer);\r\n }\r\n\r\n this.syncTimer = setTimeout(() => {\r\n void this.performSync();\r\n }, this.syncDebounceMs);\r\n }\r\n\r\n private async performSync(): Promise<void> {\r\n if (!this.syncClient) {\r\n return;\r\n }\r\n\r\n if (this.syncInFlight) {\r\n this.syncPending = true;\r\n return;\r\n }\r\n\r\n const changes = Array.from(this.pendingChanges.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp\r\n );\r\n if (changes.length === 0) {\r\n return;\r\n }\r\n\r\n this.pendingChanges.clear();\r\n this.syncInFlight = true;\r\n try {\r\n await this.syncClient.syncChanges(changes);\r\n } catch (error) {\r\n for (const change of changes) {\r\n const current = this.pendingChanges.get(change.key);\r\n if (!current || change.timestamp > current.timestamp) {\r\n this.pendingChanges.set(change.key, change);\r\n }\r\n }\r\n\r\n if (this.onSyncError) {\r\n const normalizedError =\r\n error instanceof Error ? error : new Error(String(error));\r\n this.onSyncError(normalizedError, changes);\r\n }\r\n } finally {\r\n this.syncInFlight = false;\r\n const rerun = this.syncPending || this.pendingChanges.size > 0;\r\n this.syncPending = false;\r\n if (rerun) {\r\n this.scheduleSync();\r\n }\r\n }\r\n }\r\n}\r\n","import type { StorageAdapter } from './types';\r\n\r\n/**\r\n * In-memory adapter for tests and ephemeral runtimes.\r\n */\r\nexport class InMemoryStorageAdapter implements StorageAdapter {\r\n private readonly store = new Map<string, string>();\r\n\r\n getItem(key: string): string | null {\r\n return this.store.get(key) ?? null;\r\n }\r\n\r\n setItem(key: string, value: string): void {\r\n this.store.set(key, value);\r\n }\r\n\r\n removeItem(key: string): void {\r\n this.store.delete(key);\r\n }\r\n\r\n clear(): void {\r\n this.store.clear();\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/persistence/DashStorageAdapter.ts","../../src/persistence/InMemoryStorageAdapter.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { StorageAdapter } from './types';\n\nexport interface DashStorageBackend {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\nexport interface DashStorageChange {\n key: string;\n operation: 'set' | 'delete';\n value?: string;\n timestamp: number;\n}\n\nexport interface DashSyncClient {\n syncChanges(changes: DashStorageChange[]): Promise<void>;\n}\n\nexport type DashSyncUrgency = 'realtime' | 'deferred' | 'lazy';\n\nexport interface DashSyncRule {\n /** How quickly to sync changes for keys matching this rule/prefix */\n urgency: DashSyncUrgency;\n /** Debounce/Interval for deferred/lazy sync (e.g. '1s', '1m', '1h') */\n debounce?: string | number;\n /** Maximum number of pending changes before forcing a sync */\n maxBufferSize?: number;\n /** Whether to return pending values from memory (default: true) */\n readThrough?: boolean;\n}\n\nexport interface DashSyncRules {\n default?: DashSyncRule;\n /** Key prefix mapping to sync rules */\n prefixes?: Record<string, DashSyncRule>;\n}\n\nexport interface DashStorageAdapterHooks {\n onSync?: (changes: DashStorageChange[]) => void;\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\n onBufferOverflow?: (prefix: string, size: number, max: number) => void;\n onFlush?: (count: number) => void;\n}\n\nexport interface DashStorageAdapterOptions {\n syncClient?: DashSyncClient;\n rules?: DashSyncRules;\n hooks?: DashStorageAdapterHooks;\n /** @deprecated Use rules.default.debounce */\n syncDebounceMs?: number;\n /** @deprecated Use rules.default.maxBufferSize */\n maxPendingChanges?: number;\n /** @deprecated Use hooks.onSyncError */\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\n}\n\nconst DEFAULT_RULE: DashSyncRule = {\n urgency: 'deferred',\n debounce: 50,\n maxBufferSize: 5000,\n readThrough: true,\n};\n\n/**\n * Storage adapter boundary for dash-backed persistence.\n *\n * Provides a \"Write Pool\" layer that buffers local-first writes and flushes\n * them to D1/R2 via a sync client based on declarative rules.\n */\nexport class DashStorageAdapter implements StorageAdapter {\n private readonly backend: DashStorageBackend;\n private readonly syncClient: DashSyncClient | null;\n private readonly rules: DashSyncRules;\n private readonly hooks: DashStorageAdapterHooks;\n private readonly pendingChanges = new Map<string, DashStorageChange>();\n private syncTimer: ReturnType<typeof setTimeout> | null = null;\n private syncInFlight = false;\n private syncPending = false;\n\n constructor(\n backend: DashStorageBackend,\n options: DashStorageAdapterOptions = {}\n ) {\n this.backend = backend;\n this.syncClient = options.syncClient ?? null;\n this.hooks = options.hooks ?? {};\n\n // Migration/Fallback for deprecated options\n const defaultRule: DashSyncRule = {\n ...DEFAULT_RULE,\n ...(options.rules?.default ?? {}),\n };\n if (options.syncDebounceMs !== undefined)\n defaultRule.debounce = options.syncDebounceMs;\n if (options.maxPendingChanges !== undefined)\n defaultRule.maxBufferSize = options.maxPendingChanges;\n if (options.onSyncError && !this.hooks.onSyncError)\n this.hooks.onSyncError = options.onSyncError;\n\n this.rules = {\n default: defaultRule,\n prefixes: options.rules?.prefixes ?? {},\n };\n }\n\n /**\n * Get an item, checking the write pool (pending changes) first for consistency.\n */\n async getItem(key: string): Promise<string | null> {\n const rule = this.getRuleForKey(key);\n\n // Read-through: check memory first if enabled\n if (rule.readThrough !== false) {\n const pending = this.pendingChanges.get(key);\n if (pending) {\n return pending.operation === 'delete' ? null : pending.value ?? null;\n }\n }\n\n return await this.backend.get(key);\n }\n\n async setItem(key: string, value: string): Promise<void> {\n await this.backend.set(key, value);\n this.trackChange({\n key,\n operation: 'set',\n value,\n timestamp: Date.now(),\n });\n }\n\n async removeItem(key: string): Promise<void> {\n await this.backend.delete(key);\n this.trackChange({\n key,\n operation: 'delete',\n timestamp: Date.now(),\n });\n }\n\n getPendingSyncCount(): number {\n return this.pendingChanges.size;\n }\n\n async flushSync(): Promise<void> {\n if (!this.syncClient || this.pendingChanges.size === 0) {\n return;\n }\n if (this.syncTimer) {\n clearTimeout(this.syncTimer);\n this.syncTimer = null;\n }\n await this.performSync();\n }\n\n private trackChange(change: DashStorageChange): void {\n this.pendingChanges.set(change.key, change);\n\n const rule = this.getRuleForKey(change.key);\n\n // Immediate flush for realtime\n if (rule.urgency === 'realtime') {\n void this.performSync();\n return;\n }\n\n // Check for buffer overflow\n const maxSize = rule.maxBufferSize ?? 5000;\n if (this.pendingChanges.size >= maxSize) {\n this.hooks.onBufferOverflow?.(\n this.getPrefixMatch(change.key) || 'default',\n this.pendingChanges.size,\n maxSize\n );\n void this.performSync();\n return;\n }\n\n this.scheduleSync(rule);\n }\n\n private getRuleForKey(key: string): DashSyncRule {\n const prefix = this.getPrefixMatch(key);\n return (\n (prefix ? this.rules.prefixes?.[prefix] : this.rules.default) ??\n this.rules.default!\n );\n }\n\n private getPrefixMatch(key: string): string | null {\n if (!this.rules.prefixes) {\n return null;\n }\n // Match longest prefix first\n const prefixes = Object.keys(this.rules.prefixes).sort(\n (a, b) => b.length - a.length\n );\n return prefixes.find((p) => key.startsWith(p)) ?? null;\n }\n\n private scheduleSync(rule: DashSyncRule): void {\n if (!this.syncClient || this.syncTimer) {\n return;\n }\n\n const debounceMs =\n typeof rule.debounce === 'string'\n ? this.parseInterval(rule.debounce)\n : rule.debounce ?? 50;\n\n this.syncTimer = setTimeout(() => {\n this.syncTimer = null;\n void this.performSync();\n }, debounceMs);\n }\n\n private async performSync(): Promise<void> {\n if (!this.syncClient) {\n return;\n }\n\n if (this.syncInFlight) {\n this.syncPending = true;\n return;\n }\n\n const changes = Array.from(this.pendingChanges.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n if (changes.length === 0) {\n return;\n }\n\n this.pendingChanges.clear();\n this.syncInFlight = true;\n try {\n await this.syncClient.syncChanges(changes);\n this.hooks.onSync?.(changes);\n this.hooks.onFlush?.(changes.length);\n } catch (error) {\n // Re-queue changes if they haven't been overwritten by newer local writes\n for (const change of changes) {\n const current = this.pendingChanges.get(change.key);\n if (!current || change.timestamp > current.timestamp) {\n this.pendingChanges.set(change.key, change);\n }\n }\n\n if (this.hooks.onSyncError) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n this.hooks.onSyncError(normalizedError, changes);\n }\n } finally {\n this.syncInFlight = false;\n const rerun = this.syncPending || this.pendingChanges.size > 0;\n this.syncPending = false;\n if (rerun) {\n // Use default rule for re-run or wait for next trackChange\n this.scheduleSync(this.rules.default!);\n }\n }\n }\n\n private parseInterval(input: string): number {\n const match = input.match(/^(\\d+)(ms|s|m|h|d)$/);\n if (!match) return 50;\n const value = parseInt(match[1], 10);\n const unit = match[2];\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n return 50;\n }\n }\n}\n","import type { StorageAdapter } from './types';\n\n/**\n * In-memory adapter for tests and ephemeral runtimes.\n */\nexport class InMemoryStorageAdapter implements StorageAdapter {\n private readonly store = new Map<string, string>();\n\n getItem(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n setItem(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n removeItem(key: string): void {\n this.store.delete(key);\n }\n\n async flushSync(): Promise<void> {\n /* noop */\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n"]}
@@ -1,10 +1,15 @@
1
1
  // src/persistence/DashStorageAdapter.ts
2
+ var DEFAULT_RULE = {
3
+ urgency: "deferred",
4
+ debounce: 50,
5
+ maxBufferSize: 5e3,
6
+ readThrough: true
7
+ };
2
8
  var DashStorageAdapter = class {
3
9
  backend;
4
10
  syncClient;
5
- syncDebounceMs;
6
- maxPendingChanges;
7
- onSyncError;
11
+ rules;
12
+ hooks;
8
13
  pendingChanges = /* @__PURE__ */ new Map();
9
14
  syncTimer = null;
10
15
  syncInFlight = false;
@@ -12,11 +17,33 @@ var DashStorageAdapter = class {
12
17
  constructor(backend, options = {}) {
13
18
  this.backend = backend;
14
19
  this.syncClient = options.syncClient ?? null;
15
- this.syncDebounceMs = options.syncDebounceMs ?? 50;
16
- this.maxPendingChanges = options.maxPendingChanges ?? 5e3;
17
- this.onSyncError = options.onSyncError ?? null;
20
+ this.hooks = options.hooks ?? {};
21
+ const defaultRule = {
22
+ ...DEFAULT_RULE,
23
+ ...options.rules?.default ?? {}
24
+ };
25
+ if (options.syncDebounceMs !== void 0)
26
+ defaultRule.debounce = options.syncDebounceMs;
27
+ if (options.maxPendingChanges !== void 0)
28
+ defaultRule.maxBufferSize = options.maxPendingChanges;
29
+ if (options.onSyncError && !this.hooks.onSyncError)
30
+ this.hooks.onSyncError = options.onSyncError;
31
+ this.rules = {
32
+ default: defaultRule,
33
+ prefixes: options.rules?.prefixes ?? {}
34
+ };
18
35
  }
36
+ /**
37
+ * Get an item, checking the write pool (pending changes) first for consistency.
38
+ */
19
39
  async getItem(key) {
40
+ const rule = this.getRuleForKey(key);
41
+ if (rule.readThrough !== false) {
42
+ const pending = this.pendingChanges.get(key);
43
+ if (pending) {
44
+ return pending.operation === "delete" ? null : pending.value ?? null;
45
+ }
46
+ }
20
47
  return await this.backend.get(key);
21
48
  }
22
49
  async setItem(key, value) {
@@ -51,34 +78,45 @@ var DashStorageAdapter = class {
51
78
  }
52
79
  trackChange(change) {
53
80
  this.pendingChanges.set(change.key, change);
54
- this.enforcePendingLimit();
55
- this.scheduleSync();
56
- }
57
- enforcePendingLimit() {
58
- if (this.pendingChanges.size <= this.maxPendingChanges) {
81
+ const rule = this.getRuleForKey(change.key);
82
+ if (rule.urgency === "realtime") {
83
+ void this.performSync();
59
84
  return;
60
85
  }
61
- const sorted = Array.from(this.pendingChanges.values()).sort(
62
- (a, b) => a.timestamp - b.timestamp
63
- );
64
- const overflow = this.pendingChanges.size - this.maxPendingChanges;
65
- for (let i = 0; i < overflow; i++) {
66
- const toDrop = sorted[i];
67
- if (toDrop) {
68
- this.pendingChanges.delete(toDrop.key);
69
- }
86
+ const maxSize = rule.maxBufferSize ?? 5e3;
87
+ if (this.pendingChanges.size >= maxSize) {
88
+ this.hooks.onBufferOverflow?.(
89
+ this.getPrefixMatch(change.key) || "default",
90
+ this.pendingChanges.size,
91
+ maxSize
92
+ );
93
+ void this.performSync();
94
+ return;
70
95
  }
96
+ this.scheduleSync(rule);
71
97
  }
72
- scheduleSync() {
73
- if (!this.syncClient) {
74
- return;
98
+ getRuleForKey(key) {
99
+ const prefix = this.getPrefixMatch(key);
100
+ return (prefix ? this.rules.prefixes?.[prefix] : this.rules.default) ?? this.rules.default;
101
+ }
102
+ getPrefixMatch(key) {
103
+ if (!this.rules.prefixes) {
104
+ return null;
75
105
  }
76
- if (this.syncTimer) {
77
- clearTimeout(this.syncTimer);
106
+ const prefixes = Object.keys(this.rules.prefixes).sort(
107
+ (a, b) => b.length - a.length
108
+ );
109
+ return prefixes.find((p) => key.startsWith(p)) ?? null;
110
+ }
111
+ scheduleSync(rule) {
112
+ if (!this.syncClient || this.syncTimer) {
113
+ return;
78
114
  }
115
+ const debounceMs = typeof rule.debounce === "string" ? this.parseInterval(rule.debounce) : rule.debounce ?? 50;
79
116
  this.syncTimer = setTimeout(() => {
117
+ this.syncTimer = null;
80
118
  void this.performSync();
81
- }, this.syncDebounceMs);
119
+ }, debounceMs);
82
120
  }
83
121
  async performSync() {
84
122
  if (!this.syncClient) {
@@ -98,6 +136,8 @@ var DashStorageAdapter = class {
98
136
  this.syncInFlight = true;
99
137
  try {
100
138
  await this.syncClient.syncChanges(changes);
139
+ this.hooks.onSync?.(changes);
140
+ this.hooks.onFlush?.(changes.length);
101
141
  } catch (error) {
102
142
  for (const change of changes) {
103
143
  const current = this.pendingChanges.get(change.key);
@@ -105,19 +145,39 @@ var DashStorageAdapter = class {
105
145
  this.pendingChanges.set(change.key, change);
106
146
  }
107
147
  }
108
- if (this.onSyncError) {
148
+ if (this.hooks.onSyncError) {
109
149
  const normalizedError = error instanceof Error ? error : new Error(String(error));
110
- this.onSyncError(normalizedError, changes);
150
+ this.hooks.onSyncError(normalizedError, changes);
111
151
  }
112
152
  } finally {
113
153
  this.syncInFlight = false;
114
154
  const rerun = this.syncPending || this.pendingChanges.size > 0;
115
155
  this.syncPending = false;
116
156
  if (rerun) {
117
- this.scheduleSync();
157
+ this.scheduleSync(this.rules.default);
118
158
  }
119
159
  }
120
160
  }
161
+ parseInterval(input) {
162
+ const match = input.match(/^(\d+)(ms|s|m|h|d)$/);
163
+ if (!match) return 50;
164
+ const value = parseInt(match[1], 10);
165
+ const unit = match[2];
166
+ switch (unit) {
167
+ case "ms":
168
+ return value;
169
+ case "s":
170
+ return value * 1e3;
171
+ case "m":
172
+ return value * 60 * 1e3;
173
+ case "h":
174
+ return value * 60 * 60 * 1e3;
175
+ case "d":
176
+ return value * 24 * 60 * 60 * 1e3;
177
+ default:
178
+ return 50;
179
+ }
180
+ }
121
181
  };
122
182
 
123
183
  // src/persistence/InMemoryStorageAdapter.ts
@@ -132,6 +192,8 @@ var InMemoryStorageAdapter = class {
132
192
  removeItem(key) {
133
193
  this.store.delete(key);
134
194
  }
195
+ async flushSync() {
196
+ }
135
197
  clear() {
136
198
  this.store.clear();
137
199
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/persistence/DashStorageAdapter.ts","../../src/persistence/InMemoryStorageAdapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageAdapter } from './types';\r\n\r\nexport interface DashStorageBackend {\r\n get(key: string): Promise<string | null> | string | null;\r\n set(key: string, value: string): Promise<void> | void;\r\n delete(key: string): Promise<void> | void;\r\n}\r\n\r\nexport interface DashStorageChange {\r\n key: string;\r\n operation: 'set' | 'delete';\r\n value?: string;\r\n timestamp: number;\r\n}\r\n\r\nexport interface DashSyncClient {\r\n syncChanges(changes: DashStorageChange[]): Promise<void>;\r\n}\r\n\r\nexport interface DashStorageAdapterOptions {\r\n syncClient?: DashSyncClient;\r\n syncDebounceMs?: number;\r\n maxPendingChanges?: number;\r\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\r\n}\r\n\r\n/**\r\n * Storage adapter boundary for dash-backed persistence.\r\n *\r\n * Writes are local-first through the provided backend and optionally synced\r\n * to D1/R2 via a sync client using debounced change batches.\r\n */\r\nexport class DashStorageAdapter implements StorageAdapter {\r\n private readonly backend: DashStorageBackend;\r\n private readonly syncClient: DashSyncClient | null;\r\n private readonly syncDebounceMs: number;\r\n private readonly maxPendingChanges: number;\r\n private readonly onSyncError:\r\n | ((error: Error, changes: DashStorageChange[]) => void)\r\n | null;\r\n private readonly pendingChanges = new Map<string, DashStorageChange>();\r\n private syncTimer: ReturnType<typeof setTimeout> | null = null;\r\n private syncInFlight = false;\r\n private syncPending = false;\r\n\r\n constructor(\r\n backend: DashStorageBackend,\r\n options: DashStorageAdapterOptions = {}\r\n ) {\r\n this.backend = backend;\r\n this.syncClient = options.syncClient ?? null;\r\n this.syncDebounceMs = options.syncDebounceMs ?? 50;\r\n this.maxPendingChanges = options.maxPendingChanges ?? 5000;\r\n this.onSyncError = options.onSyncError ?? null;\r\n }\r\n\r\n async getItem(key: string): Promise<string | null> {\r\n return await this.backend.get(key);\r\n }\r\n\r\n async setItem(key: string, value: string): Promise<void> {\r\n await this.backend.set(key, value);\r\n this.trackChange({\r\n key,\r\n operation: 'set',\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n async removeItem(key: string): Promise<void> {\r\n await this.backend.delete(key);\r\n this.trackChange({\r\n key,\r\n operation: 'delete',\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n getPendingSyncCount(): number {\r\n return this.pendingChanges.size;\r\n }\r\n\r\n async flushSync(): Promise<void> {\r\n if (!this.syncClient || this.pendingChanges.size === 0) {\r\n return;\r\n }\r\n if (this.syncTimer) {\r\n clearTimeout(this.syncTimer);\r\n this.syncTimer = null;\r\n }\r\n await this.performSync();\r\n }\r\n\r\n private trackChange(change: DashStorageChange): void {\r\n this.pendingChanges.set(change.key, change);\r\n this.enforcePendingLimit();\r\n this.scheduleSync();\r\n }\r\n\r\n private enforcePendingLimit(): void {\r\n if (this.pendingChanges.size <= this.maxPendingChanges) {\r\n return;\r\n }\r\n\r\n const sorted = Array.from(this.pendingChanges.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp\r\n );\r\n const overflow = this.pendingChanges.size - this.maxPendingChanges;\r\n for (let i = 0; i < overflow; i++) {\r\n const toDrop = sorted[i];\r\n if (toDrop) {\r\n this.pendingChanges.delete(toDrop.key);\r\n }\r\n }\r\n }\r\n\r\n private scheduleSync(): void {\r\n if (!this.syncClient) {\r\n return;\r\n }\r\n\r\n if (this.syncTimer) {\r\n clearTimeout(this.syncTimer);\r\n }\r\n\r\n this.syncTimer = setTimeout(() => {\r\n void this.performSync();\r\n }, this.syncDebounceMs);\r\n }\r\n\r\n private async performSync(): Promise<void> {\r\n if (!this.syncClient) {\r\n return;\r\n }\r\n\r\n if (this.syncInFlight) {\r\n this.syncPending = true;\r\n return;\r\n }\r\n\r\n const changes = Array.from(this.pendingChanges.values()).sort(\r\n (a, b) => a.timestamp - b.timestamp\r\n );\r\n if (changes.length === 0) {\r\n return;\r\n }\r\n\r\n this.pendingChanges.clear();\r\n this.syncInFlight = true;\r\n try {\r\n await this.syncClient.syncChanges(changes);\r\n } catch (error) {\r\n for (const change of changes) {\r\n const current = this.pendingChanges.get(change.key);\r\n if (!current || change.timestamp > current.timestamp) {\r\n this.pendingChanges.set(change.key, change);\r\n }\r\n }\r\n\r\n if (this.onSyncError) {\r\n const normalizedError =\r\n error instanceof Error ? error : new Error(String(error));\r\n this.onSyncError(normalizedError, changes);\r\n }\r\n } finally {\r\n this.syncInFlight = false;\r\n const rerun = this.syncPending || this.pendingChanges.size > 0;\r\n this.syncPending = false;\r\n if (rerun) {\r\n this.scheduleSync();\r\n }\r\n }\r\n }\r\n}\r\n","import type { StorageAdapter } from './types';\r\n\r\n/**\r\n * In-memory adapter for tests and ephemeral runtimes.\r\n */\r\nexport class InMemoryStorageAdapter implements StorageAdapter {\r\n private readonly store = new Map<string, string>();\r\n\r\n getItem(key: string): string | null {\r\n return this.store.get(key) ?? null;\r\n }\r\n\r\n setItem(key: string, value: string): void {\r\n this.store.set(key, value);\r\n }\r\n\r\n removeItem(key: string): void {\r\n this.store.delete(key);\r\n }\r\n\r\n clear(): void {\r\n this.store.clear();\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/persistence/DashStorageAdapter.ts","../../src/persistence/InMemoryStorageAdapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageAdapter } from './types';\n\nexport interface DashStorageBackend {\n get(key: string): Promise<string | null> | string | null;\n set(key: string, value: string): Promise<void> | void;\n delete(key: string): Promise<void> | void;\n}\n\nexport interface DashStorageChange {\n key: string;\n operation: 'set' | 'delete';\n value?: string;\n timestamp: number;\n}\n\nexport interface DashSyncClient {\n syncChanges(changes: DashStorageChange[]): Promise<void>;\n}\n\nexport type DashSyncUrgency = 'realtime' | 'deferred' | 'lazy';\n\nexport interface DashSyncRule {\n /** How quickly to sync changes for keys matching this rule/prefix */\n urgency: DashSyncUrgency;\n /** Debounce/Interval for deferred/lazy sync (e.g. '1s', '1m', '1h') */\n debounce?: string | number;\n /** Maximum number of pending changes before forcing a sync */\n maxBufferSize?: number;\n /** Whether to return pending values from memory (default: true) */\n readThrough?: boolean;\n}\n\nexport interface DashSyncRules {\n default?: DashSyncRule;\n /** Key prefix mapping to sync rules */\n prefixes?: Record<string, DashSyncRule>;\n}\n\nexport interface DashStorageAdapterHooks {\n onSync?: (changes: DashStorageChange[]) => void;\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\n onBufferOverflow?: (prefix: string, size: number, max: number) => void;\n onFlush?: (count: number) => void;\n}\n\nexport interface DashStorageAdapterOptions {\n syncClient?: DashSyncClient;\n rules?: DashSyncRules;\n hooks?: DashStorageAdapterHooks;\n /** @deprecated Use rules.default.debounce */\n syncDebounceMs?: number;\n /** @deprecated Use rules.default.maxBufferSize */\n maxPendingChanges?: number;\n /** @deprecated Use hooks.onSyncError */\n onSyncError?: (error: Error, changes: DashStorageChange[]) => void;\n}\n\nconst DEFAULT_RULE: DashSyncRule = {\n urgency: 'deferred',\n debounce: 50,\n maxBufferSize: 5000,\n readThrough: true,\n};\n\n/**\n * Storage adapter boundary for dash-backed persistence.\n *\n * Provides a \"Write Pool\" layer that buffers local-first writes and flushes\n * them to D1/R2 via a sync client based on declarative rules.\n */\nexport class DashStorageAdapter implements StorageAdapter {\n private readonly backend: DashStorageBackend;\n private readonly syncClient: DashSyncClient | null;\n private readonly rules: DashSyncRules;\n private readonly hooks: DashStorageAdapterHooks;\n private readonly pendingChanges = new Map<string, DashStorageChange>();\n private syncTimer: ReturnType<typeof setTimeout> | null = null;\n private syncInFlight = false;\n private syncPending = false;\n\n constructor(\n backend: DashStorageBackend,\n options: DashStorageAdapterOptions = {}\n ) {\n this.backend = backend;\n this.syncClient = options.syncClient ?? null;\n this.hooks = options.hooks ?? {};\n\n // Migration/Fallback for deprecated options\n const defaultRule: DashSyncRule = {\n ...DEFAULT_RULE,\n ...(options.rules?.default ?? {}),\n };\n if (options.syncDebounceMs !== undefined)\n defaultRule.debounce = options.syncDebounceMs;\n if (options.maxPendingChanges !== undefined)\n defaultRule.maxBufferSize = options.maxPendingChanges;\n if (options.onSyncError && !this.hooks.onSyncError)\n this.hooks.onSyncError = options.onSyncError;\n\n this.rules = {\n default: defaultRule,\n prefixes: options.rules?.prefixes ?? {},\n };\n }\n\n /**\n * Get an item, checking the write pool (pending changes) first for consistency.\n */\n async getItem(key: string): Promise<string | null> {\n const rule = this.getRuleForKey(key);\n\n // Read-through: check memory first if enabled\n if (rule.readThrough !== false) {\n const pending = this.pendingChanges.get(key);\n if (pending) {\n return pending.operation === 'delete' ? null : pending.value ?? null;\n }\n }\n\n return await this.backend.get(key);\n }\n\n async setItem(key: string, value: string): Promise<void> {\n await this.backend.set(key, value);\n this.trackChange({\n key,\n operation: 'set',\n value,\n timestamp: Date.now(),\n });\n }\n\n async removeItem(key: string): Promise<void> {\n await this.backend.delete(key);\n this.trackChange({\n key,\n operation: 'delete',\n timestamp: Date.now(),\n });\n }\n\n getPendingSyncCount(): number {\n return this.pendingChanges.size;\n }\n\n async flushSync(): Promise<void> {\n if (!this.syncClient || this.pendingChanges.size === 0) {\n return;\n }\n if (this.syncTimer) {\n clearTimeout(this.syncTimer);\n this.syncTimer = null;\n }\n await this.performSync();\n }\n\n private trackChange(change: DashStorageChange): void {\n this.pendingChanges.set(change.key, change);\n\n const rule = this.getRuleForKey(change.key);\n\n // Immediate flush for realtime\n if (rule.urgency === 'realtime') {\n void this.performSync();\n return;\n }\n\n // Check for buffer overflow\n const maxSize = rule.maxBufferSize ?? 5000;\n if (this.pendingChanges.size >= maxSize) {\n this.hooks.onBufferOverflow?.(\n this.getPrefixMatch(change.key) || 'default',\n this.pendingChanges.size,\n maxSize\n );\n void this.performSync();\n return;\n }\n\n this.scheduleSync(rule);\n }\n\n private getRuleForKey(key: string): DashSyncRule {\n const prefix = this.getPrefixMatch(key);\n return (\n (prefix ? this.rules.prefixes?.[prefix] : this.rules.default) ??\n this.rules.default!\n );\n }\n\n private getPrefixMatch(key: string): string | null {\n if (!this.rules.prefixes) {\n return null;\n }\n // Match longest prefix first\n const prefixes = Object.keys(this.rules.prefixes).sort(\n (a, b) => b.length - a.length\n );\n return prefixes.find((p) => key.startsWith(p)) ?? null;\n }\n\n private scheduleSync(rule: DashSyncRule): void {\n if (!this.syncClient || this.syncTimer) {\n return;\n }\n\n const debounceMs =\n typeof rule.debounce === 'string'\n ? this.parseInterval(rule.debounce)\n : rule.debounce ?? 50;\n\n this.syncTimer = setTimeout(() => {\n this.syncTimer = null;\n void this.performSync();\n }, debounceMs);\n }\n\n private async performSync(): Promise<void> {\n if (!this.syncClient) {\n return;\n }\n\n if (this.syncInFlight) {\n this.syncPending = true;\n return;\n }\n\n const changes = Array.from(this.pendingChanges.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n if (changes.length === 0) {\n return;\n }\n\n this.pendingChanges.clear();\n this.syncInFlight = true;\n try {\n await this.syncClient.syncChanges(changes);\n this.hooks.onSync?.(changes);\n this.hooks.onFlush?.(changes.length);\n } catch (error) {\n // Re-queue changes if they haven't been overwritten by newer local writes\n for (const change of changes) {\n const current = this.pendingChanges.get(change.key);\n if (!current || change.timestamp > current.timestamp) {\n this.pendingChanges.set(change.key, change);\n }\n }\n\n if (this.hooks.onSyncError) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n this.hooks.onSyncError(normalizedError, changes);\n }\n } finally {\n this.syncInFlight = false;\n const rerun = this.syncPending || this.pendingChanges.size > 0;\n this.syncPending = false;\n if (rerun) {\n // Use default rule for re-run or wait for next trackChange\n this.scheduleSync(this.rules.default!);\n }\n }\n }\n\n private parseInterval(input: string): number {\n const match = input.match(/^(\\d+)(ms|s|m|h|d)$/);\n if (!match) return 50;\n const value = parseInt(match[1], 10);\n const unit = match[2];\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n return 50;\n }\n }\n}\n","import type { StorageAdapter } from './types';\n\n/**\n * In-memory adapter for tests and ephemeral runtimes.\n */\nexport class InMemoryStorageAdapter implements StorageAdapter {\n private readonly store = new Map<string, string>();\n\n getItem(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n setItem(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n removeItem(key: string): void {\n this.store.delete(key);\n }\n\n async flushSync(): Promise<void> {\n /* noop */\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n"]}