@cosystem/core 0.0.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/index.mjs ADDED
@@ -0,0 +1,1056 @@
1
+ import { create, createReactiveTracker } from "coaction";
2
+ //#region src/errors.ts
3
+ var CosystemError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "CosystemError";
7
+ }
8
+ };
9
+ var FrozenContainerError = class extends CosystemError {
10
+ constructor() {
11
+ super("Container provider graph is frozen and can no longer be mutated.");
12
+ this.name = "FrozenContainerError";
13
+ }
14
+ };
15
+ var MissingProviderError = class extends CosystemError {
16
+ constructor(token, path) {
17
+ super([
18
+ `Missing provider for ${token}.`,
19
+ path.length > 0 ? "Resolution path:" : "",
20
+ ...path.map((entry) => ` ${entry}`)
21
+ ].filter(Boolean).join("\n"));
22
+ this.name = "MissingProviderError";
23
+ }
24
+ };
25
+ var DuplicateProviderError = class extends CosystemError {
26
+ constructor(token) {
27
+ super(`Duplicate non-multi provider for ${token}. Use override() or mark providers as multi.`);
28
+ this.name = "DuplicateProviderError";
29
+ }
30
+ };
31
+ var AmbiguousProviderError = class extends CosystemError {
32
+ constructor(token) {
33
+ super(`Multiple providers registered for ${token}. Use getAll() instead of get().`);
34
+ this.name = "AmbiguousProviderError";
35
+ }
36
+ };
37
+ var CircularDependencyError = class extends CosystemError {
38
+ constructor(path) {
39
+ super(["Circular dependency detected:", ...path.map((entry) => ` ${entry}`)].join("\n"));
40
+ this.name = "CircularDependencyError";
41
+ }
42
+ };
43
+ var AsyncProviderInSyncResolutionError = class extends CosystemError {
44
+ constructor(token) {
45
+ super(`Provider for ${token} resolved asynchronously. Use getAsync() instead of get().`);
46
+ this.name = "AsyncProviderInSyncResolutionError";
47
+ }
48
+ };
49
+ var LifetimeLeakError = class extends CosystemError {
50
+ constructor(parent, parentScope, child, childScope) {
51
+ super(`${parent} (${parentScope}) cannot depend on ${child} (${childScope}) without leakSafe.`);
52
+ this.name = "LifetimeLeakError";
53
+ }
54
+ };
55
+ var InjectContextError = class extends CosystemError {
56
+ constructor(token) {
57
+ super(`${token} can only be injected while resolving a provider or running an app hook.`);
58
+ this.name = "InjectContextError";
59
+ }
60
+ };
61
+ //#endregion
62
+ //#region src/token.ts
63
+ function token(description) {
64
+ const id = Symbol(description);
65
+ if (description === void 0) return { id };
66
+ return {
67
+ id,
68
+ description
69
+ };
70
+ }
71
+ function tokenName(tokenValue) {
72
+ if (typeof tokenValue === "string") return tokenValue;
73
+ if (typeof tokenValue === "symbol") return tokenValue.description ?? tokenValue.toString();
74
+ if (typeof tokenValue === "function") return tokenValue.name || "<anonymous class>";
75
+ return tokenValue.description ?? tokenValue.id.description ?? tokenValue.id.toString();
76
+ }
77
+ //#endregion
78
+ //#region src/inject.ts
79
+ let activeResolutionContext;
80
+ function inject(token) {
81
+ if (activeResolutionContext === void 0) throw new InjectContextError(tokenName(token));
82
+ return activeResolutionContext.requestContainer.get(token);
83
+ }
84
+ function runWithInjectContext(context, callback) {
85
+ const previousContext = activeResolutionContext;
86
+ activeResolutionContext = context;
87
+ try {
88
+ return callback();
89
+ } finally {
90
+ activeResolutionContext = previousContext;
91
+ }
92
+ }
93
+ //#endregion
94
+ //#region src/provider.ts
95
+ function provide(providerToken, options) {
96
+ return {
97
+ provide: providerToken,
98
+ ...options
99
+ };
100
+ }
101
+ function normalizeProvider(input) {
102
+ if (typeof input === "function") return normalizeProvider({
103
+ provide: input,
104
+ useClass: input
105
+ });
106
+ const token = input.provide;
107
+ const base = {
108
+ token,
109
+ tokenName: tokenName(token),
110
+ multi: input.multi ?? false,
111
+ leakSafe: input.leakSafe ?? false
112
+ };
113
+ if ("useClass" in input) return {
114
+ ...base,
115
+ provider: {
116
+ kind: "class",
117
+ useClass: input.useClass
118
+ },
119
+ scope: input.scope ?? "singleton",
120
+ deps: input.deps ?? input.useClass.inject ?? [],
121
+ eager: input.eager ?? false,
122
+ ...eraseDispose(input.dispose)
123
+ };
124
+ if ("useValue" in input) return {
125
+ ...base,
126
+ provider: {
127
+ kind: "value",
128
+ useValue: input.useValue
129
+ },
130
+ scope: "singleton",
131
+ deps: [],
132
+ eager: true,
133
+ ...eraseDispose(input.dispose)
134
+ };
135
+ if ("useFactory" in input) return {
136
+ ...base,
137
+ provider: {
138
+ kind: "factory",
139
+ useFactory: input.useFactory
140
+ },
141
+ scope: input.scope ?? "singleton",
142
+ deps: input.deps ?? [],
143
+ eager: input.eager ?? false,
144
+ ...eraseDispose(input.dispose)
145
+ };
146
+ return {
147
+ ...base,
148
+ provider: {
149
+ kind: "existing",
150
+ useExisting: input.useExisting
151
+ },
152
+ scope: "singleton",
153
+ deps: [input.useExisting],
154
+ eager: false
155
+ };
156
+ }
157
+ function eraseDispose(dispose) {
158
+ if (dispose === void 0) return {};
159
+ return { dispose };
160
+ }
161
+ //#endregion
162
+ //#region src/container.ts
163
+ const asyncDisposeSymbol = Symbol.asyncDispose;
164
+ const disposeSymbol = Symbol.dispose;
165
+ function createContainer(options = {}) {
166
+ return new RuntimeContainer(options);
167
+ }
168
+ var RuntimeContainer = class RuntimeContainer {
169
+ parent;
170
+ strictScopes;
171
+ records = /* @__PURE__ */ new Map();
172
+ scopedCache = /* @__PURE__ */ new Map();
173
+ created = [];
174
+ root;
175
+ singletonCache = /* @__PURE__ */ new Map();
176
+ frozen = false;
177
+ constructor(options = {}) {
178
+ this.parent = options.parent;
179
+ this.strictScopes = options.strictScopes ?? this.parent?.strictScopes ?? true;
180
+ this.root = this.parent?.root ?? this;
181
+ }
182
+ get(token, options) {
183
+ const record = options?.optional === true ? this.getSingleRecord(token, true) : this.getSingleRecord(token, false);
184
+ if (record === void 0) return;
185
+ const context = this.createResolutionContext("sync");
186
+ const value = this.resolveRecord(record, context);
187
+ if (isPromiseLike(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
188
+ return value;
189
+ }
190
+ getAll(token) {
191
+ const records = this.getAllRecords(token);
192
+ const context = this.createResolutionContext("sync");
193
+ return records.map((record) => {
194
+ const value = this.resolveRecord(record, context);
195
+ if (isPromiseLike(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
196
+ return value;
197
+ });
198
+ }
199
+ async getAsync(token) {
200
+ const record = this.getSingleRecord(token, false);
201
+ const context = this.createResolutionContext("async");
202
+ return await this.resolveRecord(record, context);
203
+ }
204
+ has(token) {
205
+ return this.findRecords(token).length > 0;
206
+ }
207
+ provide(provider) {
208
+ this.assertMutable();
209
+ const record = normalizeProvider(provider);
210
+ const existing = this.records.get(record.token) ?? [];
211
+ if (!record.multi && existing.some((candidate) => !candidate.multi)) throw new DuplicateProviderError(record.tokenName);
212
+ this.records.set(record.token, [...existing, record]);
213
+ }
214
+ override(provider) {
215
+ this.assertMutable();
216
+ const record = normalizeProvider(provider);
217
+ const kept = (this.records.get(record.token) ?? []).filter((candidate) => candidate.multi && !record.multi);
218
+ this.records.set(record.token, [...kept, record]);
219
+ }
220
+ createScope(options = {}) {
221
+ return new RuntimeContainer({
222
+ parent: this,
223
+ strictScopes: options.strictScopes ?? this.strictScopes
224
+ });
225
+ }
226
+ build(target, options = {}) {
227
+ const deps = options.deps ?? target.inject ?? [];
228
+ const context = this.createResolutionContext("sync");
229
+ const values = this.resolveDependencies(deps, context);
230
+ if (isPromiseLike(values)) throw new AsyncProviderInSyncResolutionError(target.name);
231
+ return Reflect.construct(target, values);
232
+ }
233
+ freeze() {
234
+ this.frozen = true;
235
+ }
236
+ async dispose() {
237
+ const errors = [];
238
+ for (const entry of [...this.created].toReversed()) {
239
+ if (entry.disposed) continue;
240
+ entry.disposed = true;
241
+ try {
242
+ await disposeValue(entry.value, entry.record);
243
+ } catch (error) {
244
+ errors.push(error);
245
+ }
246
+ }
247
+ this.created.length = 0;
248
+ this.scopedCache.clear();
249
+ if (this.root === this) this.singletonCache.clear();
250
+ if (errors.length > 0) throw new AggregateError(errors, "One or more providers failed to dispose.");
251
+ }
252
+ assertMutable() {
253
+ if (this.frozen) throw new FrozenContainerError();
254
+ }
255
+ createResolutionContext(mode) {
256
+ return {
257
+ stack: [],
258
+ resolutionCache: /* @__PURE__ */ new Map(),
259
+ mode,
260
+ requestContainer: this
261
+ };
262
+ }
263
+ getSingleRecord(token, optional) {
264
+ const records = this.findRecords(token);
265
+ if (records.length === 0) {
266
+ if (optional) return;
267
+ throw new MissingProviderError(tokenName(token), []);
268
+ }
269
+ const nonMultiRecords = records.filter((record) => !record.multi);
270
+ if (nonMultiRecords.length === 1) return nonMultiRecords[0];
271
+ if (records.length === 1) return records[0];
272
+ throw new AmbiguousProviderError(tokenName(token));
273
+ }
274
+ getAllRecords(token) {
275
+ return this.getAllRecordsFromHierarchy(token);
276
+ }
277
+ findRecords(token) {
278
+ const localRecords = this.records.get(token);
279
+ if (localRecords !== void 0 && localRecords.length > 0) return localRecords;
280
+ return this.parent?.findRecords(token) ?? [];
281
+ }
282
+ getAllRecordsFromHierarchy(token) {
283
+ const parentRecords = this.parent?.getAllRecordsFromHierarchy(token) ?? [];
284
+ const localRecords = this.records.get(token) ?? [];
285
+ return [...parentRecords, ...localRecords];
286
+ }
287
+ resolveRecord(record, context) {
288
+ const cached = this.getCached(record, context);
289
+ if (cached.found) return cached.value;
290
+ this.assertNoCycle(record, context);
291
+ this.assertLifetimeSafe(record, context);
292
+ context.stack.push(record);
293
+ const finalize = (value) => {
294
+ context.stack.pop();
295
+ this.setCached(record, value, context);
296
+ return value;
297
+ };
298
+ const fail = (error) => {
299
+ context.stack.pop();
300
+ throw error;
301
+ };
302
+ try {
303
+ const value = this.createValue(record, context);
304
+ if (isPromiseLike(value)) {
305
+ if (context.mode === "sync") throw new AsyncProviderInSyncResolutionError(record.tokenName);
306
+ return Promise.resolve(value).then(finalize, fail);
307
+ }
308
+ return finalize(value);
309
+ } catch (error) {
310
+ return fail(error);
311
+ }
312
+ }
313
+ createValue(record, context) {
314
+ switch (record.provider.kind) {
315
+ case "class": {
316
+ const provider = record.provider;
317
+ const deps = this.resolveDependencies(record.deps, context);
318
+ if (isPromiseLike(deps)) return Promise.resolve(deps).then((values) => Reflect.construct(provider.useClass, values));
319
+ return Reflect.construct(provider.useClass, deps);
320
+ }
321
+ case "value": return record.provider.useValue;
322
+ case "factory": {
323
+ const provider = record.provider;
324
+ const deps = this.resolveDependencies(record.deps, context);
325
+ if (isPromiseLike(deps)) return Promise.resolve(deps).then((values) => runWithInjectContext(context, () => provider.useFactory(...values)));
326
+ return runWithInjectContext(context, () => provider.useFactory(...deps));
327
+ }
328
+ case "existing": return this.resolveDependency(record.provider.useExisting, context);
329
+ }
330
+ }
331
+ resolveDependencies(deps, context) {
332
+ const values = Array.from({ length: deps.length });
333
+ const asyncValues = [];
334
+ for (const [index, dep] of deps.entries()) {
335
+ const value = this.resolveDependency(dep, context);
336
+ if (isPromiseLike(value)) asyncValues.push(Promise.resolve(value).then((resolved) => {
337
+ values[index] = resolved;
338
+ return resolved;
339
+ }));
340
+ else values[index] = value;
341
+ }
342
+ if (asyncValues.length === 0) return values;
343
+ return Promise.all(asyncValues).then(() => values);
344
+ }
345
+ resolveDependency(dep, context) {
346
+ if (isDependencyObject(dep)) {
347
+ if (dep.many === true) {
348
+ const records = this.getAllRecords(dep.token);
349
+ return this.resolveAllRecords(records, context);
350
+ }
351
+ const record = dep.optional === true ? this.getSingleRecord(dep.token, true) : this.getSingleRecord(dep.token, false);
352
+ if (record === void 0) return;
353
+ return this.resolveRecord(record, context);
354
+ }
355
+ const record = this.getSingleRecord(dep, false);
356
+ return this.resolveRecord(record, context);
357
+ }
358
+ resolveAllRecords(records, context) {
359
+ const values = records.map((record) => this.resolveRecord(record, context));
360
+ if (values.some(isPromiseLike)) return Promise.all(values);
361
+ return values;
362
+ }
363
+ getCached(record, context) {
364
+ const cache = this.cacheFor(record, context);
365
+ if (cache === void 0 || !cache.has(record)) return { found: false };
366
+ return {
367
+ found: true,
368
+ value: cache.get(record)
369
+ };
370
+ }
371
+ setCached(record, value, context) {
372
+ const cache = this.cacheFor(record, context);
373
+ if (cache === void 0) {
374
+ this.trackCreated(record, value, context);
375
+ return;
376
+ }
377
+ if (!cache.has(record)) {
378
+ cache.set(record, value);
379
+ this.trackCreated(record, value, context);
380
+ }
381
+ }
382
+ cacheFor(record, context) {
383
+ switch (record.scope) {
384
+ case "singleton": return this.root.singletonCache;
385
+ case "scoped": return context.requestContainer.scopedCache;
386
+ case "resolution": return context.resolutionCache;
387
+ case "transient": return;
388
+ }
389
+ }
390
+ trackCreated(record, value, context) {
391
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
392
+ (record.scope === "singleton" ? this.root : context.requestContainer).created.push({
393
+ record,
394
+ value,
395
+ disposed: false
396
+ });
397
+ }
398
+ assertNoCycle(record, context) {
399
+ const existingIndex = context.stack.indexOf(record);
400
+ if (existingIndex === -1) return;
401
+ throw new CircularDependencyError([...context.stack.slice(existingIndex), record].map((entry) => entry.tokenName));
402
+ }
403
+ assertLifetimeSafe(record, context) {
404
+ if (!this.strictScopes || record.leakSafe || context.stack.length === 0) return;
405
+ const parent = context.stack.at(-1);
406
+ if (parent === void 0) return;
407
+ if (isLifetimeLeak(parent.scope, record.scope)) throw new LifetimeLeakError(parent.tokenName, parent.scope, record.tokenName, record.scope);
408
+ }
409
+ };
410
+ function isDependencyObject(dep) {
411
+ return typeof dep === "object" && dep !== null && "token" in dep;
412
+ }
413
+ function isPromiseLike(value) {
414
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
415
+ }
416
+ function isLifetimeLeak(parentScope, childScope) {
417
+ if (parentScope === "singleton") return childScope === "scoped" || childScope === "resolution" || childScope === "transient";
418
+ if (parentScope === "scoped") return childScope === "resolution" || childScope === "transient";
419
+ return false;
420
+ }
421
+ async function disposeValue(value, record) {
422
+ if (record.dispose !== void 0) await record.dispose(value);
423
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
424
+ const maybeDisposable = value;
425
+ if (asyncDisposeSymbol !== void 0 && typeof maybeDisposable[asyncDisposeSymbol] === "function") {
426
+ await maybeDisposable[asyncDisposeSymbol]();
427
+ return;
428
+ }
429
+ if (disposeSymbol !== void 0 && typeof maybeDisposable[disposeSymbol] === "function") {
430
+ maybeDisposable[disposeSymbol]();
431
+ return;
432
+ }
433
+ if (typeof maybeDisposable.dispose === "function") {
434
+ await maybeDisposable.dispose();
435
+ return;
436
+ }
437
+ if (typeof maybeDisposable.destroy === "function") await maybeDisposable.destroy();
438
+ }
439
+ //#endregion
440
+ //#region src/metadata.ts
441
+ const moduleMetadata = /* @__PURE__ */ new WeakMap();
442
+ function defineModule(target, options = {}) {
443
+ const metadata = ensureModuleMetadata(target);
444
+ applyModuleOptions(metadata, options);
445
+ addProperties(metadata.state, options.state);
446
+ addProperties(metadata.actions, options.actions);
447
+ addProperties(metadata.computed, options.computed);
448
+ return target;
449
+ }
450
+ function getModuleMetadata(target) {
451
+ return moduleMetadata.get(target);
452
+ }
453
+ function addModuleState(target, property) {
454
+ ensureModuleMetadata(target).state.add(property);
455
+ }
456
+ function addModuleAction(target, property) {
457
+ ensureModuleMetadata(target).actions.add(property);
458
+ }
459
+ function addModuleComputed(target, property) {
460
+ ensureModuleMetadata(target).computed.add(property);
461
+ }
462
+ function applyModuleOptions(metadata, options) {
463
+ if (options.name !== void 0) metadata.name = options.name;
464
+ if (options.deps !== void 0) metadata.deps = options.deps;
465
+ if (options.scope !== void 0) metadata.scope = options.scope;
466
+ }
467
+ function ensureModuleMetadata(target) {
468
+ let metadata = moduleMetadata.get(target);
469
+ if (metadata !== void 0) return metadata;
470
+ metadata = {
471
+ kind: "module",
472
+ state: /* @__PURE__ */ new Set(),
473
+ actions: /* @__PURE__ */ new Set(),
474
+ computed: /* @__PURE__ */ new Set()
475
+ };
476
+ moduleMetadata.set(target, metadata);
477
+ return metadata;
478
+ }
479
+ function addProperties(target, properties) {
480
+ for (const property of properties ?? []) target.add(property);
481
+ }
482
+ //#endregion
483
+ //#region src/app.ts
484
+ function createApp(options = {}) {
485
+ return createAppInternal(options);
486
+ }
487
+ function createAppInternal(options = {}) {
488
+ const parent = isApp(options.parent) ? options.parent.container : options.parent;
489
+ const container = parent === void 0 ? createContainer() : createContainer({ parent });
490
+ const moduleTokens = [];
491
+ for (const provider of options.providers ?? []) {
492
+ const normalized = normalizeAppProvider(provider);
493
+ container.provide(normalized.provider);
494
+ if (normalized.moduleToken !== void 0) moduleTokens.push(normalized.moduleToken);
495
+ }
496
+ for (const override of options.overrides ?? []) container.override(normalizeAppProvider(override).provider);
497
+ container.freeze();
498
+ const modules = instantiateModules(container, moduleTokens);
499
+ const store = create(createRootState(modules), {
500
+ name: "cosystem",
501
+ sliceMode: "single"
502
+ });
503
+ const app = new RuntimeApp({
504
+ container,
505
+ devOptions: options.devOptions ?? {},
506
+ modules,
507
+ plugins: options.plugins ?? [],
508
+ state: { version: 0 },
509
+ store,
510
+ ...options.testInspector === void 0 ? {} : { testInspector: options.testInspector }
511
+ });
512
+ app.bindModules();
513
+ app.runModuleCreatedHooks();
514
+ app.init();
515
+ return app;
516
+ }
517
+ var RuntimeApp = class {
518
+ container;
519
+ state;
520
+ store;
521
+ devOptions;
522
+ modules;
523
+ moduleByToken = /* @__PURE__ */ new Map();
524
+ moduleByName = /* @__PURE__ */ new Map();
525
+ plugins;
526
+ testInspector;
527
+ initPromise = Promise.resolve();
528
+ isStarted = false;
529
+ isDisposed = false;
530
+ constructor(options) {
531
+ this.container = options.container;
532
+ this.devOptions = options.devOptions;
533
+ this.modules = options.modules;
534
+ this.plugins = options.plugins;
535
+ this.state = options.state;
536
+ this.store = options.store;
537
+ this.testInspector = options.testInspector;
538
+ for (const moduleBinding of options.modules) {
539
+ this.moduleByToken.set(moduleBinding.token, moduleBinding);
540
+ this.moduleByName.set(moduleBinding.name, moduleBinding);
541
+ }
542
+ this.store.subscribe(() => {
543
+ this.state.version += 1;
544
+ const state = this.store.getPureState();
545
+ this.testInspector?.recordPatch(state);
546
+ this.emitStateChange({ state });
547
+ });
548
+ }
549
+ get started() {
550
+ return this.isStarted;
551
+ }
552
+ get(token) {
553
+ return this.container.get(token);
554
+ }
555
+ getAsync(token) {
556
+ return this.container.getAsync(token);
557
+ }
558
+ getAll(token) {
559
+ return this.container.getAll(token);
560
+ }
561
+ getModule(token) {
562
+ const value = this.get(token);
563
+ if (!this.moduleByToken.has(token)) throw new CosystemError(`${tokenName(token)} is not a CoSystem module.`);
564
+ return value;
565
+ }
566
+ getModuleByName(name) {
567
+ const moduleBinding = this.moduleByName.get(name);
568
+ if (moduleBinding === void 0) throw new CosystemError(`${name} is not a CoSystem module.`);
569
+ return moduleBinding.instance;
570
+ }
571
+ watch(read, listener, options = {}) {
572
+ const equals = options.equals ?? Object.is;
573
+ const tracker = createReactiveTracker();
574
+ let previous = tracker.track(read);
575
+ if (options.immediate === true) listener(previous, previous);
576
+ const publish = () => {
577
+ const next = tracker.track(read);
578
+ if (equals(next, previous)) return;
579
+ const last = previous;
580
+ previous = next;
581
+ listener(next, last);
582
+ };
583
+ const unsubscribeStore = this.store.subscribe(publish);
584
+ const unsubscribeTracker = tracker.subscribe(publish);
585
+ return () => {
586
+ unsubscribeStore();
587
+ unsubscribeTracker();
588
+ tracker.dispose();
589
+ };
590
+ }
591
+ async start() {
592
+ if (this.isStarted) return;
593
+ await this.initPromise;
594
+ await this.runLifecycle("onStart");
595
+ this.isStarted = true;
596
+ }
597
+ async stop() {
598
+ if (!this.isStarted) return;
599
+ await this.runLifecycle("onStop", true);
600
+ this.isStarted = false;
601
+ }
602
+ async dispose() {
603
+ if (this.isDisposed) return;
604
+ await this.stop();
605
+ await this.runLifecycle("onDispose", true);
606
+ await Promise.all(this.plugins.map((plugin) => plugin.dispose?.()));
607
+ await this.container.dispose();
608
+ this.store.destroy();
609
+ this.isDisposed = true;
610
+ }
611
+ createScope(options) {
612
+ return { container: this.container.createScope(options) };
613
+ }
614
+ bindModules() {
615
+ for (const moduleBinding of this.modules) {
616
+ this.bindState(moduleBinding);
617
+ this.bindComputed(moduleBinding);
618
+ this.bindActions(moduleBinding);
619
+ }
620
+ }
621
+ runModuleCreatedHooks() {
622
+ for (const moduleBinding of this.modules) {
623
+ const event = {
624
+ name: moduleBinding.name,
625
+ token: moduleBinding.token,
626
+ instance: moduleBinding.instance
627
+ };
628
+ for (const plugin of this.plugins) plugin.onModuleCreated?.(event);
629
+ }
630
+ }
631
+ init() {
632
+ this.initPromise = (async () => {
633
+ await Promise.all(this.plugins.map((plugin) => plugin.setup?.(this)));
634
+ await this.runLifecycle("onInit");
635
+ })();
636
+ }
637
+ bindState(moduleBinding) {
638
+ for (const property of moduleBinding.metadata.state) Object.defineProperty(moduleBinding.instance, property, {
639
+ configurable: true,
640
+ enumerable: true,
641
+ get: () => this.readModuleState(moduleBinding, property),
642
+ set: (value) => this.writeModuleState(moduleBinding, property, value)
643
+ });
644
+ }
645
+ bindActions(moduleBinding) {
646
+ for (const property of moduleBinding.metadata.actions) {
647
+ const action = getMethod(moduleBinding.instance, property);
648
+ moduleBinding.originalActions.set(property, action);
649
+ Object.defineProperty(moduleBinding.instance, property, {
650
+ configurable: true,
651
+ value: (...args) => this.runAction(moduleBinding, property, args)
652
+ });
653
+ }
654
+ }
655
+ bindComputed(moduleBinding) {
656
+ for (const property of moduleBinding.metadata.computed) {
657
+ const getter = getGetter(moduleBinding.instance, property);
658
+ moduleBinding.originalComputed.set(property, getter);
659
+ Object.defineProperty(moduleBinding.instance, property, {
660
+ configurable: true,
661
+ enumerable: true,
662
+ get: () => getter.call(moduleBinding.instance)
663
+ });
664
+ }
665
+ }
666
+ readModuleState(moduleBinding, property) {
667
+ return this.store.getState()[moduleBinding.name]?.[property];
668
+ }
669
+ writeModuleState(moduleBinding, property, value) {
670
+ if (moduleBinding.activeDraft !== void 0) {
671
+ moduleBinding.activeDraft[property] = value;
672
+ return;
673
+ }
674
+ if (this.devOptions.strictActions === true && moduleBinding.actionDepth === 0) throw new CosystemError(`Cannot write ${moduleBinding.name}.${String(property)} outside an action.`);
675
+ this.store.setState((draft) => {
676
+ draft[moduleBinding.name] ??= {};
677
+ draft[moduleBinding.name][property] = value;
678
+ });
679
+ }
680
+ runAction(moduleBinding, property, args) {
681
+ const action = moduleBinding.originalActions.get(property);
682
+ if (action === void 0) throw new CosystemError(`${moduleBinding.name}.${String(property)} is not an action.`);
683
+ const event = {
684
+ args,
685
+ method: String(property),
686
+ module: moduleBinding.name,
687
+ startedAt: Date.now()
688
+ };
689
+ this.emitActionStart(event);
690
+ let result;
691
+ let error;
692
+ try {
693
+ moduleBinding.actionDepth += 1;
694
+ this.store.setState((draft) => {
695
+ const previousDraft = moduleBinding.activeDraft;
696
+ moduleBinding.activeDraft = draft[moduleBinding.name] ?? {};
697
+ draft[moduleBinding.name] = moduleBinding.activeDraft;
698
+ try {
699
+ result = action.apply(moduleBinding.instance, [...args]);
700
+ } finally {
701
+ moduleBinding.activeDraft = previousDraft;
702
+ }
703
+ });
704
+ } catch (caught) {
705
+ error = caught;
706
+ } finally {
707
+ moduleBinding.actionDepth -= 1;
708
+ }
709
+ const endedEvent = {
710
+ ...event,
711
+ endedAt: Date.now(),
712
+ ...error === void 0 ? {} : { error }
713
+ };
714
+ this.testInspector?.recordAction(endedEvent);
715
+ this.emitActionEnd(endedEvent);
716
+ if (error !== void 0) throw error;
717
+ return result;
718
+ }
719
+ async runLifecycle(method, reverse = false) {
720
+ const modules = reverse ? this.modules.toReversed() : this.modules;
721
+ for (const moduleBinding of modules) await moduleBinding.instance[method]?.();
722
+ }
723
+ emitActionStart(event) {
724
+ for (const plugin of this.plugins) plugin.onActionStart?.(event);
725
+ }
726
+ emitActionEnd(event) {
727
+ for (const plugin of this.plugins) plugin.onActionEnd?.(event);
728
+ }
729
+ emitStateChange(event) {
730
+ for (const plugin of this.plugins) plugin.onStateChange?.(event);
731
+ }
732
+ };
733
+ function normalizeAppProvider(provider) {
734
+ if (typeof provider === "function") {
735
+ const metadata = getModuleMetadata(provider);
736
+ if (metadata !== void 0) return {
737
+ provider: provide(provider, createModuleClassProviderOptions(provider, metadata)),
738
+ moduleToken: provider
739
+ };
740
+ return { provider };
741
+ }
742
+ if ("useClass" in provider) {
743
+ const metadata = getModuleMetadata(provider.useClass);
744
+ if (metadata !== void 0) return {
745
+ provider: {
746
+ ...provider,
747
+ ...mergeModuleClassProviderOptions(provider, metadata)
748
+ },
749
+ moduleToken: provider.provide
750
+ };
751
+ }
752
+ return { provider };
753
+ }
754
+ function createModuleClassProviderOptions(useClass, metadata) {
755
+ return {
756
+ useClass,
757
+ ...metadata.deps === void 0 ? {} : { deps: metadata.deps },
758
+ ...metadata.scope === void 0 ? {} : { scope: metadata.scope }
759
+ };
760
+ }
761
+ function mergeModuleClassProviderOptions(provider, metadata) {
762
+ return {
763
+ ..."deps" in provider || metadata.deps === void 0 ? {} : { deps: metadata.deps },
764
+ ..."scope" in provider || metadata.scope === void 0 ? {} : { scope: metadata.scope }
765
+ };
766
+ }
767
+ function instantiateModules(container, moduleTokens) {
768
+ const modules = [];
769
+ for (const moduleToken of moduleTokens) {
770
+ const instance = container.get(moduleToken);
771
+ const metadata = getModuleMetadata(instance.constructor);
772
+ if (metadata === void 0) continue;
773
+ const name = metadata.name ?? stableModuleName(moduleToken);
774
+ if (modules.some((moduleBinding) => moduleBinding.name === name)) throw new DuplicateProviderError(name);
775
+ modules.push({
776
+ actionDepth: 0,
777
+ activeDraft: void 0,
778
+ instance,
779
+ metadata,
780
+ name,
781
+ originalActions: /* @__PURE__ */ new Map(),
782
+ originalComputed: /* @__PURE__ */ new Map(),
783
+ token: moduleToken
784
+ });
785
+ }
786
+ return modules;
787
+ }
788
+ function createRootState(modules) {
789
+ const rootState = {};
790
+ for (const moduleBinding of modules) {
791
+ const state = {};
792
+ for (const property of moduleBinding.metadata.state) state[property] = moduleBinding.instance[property];
793
+ rootState[moduleBinding.name] = state;
794
+ }
795
+ return rootState;
796
+ }
797
+ function stableModuleName(token) {
798
+ const name = tokenName(token);
799
+ return `${name.slice(0, 1).toLowerCase()}${name.slice(1)}`;
800
+ }
801
+ function getMethod(instance, property) {
802
+ const descriptor = getDescriptor(instance, property);
803
+ if (descriptor?.value === void 0 || typeof descriptor.value !== "function") throw new CosystemError(`${String(property)} is not a method.`);
804
+ return descriptor.value;
805
+ }
806
+ function getGetter(instance, property) {
807
+ const descriptor = getDescriptor(instance, property);
808
+ if (descriptor?.get === void 0) throw new CosystemError(`${String(property)} is not a getter.`);
809
+ return descriptor.get;
810
+ }
811
+ function getDescriptor(instance, property) {
812
+ let current = instance;
813
+ while (current !== null) {
814
+ const descriptor = Object.getOwnPropertyDescriptor(current, property);
815
+ if (descriptor !== void 0) return descriptor;
816
+ current = Object.getPrototypeOf(current);
817
+ }
818
+ }
819
+ function isApp(value) {
820
+ return typeof value === "object" && value !== null && "get" in value && "start" in value && "dispose" in value;
821
+ }
822
+ //#endregion
823
+ //#region src/decorators.ts
824
+ function module(options = {}) {
825
+ return function moduleDecorator(target, _context) {
826
+ defineModule(target, options);
827
+ return target;
828
+ };
829
+ }
830
+ function state(_value, context) {
831
+ if (context.kind !== "accessor") throw new TypeError("@state only supports standard accessor decorators.");
832
+ context.addInitializer(function initializeState() {
833
+ addModuleState(this.constructor, context.name);
834
+ });
835
+ }
836
+ function action(_value, context) {
837
+ if (context.kind !== "method") throw new TypeError("@action only supports method decorators.");
838
+ context.addInitializer(function initializeAction() {
839
+ addModuleAction(this.constructor, context.name);
840
+ });
841
+ }
842
+ function computed(_value, context) {
843
+ if (context.kind !== "getter") throw new TypeError("@computed only supports getter decorators.");
844
+ context.addInitializer(function initializeComputed() {
845
+ addModuleComputed(this.constructor, context.name);
846
+ });
847
+ }
848
+ //#endregion
849
+ //#region src/testApp.ts
850
+ function testApp(options = {}) {
851
+ const inspector = createTestInspector();
852
+ const { autoStart, overrides, strictActions, ...createOptions } = options;
853
+ const app = createAppInternal({
854
+ ...createOptions,
855
+ devOptions: {
856
+ ...createOptions.devOptions,
857
+ ...strictActions === void 0 ? {} : { strictActions }
858
+ },
859
+ ...overrides === void 0 ? {} : { overrides },
860
+ testInspector: inspector
861
+ });
862
+ Object.defineProperty(app, "test", {
863
+ configurable: false,
864
+ enumerable: false,
865
+ value: inspector
866
+ });
867
+ if (autoStart === true) return app.start().then(() => app);
868
+ return app;
869
+ }
870
+ function createTestInspector() {
871
+ const actions = [];
872
+ const patches = [];
873
+ let lastState;
874
+ return {
875
+ clearActions() {
876
+ actions.length = 0;
877
+ },
878
+ clearPatches() {
879
+ patches.length = 0;
880
+ },
881
+ flushEffects() {
882
+ return Promise.resolve();
883
+ },
884
+ getActions() {
885
+ return actions;
886
+ },
887
+ getPatches() {
888
+ return patches;
889
+ },
890
+ getState() {
891
+ return lastState;
892
+ },
893
+ recordAction(event) {
894
+ actions.push(event);
895
+ },
896
+ recordPatch(patch) {
897
+ lastState = patch;
898
+ patches.push(patch);
899
+ }
900
+ };
901
+ }
902
+ //#endregion
903
+ //#region src/worker.ts
904
+ function createWorkerApp(options) {
905
+ const { transport, ...appOptions } = options;
906
+ const app = createApp(appOptions);
907
+ const unsubscribeTransport = transport.subscribe((message) => {
908
+ if (message.type !== "call") return;
909
+ handleCall(app, transport, message);
910
+ });
911
+ const unsubscribeStore = app.store.subscribe(() => {
912
+ publishState(app, transport);
913
+ });
914
+ transport.post({ type: "ready" });
915
+ publishState(app, transport);
916
+ return {
917
+ app,
918
+ async dispose() {
919
+ unsubscribeTransport();
920
+ unsubscribeStore();
921
+ await app.dispose();
922
+ }
923
+ };
924
+ }
925
+ function createWorkerClient(options) {
926
+ const { transport } = options;
927
+ const listeners = /* @__PURE__ */ new Set();
928
+ const pending = /* @__PURE__ */ new Map();
929
+ const state = { version: 0 };
930
+ let nextId = 1;
931
+ let snapshot;
932
+ const unsubscribe = transport.subscribe((message) => {
933
+ if (message.type === "state") {
934
+ state.version = message.version;
935
+ snapshot = message.state;
936
+ for (const listener of listeners) listener(message);
937
+ return;
938
+ }
939
+ if (message.type !== "result") return;
940
+ const entry = pending.get(message.id);
941
+ if (entry === void 0) return;
942
+ pending.delete(message.id);
943
+ if (message.error !== void 0) {
944
+ entry.reject(createRemoteError(message.error));
945
+ return;
946
+ }
947
+ entry.resolve(message.value);
948
+ });
949
+ const client = {
950
+ state,
951
+ call(module, method, ...args) {
952
+ const id = nextId;
953
+ nextId += 1;
954
+ return new Promise((resolve, reject) => {
955
+ pending.set(id, {
956
+ reject,
957
+ resolve
958
+ });
959
+ transport.post({
960
+ args,
961
+ id,
962
+ method,
963
+ module,
964
+ type: "call"
965
+ });
966
+ });
967
+ },
968
+ dispose() {
969
+ unsubscribe();
970
+ for (const entry of pending.values()) entry.reject(new CosystemError("Worker client disposed before response."));
971
+ pending.clear();
972
+ listeners.clear();
973
+ },
974
+ getState() {
975
+ return snapshot;
976
+ },
977
+ module(name) {
978
+ return new Proxy({}, { get(_target, property) {
979
+ if (typeof property !== "string") return;
980
+ return (...args) => client.call(name, property, ...args);
981
+ } });
982
+ },
983
+ subscribe(listener) {
984
+ listeners.add(listener);
985
+ return () => {
986
+ listeners.delete(listener);
987
+ };
988
+ }
989
+ };
990
+ return client;
991
+ }
992
+ function createMemoryWorkerTransportPair() {
993
+ const leftListeners = /* @__PURE__ */ new Set();
994
+ const rightListeners = /* @__PURE__ */ new Set();
995
+ return [createMemoryWorkerTransport(leftListeners, rightListeners), createMemoryWorkerTransport(rightListeners, leftListeners)];
996
+ }
997
+ async function handleCall(app, transport, message) {
998
+ try {
999
+ const module = app.getModuleByName(message.module);
1000
+ const method = module[message.method];
1001
+ if (typeof method !== "function") throw new CosystemError(`${message.module}.${message.method} is not callable.`);
1002
+ const value = await method.apply(module, message.args);
1003
+ transport.post({
1004
+ id: message.id,
1005
+ type: "result",
1006
+ value
1007
+ });
1008
+ } catch (error) {
1009
+ transport.post({
1010
+ error: serializeError(error),
1011
+ id: message.id,
1012
+ type: "result"
1013
+ });
1014
+ }
1015
+ }
1016
+ function publishState(app, transport) {
1017
+ transport.post({
1018
+ state: app.store.getPureState(),
1019
+ type: "state",
1020
+ version: app.state.version
1021
+ });
1022
+ }
1023
+ function createMemoryWorkerTransport(inbox, outbox) {
1024
+ return {
1025
+ post(message) {
1026
+ for (const listener of outbox) listener(message);
1027
+ },
1028
+ subscribe(listener) {
1029
+ inbox.add(listener);
1030
+ return () => {
1031
+ inbox.delete(listener);
1032
+ };
1033
+ }
1034
+ };
1035
+ }
1036
+ function serializeError(error) {
1037
+ if (error instanceof Error) return {
1038
+ message: error.message,
1039
+ name: error.name,
1040
+ ...error.stack === void 0 ? {} : { stack: error.stack }
1041
+ };
1042
+ return {
1043
+ message: String(error),
1044
+ name: "Error"
1045
+ };
1046
+ }
1047
+ function createRemoteError(error) {
1048
+ const remoteError = new CosystemError(`Remote worker error: ${error.message}`);
1049
+ remoteError.name = error.name;
1050
+ if (error.stack !== void 0) remoteError.stack = error.stack;
1051
+ return remoteError;
1052
+ }
1053
+ //#endregion
1054
+ export { AmbiguousProviderError, AsyncProviderInSyncResolutionError, CircularDependencyError, CosystemError, DuplicateProviderError, FrozenContainerError, InjectContextError, LifetimeLeakError, MissingProviderError, action, computed, createApp, createContainer, createMemoryWorkerTransportPair, createWorkerApp, createWorkerClient, defineModule, getModuleMetadata, inject, module, provide, state, testApp, token, tokenName };
1055
+
1056
+ //# sourceMappingURL=index.mjs.map