nvent 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.mjs +433 -175
  3. package/dist/runtime/adapters/base/index.d.ts +6 -0
  4. package/dist/runtime/adapters/base/index.js +1 -0
  5. package/dist/runtime/adapters/base/store-validator.d.ts +48 -0
  6. package/dist/runtime/adapters/base/store-validator.js +147 -0
  7. package/dist/runtime/adapters/builtin/file-queue.d.ts +15 -1
  8. package/dist/runtime/adapters/builtin/file-queue.js +70 -6
  9. package/dist/runtime/adapters/builtin/file-store.d.ts +4 -18
  10. package/dist/runtime/adapters/builtin/file-store.js +90 -109
  11. package/dist/runtime/adapters/builtin/memory-queue.js +4 -0
  12. package/dist/runtime/adapters/builtin/memory-store.d.ts +42 -31
  13. package/dist/runtime/adapters/builtin/memory-store.js +253 -183
  14. package/dist/runtime/adapters/factory.d.ts +2 -2
  15. package/dist/runtime/adapters/factory.js +54 -20
  16. package/dist/runtime/adapters/interfaces/store.d.ts +177 -113
  17. package/dist/runtime/config/index.d.ts +2 -2
  18. package/dist/runtime/config/index.js +14 -6
  19. package/dist/runtime/config/types.d.ts +32 -2
  20. package/dist/runtime/events/eventBus.d.ts +1 -1
  21. package/dist/runtime/events/types.d.ts +31 -2
  22. package/dist/runtime/events/utils/scheduleTrigger.d.ts +8 -0
  23. package/dist/runtime/events/utils/scheduleTrigger.js +69 -0
  24. package/dist/runtime/events/utils/stallDetector.d.ts +44 -3
  25. package/dist/runtime/events/utils/stallDetector.js +288 -89
  26. package/dist/runtime/events/utils/triggerRuntime.d.ts +58 -0
  27. package/dist/runtime/events/utils/triggerRuntime.js +212 -0
  28. package/dist/runtime/events/wiring/flowWiring.d.ts +11 -5
  29. package/dist/runtime/events/wiring/flowWiring.js +620 -92
  30. package/dist/runtime/events/wiring/registry.d.ts +2 -2
  31. package/dist/runtime/events/wiring/registry.js +8 -6
  32. package/dist/runtime/events/wiring/streamWiring.d.ts +15 -11
  33. package/dist/runtime/events/wiring/streamWiring.js +88 -11
  34. package/dist/runtime/events/wiring/triggerWiring.d.ts +21 -0
  35. package/dist/runtime/events/wiring/triggerWiring.js +412 -0
  36. package/dist/runtime/{server → nitro}/plugins/00.adapters.js +8 -4
  37. package/dist/runtime/{server → nitro}/plugins/02.workers.js +21 -3
  38. package/dist/runtime/nitro/plugins/03.triggers.d.ts +12 -0
  39. package/dist/runtime/nitro/plugins/03.triggers.js +55 -0
  40. package/dist/runtime/nitro/routes/webhook.await.d.ts +23 -0
  41. package/dist/runtime/nitro/routes/webhook.await.js +90 -0
  42. package/dist/runtime/nitro/routes/webhook.trigger.d.ts +69 -0
  43. package/dist/runtime/nitro/routes/webhook.trigger.js +64 -0
  44. package/dist/runtime/{utils → nitro/utils}/adapters.d.ts +6 -6
  45. package/dist/runtime/nitro/utils/awaitPatterns/event.d.ts +15 -0
  46. package/dist/runtime/nitro/utils/awaitPatterns/event.js +120 -0
  47. package/dist/runtime/nitro/utils/awaitPatterns/index.d.ts +28 -0
  48. package/dist/runtime/nitro/utils/awaitPatterns/index.js +55 -0
  49. package/dist/runtime/nitro/utils/awaitPatterns/schedule.d.ts +16 -0
  50. package/dist/runtime/nitro/utils/awaitPatterns/schedule.js +78 -0
  51. package/dist/runtime/nitro/utils/awaitPatterns/time.d.ts +15 -0
  52. package/dist/runtime/nitro/utils/awaitPatterns/time.js +67 -0
  53. package/dist/runtime/nitro/utils/awaitPatterns/webhook.d.ts +15 -0
  54. package/dist/runtime/nitro/utils/awaitPatterns/webhook.js +120 -0
  55. package/dist/runtime/{utils → nitro/utils}/defineFunction.d.ts +2 -2
  56. package/dist/runtime/{utils → nitro/utils}/defineFunction.js +3 -3
  57. package/dist/runtime/{utils → nitro/utils}/defineFunctionConfig.d.ts +156 -0
  58. package/dist/runtime/{utils → nitro/utils}/defineFunctionConfig.js +1 -0
  59. package/dist/runtime/nitro/utils/defineHooks.d.ts +41 -0
  60. package/dist/runtime/nitro/utils/defineHooks.js +6 -0
  61. package/dist/runtime/{utils → nitro/utils}/registerAdapter.d.ts +3 -3
  62. package/dist/runtime/{utils → nitro/utils}/registerAdapter.js +1 -1
  63. package/dist/runtime/nitro/utils/useAwait.d.ts +71 -0
  64. package/dist/runtime/nitro/utils/useAwait.js +139 -0
  65. package/dist/runtime/{utils → nitro/utils}/useEventManager.d.ts +2 -2
  66. package/dist/runtime/{utils → nitro/utils}/useEventManager.js +1 -1
  67. package/dist/runtime/nitro/utils/useFlow.d.ts +68 -0
  68. package/dist/runtime/nitro/utils/useFlow.js +226 -0
  69. package/dist/runtime/nitro/utils/useHookRegistry.d.ts +34 -0
  70. package/dist/runtime/nitro/utils/useHookRegistry.js +25 -0
  71. package/dist/runtime/nitro/utils/useRunContext.d.ts +6 -0
  72. package/dist/runtime/nitro/utils/useRunContext.js +102 -0
  73. package/dist/runtime/nitro/utils/useStreamTopics.d.ts +83 -0
  74. package/dist/runtime/nitro/utils/useStreamTopics.js +94 -0
  75. package/dist/runtime/nitro/utils/useTrigger.d.ts +150 -0
  76. package/dist/runtime/nitro/utils/useTrigger.js +320 -0
  77. package/dist/runtime/scheduler/index.d.ts +33 -0
  78. package/dist/runtime/scheduler/index.js +38 -0
  79. package/dist/runtime/scheduler/scheduler.d.ts +113 -0
  80. package/dist/runtime/scheduler/scheduler.js +623 -0
  81. package/dist/runtime/scheduler/types.d.ts +116 -0
  82. package/dist/runtime/scheduler/types.js +0 -0
  83. package/dist/runtime/worker/node/runner.d.ts +12 -2
  84. package/dist/runtime/worker/node/runner.js +141 -37
  85. package/package.json +6 -6
  86. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +0 -10
  87. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -55
  88. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +0 -2
  89. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +0 -21
  90. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -17
  91. package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -64
  92. package/dist/runtime/server/api/_flows/[name]/schedule.post.d.ts +0 -2
  93. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +0 -66
  94. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.d.ts +0 -2
  95. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +0 -47
  96. package/dist/runtime/server/api/_flows/[name]/schedules.get.d.ts +0 -2
  97. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +0 -50
  98. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
  99. package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
  100. package/dist/runtime/server/api/_flows/index.get.d.ts +0 -6
  101. package/dist/runtime/server/api/_flows/index.get.js +0 -5
  102. package/dist/runtime/server/api/_flows/ws.d.ts +0 -60
  103. package/dist/runtime/server/api/_flows/ws.js +0 -209
  104. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
  105. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -14
  106. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
  107. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -27
  108. package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
  109. package/dist/runtime/server/api/_queues/index.get.js +0 -106
  110. package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
  111. package/dist/runtime/server/api/_queues/ws.js +0 -215
  112. package/dist/runtime/utils/useFlowEngine.d.ts +0 -19
  113. package/dist/runtime/utils/useFlowEngine.js +0 -108
  114. package/dist/runtime/utils/useStreamTopics.d.ts +0 -72
  115. package/dist/runtime/utils/useStreamTopics.js +0 -47
  116. /package/dist/runtime/{server → nitro}/plugins/00.adapters.d.ts +0 -0
  117. /package/dist/runtime/{server → nitro}/plugins/01.ws-lifecycle.d.ts +0 -0
  118. /package/dist/runtime/{server → nitro}/plugins/01.ws-lifecycle.js +0 -0
  119. /package/dist/runtime/{server → nitro}/plugins/02.workers.d.ts +0 -0
  120. /package/dist/runtime/{utils → nitro/utils}/adapters.js +0 -0
  121. /package/dist/runtime/{utils → nitro/utils}/useNventLogger.d.ts +0 -0
  122. /package/dist/runtime/{utils → nitro/utils}/useNventLogger.js +0 -0
  123. /package/dist/runtime/{utils → nitro/utils}/wsPeerManager.d.ts +0 -0
  124. /package/dist/runtime/{utils → nitro/utils}/wsPeerManager.js +0 -0
