@rotorsoft/act 1.10.1 → 1.11.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/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +71 -9
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/slice-builder.d.ts +1 -1
- package/dist/@types/builders/state-builder.d.ts +88 -1
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-config.d.ts +72 -0
- package/dist/@types/internal/autoclose-config.d.ts.map +1 -0
- package/dist/@types/internal/autoclose-cycle.d.ts +171 -0
- package/dist/@types/internal/autoclose-cycle.d.ts.map +1 -0
- package/dist/@types/internal/event-sourcing.d.ts +7 -7
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +3 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/reactions.d.ts.map +1 -1
- package/dist/@types/internal/sensitive.d.ts +6 -23
- package/dist/@types/internal/sensitive.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +141 -16
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +35 -3
- package/dist/@types/types/registry.d.ts.map +1 -1
- package/dist/{chunk-3Z2HU726.js → chunk-5PWLGL5E.js} +2 -2
- package/dist/{chunk-BY5JPOZR.js → chunk-NGVWJXEF.js} +9 -24
- package/dist/{chunk-BY5JPOZR.js.map → chunk-NGVWJXEF.js.map} +1 -1
- package/dist/index.cjs +411 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +399 -174
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +2 -2
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +4 -4
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-3Z2HU726.js.map → chunk-5PWLGL5E.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,9 @@ __export(index_exports, {
|
|
|
37
37
|
ConcurrencyError: () => ConcurrencyError,
|
|
38
38
|
ConsoleLogger: () => ConsoleLogger,
|
|
39
39
|
CsvFile: () => CsvFile,
|
|
40
|
+
DEFAULT_AUTOCLOSE_CYCLE_MS: () => DEFAULT_AUTOCLOSE_CYCLE_MS,
|
|
41
|
+
DEFAULT_CLOSE_BATCH_SIZE: () => DEFAULT_CLOSE_BATCH_SIZE,
|
|
42
|
+
DEFAULT_CLOSE_YIELD_MS: () => DEFAULT_CLOSE_YIELD_MS,
|
|
40
43
|
DEFAULT_LANE: () => DEFAULT_LANE,
|
|
41
44
|
DEFAULT_MAX_SUBSCRIBED_STREAMS: () => DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
42
45
|
DEFAULT_SETTLE_DEBOUNCE_MS: () => DEFAULT_SETTLE_DEBOUNCE_MS,
|
|
@@ -68,6 +71,7 @@ __export(index_exports, {
|
|
|
68
71
|
log: () => log,
|
|
69
72
|
port: () => port,
|
|
70
73
|
projection: () => projection,
|
|
74
|
+
resolveAutocloseConfig: () => resolveAutocloseConfig,
|
|
71
75
|
scoped: () => scoped,
|
|
72
76
|
sensitive: () => sensitive,
|
|
73
77
|
sleep: () => sleep,
|
|
@@ -416,32 +420,18 @@ function pii_fields(schema) {
|
|
|
416
420
|
return fields;
|
|
417
421
|
}
|
|
418
422
|
function pii_split(emitted, fields) {
|
|
419
|
-
const
|
|
420
|
-
const clean = {};
|
|
423
|
+
const data = { ...emitted.data };
|
|
421
424
|
const pii = {};
|
|
422
|
-
for (const
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
+
for (const f of fields) {
|
|
426
|
+
if (f in data) {
|
|
427
|
+
pii[f] = data[f];
|
|
428
|
+
delete data[f];
|
|
429
|
+
}
|
|
425
430
|
}
|
|
426
|
-
return { name: emitted.name, data
|
|
427
|
-
}
|
|
428
|
-
function pii_merge(event, fields) {
|
|
429
|
-
const data = event.data;
|
|
430
|
-
const pii = event.pii;
|
|
431
|
-
if (pii != null) {
|
|
432
|
-
return {
|
|
433
|
-
...event,
|
|
434
|
-
data: { ...data, ...pii }
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
const shredded = { ...data };
|
|
438
|
-
for (const f of fields) shredded[f] = SHREDDED;
|
|
439
|
-
return {
|
|
440
|
-
...event,
|
|
441
|
-
data: shredded
|
|
442
|
-
};
|
|
431
|
+
return { name: emitted.name, data, pii };
|
|
443
432
|
}
|
|
444
433
|
function pii_gate(event, fields, predicate, actor) {
|
|
434
|
+
if (fields.length === 0) return event;
|
|
445
435
|
const data = event.data;
|
|
446
436
|
if (event.pii == null) {
|
|
447
437
|
const shredded = { ...data };
|
|
@@ -1868,57 +1858,24 @@ var PASS_FACTORIES = {
|
|
|
1868
1858
|
"clock-anomalies": make_clock_anomalies_pass
|
|
1869
1859
|
};
|
|
1870
1860
|
|
|
1871
|
-
// src/internal/
|
|
1872
|
-
var
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
const set = existing_lanes ?? /* @__PURE__ */ new Set();
|
|
1890
|
-
set.add(lane_name);
|
|
1891
|
-
event_to_lanes.set(name, set);
|
|
1892
|
-
}
|
|
1893
|
-
const key = `${target}|${source ?? ""}`;
|
|
1894
|
-
const existing = statics.get(key);
|
|
1895
|
-
if (!existing) {
|
|
1896
|
-
statics.set(key, { stream: target, source, priority, lane });
|
|
1897
|
-
} else {
|
|
1898
|
-
if ((existing.lane ?? void 0) !== (lane ?? void 0))
|
|
1899
|
-
throw new Error(
|
|
1900
|
-
`Stream "${target}" has conflicting lane assignments ("${existing.lane ?? "default"}" vs "${lane ?? "default"}")`
|
|
1901
|
-
);
|
|
1902
|
-
if (priority > existing.priority) {
|
|
1903
|
-
statics.set(key, { ...existing, priority });
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
const event_to_state = /* @__PURE__ */ new Map();
|
|
1910
|
-
for (const merged of states.values()) {
|
|
1911
|
-
for (const event_name of Object.keys(merged.events)) {
|
|
1912
|
-
event_to_state.set(event_name, merged);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
return {
|
|
1916
|
-
static_targets: [...statics.values()],
|
|
1917
|
-
has_dynamic_resolvers,
|
|
1918
|
-
reactive_events,
|
|
1919
|
-
event_to_state,
|
|
1920
|
-
event_to_lanes
|
|
1921
|
-
};
|
|
1861
|
+
// src/internal/autoclose-config.ts
|
|
1862
|
+
var import_zod5 = require("zod");
|
|
1863
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MS = 6e4;
|
|
1864
|
+
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
1865
|
+
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
1866
|
+
var AutocloseOptionsSchema = import_zod5.z.object({
|
|
1867
|
+
autocloseCycleMs: import_zod5.z.number().min(1e4).max(36e5).default(DEFAULT_AUTOCLOSE_CYCLE_MS),
|
|
1868
|
+
closeBatchSize: import_zod5.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
1869
|
+
closeYieldMs: import_zod5.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
1870
|
+
closeOnError: import_zod5.z.boolean().default(false)
|
|
1871
|
+
});
|
|
1872
|
+
function resolveAutocloseConfig(options) {
|
|
1873
|
+
return AutocloseOptionsSchema.parse({
|
|
1874
|
+
autocloseCycleMs: options?.autocloseCycleMs,
|
|
1875
|
+
closeBatchSize: options?.closeBatchSize,
|
|
1876
|
+
closeYieldMs: options?.closeYieldMs,
|
|
1877
|
+
closeOnError: options?.closeOnError
|
|
1878
|
+
});
|
|
1922
1879
|
}
|
|
1923
1880
|
|
|
1924
1881
|
// src/internal/close-cycle.ts
|
|
@@ -2020,7 +1977,7 @@ async function load_restart_seeds(guarded, target_map, stream_info, event_to_sta
|
|
|
2020
1977
|
);
|
|
2021
1978
|
return;
|
|
2022
1979
|
}
|
|
2023
|
-
const snap2 = await load2(owner_state, stream);
|
|
1980
|
+
const snap2 = await load2(owner_state, { stream });
|
|
2024
1981
|
seed_states.set(stream, snap2.state);
|
|
2025
1982
|
})
|
|
2026
1983
|
);
|
|
@@ -2068,6 +2025,233 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
2068
2025
|
return truncated;
|
|
2069
2026
|
}
|
|
2070
2027
|
|
|
2028
|
+
// src/internal/correlator.ts
|
|
2029
|
+
var import_node_crypto = require("crypto");
|
|
2030
|
+
var BASE = 36;
|
|
2031
|
+
var SEG_WIDTH = 4;
|
|
2032
|
+
var SEG_SPACE = BASE ** SEG_WIDTH;
|
|
2033
|
+
function seg(n) {
|
|
2034
|
+
return n.toString(BASE).padStart(SEG_WIDTH, "0");
|
|
2035
|
+
}
|
|
2036
|
+
var default_correlator = ({ state: state2, action: action2 }) => {
|
|
2037
|
+
const s = state2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2038
|
+
const a = action2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2039
|
+
const ts = seg(Date.now() % SEG_SPACE);
|
|
2040
|
+
const rnd = seg((0, import_node_crypto.randomInt)(SEG_SPACE));
|
|
2041
|
+
return `${s}-${a}-${ts}${rnd}`;
|
|
2042
|
+
};
|
|
2043
|
+
function close_correlation(correlator, actor) {
|
|
2044
|
+
return correlator({
|
|
2045
|
+
state: "$close",
|
|
2046
|
+
action: "close",
|
|
2047
|
+
stream: "$close",
|
|
2048
|
+
actor
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// src/internal/autoclose-cycle.ts
|
|
2053
|
+
function yield_between_batches(ms) {
|
|
2054
|
+
if (ms <= 0) return Promise.resolve();
|
|
2055
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2056
|
+
}
|
|
2057
|
+
async function run_autoclose_cycle(deps) {
|
|
2058
|
+
const s = store2();
|
|
2059
|
+
let inspected = 0;
|
|
2060
|
+
let evaluated = 0;
|
|
2061
|
+
let predicate_errors = 0;
|
|
2062
|
+
const truncated = /* @__PURE__ */ new Map();
|
|
2063
|
+
const skipped = [];
|
|
2064
|
+
const stats = await s.query_stats(
|
|
2065
|
+
{},
|
|
2066
|
+
{ count: true, exclude: [TOMBSTONE_EVENT] }
|
|
2067
|
+
);
|
|
2068
|
+
const flush_batch = async (candidates2) => {
|
|
2069
|
+
if (candidates2.length === 0) return;
|
|
2070
|
+
const result = await run_close_cycle(candidates2, {
|
|
2071
|
+
reactive_events_size: deps.reactive_events_size,
|
|
2072
|
+
event_to_state: deps.event_to_state,
|
|
2073
|
+
load: deps.load,
|
|
2074
|
+
tombstone: deps.tombstone,
|
|
2075
|
+
logger: deps.logger,
|
|
2076
|
+
correlation: deps.correlation
|
|
2077
|
+
});
|
|
2078
|
+
for (const [stream, entry] of result.truncated) {
|
|
2079
|
+
truncated.set(stream, entry);
|
|
2080
|
+
}
|
|
2081
|
+
for (const stream of result.skipped) skipped.push(stream);
|
|
2082
|
+
await yield_between_batches(deps.config.closeYieldMs);
|
|
2083
|
+
};
|
|
2084
|
+
let candidates = [];
|
|
2085
|
+
for (const [stream, { head, count }] of stats) {
|
|
2086
|
+
inspected += 1;
|
|
2087
|
+
const owner = deps.event_to_state.get(head.name);
|
|
2088
|
+
if (!owner) continue;
|
|
2089
|
+
const predicate = deps.autoclose_policy(owner.name);
|
|
2090
|
+
if (!predicate) continue;
|
|
2091
|
+
let close = false;
|
|
2092
|
+
try {
|
|
2093
|
+
close = predicate(stream, head, count ?? 0);
|
|
2094
|
+
evaluated += 1;
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
predicate_errors += 1;
|
|
2097
|
+
deps.logger.error(
|
|
2098
|
+
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
2099
|
+
);
|
|
2100
|
+
close = deps.config.closeOnError;
|
|
2101
|
+
}
|
|
2102
|
+
if (!close) continue;
|
|
2103
|
+
const archiver = deps.autoclose_archiver(owner.name);
|
|
2104
|
+
const archive = archiver ? () => archiver(stream, head) : void 0;
|
|
2105
|
+
candidates.push({ stream, archive });
|
|
2106
|
+
if (candidates.length >= deps.config.closeBatchSize) {
|
|
2107
|
+
await flush_batch(candidates);
|
|
2108
|
+
candidates = [];
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
await flush_batch(candidates);
|
|
2112
|
+
return {
|
|
2113
|
+
inspected,
|
|
2114
|
+
evaluated,
|
|
2115
|
+
predicate_errors,
|
|
2116
|
+
close_result: { truncated, skipped }
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
var AutocloseController = class {
|
|
2120
|
+
deps;
|
|
2121
|
+
_timer;
|
|
2122
|
+
/**
|
|
2123
|
+
* Reentrancy guard. The interval fires the next tick on cadence
|
|
2124
|
+
* regardless of how long the previous one took; this flag drops
|
|
2125
|
+
* overlapping ticks so the cycle stays sequential per controller.
|
|
2126
|
+
*/
|
|
2127
|
+
_running = false;
|
|
2128
|
+
constructor(deps) {
|
|
2129
|
+
this.deps = deps;
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Start the cycle ticker. Returns `false` if already running so
|
|
2133
|
+
* accidental double-start doesn't stack timers (matches
|
|
2134
|
+
* `_correlate.start_polling` semantics).
|
|
2135
|
+
*/
|
|
2136
|
+
start() {
|
|
2137
|
+
if (this._timer) return false;
|
|
2138
|
+
this._timer = setInterval(() => {
|
|
2139
|
+
this.run_once().catch((err) => {
|
|
2140
|
+
this.deps.logger.error(
|
|
2141
|
+
`Autoclose cycle errored: ${err instanceof Error ? err.message : String(err)}`
|
|
2142
|
+
);
|
|
2143
|
+
});
|
|
2144
|
+
}, this.deps.config.autocloseCycleMs);
|
|
2145
|
+
this._timer.unref();
|
|
2146
|
+
return true;
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Stop the cycle ticker. Idempotent.
|
|
2150
|
+
*/
|
|
2151
|
+
stop() {
|
|
2152
|
+
if (this._timer) {
|
|
2153
|
+
clearInterval(this._timer);
|
|
2154
|
+
this._timer = void 0;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Whether the ticker is currently running.
|
|
2159
|
+
*/
|
|
2160
|
+
get is_running() {
|
|
2161
|
+
return this._timer !== void 0;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Run one cycle synchronously. The controller's interval callback
|
|
2165
|
+
* delegates here; tests can invoke it directly to deterministically
|
|
2166
|
+
* exercise the cycle without spinning real timers.
|
|
2167
|
+
*
|
|
2168
|
+
* Overlapping invocations short-circuit: if a previous tick is
|
|
2169
|
+
* still running, this one drops and returns `null`. The orchestrator
|
|
2170
|
+
* never sees a queue of pending ticks regardless of how slow the
|
|
2171
|
+
* predicate / archive / truncate are.
|
|
2172
|
+
*/
|
|
2173
|
+
async run_once() {
|
|
2174
|
+
if (this._running) return null;
|
|
2175
|
+
this._running = true;
|
|
2176
|
+
try {
|
|
2177
|
+
const correlation = close_correlation(
|
|
2178
|
+
this.deps.correlator,
|
|
2179
|
+
this.deps.close_actor
|
|
2180
|
+
);
|
|
2181
|
+
const result = await run_autoclose_cycle({
|
|
2182
|
+
autoclose_policy: this.deps.autoclose_policy,
|
|
2183
|
+
autoclose_archiver: this.deps.autoclose_archiver,
|
|
2184
|
+
event_to_state: this.deps.event_to_state,
|
|
2185
|
+
reactive_events_size: this.deps.reactive_events_size,
|
|
2186
|
+
load: this.deps.load,
|
|
2187
|
+
tombstone: this.deps.tombstone,
|
|
2188
|
+
logger: this.deps.logger,
|
|
2189
|
+
config: this.deps.config,
|
|
2190
|
+
correlation
|
|
2191
|
+
});
|
|
2192
|
+
if (result.close_result.truncated.size > 0) {
|
|
2193
|
+
this.deps.on_closed(result.close_result);
|
|
2194
|
+
}
|
|
2195
|
+
return result;
|
|
2196
|
+
} finally {
|
|
2197
|
+
this._running = false;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
// src/internal/build-classify.ts
|
|
2203
|
+
var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
|
|
2204
|
+
function classify_registry(registry, states) {
|
|
2205
|
+
const statics = /* @__PURE__ */ new Map();
|
|
2206
|
+
const reactive_events = /* @__PURE__ */ new Set();
|
|
2207
|
+
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
2208
|
+
let has_dynamic_resolvers = false;
|
|
2209
|
+
for (const [name, register] of Object.entries(registry.events)) {
|
|
2210
|
+
if (register.reactions.size > 0) reactive_events.add(name);
|
|
2211
|
+
for (const reaction of register.reactions.values()) {
|
|
2212
|
+
if (typeof reaction.resolver === "function") {
|
|
2213
|
+
has_dynamic_resolvers = true;
|
|
2214
|
+
event_to_lanes.set(name, ALL_LANES);
|
|
2215
|
+
} else {
|
|
2216
|
+
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
2217
|
+
const lane_name = lane ?? "default";
|
|
2218
|
+
const existing_lanes = event_to_lanes.get(name);
|
|
2219
|
+
if (existing_lanes !== ALL_LANES) {
|
|
2220
|
+
const set = existing_lanes ?? /* @__PURE__ */ new Set();
|
|
2221
|
+
set.add(lane_name);
|
|
2222
|
+
event_to_lanes.set(name, set);
|
|
2223
|
+
}
|
|
2224
|
+
const key = `${target}|${source ?? ""}`;
|
|
2225
|
+
const existing = statics.get(key);
|
|
2226
|
+
if (!existing) {
|
|
2227
|
+
statics.set(key, { stream: target, source, priority, lane });
|
|
2228
|
+
} else {
|
|
2229
|
+
if ((existing.lane ?? void 0) !== (lane ?? void 0))
|
|
2230
|
+
throw new Error(
|
|
2231
|
+
`Stream "${target}" has conflicting lane assignments ("${existing.lane ?? "default"}" vs "${lane ?? "default"}")`
|
|
2232
|
+
);
|
|
2233
|
+
if (priority > existing.priority) {
|
|
2234
|
+
statics.set(key, { ...existing, priority });
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
const event_to_state = /* @__PURE__ */ new Map();
|
|
2241
|
+
for (const merged of states.values()) {
|
|
2242
|
+
for (const event_name of Object.keys(merged.events)) {
|
|
2243
|
+
event_to_state.set(event_name, merged);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
return {
|
|
2247
|
+
static_targets: [...statics.values()],
|
|
2248
|
+
has_dynamic_resolvers,
|
|
2249
|
+
reactive_events,
|
|
2250
|
+
event_to_state,
|
|
2251
|
+
event_to_lanes
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2071
2255
|
// src/internal/correlate-cycle.ts
|
|
2072
2256
|
var CorrelateCycle = class {
|
|
2073
2257
|
_checkpoint = -1;
|
|
@@ -2196,30 +2380,6 @@ var CorrelateCycle = class {
|
|
|
2196
2380
|
}
|
|
2197
2381
|
};
|
|
2198
2382
|
|
|
2199
|
-
// src/internal/correlator.ts
|
|
2200
|
-
var import_node_crypto = require("crypto");
|
|
2201
|
-
var BASE = 36;
|
|
2202
|
-
var SEG_WIDTH = 4;
|
|
2203
|
-
var SEG_SPACE = BASE ** SEG_WIDTH;
|
|
2204
|
-
function seg(n) {
|
|
2205
|
-
return n.toString(BASE).padStart(SEG_WIDTH, "0");
|
|
2206
|
-
}
|
|
2207
|
-
var default_correlator = ({ state: state2, action: action2 }) => {
|
|
2208
|
-
const s = state2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2209
|
-
const a = action2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2210
|
-
const ts = seg(Date.now() % SEG_SPACE);
|
|
2211
|
-
const rnd = seg((0, import_node_crypto.randomInt)(SEG_SPACE));
|
|
2212
|
-
return `${s}-${a}-${ts}${rnd}`;
|
|
2213
|
-
};
|
|
2214
|
-
function close_correlation(correlator, actor) {
|
|
2215
|
-
return correlator({
|
|
2216
|
-
state: "$close",
|
|
2217
|
-
action: "close",
|
|
2218
|
-
stream: "$close",
|
|
2219
|
-
actor
|
|
2220
|
-
});
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
2383
|
// src/internal/drain-cycle.ts
|
|
2224
2384
|
var import_node_crypto2 = require("crypto");
|
|
2225
2385
|
|
|
@@ -2384,7 +2544,6 @@ async function scan(source, opts = {}, callback) {
|
|
|
2384
2544
|
migrated = {
|
|
2385
2545
|
...event,
|
|
2386
2546
|
name: migration.to,
|
|
2387
|
-
// biome-ignore lint/suspicious/noExplicitAny: migration target shape is caller-defined
|
|
2388
2547
|
data: new_data
|
|
2389
2548
|
};
|
|
2390
2549
|
migrated_count++;
|
|
@@ -2433,39 +2592,37 @@ async function scan(source, opts = {}, callback) {
|
|
|
2433
2592
|
}
|
|
2434
2593
|
};
|
|
2435
2594
|
}
|
|
2436
|
-
async function load(me,
|
|
2595
|
+
async function load(me, target, callback) {
|
|
2596
|
+
const { stream, actor, asOf } = target;
|
|
2437
2597
|
const time_travel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
2438
2598
|
const cached = time_travel ? void 0 : await cache2().get(stream);
|
|
2439
2599
|
const cache_hit = !!cached;
|
|
2440
2600
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
2441
2601
|
let patches = cached?.patches ?? 0;
|
|
2442
2602
|
let snaps = cached?.snaps ?? 0;
|
|
2443
|
-
let version = cached?.version ?? -1;
|
|
2444
2603
|
let replayed = 0;
|
|
2445
2604
|
let event;
|
|
2446
2605
|
await store2().query(
|
|
2447
|
-
(
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
if (e.name === SNAP_EVENT) {
|
|
2452
|
-
state2 = e.data;
|
|
2606
|
+
(raw) => {
|
|
2607
|
+
event = me.view(raw, actor);
|
|
2608
|
+
if (event.name === SNAP_EVENT) {
|
|
2609
|
+
state2 = event.data;
|
|
2453
2610
|
snaps++;
|
|
2454
2611
|
patches = 0;
|
|
2455
2612
|
replayed++;
|
|
2456
|
-
} else if (me.patch[
|
|
2457
|
-
state2 = (0, import_act_patch.patch)(state2, me.patch[
|
|
2613
|
+
} else if (me.patch[event.name]) {
|
|
2614
|
+
state2 = (0, import_act_patch.patch)(state2, me.patch[event.name](event, state2));
|
|
2458
2615
|
patches++;
|
|
2459
2616
|
replayed++;
|
|
2460
|
-
} else if (
|
|
2617
|
+
} else if (event.name !== TOMBSTONE_EVENT) {
|
|
2461
2618
|
log().warn(
|
|
2462
|
-
`Skipping unknown event "${String(
|
|
2619
|
+
`Skipping unknown event "${String(event.name)}" on stream "${stream}" (id=${event.id}) \u2014 no reducer in state "${me.name}"`
|
|
2463
2620
|
);
|
|
2464
2621
|
}
|
|
2465
2622
|
callback?.({
|
|
2466
2623
|
event,
|
|
2467
2624
|
state: state2,
|
|
2468
|
-
version,
|
|
2625
|
+
version: event.version,
|
|
2469
2626
|
patches,
|
|
2470
2627
|
snaps,
|
|
2471
2628
|
cache_hit,
|
|
@@ -2478,39 +2635,43 @@ async function load(me, stream, callback, asOf, actor) {
|
|
|
2478
2635
|
...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
|
|
2479
2636
|
}
|
|
2480
2637
|
);
|
|
2481
|
-
if (replayed > 0 && !time_travel && event) {
|
|
2638
|
+
if (replayed > 0 && !time_travel && event && !me.pii_aware) {
|
|
2482
2639
|
await cache2().set(stream, {
|
|
2483
2640
|
state: state2,
|
|
2484
|
-
version,
|
|
2641
|
+
version: event.version,
|
|
2485
2642
|
event_id: event.id,
|
|
2486
2643
|
patches,
|
|
2487
2644
|
snaps
|
|
2488
2645
|
});
|
|
2489
2646
|
}
|
|
2490
|
-
return {
|
|
2647
|
+
return {
|
|
2648
|
+
event,
|
|
2649
|
+
state: state2,
|
|
2650
|
+
version: event?.version ?? cached?.version ?? -1,
|
|
2651
|
+
patches,
|
|
2652
|
+
snaps,
|
|
2653
|
+
cache_hit,
|
|
2654
|
+
replayed
|
|
2655
|
+
};
|
|
2491
2656
|
}
|
|
2492
|
-
async function action(me, action2, target, payload,
|
|
2657
|
+
async function action(me, action2, target, payload, options) {
|
|
2493
2658
|
const { stream, expectedVersion, actor } = target;
|
|
2494
2659
|
if (!stream) throw new Error("Missing target stream");
|
|
2495
|
-
const
|
|
2660
|
+
const reactingTo = options?.reactingTo;
|
|
2661
|
+
const correlator = options?.correlator ?? default_correlator;
|
|
2662
|
+
const validated = validate(action2, payload, me.actions[action2]);
|
|
2496
2663
|
const opts = me.options?.[action2];
|
|
2497
2664
|
const max_retries = opts?.maxRetries ?? 0;
|
|
2498
2665
|
for (let attempt = 0; ; attempt++) {
|
|
2499
2666
|
try {
|
|
2500
|
-
const snapshot = await load(
|
|
2501
|
-
me,
|
|
2502
|
-
stream,
|
|
2503
|
-
void 0,
|
|
2504
|
-
void 0,
|
|
2505
|
-
target.actor
|
|
2506
|
-
);
|
|
2667
|
+
const snapshot = await load(me, { stream, actor: target.actor });
|
|
2507
2668
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
2508
2669
|
throw new StreamClosedError(stream);
|
|
2509
2670
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
2510
2671
|
if (me.given) {
|
|
2511
2672
|
const invariants = me.given[action2] || [];
|
|
2512
|
-
invariants.forEach(({ valid, description }) => {
|
|
2513
|
-
if (!
|
|
2673
|
+
invariants.forEach(({ valid: valid2, description }) => {
|
|
2674
|
+
if (!valid2(snapshot.state, actor))
|
|
2514
2675
|
throw new InvariantError(
|
|
2515
2676
|
action2,
|
|
2516
2677
|
validated,
|
|
@@ -2526,24 +2687,11 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2526
2687
|
return [snapshot];
|
|
2527
2688
|
}
|
|
2528
2689
|
const tuples = Array.isArray(result[0]) ? result : [result];
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
const evt = name;
|
|
2535
|
-
if (deprecated.has(evt) && !warned.has(evt)) {
|
|
2536
|
-
warned.add(evt);
|
|
2537
|
-
log().warn(
|
|
2538
|
-
`Action "${String(action2)}" emitted deprecated event "${evt}". A newer version exists in the registry \u2014 update the action's .emit() to target the current version. (warned once per process)`
|
|
2539
|
-
);
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
const emitted = tuples.map(([name, data]) => {
|
|
2544
|
-
const validated2 = skipValidation ? data : validate(name, data, me.events[name]);
|
|
2545
|
-
return me.message({ name, data: validated2 });
|
|
2546
|
-
});
|
|
2690
|
+
const valid = tuples.map(([name, data]) => ({
|
|
2691
|
+
name,
|
|
2692
|
+
data: validate(name, data, me.events[name])
|
|
2693
|
+
}));
|
|
2694
|
+
const emitted = valid.map((e) => me.message(e));
|
|
2547
2695
|
const meta = {
|
|
2548
2696
|
correlation: reactingTo?.meta.correlation || correlator({
|
|
2549
2697
|
action: action2,
|
|
@@ -2584,12 +2732,13 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2584
2732
|
throw error;
|
|
2585
2733
|
}
|
|
2586
2734
|
let { state: state2, patches } = snapshot;
|
|
2587
|
-
const snapshots = committed.map((
|
|
2735
|
+
const snapshots = committed.map((row, i) => {
|
|
2736
|
+
const event = { ...row, data: valid[i].data };
|
|
2588
2737
|
const p = me.patch[event.name](event, state2);
|
|
2589
2738
|
state2 = (0, import_act_patch.patch)(state2, p);
|
|
2590
2739
|
patches++;
|
|
2591
2740
|
return {
|
|
2592
|
-
event
|
|
2741
|
+
event,
|
|
2593
2742
|
state: state2,
|
|
2594
2743
|
version: event.version,
|
|
2595
2744
|
patches,
|
|
@@ -2601,13 +2750,15 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2601
2750
|
});
|
|
2602
2751
|
const last = snapshots.at(-1);
|
|
2603
2752
|
const snapped = me.snap?.(last);
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2753
|
+
if (!me.pii_aware) {
|
|
2754
|
+
cache2().set(stream, {
|
|
2755
|
+
state: last.state,
|
|
2756
|
+
version: last.event.version,
|
|
2757
|
+
event_id: last.event.id,
|
|
2758
|
+
patches: snapped ? 0 : last.patches,
|
|
2759
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
2760
|
+
}).catch((err) => log().error(err));
|
|
2761
|
+
}
|
|
2611
2762
|
if (snapped) void snap(last);
|
|
2612
2763
|
return snapshots;
|
|
2613
2764
|
} catch (error) {
|
|
@@ -2674,15 +2825,7 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
2674
2825
|
return result;
|
|
2675
2826
|
});
|
|
2676
2827
|
function build_es(logger, correlator = default_correlator) {
|
|
2677
|
-
const bound_action = (me, action_name, target, payload,
|
|
2678
|
-
me,
|
|
2679
|
-
action_name,
|
|
2680
|
-
target,
|
|
2681
|
-
payload,
|
|
2682
|
-
reactingTo,
|
|
2683
|
-
skipValidation,
|
|
2684
|
-
correlator
|
|
2685
|
-
);
|
|
2828
|
+
const bound_action = (me, action_name, target, payload, options) => action(me, action_name, target, payload, { correlator, ...options });
|
|
2686
2829
|
if (logger.level !== "trace") {
|
|
2687
2830
|
return {
|
|
2688
2831
|
snap,
|
|
@@ -2701,7 +2844,7 @@ function build_es(logger, correlator = default_correlator) {
|
|
|
2701
2844
|
)
|
|
2702
2845
|
);
|
|
2703
2846
|
}),
|
|
2704
|
-
load: traced(load, (result, _me,
|
|
2847
|
+
load: traced(load, (result, _me, target) => {
|
|
2705
2848
|
const stats = stats_marker(
|
|
2706
2849
|
result.version,
|
|
2707
2850
|
result.replayed,
|
|
@@ -2712,7 +2855,7 @@ function build_es(logger, correlator = default_correlator) {
|
|
|
2712
2855
|
es_caption(
|
|
2713
2856
|
"load",
|
|
2714
2857
|
C_GREEN,
|
|
2715
|
-
`${stream}${as_of_marker(asOf)} ${cache_marker(result.cache_hit)} ${stats}`
|
|
2858
|
+
`${target.stream}${as_of_marker(target.asOf)} ${cache_marker(result.cache_hit)} ${stats}`
|
|
2716
2859
|
)
|
|
2717
2860
|
);
|
|
2718
2861
|
}),
|
|
@@ -3012,7 +3155,7 @@ var DrainController = class {
|
|
|
3012
3155
|
};
|
|
3013
3156
|
|
|
3014
3157
|
// src/internal/merge.ts
|
|
3015
|
-
var
|
|
3158
|
+
var import_zod6 = require("zod");
|
|
3016
3159
|
function base_type_name(zodType) {
|
|
3017
3160
|
let t = zodType;
|
|
3018
3161
|
while (typeof t.unwrap === "function") {
|
|
@@ -3021,7 +3164,7 @@ function base_type_name(zodType) {
|
|
|
3021
3164
|
return t.constructor.name;
|
|
3022
3165
|
}
|
|
3023
3166
|
function merge_schemas(existing, incoming, state_name) {
|
|
3024
|
-
if (existing instanceof
|
|
3167
|
+
if (existing instanceof import_zod6.ZodObject && incoming instanceof import_zod6.ZodObject) {
|
|
3025
3168
|
const existing_shape = existing.shape;
|
|
3026
3169
|
const incoming_shape = incoming.shape;
|
|
3027
3170
|
for (const key of Object.keys(incoming_shape)) {
|
|
@@ -3200,13 +3343,10 @@ function build_handle(deps) {
|
|
|
3200
3343
|
};
|
|
3201
3344
|
for (const payload of payloads) {
|
|
3202
3345
|
const { event, handler } = payload;
|
|
3203
|
-
scoped_app.do = (action2, target, action_payload,
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
reactingTo ?? event,
|
|
3208
|
-
skipValidation
|
|
3209
|
-
);
|
|
3346
|
+
scoped_app.do = (action2, target, action_payload, options) => bound_do(action2, target, action_payload, {
|
|
3347
|
+
...options,
|
|
3348
|
+
reactingTo: options?.reactingTo ?? event
|
|
3349
|
+
});
|
|
3210
3350
|
try {
|
|
3211
3351
|
await handler(event, stream, scoped_app);
|
|
3212
3352
|
at = event.id;
|
|
@@ -3343,6 +3483,22 @@ var Act = class {
|
|
|
3343
3483
|
registry;
|
|
3344
3484
|
/** Map of state name → state definition; populated by the builder. */
|
|
3345
3485
|
_states;
|
|
3486
|
+
/**
|
|
3487
|
+
* Resolved autoclose configuration (#837 / epic #802). Frozen at
|
|
3488
|
+
* `act().build()` via {@link resolveAutocloseConfig}.
|
|
3489
|
+
*
|
|
3490
|
+
* @internal
|
|
3491
|
+
*/
|
|
3492
|
+
_autoclose_config;
|
|
3493
|
+
/**
|
|
3494
|
+
* App-level autoclose controller. `undefined` when no state declares
|
|
3495
|
+
* `.autocloses(...)` — the controller is never constructed, so
|
|
3496
|
+
* `start_correlations()` / `stop_correlations()` skip the autoclose
|
|
3497
|
+
* lifecycle and the orchestrator pays zero per-tick cost.
|
|
3498
|
+
*
|
|
3499
|
+
* @internal
|
|
3500
|
+
*/
|
|
3501
|
+
_autoclose;
|
|
3346
3502
|
/**
|
|
3347
3503
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
3348
3504
|
* via {@link ActLifecycleEvents}.
|
|
@@ -3477,6 +3633,7 @@ var Act = class {
|
|
|
3477
3633
|
this._reactive_events = reactive_events;
|
|
3478
3634
|
this._listen = options.listen !== false;
|
|
3479
3635
|
this._drain = options.drain !== false;
|
|
3636
|
+
this._autoclose_config = resolveAutocloseConfig(options);
|
|
3480
3637
|
this._event_to_state = event_to_state;
|
|
3481
3638
|
this._event_to_lanes = event_to_lanes;
|
|
3482
3639
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -3540,6 +3697,26 @@ var Act = class {
|
|
|
3540
3697
|
},
|
|
3541
3698
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
3542
3699
|
);
|
|
3700
|
+
const states_with_policy = [...this._states.values()].filter(
|
|
3701
|
+
(s) => s.autoclose
|
|
3702
|
+
);
|
|
3703
|
+
this._autoclose = states_with_policy.length ? new AutocloseController({
|
|
3704
|
+
// Internal cycle takes the loose `(string) => …` signature; the
|
|
3705
|
+
// public `registry.autoclose_policy` narrows to
|
|
3706
|
+
// `keyof TStateMap & string`. Thin lambdas bridge across the
|
|
3707
|
+
// narrowing — runtime semantics are unchanged.
|
|
3708
|
+
autoclose_policy: (name) => this.registry.autoclose_policy(name),
|
|
3709
|
+
autoclose_archiver: (name) => this.registry.autoclose_archiver(name),
|
|
3710
|
+
event_to_state: this._event_to_state,
|
|
3711
|
+
reactive_events_size: this._reactive_events.size,
|
|
3712
|
+
load: this._es.load,
|
|
3713
|
+
tombstone: this._es.tombstone,
|
|
3714
|
+
logger: this._logger,
|
|
3715
|
+
config: this._autoclose_config,
|
|
3716
|
+
correlator: this._correlator,
|
|
3717
|
+
close_actor: { id: "$autoclose", name: "autoclose" },
|
|
3718
|
+
on_closed: (result) => this.emit("closed", result)
|
|
3719
|
+
}) : void 0;
|
|
3543
3720
|
this._notify_disposer = this._wire_notify(options.scoped?.store ?? store2());
|
|
3544
3721
|
dispose(() => this.shutdown());
|
|
3545
3722
|
}
|
|
@@ -3612,8 +3789,9 @@ var Act = class {
|
|
|
3612
3789
|
* @param action - The name of the action to execute
|
|
3613
3790
|
* @param target - Target specification with stream ID and actor context
|
|
3614
3791
|
* @param payload - Action payload matching the action's schema
|
|
3615
|
-
* @param
|
|
3616
|
-
*
|
|
3792
|
+
* @param options - Per-call dispatch options ({@link DoOptions}) —
|
|
3793
|
+
* `reactingTo` to thread correlation, `correlator` to override the
|
|
3794
|
+
* framework or orchestrator-level correlator for this call only.
|
|
3617
3795
|
* @returns Array of snapshots for all affected states (usually one)
|
|
3618
3796
|
*
|
|
3619
3797
|
* @throws {ValidationError} If payload doesn't match action schema
|
|
@@ -3668,7 +3846,7 @@ var Act = class {
|
|
|
3668
3846
|
* { amount: event.data.items.length }
|
|
3669
3847
|
* );
|
|
3670
3848
|
* // To use a different correlation, pass reactingTo explicitly:
|
|
3671
|
-
* // await app.do("reduceStock", target, payload, customEvent);
|
|
3849
|
+
* // await app.do("reduceStock", target, payload, { reactingTo: customEvent });
|
|
3672
3850
|
* })
|
|
3673
3851
|
* .to("inventory-1")
|
|
3674
3852
|
* .build();
|
|
@@ -3678,15 +3856,14 @@ var Act = class {
|
|
|
3678
3856
|
* @see {@link Snapshot} for return value structure
|
|
3679
3857
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
3680
3858
|
*/
|
|
3681
|
-
async do(action2, target, payload,
|
|
3859
|
+
async do(action2, target, payload, options) {
|
|
3682
3860
|
return this._scoped(async () => {
|
|
3683
3861
|
const snapshots = await this._es.action(
|
|
3684
3862
|
this.registry.actions[action2],
|
|
3685
3863
|
action2,
|
|
3686
3864
|
target,
|
|
3687
3865
|
payload,
|
|
3688
|
-
|
|
3689
|
-
skipValidation
|
|
3866
|
+
options
|
|
3690
3867
|
);
|
|
3691
3868
|
if (this._reactive_events.size > 0)
|
|
3692
3869
|
this._arm_for_event_names(
|
|
@@ -3696,7 +3873,7 @@ var Act = class {
|
|
|
3696
3873
|
return snapshots;
|
|
3697
3874
|
});
|
|
3698
3875
|
}
|
|
3699
|
-
async load(stateOrName,
|
|
3876
|
+
async load(stateOrName, streamOrTarget, callback, asOf) {
|
|
3700
3877
|
return this._scoped(async () => {
|
|
3701
3878
|
let merged;
|
|
3702
3879
|
if (typeof stateOrName === "string") {
|
|
@@ -3706,7 +3883,12 @@ var Act = class {
|
|
|
3706
3883
|
} else {
|
|
3707
3884
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
3708
3885
|
}
|
|
3709
|
-
|
|
3886
|
+
const target = typeof streamOrTarget === "string" ? {
|
|
3887
|
+
stream: streamOrTarget,
|
|
3888
|
+
actor: void 0,
|
|
3889
|
+
asOf
|
|
3890
|
+
} : streamOrTarget;
|
|
3891
|
+
return await this._es.load(merged, target, callback);
|
|
3710
3892
|
});
|
|
3711
3893
|
}
|
|
3712
3894
|
/**
|
|
@@ -4026,7 +4208,9 @@ var Act = class {
|
|
|
4026
4208
|
* @see {@link stop_correlations} to stop the worker
|
|
4027
4209
|
*/
|
|
4028
4210
|
start_correlations(query = {}, frequency = 1e4, callback) {
|
|
4029
|
-
|
|
4211
|
+
const started = this._correlate.start_polling(query, frequency, callback);
|
|
4212
|
+
this._autoclose?.start();
|
|
4213
|
+
return started;
|
|
4030
4214
|
}
|
|
4031
4215
|
/**
|
|
4032
4216
|
* Stops the automatic correlation worker.
|
|
@@ -4047,6 +4231,7 @@ var Act = class {
|
|
|
4047
4231
|
*/
|
|
4048
4232
|
stop_correlations() {
|
|
4049
4233
|
this._correlate.stop_polling();
|
|
4234
|
+
this._autoclose?.stop();
|
|
4050
4235
|
}
|
|
4051
4236
|
/**
|
|
4052
4237
|
* Cancels any pending or active settle cycle.
|
|
@@ -4430,11 +4615,18 @@ function act() {
|
|
|
4430
4615
|
const states = /* @__PURE__ */ new Map();
|
|
4431
4616
|
const _sf = /* @__PURE__ */ new Map();
|
|
4432
4617
|
const _dp = /* @__PURE__ */ new Map();
|
|
4618
|
+
const _de = /* @__PURE__ */ new Map();
|
|
4619
|
+
const _ac = /* @__PURE__ */ new Map();
|
|
4620
|
+
const _aa = /* @__PURE__ */ new Map();
|
|
4621
|
+
const EMPTY_DEPRECATED = /* @__PURE__ */ new Set();
|
|
4433
4622
|
const registry = {
|
|
4434
4623
|
actions: {},
|
|
4435
4624
|
events: {},
|
|
4436
4625
|
sensitive_fields: (event_name) => _sf.get(event_name) ?? [],
|
|
4437
|
-
disclosure_predicate: (state_name) => _dp.get(state_name) ?? null
|
|
4626
|
+
disclosure_predicate: (state_name) => _dp.get(state_name) ?? null,
|
|
4627
|
+
deprecated_events: (state_name) => _de.get(state_name) ?? EMPTY_DEPRECATED,
|
|
4628
|
+
autoclose_policy: (state_name) => _ac.get(state_name) ?? null,
|
|
4629
|
+
autoclose_archiver: (state_name) => _aa.get(state_name) ?? null
|
|
4438
4630
|
};
|
|
4439
4631
|
const pending_projections = [];
|
|
4440
4632
|
const batch_handlers = /* @__PURE__ */ new Map();
|
|
@@ -4446,7 +4638,7 @@ function act() {
|
|
|
4446
4638
|
const event_names = Object.keys(state2.events);
|
|
4447
4639
|
const deprecated = deprecated_event_names(event_names);
|
|
4448
4640
|
if (deprecated.size === 0) continue;
|
|
4449
|
-
state2.
|
|
4641
|
+
_de.set(state2.name, deprecated);
|
|
4450
4642
|
for (const name of deprecated) {
|
|
4451
4643
|
const current = current_version_of(name, event_names);
|
|
4452
4644
|
deprecation_summary.push({
|
|
@@ -4561,6 +4753,8 @@ function act() {
|
|
|
4561
4753
|
}
|
|
4562
4754
|
for (const state2 of states.values()) {
|
|
4563
4755
|
if (state2.disclose) _dp.set(state2.name, state2.disclose);
|
|
4756
|
+
if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
|
|
4757
|
+
if (state2.archive) _aa.set(state2.name, state2.archive);
|
|
4564
4758
|
const fields_by_event = /* @__PURE__ */ new Map();
|
|
4565
4759
|
for (const event_name of Object.keys(state2.events)) {
|
|
4566
4760
|
const fields = _sf.get(event_name);
|
|
@@ -4574,18 +4768,17 @@ function act() {
|
|
|
4574
4768
|
);
|
|
4575
4769
|
}
|
|
4576
4770
|
const disclose = state2.disclose ?? null;
|
|
4577
|
-
state2.
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4771
|
+
state2.pii_aware = true;
|
|
4772
|
+
state2.view = (event, actor) => pii_gate(
|
|
4773
|
+
event,
|
|
4774
|
+
fields_by_event.get(event.name) ?? [],
|
|
4775
|
+
disclose,
|
|
4776
|
+
actor
|
|
4777
|
+
);
|
|
4581
4778
|
state2.message = (validated) => {
|
|
4582
4779
|
const fields = fields_by_event.get(validated.name);
|
|
4583
4780
|
return fields ? pii_split(validated, fields) : validated;
|
|
4584
4781
|
};
|
|
4585
|
-
for (const [event_name, fields] of fields_by_event) {
|
|
4586
|
-
const original = state2.patch[event_name];
|
|
4587
|
-
state2.patch[event_name] = (event, s) => original(pii_merge(event, fields), s);
|
|
4588
|
-
}
|
|
4589
4782
|
}
|
|
4590
4783
|
for (const [target, original] of batch_handlers) {
|
|
4591
4784
|
const wrapped = async (events, stream) => {
|
|
@@ -4765,9 +4958,10 @@ function state(entry) {
|
|
|
4765
4958
|
init,
|
|
4766
4959
|
patch: default_patch,
|
|
4767
4960
|
on: {},
|
|
4768
|
-
// Step delegates initialized as identity
|
|
4769
|
-
//
|
|
4770
|
-
// the gate / split.
|
|
4961
|
+
// Step delegates initialized as identity, `pii_aware` false.
|
|
4962
|
+
// `act().build()` flips both on states with `sensitive(...)`
|
|
4963
|
+
// events to bake in the gate / split and gate the cache write.
|
|
4964
|
+
pii_aware: false,
|
|
4771
4965
|
view: (event) => event,
|
|
4772
4966
|
message: (validated) => validated
|
|
4773
4967
|
};
|
|
@@ -4828,6 +5022,24 @@ function action_builder(state2) {
|
|
|
4828
5022
|
internal.disclose = disclose;
|
|
4829
5023
|
return builder;
|
|
4830
5024
|
},
|
|
5025
|
+
autocloses(predicate) {
|
|
5026
|
+
if (typeof predicate !== "function") {
|
|
5027
|
+
throw new Error(
|
|
5028
|
+
".autocloses(predicate) requires a function; got " + typeof predicate
|
|
5029
|
+
);
|
|
5030
|
+
}
|
|
5031
|
+
internal.autoclose = predicate;
|
|
5032
|
+
return builder;
|
|
5033
|
+
},
|
|
5034
|
+
archives(archive) {
|
|
5035
|
+
if (typeof archive !== "function") {
|
|
5036
|
+
throw new Error(
|
|
5037
|
+
".archives(archive) requires a function; got " + typeof archive
|
|
5038
|
+
);
|
|
5039
|
+
}
|
|
5040
|
+
internal.archive = archive;
|
|
5041
|
+
return builder;
|
|
5042
|
+
},
|
|
4831
5043
|
build() {
|
|
4832
5044
|
return internal;
|
|
4833
5045
|
}
|
|
@@ -5004,6 +5216,9 @@ function write_line(writer, line) {
|
|
|
5004
5216
|
ConcurrencyError,
|
|
5005
5217
|
ConsoleLogger,
|
|
5006
5218
|
CsvFile,
|
|
5219
|
+
DEFAULT_AUTOCLOSE_CYCLE_MS,
|
|
5220
|
+
DEFAULT_CLOSE_BATCH_SIZE,
|
|
5221
|
+
DEFAULT_CLOSE_YIELD_MS,
|
|
5007
5222
|
DEFAULT_LANE,
|
|
5008
5223
|
DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
5009
5224
|
DEFAULT_SETTLE_DEBOUNCE_MS,
|
|
@@ -5035,6 +5250,7 @@ function write_line(writer, line) {
|
|
|
5035
5250
|
log,
|
|
5036
5251
|
port,
|
|
5037
5252
|
projection,
|
|
5253
|
+
resolveAutocloseConfig,
|
|
5038
5254
|
scoped,
|
|
5039
5255
|
sensitive,
|
|
5040
5256
|
sleep,
|