@rotorsoft/act 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +13 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/test/index.d.ts +2 -0
- package/dist/@types/test/index.d.ts.map +1 -0
- package/dist/@types/test/sandbox.d.ts +113 -0
- package/dist/@types/test/sandbox.d.ts.map +1 -0
- package/dist/@types/types/action.d.ts +2 -2
- package/dist/@types/types/ports.d.ts +13 -8
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/chunk-TP2OZWHP.js +877 -0
- package/dist/chunk-TP2OZWHP.js.map +1 -0
- package/dist/dist-INXH4GUE.js +546 -0
- package/dist/dist-INXH4GUE.js.map +1 -0
- package/dist/index.cjs +30 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +74 -882
- package/dist/index.js.map +1 -1
- package/dist/magic-string.es-ZGO77SPS.js +1309 -0
- package/dist/magic-string.es-ZGO77SPS.js.map +1 -0
- package/dist/test/index.cjs +4833 -0
- package/dist/test/index.cjs.map +1 -0
- package/dist/test/index.js +3912 -0
- package/dist/test/index.js.map +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +9 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConsoleLogger,
|
|
3
|
+
ExitCodes,
|
|
4
|
+
InMemoryCache,
|
|
5
|
+
InMemoryStore,
|
|
6
|
+
LruSet,
|
|
7
|
+
PackageSchema,
|
|
8
|
+
SNAP_EVENT,
|
|
9
|
+
TOMBSTONE_EVENT,
|
|
10
|
+
cache,
|
|
11
|
+
config,
|
|
12
|
+
dispose,
|
|
13
|
+
disposeAndExit,
|
|
14
|
+
extend,
|
|
15
|
+
log,
|
|
16
|
+
port,
|
|
17
|
+
scoped,
|
|
18
|
+
sleep,
|
|
19
|
+
store,
|
|
20
|
+
validate
|
|
21
|
+
} from "./chunk-TP2OZWHP.js";
|
|
1
22
|
import {
|
|
2
23
|
ActorSchema,
|
|
3
24
|
CausationEventSchema,
|
|
@@ -14,854 +35,7 @@ import {
|
|
|
14
35
|
ValidationError,
|
|
15
36
|
ZodEmpty
|
|
16
37
|
} from "./chunk-AGWZY6YT.js";
|
|
17
|
-
|
|
18
|
-
// src/ports.ts
|
|
19
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
20
|
-
|
|
21
|
-
// src/adapters/console-logger.ts
|
|
22
|
-
var LEVEL_VALUES = {
|
|
23
|
-
fatal: 60,
|
|
24
|
-
error: 50,
|
|
25
|
-
warn: 40,
|
|
26
|
-
info: 30,
|
|
27
|
-
debug: 20,
|
|
28
|
-
trace: 10
|
|
29
|
-
};
|
|
30
|
-
var LEVEL_COLORS = {
|
|
31
|
-
fatal: "\x1B[41m\x1B[37m",
|
|
32
|
-
// white on red bg
|
|
33
|
-
error: "\x1B[31m",
|
|
34
|
-
// red
|
|
35
|
-
warn: "\x1B[33m",
|
|
36
|
-
// yellow
|
|
37
|
-
info: "\x1B[32m",
|
|
38
|
-
// green
|
|
39
|
-
debug: "\x1B[36m",
|
|
40
|
-
// cyan
|
|
41
|
-
trace: "\x1B[90m"
|
|
42
|
-
// gray
|
|
43
|
-
};
|
|
44
|
-
var RESET = "\x1B[0m";
|
|
45
|
-
var noop = () => {
|
|
46
|
-
};
|
|
47
|
-
var ConsoleLogger = class _ConsoleLogger {
|
|
48
|
-
level;
|
|
49
|
-
_pretty;
|
|
50
|
-
fatal;
|
|
51
|
-
error;
|
|
52
|
-
warn;
|
|
53
|
-
info;
|
|
54
|
-
debug;
|
|
55
|
-
trace;
|
|
56
|
-
constructor(options = {}) {
|
|
57
|
-
const {
|
|
58
|
-
level = "info",
|
|
59
|
-
pretty = process.env.NODE_ENV !== "production",
|
|
60
|
-
bindings
|
|
61
|
-
} = options;
|
|
62
|
-
this._pretty = pretty;
|
|
63
|
-
this.level = level;
|
|
64
|
-
const threshold = LEVEL_VALUES[level] ?? 30;
|
|
65
|
-
const write = pretty ? this._prettyWrite.bind(this, bindings) : this._jsonWrite.bind(this, bindings);
|
|
66
|
-
this.fatal = write.bind(this, "fatal", 60);
|
|
67
|
-
this.error = threshold <= 50 ? write.bind(this, "error", 50) : noop;
|
|
68
|
-
this.warn = threshold <= 40 ? write.bind(this, "warn", 40) : noop;
|
|
69
|
-
this.info = threshold <= 30 ? write.bind(this, "info", 30) : noop;
|
|
70
|
-
this.debug = threshold <= 20 ? write.bind(this, "debug", 20) : noop;
|
|
71
|
-
this.trace = threshold <= 10 ? write.bind(this, "trace", 10) : noop;
|
|
72
|
-
}
|
|
73
|
-
/** No-op — `console.log` has no resources to release. */
|
|
74
|
-
async dispose() {
|
|
75
|
-
}
|
|
76
|
-
/** @inheritDoc */
|
|
77
|
-
child(bindings) {
|
|
78
|
-
return new _ConsoleLogger({
|
|
79
|
-
level: this.level,
|
|
80
|
-
pretty: this._pretty,
|
|
81
|
-
bindings
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
_jsonWrite(bindings, level, _num, objOrMsg, msg) {
|
|
85
|
-
let obj;
|
|
86
|
-
let message;
|
|
87
|
-
if (typeof objOrMsg === "string") {
|
|
88
|
-
message = objOrMsg;
|
|
89
|
-
obj = {};
|
|
90
|
-
} else if (objOrMsg !== null && typeof objOrMsg === "object") {
|
|
91
|
-
message = msg;
|
|
92
|
-
obj = { ...objOrMsg };
|
|
93
|
-
} else {
|
|
94
|
-
message = msg;
|
|
95
|
-
obj = { value: objOrMsg };
|
|
96
|
-
}
|
|
97
|
-
const entry = Object.assign({ level, time: Date.now() }, bindings, obj);
|
|
98
|
-
if (message) entry.msg = message;
|
|
99
|
-
let line;
|
|
100
|
-
try {
|
|
101
|
-
line = JSON.stringify(entry);
|
|
102
|
-
} catch {
|
|
103
|
-
line = JSON.stringify({
|
|
104
|
-
level,
|
|
105
|
-
time: entry.time,
|
|
106
|
-
msg: message ?? "[unserializable]",
|
|
107
|
-
unserializable: true
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
process.stdout.write(line + "\n");
|
|
111
|
-
}
|
|
112
|
-
_prettyWrite(bindings, level, _num, objOrMsg, msg) {
|
|
113
|
-
const color = LEVEL_COLORS[level];
|
|
114
|
-
const tag = `${color}${level.toUpperCase().padEnd(5)}${RESET}`;
|
|
115
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
116
|
-
let message;
|
|
117
|
-
let data;
|
|
118
|
-
if (typeof objOrMsg === "string") {
|
|
119
|
-
message = objOrMsg;
|
|
120
|
-
} else {
|
|
121
|
-
message = msg ?? "";
|
|
122
|
-
if (objOrMsg !== void 0 && objOrMsg !== null) {
|
|
123
|
-
try {
|
|
124
|
-
data = JSON.stringify(objOrMsg);
|
|
125
|
-
} catch {
|
|
126
|
-
data = "[unserializable]";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const bindStr = bindings && Object.keys(bindings).length ? ` ${JSON.stringify(bindings)}` : "";
|
|
131
|
-
const parts = [ts, tag, message, data, bindStr].filter(Boolean);
|
|
132
|
-
process.stdout.write(parts.join(" ") + "\n");
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// src/lru-map.ts
|
|
137
|
-
var LruMap = class {
|
|
138
|
-
constructor(_maxSize) {
|
|
139
|
-
this._maxSize = _maxSize;
|
|
140
|
-
}
|
|
141
|
-
_entries = /* @__PURE__ */ new Map();
|
|
142
|
-
get(key) {
|
|
143
|
-
const v = this._entries.get(key);
|
|
144
|
-
if (v === void 0) return void 0;
|
|
145
|
-
this._entries.delete(key);
|
|
146
|
-
this._entries.set(key, v);
|
|
147
|
-
return v;
|
|
148
|
-
}
|
|
149
|
-
has(key) {
|
|
150
|
-
return this._entries.has(key);
|
|
151
|
-
}
|
|
152
|
-
set(key, value) {
|
|
153
|
-
this._entries.delete(key);
|
|
154
|
-
if (this._entries.size >= this._maxSize) {
|
|
155
|
-
const oldest = this._entries.keys().next().value;
|
|
156
|
-
this._entries.delete(oldest);
|
|
157
|
-
}
|
|
158
|
-
this._entries.set(key, value);
|
|
159
|
-
}
|
|
160
|
-
delete(key) {
|
|
161
|
-
return this._entries.delete(key);
|
|
162
|
-
}
|
|
163
|
-
clear() {
|
|
164
|
-
this._entries.clear();
|
|
165
|
-
}
|
|
166
|
-
get size() {
|
|
167
|
-
return this._entries.size;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
var LruSet = class {
|
|
171
|
-
_map;
|
|
172
|
-
constructor(maxSize) {
|
|
173
|
-
this._map = new LruMap(maxSize);
|
|
174
|
-
}
|
|
175
|
-
has(value) {
|
|
176
|
-
return this._map.has(value);
|
|
177
|
-
}
|
|
178
|
-
add(value) {
|
|
179
|
-
this._map.set(value, true);
|
|
180
|
-
}
|
|
181
|
-
delete(value) {
|
|
182
|
-
return this._map.delete(value);
|
|
183
|
-
}
|
|
184
|
-
clear() {
|
|
185
|
-
this._map.clear();
|
|
186
|
-
}
|
|
187
|
-
get size() {
|
|
188
|
-
return this._map.size;
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// src/adapters/in-memory-cache.ts
|
|
193
|
-
var InMemoryCache = class {
|
|
194
|
-
// CacheEntry<any> lets `get<TState>` and `set<TState>` flow without casts:
|
|
195
|
-
// any is bidirectionally compatible with the per-call TState binding, while
|
|
196
|
-
// the public Cache interface still presents a typed surface to callers.
|
|
197
|
-
_entries;
|
|
198
|
-
constructor(options) {
|
|
199
|
-
this._entries = new LruMap(options?.maxSize ?? 1e3);
|
|
200
|
-
}
|
|
201
|
-
/** @inheritDoc */
|
|
202
|
-
async get(stream) {
|
|
203
|
-
return this._entries.get(stream);
|
|
204
|
-
}
|
|
205
|
-
/** @inheritDoc */
|
|
206
|
-
async set(stream, entry) {
|
|
207
|
-
this._entries.set(stream, entry);
|
|
208
|
-
}
|
|
209
|
-
/** @inheritDoc */
|
|
210
|
-
async invalidate(stream) {
|
|
211
|
-
this._entries.delete(stream);
|
|
212
|
-
}
|
|
213
|
-
/** @inheritDoc */
|
|
214
|
-
async clear() {
|
|
215
|
-
this._entries.clear();
|
|
216
|
-
}
|
|
217
|
-
/** @inheritDoc */
|
|
218
|
-
async dispose() {
|
|
219
|
-
this._entries.clear();
|
|
220
|
-
}
|
|
221
|
-
/** Current number of entries held by the LRU. */
|
|
222
|
-
get size() {
|
|
223
|
-
return this._entries.size;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
// src/utils.ts
|
|
228
|
-
import { prettifyError, ZodError } from "zod";
|
|
229
|
-
|
|
230
|
-
// src/config.ts
|
|
231
|
-
import * as fs from "fs";
|
|
232
|
-
import { z } from "zod";
|
|
233
|
-
var PackageSchema = z.object({
|
|
234
|
-
name: z.string().min(1),
|
|
235
|
-
version: z.string().min(1),
|
|
236
|
-
description: z.string().min(1).optional(),
|
|
237
|
-
author: z.object({ name: z.string().min(1), email: z.string().optional() }).optional().or(z.string().min(1)).optional(),
|
|
238
|
-
license: z.string().min(1).optional(),
|
|
239
|
-
dependencies: z.record(z.string(), z.string()).optional()
|
|
240
|
-
});
|
|
241
|
-
var FALLBACK_PACKAGE = {
|
|
242
|
-
name: "act-fallback",
|
|
243
|
-
version: "0.0.0-fallback",
|
|
244
|
-
description: "Synthetic fallback \u2014 package.json could not be loaded"
|
|
245
|
-
};
|
|
246
|
-
var getPackage = () => {
|
|
247
|
-
try {
|
|
248
|
-
const raw = fs.readFileSync("package.json");
|
|
249
|
-
return JSON.parse(raw.toString());
|
|
250
|
-
} catch (err) {
|
|
251
|
-
pkgLoadError = err;
|
|
252
|
-
return FALLBACK_PACKAGE;
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
var pkgLoadError;
|
|
256
|
-
var BaseSchema = PackageSchema.extend({
|
|
257
|
-
env: z.enum(Environments),
|
|
258
|
-
logLevel: z.enum(LogLevels),
|
|
259
|
-
logSingleLine: z.boolean(),
|
|
260
|
-
sleepMs: z.number().int().min(0).max(5e3)
|
|
261
|
-
});
|
|
262
|
-
var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
263
|
-
var env = NODE_ENV || "development";
|
|
264
|
-
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "fatal" : NODE_ENV === "production" ? "info" : "trace");
|
|
265
|
-
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
266
|
-
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
267
|
-
var pkg = getPackage();
|
|
268
|
-
var _validated;
|
|
269
|
-
var config = () => {
|
|
270
|
-
if (!_validated) {
|
|
271
|
-
_validated = extend(
|
|
272
|
-
{ ...pkg, env, logLevel, logSingleLine, sleepMs },
|
|
273
|
-
BaseSchema
|
|
274
|
-
);
|
|
275
|
-
if (pkgLoadError) {
|
|
276
|
-
const msg = pkgLoadError instanceof Error ? pkgLoadError.message : typeof pkgLoadError === "string" ? pkgLoadError : "unknown error";
|
|
277
|
-
log().warn(
|
|
278
|
-
`[act] Could not read package.json (${msg}); using synthetic name="${FALLBACK_PACKAGE.name}" version="${FALLBACK_PACKAGE.version}".`
|
|
279
|
-
);
|
|
280
|
-
pkgLoadError = void 0;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return _validated;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// src/utils.ts
|
|
287
|
-
var validate = (target, payload, schema) => {
|
|
288
|
-
try {
|
|
289
|
-
return schema ? schema.parse(payload) : payload;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
if (error instanceof ZodError) {
|
|
292
|
-
throw new ValidationError(target, payload, prettifyError(error));
|
|
293
|
-
}
|
|
294
|
-
throw new ValidationError(target, payload, error);
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
var extend = (source, schema, target) => {
|
|
298
|
-
const value = validate("config", source, schema);
|
|
299
|
-
return { ...target, ...value };
|
|
300
|
-
};
|
|
301
|
-
async function sleep(ms) {
|
|
302
|
-
return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/adapters/in-memory-store.ts
|
|
306
|
-
var InMemoryStream = class {
|
|
307
|
-
constructor(stream, source, priority = 0) {
|
|
308
|
-
this.stream = stream;
|
|
309
|
-
this.source = source;
|
|
310
|
-
this._priority = priority;
|
|
311
|
-
}
|
|
312
|
-
_at = -1;
|
|
313
|
-
_retry = -1;
|
|
314
|
-
_blocked = false;
|
|
315
|
-
_error = "";
|
|
316
|
-
_leased_by = void 0;
|
|
317
|
-
_leased_until = void 0;
|
|
318
|
-
_priority = 0;
|
|
319
|
-
get priority() {
|
|
320
|
-
return this._priority;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Bump the priority via {@link subscribe}: keeps the maximum across
|
|
324
|
-
* reactions so the highest-priority registrant wins.
|
|
325
|
-
*/
|
|
326
|
-
bumpPriority(priority) {
|
|
327
|
-
if (priority > this._priority) this._priority = priority;
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Set the priority outright via {@link prioritize}: operator
|
|
331
|
-
* runtime override that ignores the build-time `max()` invariant.
|
|
332
|
-
*/
|
|
333
|
-
setPriority(priority) {
|
|
334
|
-
this._priority = priority;
|
|
335
|
-
}
|
|
336
|
-
get is_available() {
|
|
337
|
-
return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date());
|
|
338
|
-
}
|
|
339
|
-
get at() {
|
|
340
|
-
return this._at;
|
|
341
|
-
}
|
|
342
|
-
get retry() {
|
|
343
|
-
return this._retry;
|
|
344
|
-
}
|
|
345
|
-
get blocked() {
|
|
346
|
-
return this._blocked;
|
|
347
|
-
}
|
|
348
|
-
get error() {
|
|
349
|
-
return this._error;
|
|
350
|
-
}
|
|
351
|
-
get leased_by() {
|
|
352
|
-
return this._leased_by;
|
|
353
|
-
}
|
|
354
|
-
get leased_until() {
|
|
355
|
-
return this._leased_until;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Attempt to lease this stream for processing.
|
|
359
|
-
* @param lease - The lease request.
|
|
360
|
-
* @param millis - Lease duration in milliseconds.
|
|
361
|
-
* @returns The granted lease or undefined if blocked.
|
|
362
|
-
*/
|
|
363
|
-
lease(lease, millis) {
|
|
364
|
-
if (millis > 0) {
|
|
365
|
-
this._leased_by = lease.by;
|
|
366
|
-
this._leased_until = new Date(Date.now() + millis);
|
|
367
|
-
}
|
|
368
|
-
this._retry = this._retry + 1;
|
|
369
|
-
return {
|
|
370
|
-
stream: this.stream,
|
|
371
|
-
source: this.source,
|
|
372
|
-
at: lease.at,
|
|
373
|
-
by: lease.by,
|
|
374
|
-
retry: this._retry,
|
|
375
|
-
lagging: lease.lagging
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Acknowledge completion of processing for this stream.
|
|
380
|
-
* @param lease - The lease request.
|
|
381
|
-
*/
|
|
382
|
-
ack(lease) {
|
|
383
|
-
if (this._leased_by === lease.by) {
|
|
384
|
-
this._leased_by = void 0;
|
|
385
|
-
this._leased_until = void 0;
|
|
386
|
-
this._at = lease.at;
|
|
387
|
-
this._retry = -1;
|
|
388
|
-
return {
|
|
389
|
-
stream: this.stream,
|
|
390
|
-
source: this.source,
|
|
391
|
-
at: this._at,
|
|
392
|
-
by: lease.by,
|
|
393
|
-
retry: this._retry,
|
|
394
|
-
lagging: lease.lagging
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Block a stream for processing after failing to process and reaching max retries with blocking enabled.
|
|
400
|
-
* @param lease - The lease request.
|
|
401
|
-
* @param error Blocked error message.
|
|
402
|
-
*/
|
|
403
|
-
block(lease, error) {
|
|
404
|
-
if (this._leased_by === lease.by) {
|
|
405
|
-
this._blocked = true;
|
|
406
|
-
this._error = error;
|
|
407
|
-
return {
|
|
408
|
-
stream: this.stream,
|
|
409
|
-
source: this.source,
|
|
410
|
-
at: this._at,
|
|
411
|
-
by: this._leased_by,
|
|
412
|
-
retry: this._retry,
|
|
413
|
-
error: this._error,
|
|
414
|
-
lagging: lease.lagging
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Reset this stream's watermark and state for replay. The retry counter
|
|
420
|
-
* resets to -1 to match the constructor + ack() invariant ("released
|
|
421
|
-
* stream"); the next claim() bumps it to 0 (first attempt).
|
|
422
|
-
*/
|
|
423
|
-
reset() {
|
|
424
|
-
this._at = -1;
|
|
425
|
-
this._retry = -1;
|
|
426
|
-
this._blocked = false;
|
|
427
|
-
this._error = "";
|
|
428
|
-
this._leased_by = void 0;
|
|
429
|
-
this._leased_until = void 0;
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
var InMemoryStore = class {
|
|
433
|
-
// stored events
|
|
434
|
-
_events = [];
|
|
435
|
-
// stored stream positions and other metadata
|
|
436
|
-
_streams = /* @__PURE__ */ new Map();
|
|
437
|
-
// last committed version per stream — O(1) replacement for filter-on-commit
|
|
438
|
-
_streamVersions = /* @__PURE__ */ new Map();
|
|
439
|
-
// max non-snapshot event id per stream — drives the source-pattern probe in claim()
|
|
440
|
-
// without scanning the full event log.
|
|
441
|
-
_maxEventIdByStream = /* @__PURE__ */ new Map();
|
|
442
|
-
// global max non-snapshot event id — fast pre-check for source-less streams in claim()
|
|
443
|
-
_maxNonSnapEventId = -1;
|
|
444
|
-
_resetIndexes() {
|
|
445
|
-
this._events.length = 0;
|
|
446
|
-
this._streamVersions.clear();
|
|
447
|
-
this._maxEventIdByStream.clear();
|
|
448
|
-
this._maxNonSnapEventId = -1;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Dispose of the store and clear all events.
|
|
452
|
-
* @returns Promise that resolves when disposal is complete.
|
|
453
|
-
*/
|
|
454
|
-
async dispose() {
|
|
455
|
-
await sleep();
|
|
456
|
-
this._resetIndexes();
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Seed the store with initial data (no-op for in-memory).
|
|
460
|
-
* @returns Promise that resolves when seeding is complete.
|
|
461
|
-
*/
|
|
462
|
-
async seed() {
|
|
463
|
-
await sleep();
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Drop all data from the store.
|
|
467
|
-
* @returns Promise that resolves when the store is cleared.
|
|
468
|
-
*/
|
|
469
|
-
async drop() {
|
|
470
|
-
await sleep();
|
|
471
|
-
this._resetIndexes();
|
|
472
|
-
this._streams = /* @__PURE__ */ new Map();
|
|
473
|
-
}
|
|
474
|
-
in_query(query, e) {
|
|
475
|
-
if (query.stream) {
|
|
476
|
-
if (query.stream_exact) {
|
|
477
|
-
if (e.stream !== query.stream) return false;
|
|
478
|
-
} else if (!RegExp(`^${query.stream}$`).test(e.stream)) return false;
|
|
479
|
-
}
|
|
480
|
-
if (query.names && !query.names.includes(e.name)) return false;
|
|
481
|
-
if (query.correlation && e.meta?.correlation !== query.correlation)
|
|
482
|
-
return false;
|
|
483
|
-
if (e.name === SNAP_EVENT && !query.with_snaps) return false;
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Query events in the store, optionally filtered by query options.
|
|
488
|
-
* @param callback - Function to call for each event.
|
|
489
|
-
* @param query - Optional query options.
|
|
490
|
-
* @returns The number of events processed.
|
|
491
|
-
*/
|
|
492
|
-
async query(callback, query) {
|
|
493
|
-
await sleep();
|
|
494
|
-
let count = 0;
|
|
495
|
-
if (query?.backward) {
|
|
496
|
-
let i = (query?.before || this._events.length) - 1;
|
|
497
|
-
while (i >= 0) {
|
|
498
|
-
const e = this._events[i--];
|
|
499
|
-
if (query && !this.in_query(query, e)) continue;
|
|
500
|
-
if (query?.created_before && e.created >= query.created_before)
|
|
501
|
-
continue;
|
|
502
|
-
if (query.after && e.id <= query.after) break;
|
|
503
|
-
if (query.created_after && e.created <= query.created_after) break;
|
|
504
|
-
callback(e);
|
|
505
|
-
count++;
|
|
506
|
-
if (query?.limit && count >= query.limit) break;
|
|
507
|
-
}
|
|
508
|
-
} else {
|
|
509
|
-
let i = (query?.after ?? -1) + 1;
|
|
510
|
-
while (i < this._events.length) {
|
|
511
|
-
const e = this._events[i++];
|
|
512
|
-
if (query && !this.in_query(query, e)) continue;
|
|
513
|
-
if (query?.created_after && e.created <= query.created_after) continue;
|
|
514
|
-
if (query?.before && e.id >= query.before) break;
|
|
515
|
-
if (query?.created_before && e.created >= query.created_before) break;
|
|
516
|
-
callback(e);
|
|
517
|
-
count++;
|
|
518
|
-
if (query?.limit && count >= query.limit) break;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return count;
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Commit one or more events to a stream.
|
|
525
|
-
* @param stream - The stream name.
|
|
526
|
-
* @param msgs - The events/messages to commit.
|
|
527
|
-
* @param meta - Event metadata.
|
|
528
|
-
* @param expectedVersion - Optional optimistic concurrency check.
|
|
529
|
-
* @returns The committed events with metadata.
|
|
530
|
-
* @throws ConcurrencyError if expectedVersion does not match.
|
|
531
|
-
*/
|
|
532
|
-
async commit(stream, msgs, meta, expectedVersion) {
|
|
533
|
-
await sleep();
|
|
534
|
-
const currentVersion = this._streamVersions.get(stream) ?? -1;
|
|
535
|
-
if (typeof expectedVersion === "number" && currentVersion !== expectedVersion) {
|
|
536
|
-
throw new ConcurrencyError(
|
|
537
|
-
stream,
|
|
538
|
-
currentVersion,
|
|
539
|
-
msgs,
|
|
540
|
-
expectedVersion
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
let version = currentVersion + 1;
|
|
544
|
-
let lastNonSnapId = -1;
|
|
545
|
-
const committed = msgs.map(({ name, data }) => {
|
|
546
|
-
const c = {
|
|
547
|
-
id: this._events.length,
|
|
548
|
-
stream,
|
|
549
|
-
version,
|
|
550
|
-
created: /* @__PURE__ */ new Date(),
|
|
551
|
-
name,
|
|
552
|
-
data,
|
|
553
|
-
meta
|
|
554
|
-
};
|
|
555
|
-
this._events.push(c);
|
|
556
|
-
if (name !== SNAP_EVENT) lastNonSnapId = c.id;
|
|
557
|
-
version++;
|
|
558
|
-
return c;
|
|
559
|
-
});
|
|
560
|
-
this._streamVersions.set(stream, version - 1);
|
|
561
|
-
if (lastNonSnapId >= 0) {
|
|
562
|
-
this._maxEventIdByStream.set(stream, lastNonSnapId);
|
|
563
|
-
this._maxNonSnapEventId = lastNonSnapId;
|
|
564
|
-
}
|
|
565
|
-
return committed;
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Atomically discovers and leases streams for processing.
|
|
569
|
-
* Fuses poll + lease into a single operation.
|
|
570
|
-
* @param lagging - Max streams from lagging frontier.
|
|
571
|
-
* @param leading - Max streams from leading frontier.
|
|
572
|
-
* @param by - Lease holder identifier.
|
|
573
|
-
* @param millis - Lease duration in milliseconds.
|
|
574
|
-
* @returns Granted leases.
|
|
575
|
-
*/
|
|
576
|
-
async claim(lagging, leading, by, millis) {
|
|
577
|
-
await sleep();
|
|
578
|
-
const sourceRegex = /* @__PURE__ */ new Map();
|
|
579
|
-
const getRegex = (source) => {
|
|
580
|
-
let re = sourceRegex.get(source);
|
|
581
|
-
if (!re) {
|
|
582
|
-
re = new RegExp(source);
|
|
583
|
-
sourceRegex.set(source, re);
|
|
584
|
-
}
|
|
585
|
-
return re;
|
|
586
|
-
};
|
|
587
|
-
const hasWork = (s) => {
|
|
588
|
-
if (s.at < 0) return true;
|
|
589
|
-
if (!s.source) return s.at < this._maxNonSnapEventId;
|
|
590
|
-
const re = getRegex(s.source);
|
|
591
|
-
for (const [streamName, maxId] of this._maxEventIdByStream) {
|
|
592
|
-
if (maxId > s.at && re.test(streamName)) return true;
|
|
593
|
-
}
|
|
594
|
-
return false;
|
|
595
|
-
};
|
|
596
|
-
const available = [...this._streams.values()].filter(
|
|
597
|
-
(s) => s.is_available && hasWork(s)
|
|
598
|
-
);
|
|
599
|
-
const lag = available.sort((a, b) => b.priority - a.priority || a.at - b.at).slice(0, lagging).map((s) => ({
|
|
600
|
-
stream: s.stream,
|
|
601
|
-
source: s.source,
|
|
602
|
-
at: s.at,
|
|
603
|
-
lagging: true
|
|
604
|
-
}));
|
|
605
|
-
const lead = available.sort((a, b) => b.at - a.at).slice(0, leading).map((s) => ({
|
|
606
|
-
stream: s.stream,
|
|
607
|
-
source: s.source,
|
|
608
|
-
at: s.at,
|
|
609
|
-
lagging: false
|
|
610
|
-
}));
|
|
611
|
-
const seen = /* @__PURE__ */ new Set();
|
|
612
|
-
const combined = [...lag, ...lead].filter((p) => {
|
|
613
|
-
if (seen.has(p.stream)) return false;
|
|
614
|
-
seen.add(p.stream);
|
|
615
|
-
return true;
|
|
616
|
-
});
|
|
617
|
-
return combined.map(
|
|
618
|
-
(p) => this._streams.get(p.stream)?.lease({ ...p, by, retry: 0 }, millis)
|
|
619
|
-
).filter((l) => !!l);
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Registers streams for event processing. When the same stream is
|
|
623
|
-
* resubscribed with a different priority, the **maximum** wins — so
|
|
624
|
-
* the highest-priority registered reaction sets the scheduling lane.
|
|
625
|
-
* Use {@link prioritize} for operator runtime overrides.
|
|
626
|
-
*
|
|
627
|
-
* @param streams - Streams to register with optional source + priority.
|
|
628
|
-
* @returns subscribed count and current max watermark.
|
|
629
|
-
*/
|
|
630
|
-
async subscribe(streams) {
|
|
631
|
-
await sleep();
|
|
632
|
-
let subscribed = 0;
|
|
633
|
-
for (const { stream, source, priority = 0 } of streams) {
|
|
634
|
-
const existing = this._streams.get(stream);
|
|
635
|
-
if (existing) {
|
|
636
|
-
existing.bumpPriority(priority);
|
|
637
|
-
} else {
|
|
638
|
-
this._streams.set(stream, new InMemoryStream(stream, source, priority));
|
|
639
|
-
subscribed++;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
let watermark = -1;
|
|
643
|
-
for (const s of this._streams.values()) {
|
|
644
|
-
if (s.at > watermark) watermark = s.at;
|
|
645
|
-
}
|
|
646
|
-
return { subscribed, watermark };
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Acknowledge completion of processing for leased streams.
|
|
650
|
-
* @param leases - Leases to acknowledge, including last processed watermark and lease holder.
|
|
651
|
-
*/
|
|
652
|
-
async ack(leases) {
|
|
653
|
-
await sleep();
|
|
654
|
-
return leases.map((l) => this._streams.get(l.stream)?.ack(l)).filter((l) => !!l);
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Block a stream for processing after failing to process and reaching max retries with blocking enabled.
|
|
658
|
-
* @param leases - Leases to block, including lease holder and last error message.
|
|
659
|
-
* @returns Blocked leases.
|
|
660
|
-
*/
|
|
661
|
-
async block(leases) {
|
|
662
|
-
await sleep();
|
|
663
|
-
return leases.map((l) => this._streams.get(l.stream)?.block(l, l.error)).filter((l) => !!l);
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Reset watermarks for the given streams to -1, clearing retry, blocked,
|
|
667
|
-
* error, and lease state so they can be replayed from the beginning.
|
|
668
|
-
* @param streams - Stream names to reset.
|
|
669
|
-
* @returns Count of streams that were actually reset.
|
|
670
|
-
*/
|
|
671
|
-
async reset(streams) {
|
|
672
|
-
await sleep();
|
|
673
|
-
let count = 0;
|
|
674
|
-
for (const name of streams) {
|
|
675
|
-
const s = this._streams.get(name);
|
|
676
|
-
if (s) {
|
|
677
|
-
s.reset();
|
|
678
|
-
count++;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return count;
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Bulk-update priority of streams matching `filter`. Mirrors
|
|
685
|
-
* {@link query_streams}'s filter semantics — see {@link Store.prioritize}.
|
|
686
|
-
* Unlike {@link subscribe} (which keeps `max()` of registered
|
|
687
|
-
* priorities), this sets the priority outright — operator override
|
|
688
|
-
* for the build-time scheduling policy.
|
|
689
|
-
*
|
|
690
|
-
* @returns Count of streams whose priority changed.
|
|
691
|
-
*/
|
|
692
|
-
async prioritize(filter, priority) {
|
|
693
|
-
await sleep();
|
|
694
|
-
const streamRe = filter.stream && !filter.stream_exact ? new RegExp(`^${filter.stream}$`) : void 0;
|
|
695
|
-
const sourceRe = filter.source && !filter.source_exact ? new RegExp(`^${filter.source}$`) : void 0;
|
|
696
|
-
let count = 0;
|
|
697
|
-
for (const s of this._streams.values()) {
|
|
698
|
-
if (filter.stream !== void 0) {
|
|
699
|
-
if (filter.stream_exact ? s.stream !== filter.stream : !streamRe.test(s.stream))
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
if (filter.source !== void 0) {
|
|
703
|
-
if (s.source === void 0) continue;
|
|
704
|
-
if (filter.source_exact ? s.source !== filter.source : !sourceRe.test(s.source))
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
if (filter.blocked !== void 0 && s.blocked !== filter.blocked)
|
|
708
|
-
continue;
|
|
709
|
-
if (s.priority !== priority) {
|
|
710
|
-
s.setPriority(priority);
|
|
711
|
-
count++;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
return count;
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Streams registered subscription positions to the callback, ordered by
|
|
718
|
-
* stream name. Returns the highest event id in the store and the count
|
|
719
|
-
* of positions emitted.
|
|
720
|
-
*/
|
|
721
|
-
async query_streams(callback, query) {
|
|
722
|
-
await sleep();
|
|
723
|
-
const limit = query?.limit ?? 100;
|
|
724
|
-
const after = query?.after;
|
|
725
|
-
const blocked = query?.blocked;
|
|
726
|
-
const streamRe = query?.stream && !query.stream_exact ? new RegExp(`^${query.stream}$`) : void 0;
|
|
727
|
-
const sourceRe = query?.source && !query.source_exact ? new RegExp(`^${query.source}$`) : void 0;
|
|
728
|
-
const sorted = [...this._streams.values()].sort(
|
|
729
|
-
(a, b) => a.stream.localeCompare(b.stream)
|
|
730
|
-
);
|
|
731
|
-
let count = 0;
|
|
732
|
-
for (const s of sorted) {
|
|
733
|
-
if (after !== void 0 && s.stream <= after) continue;
|
|
734
|
-
if (query?.stream !== void 0) {
|
|
735
|
-
if (query.stream_exact ? s.stream !== query.stream : !streamRe.test(s.stream))
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
if (query?.source !== void 0) {
|
|
739
|
-
if (s.source === void 0) continue;
|
|
740
|
-
if (query.source_exact ? s.source !== query.source : !sourceRe.test(s.source))
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
if (blocked !== void 0 && s.blocked !== blocked) continue;
|
|
744
|
-
callback({
|
|
745
|
-
stream: s.stream,
|
|
746
|
-
source: s.source,
|
|
747
|
-
at: s.at,
|
|
748
|
-
retry: s.retry,
|
|
749
|
-
blocked: s.blocked,
|
|
750
|
-
error: s.error,
|
|
751
|
-
priority: s.priority,
|
|
752
|
-
leased_by: s.leased_by,
|
|
753
|
-
leased_until: s.leased_until
|
|
754
|
-
});
|
|
755
|
-
count++;
|
|
756
|
-
if (count >= limit) break;
|
|
757
|
-
}
|
|
758
|
-
return { maxEventId: this._events.length - 1, count };
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Atomically truncates streams and seeds each with a snapshot or tombstone.
|
|
762
|
-
* @param targets - Streams to truncate with optional snapshot state and meta.
|
|
763
|
-
* @returns Map keyed by stream name, each entry with `deleted` count and `committed` event.
|
|
764
|
-
*/
|
|
765
|
-
async truncate(targets) {
|
|
766
|
-
await sleep();
|
|
767
|
-
const deletedCounts = /* @__PURE__ */ new Map();
|
|
768
|
-
const streamSet = new Set(targets.map((t) => t.stream));
|
|
769
|
-
for (const e of this._events) {
|
|
770
|
-
if (streamSet.has(e.stream)) {
|
|
771
|
-
deletedCounts.set(e.stream, (deletedCounts.get(e.stream) ?? 0) + 1);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
this._events = this._events.filter((e) => !streamSet.has(e.stream));
|
|
775
|
-
for (const stream of streamSet) {
|
|
776
|
-
this._streams.delete(stream);
|
|
777
|
-
this._streamVersions.delete(stream);
|
|
778
|
-
this._maxEventIdByStream.delete(stream);
|
|
779
|
-
}
|
|
780
|
-
const result = /* @__PURE__ */ new Map();
|
|
781
|
-
for (const { stream, snapshot, meta } of targets) {
|
|
782
|
-
const event = {
|
|
783
|
-
id: this._events.length,
|
|
784
|
-
stream,
|
|
785
|
-
version: 0,
|
|
786
|
-
created: /* @__PURE__ */ new Date(),
|
|
787
|
-
name: snapshot !== void 0 ? SNAP_EVENT : TOMBSTONE_EVENT,
|
|
788
|
-
data: snapshot ?? {},
|
|
789
|
-
meta: meta ?? { correlation: "", causation: {} }
|
|
790
|
-
};
|
|
791
|
-
this._events.push(event);
|
|
792
|
-
this._streamVersions.set(stream, 0);
|
|
793
|
-
if (event.name !== SNAP_EVENT) {
|
|
794
|
-
this._maxEventIdByStream.set(stream, event.id);
|
|
795
|
-
}
|
|
796
|
-
result.set(stream, {
|
|
797
|
-
deleted: deletedCounts.get(stream) ?? 0,
|
|
798
|
-
committed: event
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
let max = -1;
|
|
802
|
-
for (const id of this._maxEventIdByStream.values()) if (id > max) max = id;
|
|
803
|
-
this._maxNonSnapEventId = max;
|
|
804
|
-
return result;
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
// src/ports.ts
|
|
809
|
-
var scoped = new AsyncLocalStorage();
|
|
810
|
-
var ExitCodes = ["ERROR", "EXIT"];
|
|
811
|
-
var adapters = /* @__PURE__ */ new Map();
|
|
812
|
-
function port(injector) {
|
|
813
|
-
return (adapter) => {
|
|
814
|
-
if (!adapters.has(injector.name)) {
|
|
815
|
-
const injected = injector(adapter);
|
|
816
|
-
adapters.set(injector.name, injected);
|
|
817
|
-
log().info(`[act] + ${injector.name}:${injected.constructor.name}`);
|
|
818
|
-
}
|
|
819
|
-
return adapters.get(injector.name);
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
var log = port(function log2(adapter) {
|
|
823
|
-
const cfg = config();
|
|
824
|
-
return adapter || new ConsoleLogger({
|
|
825
|
-
level: cfg.logLevel,
|
|
826
|
-
pretty: cfg.env !== "production"
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
var _store = port(function store(adapter) {
|
|
830
|
-
return adapter ?? new InMemoryStore();
|
|
831
|
-
});
|
|
832
|
-
var store2 = ((adapter) => {
|
|
833
|
-
return scoped.getStore()?.store ?? _store(adapter);
|
|
834
|
-
});
|
|
835
|
-
var _cache = port(function cache(adapter) {
|
|
836
|
-
return adapter ?? new InMemoryCache();
|
|
837
|
-
});
|
|
838
|
-
var cache2 = ((adapter) => {
|
|
839
|
-
return scoped.getStore()?.cache ?? _cache(adapter);
|
|
840
|
-
});
|
|
841
|
-
var disposers = [];
|
|
842
|
-
async function disposeAndExit(code = "EXIT") {
|
|
843
|
-
if (code === "ERROR" && config().env === "production") {
|
|
844
|
-
log().warn(
|
|
845
|
-
"disposeAndExit('ERROR') ignored in production \u2014 process kept alive"
|
|
846
|
-
);
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
for (const disposer of [...disposers].reverse()) {
|
|
850
|
-
await disposer();
|
|
851
|
-
}
|
|
852
|
-
for (const adapter of [...adapters.values()].reverse()) {
|
|
853
|
-
await adapter.dispose();
|
|
854
|
-
log().info(`[act] - ${adapter.constructor.name}`);
|
|
855
|
-
}
|
|
856
|
-
adapters.clear();
|
|
857
|
-
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
858
|
-
}
|
|
859
|
-
function dispose(disposer) {
|
|
860
|
-
disposer && disposers.push(disposer);
|
|
861
|
-
return disposeAndExit;
|
|
862
|
-
}
|
|
863
|
-
var SNAP_EVENT = "__snapshot__";
|
|
864
|
-
var TOMBSTONE_EVENT = "__tombstone__";
|
|
38
|
+
import "./chunk-5WRI5ZAA.js";
|
|
865
39
|
|
|
866
40
|
// src/signals.ts
|
|
867
41
|
process.once("SIGINT", async (arg) => {
|
|
@@ -966,7 +140,7 @@ async function scanStreamHeads(streams) {
|
|
|
966
140
|
let maxId = -1;
|
|
967
141
|
let version = -1;
|
|
968
142
|
let lastEventName = "";
|
|
969
|
-
await
|
|
143
|
+
await store().query(
|
|
970
144
|
(e) => {
|
|
971
145
|
if (e.name === TOMBSTONE_EVENT || maxId !== -1) return;
|
|
972
146
|
maxId = e.id;
|
|
@@ -983,7 +157,7 @@ async function scanStreamHeads(streams) {
|
|
|
983
157
|
async function partitionBySafety(streamInfo, reactiveEventsSize, skipped) {
|
|
984
158
|
if (reactiveEventsSize === 0) return [...streamInfo.keys()];
|
|
985
159
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
986
|
-
await
|
|
160
|
+
await store().query_streams((position) => {
|
|
987
161
|
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
988
162
|
for (const [stream, info] of streamInfo) {
|
|
989
163
|
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
@@ -1054,13 +228,13 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
1054
228
|
}
|
|
1055
229
|
};
|
|
1056
230
|
});
|
|
1057
|
-
const truncated = await
|
|
231
|
+
const truncated = await store().truncate(truncTargets);
|
|
1058
232
|
await Promise.all(
|
|
1059
233
|
guarded.map(async (stream) => {
|
|
1060
234
|
const entry = truncated.get(stream);
|
|
1061
235
|
const state2 = seedStates.get(stream);
|
|
1062
236
|
if (state2 && entry) {
|
|
1063
|
-
await
|
|
237
|
+
await cache().set(stream, {
|
|
1064
238
|
state: state2,
|
|
1065
239
|
version: entry.committed.version,
|
|
1066
240
|
event_id: entry.committed.id,
|
|
@@ -1068,7 +242,7 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
1068
242
|
snaps: 1
|
|
1069
243
|
});
|
|
1070
244
|
} else {
|
|
1071
|
-
await
|
|
245
|
+
await cache().invalidate(stream);
|
|
1072
246
|
}
|
|
1073
247
|
})
|
|
1074
248
|
);
|
|
@@ -1103,7 +277,7 @@ var CorrelateCycle = class {
|
|
|
1103
277
|
async init() {
|
|
1104
278
|
if (this._initialized) return;
|
|
1105
279
|
this._initialized = true;
|
|
1106
|
-
const { watermark } = await
|
|
280
|
+
const { watermark } = await store().subscribe([...this.staticTargets]);
|
|
1107
281
|
this._checkpoint = watermark;
|
|
1108
282
|
this.onInit?.();
|
|
1109
283
|
for (const { stream } of this.staticTargets) {
|
|
@@ -1122,7 +296,7 @@ var CorrelateCycle = class {
|
|
|
1122
296
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1123
297
|
const correlated = /* @__PURE__ */ new Map();
|
|
1124
298
|
let last_id = after;
|
|
1125
|
-
await
|
|
299
|
+
await store().query(
|
|
1126
300
|
(event) => {
|
|
1127
301
|
last_id = event.id;
|
|
1128
302
|
const register = this.registry.events[event.name];
|
|
@@ -1642,12 +816,12 @@ var SettleLoop = class {
|
|
|
1642
816
|
};
|
|
1643
817
|
|
|
1644
818
|
// src/internal/drain.ts
|
|
1645
|
-
var claim = (lagging, leading, by, millis) =>
|
|
819
|
+
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
1646
820
|
async function fetch(leased, eventLimit) {
|
|
1647
821
|
return Promise.all(
|
|
1648
822
|
leased.map(async ({ stream, source, at, lagging }) => {
|
|
1649
823
|
const events = [];
|
|
1650
|
-
await
|
|
824
|
+
await store().query((e) => events.push(e), {
|
|
1651
825
|
stream: source,
|
|
1652
826
|
after: at,
|
|
1653
827
|
limit: eventLimit
|
|
@@ -1656,9 +830,9 @@ async function fetch(leased, eventLimit) {
|
|
|
1656
830
|
})
|
|
1657
831
|
);
|
|
1658
832
|
}
|
|
1659
|
-
var ack = (leases) =>
|
|
1660
|
-
var block = (leases) =>
|
|
1661
|
-
var subscribe = (streams) =>
|
|
833
|
+
var ack = (leases) => store().ack(leases);
|
|
834
|
+
var block = (leases) => store().block(leases);
|
|
835
|
+
var subscribe = (streams) => store().subscribe(streams);
|
|
1662
836
|
|
|
1663
837
|
// src/internal/event-sourcing.ts
|
|
1664
838
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -1666,7 +840,7 @@ import { patch } from "@rotorsoft/act-patch";
|
|
|
1666
840
|
async function snap(snapshot) {
|
|
1667
841
|
try {
|
|
1668
842
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
1669
|
-
await
|
|
843
|
+
await store().commit(
|
|
1670
844
|
stream,
|
|
1671
845
|
[{ name: SNAP_EVENT, data: snapshot.state }],
|
|
1672
846
|
{
|
|
@@ -1682,7 +856,7 @@ async function snap(snapshot) {
|
|
|
1682
856
|
}
|
|
1683
857
|
async function tombstone(stream, expectedVersion, correlation) {
|
|
1684
858
|
try {
|
|
1685
|
-
const [committed] = await
|
|
859
|
+
const [committed] = await store().commit(
|
|
1686
860
|
stream,
|
|
1687
861
|
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
1688
862
|
{ correlation, causation: {} },
|
|
@@ -1696,7 +870,7 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
1696
870
|
}
|
|
1697
871
|
async function load(me, stream, callback, asOf) {
|
|
1698
872
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
1699
|
-
const cached = timeTravel ? void 0 : await
|
|
873
|
+
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
1700
874
|
const cache_hit = !!cached;
|
|
1701
875
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
1702
876
|
let patches = cached?.patches ?? 0;
|
|
@@ -1704,7 +878,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1704
878
|
let version = cached?.version ?? -1;
|
|
1705
879
|
let replayed = 0;
|
|
1706
880
|
let event;
|
|
1707
|
-
await
|
|
881
|
+
await store().query(
|
|
1708
882
|
(e) => {
|
|
1709
883
|
event = e;
|
|
1710
884
|
version = e.version;
|
|
@@ -1739,7 +913,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1739
913
|
}
|
|
1740
914
|
);
|
|
1741
915
|
if (replayed > 0 && !timeTravel && event) {
|
|
1742
|
-
await
|
|
916
|
+
await cache().set(stream, {
|
|
1743
917
|
state: state2,
|
|
1744
918
|
version,
|
|
1745
919
|
event_id: event.id,
|
|
@@ -1812,7 +986,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1812
986
|
};
|
|
1813
987
|
let committed;
|
|
1814
988
|
try {
|
|
1815
|
-
committed = await
|
|
989
|
+
committed = await store().commit(
|
|
1816
990
|
stream,
|
|
1817
991
|
emitted,
|
|
1818
992
|
meta,
|
|
@@ -1824,7 +998,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1824
998
|
);
|
|
1825
999
|
} catch (error) {
|
|
1826
1000
|
if (error instanceof ConcurrencyError) {
|
|
1827
|
-
await
|
|
1001
|
+
await cache().invalidate(stream);
|
|
1828
1002
|
}
|
|
1829
1003
|
throw error;
|
|
1830
1004
|
}
|
|
@@ -1846,7 +1020,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1846
1020
|
});
|
|
1847
1021
|
const last = snapshots.at(-1);
|
|
1848
1022
|
const snapped = me.snap?.(last);
|
|
1849
|
-
|
|
1023
|
+
cache().set(stream, {
|
|
1850
1024
|
state: last.state,
|
|
1851
1025
|
version: last.event.version,
|
|
1852
1026
|
event_id: last.event.id,
|
|
@@ -2087,14 +1261,8 @@ var Act = class {
|
|
|
2087
1261
|
},
|
|
2088
1262
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
2089
1263
|
);
|
|
2090
|
-
this._notify_disposer = this._wireNotify(options.scoped?.store ??
|
|
2091
|
-
dispose(
|
|
2092
|
-
this._emitter.removeAllListeners();
|
|
2093
|
-
this.stop_correlations();
|
|
2094
|
-
this.stop_settling();
|
|
2095
|
-
const disposer = await this._notify_disposer;
|
|
2096
|
-
if (disposer) await disposer();
|
|
2097
|
-
});
|
|
1264
|
+
this._notify_disposer = this._wireNotify(options.scoped?.store ?? store());
|
|
1265
|
+
dispose(() => this.shutdown());
|
|
2098
1266
|
}
|
|
2099
1267
|
_emitter = new EventEmitter();
|
|
2100
1268
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
@@ -2170,6 +1338,30 @@ var Act = class {
|
|
|
2170
1338
|
/** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
|
|
2171
1339
|
_handle;
|
|
2172
1340
|
_handle_batch;
|
|
1341
|
+
/** True after the first `shutdown()` call. Guards idempotency. */
|
|
1342
|
+
_shutdown_promise;
|
|
1343
|
+
/**
|
|
1344
|
+
* Per-instance teardown: remove lifecycle listeners, stop the
|
|
1345
|
+
* correlation worker, cancel any pending settle cycle, and tear
|
|
1346
|
+
* down the cross-process notify subscription.
|
|
1347
|
+
*
|
|
1348
|
+
* Idempotent — repeated calls return the same promise. Registered
|
|
1349
|
+
* automatically with the global `dispose()` registry at construction,
|
|
1350
|
+
* so process-wide `dispose()()` covers it; test helpers (or operators
|
|
1351
|
+
* that mint short-lived Acts) call it explicitly for prompt cleanup.
|
|
1352
|
+
*/
|
|
1353
|
+
shutdown() {
|
|
1354
|
+
if (!this._shutdown_promise) {
|
|
1355
|
+
this._shutdown_promise = (async () => {
|
|
1356
|
+
this._emitter.removeAllListeners();
|
|
1357
|
+
this.stop_correlations();
|
|
1358
|
+
this.stop_settling();
|
|
1359
|
+
const disposer = await this._notify_disposer;
|
|
1360
|
+
if (disposer) await disposer();
|
|
1361
|
+
})();
|
|
1362
|
+
}
|
|
1363
|
+
return this._shutdown_promise;
|
|
1364
|
+
}
|
|
2173
1365
|
/**
|
|
2174
1366
|
* Subscribe to {@link Store.notify} when both the store and the
|
|
2175
1367
|
* registry support it. Returns the disposer (or `undefined` when no
|
|
@@ -2365,7 +1557,7 @@ var Act = class {
|
|
|
2365
1557
|
return this._scoped(async () => {
|
|
2366
1558
|
let first;
|
|
2367
1559
|
let last;
|
|
2368
|
-
const count = await
|
|
1560
|
+
const count = await store().query((e) => {
|
|
2369
1561
|
if (!first) first = e;
|
|
2370
1562
|
last = e;
|
|
2371
1563
|
callback?.(e);
|
|
@@ -2402,7 +1594,7 @@ var Act = class {
|
|
|
2402
1594
|
async query_array(query) {
|
|
2403
1595
|
return this._scoped(async () => {
|
|
2404
1596
|
const events = [];
|
|
2405
|
-
await
|
|
1597
|
+
await store().query((e) => events.push(e), query);
|
|
2406
1598
|
return events;
|
|
2407
1599
|
});
|
|
2408
1600
|
}
|
|
@@ -2614,7 +1806,7 @@ var Act = class {
|
|
|
2614
1806
|
*/
|
|
2615
1807
|
async reset(streams) {
|
|
2616
1808
|
return this._scoped(async () => {
|
|
2617
|
-
const count = await
|
|
1809
|
+
const count = await store().reset(streams);
|
|
2618
1810
|
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
2619
1811
|
return count;
|
|
2620
1812
|
});
|
|
@@ -2658,7 +1850,7 @@ var Act = class {
|
|
|
2658
1850
|
* @see {@link claim} for how priority biases scheduling
|
|
2659
1851
|
*/
|
|
2660
1852
|
async prioritize(filter, priority) {
|
|
2661
|
-
return this._scoped(() =>
|
|
1853
|
+
return this._scoped(() => store().prioritize(filter, priority));
|
|
2662
1854
|
}
|
|
2663
1855
|
/**
|
|
2664
1856
|
* Close the books — guard, archive, truncate, and optionally restart streams.
|
|
@@ -3079,7 +2271,7 @@ export {
|
|
|
3079
2271
|
ValidationError,
|
|
3080
2272
|
ZodEmpty,
|
|
3081
2273
|
act,
|
|
3082
|
-
|
|
2274
|
+
cache,
|
|
3083
2275
|
config,
|
|
3084
2276
|
dispose,
|
|
3085
2277
|
disposeAndExit,
|
|
@@ -3091,7 +2283,7 @@ export {
|
|
|
3091
2283
|
sleep,
|
|
3092
2284
|
slice,
|
|
3093
2285
|
state,
|
|
3094
|
-
|
|
2286
|
+
store,
|
|
3095
2287
|
validate
|
|
3096
2288
|
};
|
|
3097
2289
|
//# sourceMappingURL=index.js.map
|