@@ -4,30 +4,28 @@
4
4
  * In-memory storage implementation for development
5
5
  * Three-tier storage:
6
6
  * 1. Event Stream - Append-only event log
7
- * 2. Document Store - Generic document storage
7
+ * 2. Sorted Index - Time-ordered metadata storage
8
8
  * 3. Key-Value Store - Fast lookups
9
9
  *
10
10
  * All data is lost on restart (ephemeral)
11
11
  */
12
- import type { StoreAdapter, EventRecord, EventReadOptions, EventSubscription, ListOptions } from '../interfaces/store.js';
12
+ import type { StoreAdapter, EventRecord, EventReadOptions, EventSubscription } from '../interfaces/store.js';
13
13
  export declare class MemoryStoreAdapter implements StoreAdapter {
14
14
  private eventStreams;
15
15
  private eventSubscriptions;
16
16
  private subscriptionCounter;
17
- private documents;
18
17
  private kvStore;
18
+ private sortedIndices;
19
+ protected validator: import("../base/index.js").StoreValidator;
20
+ private indexLocks;
19
21
  close(): Promise<void>;
20
- append(subject: string, event: Omit<EventRecord, 'id' | 'ts'>): Promise<EventRecord>;
21
- read(subject: string, opts?: EventReadOptions): Promise<EventRecord[]>;
22
- subscribe(subject: string, onEvent: (event: EventRecord) => void): Promise<EventSubscription>;
22
+ stream: {
23
+ append: (subject: string, event: Omit<EventRecord, "id" | "ts">) => Promise<EventRecord>;
24
+ read: (subject: string, opts?: EventReadOptions) => Promise<EventRecord[]>;
25
+ subscribe: (subject: string, onEvent: (event: EventRecord) => void) => Promise<EventSubscription>;
26
+ delete: (subject: string) => Promise<boolean>;
27
+ };
23
28
  private notifySubscribers;
24
- save(collection: string, id: string, doc: Record<string, any>): Promise<void>;
25
- get(collection: string, id: string): Promise<Record<string, any> | null>;
26
- list(collection: string, opts?: ListOptions): Promise<Array<{
27
- id: string;
28
- doc: any;
29
- }>>;
30
- delete(collection: string, id: string): Promise<void>;
31
29
  kv: {
32
30
  get: <T = any>(key: string) => Promise<T | null>;
33
31
  set: <T = any>(key: string, value: T, _ttl?: number) => Promise<void>;
@@ -35,23 +33,36 @@ export declare class MemoryStoreAdapter implements StoreAdapter {
35
33
  clear: (pattern: string) => Promise<number>;
36
34
  increment: (key: string, by?: number) => Promise<number>;
37
35
  };
38
- private sortedIndices;
39
- indexAdd(key: string, id: string, score: number, metadata?: Record<string, any>): Promise<void>;
40
- indexGet(key: string, id: string): Promise<{
41
- id: string;
42
- score: number;
43
- metadata?: any;
44
- } | null>;
45
- indexRead(key: string, opts?: {
46
- offset?: number;
47
- limit?: number;
48
- }): Promise<Array<{
49
- id: string;
50
- score: number;
51
- metadata?: any;
52
- }>>;
53
- indexUpdate(key: string, id: string, metadata: Record<string, any>): Promise<boolean>;
54
- indexUpdateWithRetry(key: string, id: string, metadata: Record<string, any>, maxRetries?: number): Promise<void>;
55
- indexIncrement(key: string, id: string, field: string, increment?: number): Promise<number>;
36
+ index: {
37
+ add: (key: string, id: string, score: number, metadata?: Record<string, any>) => Promise<void>;
38
+ get: (key: string, id: string) => Promise<{
39
+ id: string;
40
+ score: number;
41
+ metadata?: any;
42
+ } | null>;
43
+ read: (key: string, opts?: {
44
+ offset?: number;
45
+ limit?: number;
46
+ }) => Promise<Array<{
47
+ id: string;
48
+ score: number;
49
+ metadata?: any;
50
+ }>>;
51
+ update: (key: string, id: string, metadata: Record<string, any>) => Promise<boolean>;
52
+ updateWithRetry: (key: string, id: string, metadata: Record<string, any>, maxRetries?: number) => Promise<void>;
53
+ increment: (key: string, id: string, field: string, increment?: number) => Promise<number>;
54
+ delete: (key: string, id: string) => Promise<boolean>;
55
+ };
56
+ /**
57
+ * Acquire a lock for atomic index operations
58
+ * Protected to allow access from FileStoreAdapter
59
+ */
60
+ protected acquireIndexLock(key: string): Promise<() => void>;
61
+ /**
62
+ * Convert dot notation keys to nested objects
63
+ * e.g., { 'stats.totalFires': 5 } -> { stats: { totalFires: 5 } }
64
+ * null values are preserved for deletion
65
+ */
66
+ private expandDotNotation;
56
67
  private generateId;
57
68
  }
@@ -1,88 +1,104 @@
1
+ import { defu } from "defu";
2
+ import { createStoreValidator } from "../base/store-validator.js";
1
3
  export class MemoryStoreAdapter {
2
4
  // Event Stream storage: subject -> events
3
5
  eventStreams = /* @__PURE__ */ new Map();
4
6
  eventSubscriptions = /* @__PURE__ */ new Map();
5
7
  subscriptionCounter = 0;
6
- // Document Store storage: collection -> id -> document
7
- documents = /* @__PURE__ */ new Map();
8
8
  // Key-Value Store storage: key -> value
9
9
  kvStore = /* @__PURE__ */ new Map();
10
+ // Sorted index storage: key -> sorted array of {id, score, metadata}
11
+ sortedIndices = /* @__PURE__ */ new Map();
12
+ // Validator for update operations
13
+ validator = createStoreValidator("MemoryStoreAdapter");
14
+ // Lock mechanism for atomic index operations
15
+ indexLocks = /* @__PURE__ */ new Map();
10
16
  async close() {
11
17
  this.eventStreams.clear();
12
18
  this.eventSubscriptions.clear();
13
- this.documents.clear();
14
19
  this.kvStore.clear();
20
+ this.sortedIndices.clear();
15
21
  }
16
22
  // ============================================================
17
23
  // Event Stream
18
24
  // ============================================================
19
- async append(subject, event) {
20
- const eventRecord = {
21
- id: this.generateId(),
22
- ts: Date.now(),
23
- ...event
24
- };
25
- if (!this.eventStreams.has(subject)) {
26
- this.eventStreams.set(subject, []);
27
- }
28
- const stream = this.eventStreams.get(subject);
29
- stream.push(eventRecord);
30
- this.notifySubscribers(subject, eventRecord);
31
- return eventRecord;
32
- }
33
- async read(subject, opts) {
34
- const stream = this.eventStreams.get(subject) || [];
35
- let events = [...stream];
36
- if (opts?.types && opts.types.length > 0) {
37
- events = events.filter((e) => opts.types.includes(e.type));
38
- }
39
- if (opts?.after) {
40
- const afterIndex = events.findIndex((e) => e.id === opts.after);
41
- if (afterIndex >= 0) {
42
- events = events.slice(afterIndex + 1);
25
+ stream = {
26
+ append: async (subject, event) => {
27
+ const eventRecord = {
28
+ id: this.generateId(),
29
+ ts: Date.now(),
30
+ ...event
31
+ };
32
+ if (!this.eventStreams.has(subject)) {
33
+ this.eventStreams.set(subject, []);
43
34
  }
44
- }
45
- if (opts?.before) {
46
- const beforeIndex = events.findIndex((e) => e.id === opts.before);
47
- if (beforeIndex >= 0) {
48
- events = events.slice(0, beforeIndex);
35
+ const stream = this.eventStreams.get(subject);
36
+ stream.push(eventRecord);
37
+ this.notifySubscribers(subject, eventRecord);
38
+ return eventRecord;
39
+ },
40
+ read: async (subject, opts) => {
41
+ const stream = this.eventStreams.get(subject) || [];
42
+ let events = [...stream];
43
+ if (opts?.types && opts.types.length > 0) {
44
+ events = events.filter((e) => opts.types.includes(e.type));
49
45
  }
50
- }
51
- if (opts?.from) {
52
- events = events.filter((e) => e.ts >= opts.from);
53
- }
54
- if (opts?.to) {
55
- events = events.filter((e) => e.ts <= opts.to);
56
- }
57
- if (opts?.order === "desc") {
58
- events.reverse();
59
- }
60
- if (opts?.limit) {
61
- events = events.slice(0, opts.limit);
62
- }
63
- return events;
64
- }
65
- async subscribe(subject, onEvent) {
66
- const subscriptionId = `sub-${++this.subscriptionCounter}`;
67
- if (!this.eventSubscriptions.has(subject)) {
68
- this.eventSubscriptions.set(subject, /* @__PURE__ */ new Map());
69
- }
70
- const subjectSubs = this.eventSubscriptions.get(subject);
71
- subjectSubs.set(subscriptionId, onEvent);
72
- return {
73
- id: subscriptionId,
74
- subject,
75
- unsubscribe: async () => {
76
- const subs = this.eventSubscriptions.get(subject);
77
- if (subs) {
78
- subs.delete(subscriptionId);
79
- if (subs.size === 0) {
80
- this.eventSubscriptions.delete(subject);
46
+ if (opts?.after) {
47
+ const afterIndex = events.findIndex((e) => e.id === opts.after);
48
+ if (afterIndex >= 0) {
49
+ events = events.slice(afterIndex + 1);
50
+ }
51
+ }
52
+ if (opts?.before) {
53
+ const beforeIndex = events.findIndex((e) => e.id === opts.before);
54
+ if (beforeIndex >= 0) {
55
+ events = events.slice(0, beforeIndex);
56
+ }
57
+ }
58
+ if (opts?.from) {
59
+ events = events.filter((e) => e.ts >= opts.from);
60
+ }
61
+ if (opts?.to) {
62
+ events = events.filter((e) => e.ts <= opts.to);
63
+ }
64
+ if (opts?.order === "desc") {
65
+ events.reverse();
66
+ }
67
+ if (opts?.limit) {
68
+ events = events.slice(0, opts.limit);
69
+ }
70
+ return events;
71
+ },
72
+ subscribe: async (subject, onEvent) => {
73
+ const subscriptionId = `sub-${++this.subscriptionCounter}`;
74
+ if (!this.eventSubscriptions.has(subject)) {
75
+ this.eventSubscriptions.set(subject, /* @__PURE__ */ new Map());
76
+ }
77
+ const subjectSubs = this.eventSubscriptions.get(subject);
78
+ subjectSubs.set(subscriptionId, onEvent);
79
+ return {
80
+ id: subscriptionId,
81
+ subject,
82
+ unsubscribe: async () => {
83
+ const subs = this.eventSubscriptions.get(subject);
84
+ if (subs) {
85
+ subs.delete(subscriptionId);
86
+ if (subs.size === 0) {
87
+ this.eventSubscriptions.delete(subject);
88
+ }
81
89
  }
82
90
  }
91
+ };
92
+ },
93
+ delete: async (subject) => {
94
+ const existed = this.eventStreams.has(subject);
95
+ if (existed) {
96
+ this.eventStreams.delete(subject);
97
+ this.eventSubscriptions.delete(subject);
83
98
  }
84
- };
85
- }
99
+ return existed;
100
+ }
101
+ };
86
102
  notifySubscribers(subject, event) {
87
103
  const subs = this.eventSubscriptions.get(subject);
88
104
  if (subs) {
@@ -96,62 +112,6 @@ export class MemoryStoreAdapter {
96
112
  }
97
113
  }
98
114
  // ============================================================
99
- // Document Store
100
- // ============================================================
101
- async save(collection, id, doc) {
102
- if (!this.documents.has(collection)) {
103
- this.documents.set(collection, /* @__PURE__ */ new Map());
104
- }
105
- const collectionDocs = this.documents.get(collection);
106
- collectionDocs.set(id, doc);
107
- }
108
- async get(collection, id) {
109
- const collectionDocs = this.documents.get(collection);
110
- if (!collectionDocs) {
111
- return null;
112
- }
113
- return collectionDocs.get(id) || null;
114
- }
115
- async list(collection, opts) {
116
- const collectionDocs = this.documents.get(collection);
117
- if (!collectionDocs) {
118
- return [];
119
- }
120
- let results = Array.from(collectionDocs.entries()).map(([id, doc]) => ({ id, doc }));
121
- if (opts?.filter) {
122
- results = results.filter((item) => {
123
- return Object.entries(opts.filter).every(([key, value]) => {
124
- return item.doc[key] === value;
125
- });
126
- });
127
- }
128
- if (opts?.sortBy) {
129
- results.sort((a, b) => {
130
- const aVal = a.doc[opts.sortBy];
131
- const bVal = b.doc[opts.sortBy];
132
- if (aVal < bVal) return opts.order === "desc" ? 1 : -1;
133
- if (aVal > bVal) return opts.order === "desc" ? -1 : 1;
134
- return 0;
135
- });
136
- }
137
- if (opts?.offset) {
138
- results = results.slice(opts.offset);
139
- }
140
- if (opts?.limit) {
141
- results = results.slice(0, opts.limit);
142
- }
143
- return results;
144
- }
145
- async delete(collection, id) {
146
- const collectionDocs = this.documents.get(collection);
147
- if (collectionDocs) {
148
- collectionDocs.delete(id);
149
- if (collectionDocs.size === 0) {
150
- this.documents.delete(collection);
151
- }
152
- }
153
- }
154
- // ============================================================
155
115
  // Key-Value Store
156
116
  // ============================================================
157
117
  kv = {
@@ -184,75 +144,185 @@ export class MemoryStoreAdapter {
184
144
  }
185
145
  };
186
146
  // ============================================================
187
- // Sorted Index (optional, for time-ordered listings)
147
+ // Sorted Index (for time-ordered listings)
188
148
  // ============================================================
189
- // Sorted index storage: key -> sorted array of {id, score, metadata}
190
- sortedIndices = /* @__PURE__ */ new Map();
191
- async indexAdd(key, id, score, metadata) {
192
- if (!this.sortedIndices.has(key)) {
193
- this.sortedIndices.set(key, []);
149
+ index = {
150
+ add: async (key, id, score, metadata) => {
151
+ if (!this.sortedIndices.has(key)) {
152
+ this.sortedIndices.set(key, []);
153
+ }
154
+ const index = this.sortedIndices.get(key);
155
+ const existingIndex = index.findIndex((entry2) => entry2.id === id);
156
+ const expandedMetadata = metadata ? this.expandDotNotation(metadata) : void 0;
157
+ const entry = {
158
+ id,
159
+ score,
160
+ metadata: expandedMetadata ? { version: 0, ...expandedMetadata } : void 0
161
+ };
162
+ if (existingIndex >= 0) {
163
+ index[existingIndex] = entry;
164
+ } else {
165
+ index.push(entry);
166
+ }
167
+ index.sort((a, b) => b.score - a.score);
168
+ },
169
+ get: async (key, id) => {
170
+ const index = this.sortedIndices.get(key);
171
+ if (!index) return null;
172
+ const entry = index.find((e) => e.id === id);
173
+ return entry ? { ...entry } : null;
174
+ },
175
+ read: async (key, opts) => {
176
+ const index = this.sortedIndices.get(key) || [];
177
+ const offset = opts?.offset || 0;
178
+ const limit = opts?.limit || 50;
179
+ return index.slice(offset, offset + limit).map((e) => ({ ...e }));
180
+ },
181
+ update: async (key, id, metadata) => {
182
+ this.validator.validateUpdatePayload(metadata, "index.update");
183
+ const lockKey = `${key}:${id}`;
184
+ const release = await this.acquireIndexLock(lockKey);
185
+ try {
186
+ const index = this.sortedIndices.get(key);
187
+ if (!index) return false;
188
+ const entry = index.find((e) => e.id === id);
189
+ if (!entry || !entry.metadata) return false;
190
+ const currentVersion = entry.metadata.version || 0;
191
+ const updates = this.expandDotNotation(metadata);
192
+ const deleteMarkers = updates.__deleteMarkers;
193
+ delete updates.__deleteMarkers;
194
+ entry.metadata = defu(updates, entry.metadata);
195
+ if (deleteMarkers) {
196
+ for (const { path } of deleteMarkers) {
197
+ let current = entry.metadata;
198
+ for (let i = 0; i < path.length - 1; i++) {
199
+ if (!current[path[i]]) break;
200
+ current = current[path[i]];
201
+ }
202
+ if (current) {
203
+ const lastKey = path[path.length - 1];
204
+ Reflect.deleteProperty(current, lastKey);
205
+ }
206
+ }
207
+ }
208
+ entry.metadata.version = currentVersion + 1;
209
+ return true;
210
+ } finally {
211
+ release();
212
+ }
213
+ },
214
+ updateWithRetry: async (key, id, metadata, maxRetries = 3) => {
215
+ this.validator.validateUpdatePayload(metadata, "index.updateWithRetry");
216
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
217
+ const success = await this.index.update(key, id, metadata);
218
+ if (success) return;
219
+ await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, attempt)));
220
+ }
221
+ throw new Error(`Failed to update index after ${maxRetries} retries`);
222
+ },
223
+ increment: async (key, id, field, increment = 1) => {
224
+ const lockKey = `${key}:${id}`;
225
+ const release = await this.acquireIndexLock(lockKey);
226
+ try {
227
+ const index = this.sortedIndices.get(key);
228
+ if (!index) throw new Error(`Index not found: ${key}`);
229
+ const entry = index.find((e) => e.id === id);
230
+ if (!entry) throw new Error(`Entry not found: ${id} in index ${key}`);
231
+ if (!entry.metadata) {
232
+ entry.metadata = { version: 0 };
233
+ }
234
+ let currentValue;
235
+ let newValue;
236
+ if (field.includes(".")) {
237
+ const keys = field.split(".");
238
+ let current = entry.metadata;
239
+ for (let i = 0; i < keys.length - 1; i++) {
240
+ const k = keys[i];
241
+ if (!current[k] || typeof current[k] !== "object") {
242
+ current[k] = {};
243
+ }
244
+ current = current[k];
245
+ }
246
+ const lastKey = keys[keys.length - 1];
247
+ currentValue = current[lastKey] || 0;
248
+ newValue = (typeof currentValue === "number" ? currentValue : 0) + increment;
249
+ current[lastKey] = newValue;
250
+ } else {
251
+ currentValue = entry.metadata[field] || 0;
252
+ newValue = (typeof currentValue === "number" ? currentValue : 0) + increment;
253
+ entry.metadata[field] = newValue;
254
+ }
255
+ entry.metadata.version = (entry.metadata.version || 0) + 1;
256
+ return newValue;
257
+ } finally {
258
+ release();
259
+ }
260
+ },
261
+ delete: async (key, id) => {
262
+ const index = this.sortedIndices.get(key);
263
+ if (!index) return false;
264
+ const initialLength = index.length;
265
+ const filtered = index.filter((e) => e.id !== id);
266
+ if (filtered.length === initialLength) {
267
+ return false;
268
+ }
269
+ this.sortedIndices.set(key, filtered);
270
+ return true;
194
271
  }
195
- const index = this.sortedIndices.get(key);
196
- const existingIndex = index.findIndex((entry2) => entry2.id === id);
197
- const entry = {
198
- id,
199
- score,
200
- metadata: metadata ? { version: 0, ...metadata } : void 0
201
- };
202
- if (existingIndex >= 0) {
203
- index[existingIndex] = entry;
204
- } else {
205
- index.push(entry);
272
+ };
273
+ /**
274
+ * Acquire a lock for atomic index operations
275
+ * Protected to allow access from FileStoreAdapter
276
+ */
277
+ async acquireIndexLock(key) {
278
+ while (this.indexLocks.has(key)) {
279
+ await this.indexLocks.get(key);
206
280
  }
207
- index.sort((a, b) => b.score - a.score);
208
- }
209
- async indexGet(key, id) {
210
- const index = this.sortedIndices.get(key);
211
- if (!index) return null;
212
- const entry = index.find((e) => e.id === id);
213
- return entry ? { ...entry } : null;
214
- }
215
- async indexRead(key, opts) {
216
- const index = this.sortedIndices.get(key) || [];
217
- const offset = opts?.offset || 0;
218
- const limit = opts?.limit || 50;
219
- return index.slice(offset, offset + limit).map((e) => ({ ...e }));
220
- }
221
- async indexUpdate(key, id, metadata) {
222
- const index = this.sortedIndices.get(key);
223
- if (!index) return false;
224
- const entry = index.find((e) => e.id === id);
225
- if (!entry || !entry.metadata) return false;
226
- const currentVersion = entry.metadata.version || 0;
227
- const expectedVersion = currentVersion;
228
- entry.metadata = {
229
- ...entry.metadata,
230
- ...metadata,
231
- version: expectedVersion + 1
281
+ let releaseLock;
282
+ const lockPromise = new Promise((resolve) => {
283
+ releaseLock = resolve;
284
+ });
285
+ this.indexLocks.set(key, lockPromise);
286
+ return () => {
287
+ this.indexLocks.delete(key);
288
+ releaseLock();
232
289
  };
233
- return true;
234
290
  }
235
- async indexUpdateWithRetry(key, id, metadata, maxRetries = 3) {
236
- for (let attempt = 0; attempt < maxRetries; attempt++) {
237
- const success = await this.indexUpdate(key, id, metadata);
238
- if (success) return;
239
- await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, attempt)));
291
+ /**
292
+ * Convert dot notation keys to nested objects
293
+ * e.g., { 'stats.totalFires': 5 } -> { stats: { totalFires: 5 } }
294
+ * null values are preserved for deletion
295
+ */
296
+ expandDotNotation(obj) {
297
+ const result = {};
298
+ const deleteMarkers = [];
299
+ for (const [key, value] of Object.entries(obj)) {
300
+ if (key === "version") {
301
+ continue;
302
+ }
303
+ if (key.includes(".")) {
304
+ const keys = key.split(".");
305
+ if (value === null || value === void 0) {
306
+ deleteMarkers.push({ path: keys, delete: true });
307
+ continue;
308
+ }
309
+ let current = result;
310
+ for (let i = 0; i < keys.length - 1; i++) {
311
+ const k = keys[i];
312
+ if (!current[k]) {
313
+ current[k] = {};
314
+ }
315
+ current = current[k];
316
+ }
317
+ current[keys[keys.length - 1]] = value;
318
+ } else {
319
+ result[key] = value;
320
+ }
240
321
  }
241
- throw new Error(`Failed to update index after ${maxRetries} retries`);
242
- }
243
- async indexIncrement(key, id, field, increment = 1) {
244
- const index = this.sortedIndices.get(key);
245
- if (!index) throw new Error(`Index not found: ${key}`);
246
- const entry = index.find((e) => e.id === id);
247
- if (!entry) throw new Error(`Entry not found: ${id} in index ${key}`);
248
- if (!entry.metadata) {
249
- entry.metadata = { version: 0 };
322
+ if (deleteMarkers.length > 0) {
323
+ result.__deleteMarkers = deleteMarkers;
250
324
  }
251
- const currentValue = entry.metadata[field] || 0;
252
- const newValue = (typeof currentValue === "number" ? currentValue : 0) + increment;
253
- entry.metadata[field] = newValue;
254
- entry.metadata.version = (entry.metadata.version || 0) + 1;
255
- return newValue;
325
+ return result;
256
326
  }
257
327
  // ============================================================
258
328
  // Helpers
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Adapter Factory (v0.4.1)
2
+ * Adapter Factory
3
3
  *
4
4
  * Creates adapters independently without dependencies
5
5
  * StoreAdapter is pure storage - streaming handled by wiring layer
@@ -17,7 +17,7 @@ export interface AdapterSet {
17
17
  store: StoreAdapter;
18
18
  }
19
19
  /**
20
- * Create a complete set of adapters (v0.4.1)
20
+ * Create a complete set of adapters
21
21
  * All adapters are independent - wiring layer handles coordination
22
22
  */
23
23
  export declare function createAdapters(config: {