@cosystem/core 0.0.0 → 1.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 CHANGED
@@ -1,4 +1,4 @@
1
- import { create, createReactiveTracker } from "coaction";
1
+ import { computed as computed$1, create, createReactiveTracker } from "coaction";
2
2
  //#region src/errors.ts
3
3
  var CosystemError = class extends Error {
4
4
  constructor(message) {
@@ -184,7 +184,7 @@ var RuntimeContainer = class RuntimeContainer {
184
184
  if (record === void 0) return;
185
185
  const context = this.createResolutionContext("sync");
186
186
  const value = this.resolveRecord(record, context);
187
- if (isPromiseLike(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
187
+ if (isPromiseLike$1(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
188
188
  return value;
189
189
  }
190
190
  getAll(token) {
@@ -192,7 +192,7 @@ var RuntimeContainer = class RuntimeContainer {
192
192
  const context = this.createResolutionContext("sync");
193
193
  return records.map((record) => {
194
194
  const value = this.resolveRecord(record, context);
195
- if (isPromiseLike(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
195
+ if (isPromiseLike$1(value)) throw new AsyncProviderInSyncResolutionError(record.tokenName);
196
196
  return value;
197
197
  });
198
198
  }
@@ -227,7 +227,13 @@ var RuntimeContainer = class RuntimeContainer {
227
227
  const deps = options.deps ?? target.inject ?? [];
228
228
  const context = this.createResolutionContext("sync");
229
229
  const values = this.resolveDependencies(deps, context);
230
- if (isPromiseLike(values)) throw new AsyncProviderInSyncResolutionError(target.name);
230
+ if (isPromiseLike$1(values)) throw new AsyncProviderInSyncResolutionError(target.name);
231
+ return Reflect.construct(target, values);
232
+ }
233
+ async buildAsync(target, options = {}) {
234
+ const deps = options.deps ?? target.inject ?? [];
235
+ const context = this.createResolutionContext("async");
236
+ const values = await this.resolveDependencies(deps, context);
231
237
  return Reflect.construct(target, values);
232
238
  }
233
239
  freeze() {
@@ -301,7 +307,7 @@ var RuntimeContainer = class RuntimeContainer {
301
307
  };
302
308
  try {
303
309
  const value = this.createValue(record, context);
304
- if (isPromiseLike(value)) {
310
+ if (isPromiseLike$1(value)) {
305
311
  if (context.mode === "sync") throw new AsyncProviderInSyncResolutionError(record.tokenName);
306
312
  return Promise.resolve(value).then(finalize, fail);
307
313
  }
@@ -315,14 +321,14 @@ var RuntimeContainer = class RuntimeContainer {
315
321
  case "class": {
316
322
  const provider = record.provider;
317
323
  const deps = this.resolveDependencies(record.deps, context);
318
- if (isPromiseLike(deps)) return Promise.resolve(deps).then((values) => Reflect.construct(provider.useClass, values));
324
+ if (isPromiseLike$1(deps)) return Promise.resolve(deps).then((values) => Reflect.construct(provider.useClass, values));
319
325
  return Reflect.construct(provider.useClass, deps);
320
326
  }
321
327
  case "value": return record.provider.useValue;
322
328
  case "factory": {
323
329
  const provider = record.provider;
324
330
  const deps = this.resolveDependencies(record.deps, context);
325
- if (isPromiseLike(deps)) return Promise.resolve(deps).then((values) => runWithInjectContext(context, () => provider.useFactory(...values)));
331
+ if (isPromiseLike$1(deps)) return Promise.resolve(deps).then((values) => runWithInjectContext(context, () => provider.useFactory(...values)));
326
332
  return runWithInjectContext(context, () => provider.useFactory(...deps));
327
333
  }
328
334
  case "existing": return this.resolveDependency(record.provider.useExisting, context);
@@ -333,7 +339,7 @@ var RuntimeContainer = class RuntimeContainer {
333
339
  const asyncValues = [];
334
340
  for (const [index, dep] of deps.entries()) {
335
341
  const value = this.resolveDependency(dep, context);
336
- if (isPromiseLike(value)) asyncValues.push(Promise.resolve(value).then((resolved) => {
342
+ if (isPromiseLike$1(value)) asyncValues.push(Promise.resolve(value).then((resolved) => {
337
343
  values[index] = resolved;
338
344
  return resolved;
339
345
  }));
@@ -348,16 +354,21 @@ var RuntimeContainer = class RuntimeContainer {
348
354
  const records = this.getAllRecords(dep.token);
349
355
  return this.resolveAllRecords(records, context);
350
356
  }
351
- const record = dep.optional === true ? this.getSingleRecord(dep.token, true) : this.getSingleRecord(dep.token, false);
357
+ const record = dep.optional === true ? this.getSingleRecord(dep.token, true) : this.getRequiredRecord(dep.token, context);
352
358
  if (record === void 0) return;
353
359
  return this.resolveRecord(record, context);
354
360
  }
355
- const record = this.getSingleRecord(dep, false);
361
+ const record = this.getRequiredRecord(dep, context);
356
362
  return this.resolveRecord(record, context);
357
363
  }
364
+ getRequiredRecord(token, context) {
365
+ const record = this.getSingleRecord(token, true);
366
+ if (record === void 0) throw new MissingProviderError(tokenName(token), context.stack.map((entry) => entry.tokenName));
367
+ return record;
368
+ }
358
369
  resolveAllRecords(records, context) {
359
370
  const values = records.map((record) => this.resolveRecord(record, context));
360
- if (values.some(isPromiseLike)) return Promise.all(values);
371
+ if (values.some(isPromiseLike$1)) return Promise.all(values);
361
372
  return values;
362
373
  }
363
374
  getCached(record, context) {
@@ -410,7 +421,7 @@ var RuntimeContainer = class RuntimeContainer {
410
421
  function isDependencyObject(dep) {
411
422
  return typeof dep === "object" && dep !== null && "token" in dep;
412
423
  }
413
- function isPromiseLike(value) {
424
+ function isPromiseLike$1(value) {
414
425
  return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
415
426
  }
416
427
  function isLifetimeLeak(parentScope, childScope) {
@@ -437,18 +448,69 @@ async function disposeValue(value, record) {
437
448
  if (typeof maybeDisposable.destroy === "function") await maybeDisposable.destroy();
438
449
  }
439
450
  //#endregion
451
+ //#region src/lazyModule.ts
452
+ function lazyModule(load) {
453
+ return {
454
+ kind: "lazyModule",
455
+ load
456
+ };
457
+ }
458
+ function isLazyModule(input) {
459
+ return typeof input === "object" && input !== null && "kind" in input && input.kind === "lazyModule" && "load" in input && typeof input.load === "function";
460
+ }
461
+ function normalizeLazyModuleProviders(input) {
462
+ if (isProviderInput(input)) return [input];
463
+ if (Array.isArray(input)) {
464
+ assertProviderArray(input);
465
+ return input;
466
+ }
467
+ if (typeof input === "object" && input !== null) {
468
+ const exports = input;
469
+ const providers = [];
470
+ if (exports.providers !== void 0) {
471
+ assertProviderArray(exports.providers);
472
+ providers.push(...exports.providers);
473
+ }
474
+ if (exports.default !== void 0) if (Array.isArray(exports.default)) {
475
+ assertProviderArray(exports.default);
476
+ providers.push(...exports.default);
477
+ } else if (isProviderInput(exports.default)) providers.push(exports.default);
478
+ else throw new CosystemError("Lazy module default export must be a provider or provider array.");
479
+ if (providers.length > 0) return providers;
480
+ }
481
+ throw new CosystemError("Lazy module loader must return a provider, provider array, or module exports with providers/default.");
482
+ }
483
+ function assertProviderArray(values) {
484
+ if (values.every(isProviderInput)) return;
485
+ throw new CosystemError("Lazy module provider arrays may only contain provider entries.");
486
+ }
487
+ function isProviderInput(value) {
488
+ if (typeof value === "function") return true;
489
+ return typeof value === "object" && value !== null && "provide" in value && ("useClass" in value || "useValue" in value || "useFactory" in value || "useExisting" in value);
490
+ }
491
+ //#endregion
440
492
  //#region src/metadata.ts
441
493
  const moduleMetadata = /* @__PURE__ */ new WeakMap();
442
- function defineModule(target, options = {}) {
494
+ const moduleMetadataKey = Symbol.for("@cosystem/core/moduleMetadata");
495
+ const symbolMetadataKey = Symbol.metadata;
496
+ function defineModule(target, options = {}, context) {
443
497
  const metadata = ensureModuleMetadata(target);
498
+ mergeModuleMetadata(metadata, readContextMetadata(context));
444
499
  applyModuleOptions(metadata, options);
445
500
  addProperties(metadata.state, options.state);
446
501
  addProperties(metadata.actions, options.actions);
447
502
  addProperties(metadata.computed, options.computed);
503
+ addProperties(metadata.effects, options.effects);
504
+ writeContextMetadata(context, metadata);
448
505
  return target;
449
506
  }
450
507
  function getModuleMetadata(target) {
451
- return moduleMetadata.get(target);
508
+ const existing = moduleMetadata.get(target);
509
+ if (existing !== void 0) return existing;
510
+ const metadata = readSymbolMetadata(target);
511
+ if (metadata === void 0) return;
512
+ moduleMetadata.set(target, metadata);
513
+ return metadata;
452
514
  }
453
515
  function addModuleState(target, property) {
454
516
  ensureModuleMetadata(target).state.add(property);
@@ -459,6 +521,9 @@ function addModuleAction(target, property) {
459
521
  function addModuleComputed(target, property) {
460
522
  ensureModuleMetadata(target).computed.add(property);
461
523
  }
524
+ function addModuleEffect(target, property) {
525
+ ensureModuleMetadata(target).effects.add(property);
526
+ }
462
527
  function applyModuleOptions(metadata, options) {
463
528
  if (options.name !== void 0) metadata.name = options.name;
464
529
  if (options.deps !== void 0) metadata.deps = options.deps;
@@ -471,38 +536,96 @@ function ensureModuleMetadata(target) {
471
536
  kind: "module",
472
537
  state: /* @__PURE__ */ new Set(),
473
538
  actions: /* @__PURE__ */ new Set(),
474
- computed: /* @__PURE__ */ new Set()
539
+ computed: /* @__PURE__ */ new Set(),
540
+ effects: /* @__PURE__ */ new Set()
475
541
  };
476
542
  moduleMetadata.set(target, metadata);
477
543
  return metadata;
478
544
  }
545
+ function ensureContextModuleMetadata(context) {
546
+ if (context?.metadata === void 0) return;
547
+ const existing = readContextMetadata(context);
548
+ if (existing !== void 0) return existing;
549
+ const metadata = createModuleMetadata();
550
+ writeContextMetadata(context, metadata);
551
+ return metadata;
552
+ }
479
553
  function addProperties(target, properties) {
480
554
  for (const property of properties ?? []) target.add(property);
481
555
  }
556
+ function createModuleMetadata() {
557
+ return {
558
+ kind: "module",
559
+ state: /* @__PURE__ */ new Set(),
560
+ actions: /* @__PURE__ */ new Set(),
561
+ computed: /* @__PURE__ */ new Set(),
562
+ effects: /* @__PURE__ */ new Set()
563
+ };
564
+ }
565
+ function readContextMetadata(context) {
566
+ const value = context?.metadata?.[moduleMetadataKey];
567
+ return isModuleMetadata(value) ? value : void 0;
568
+ }
569
+ function writeContextMetadata(context, metadata) {
570
+ if (context?.metadata === void 0) return;
571
+ context.metadata[moduleMetadataKey] = metadata;
572
+ }
573
+ function readSymbolMetadata(target) {
574
+ if (symbolMetadataKey === void 0) return;
575
+ const metadata = target[symbolMetadataKey];
576
+ if (metadata === void 0 || metadata === null || typeof metadata !== "object") return;
577
+ const value = metadata[moduleMetadataKey];
578
+ return isModuleMetadata(value) ? value : void 0;
579
+ }
580
+ function mergeModuleMetadata(target, source) {
581
+ if (source === void 0 || source === target) return;
582
+ applyModuleOptions(target, source);
583
+ addProperties(target.state, [...source.state]);
584
+ addProperties(target.actions, [...source.actions]);
585
+ addProperties(target.computed, [...source.computed]);
586
+ addProperties(target.effects, [...source.effects]);
587
+ }
588
+ function isModuleMetadata(value) {
589
+ return typeof value === "object" && value !== null && value.kind === "module" && value instanceof Object && value.state instanceof Set && value.actions instanceof Set && value.computed instanceof Set && value.effects instanceof Set;
590
+ }
482
591
  //#endregion
483
592
  //#region src/app.ts
593
+ const runtimeModuleMetadataKey = Symbol.for("@cosystem/core/runtimeModule");
594
+ const appContainerMap = /* @__PURE__ */ new WeakMap();
484
595
  function createApp(options = {}) {
485
596
  return createAppInternal(options);
486
597
  }
598
+ function runInAction(module, callback, options) {
599
+ const metadata = getRuntimeModuleMetadata(module);
600
+ if (metadata === void 0) throw new CosystemError("runInAction() target is not a CoSystem module instance.");
601
+ return metadata.app.runInAction(module, callback, options);
602
+ }
487
603
  function createAppInternal(options = {}) {
488
- const parent = isApp(options.parent) ? options.parent.container : options.parent;
604
+ const parent = isApp(options.parent) ? getAppContainer(options.parent) : options.parent;
489
605
  const container = parent === void 0 ? createContainer() : createContainer({ parent });
490
606
  const moduleTokens = [];
607
+ const lazyModules = [];
491
608
  for (const provider of options.providers ?? []) {
609
+ if (isLazyModule(provider)) {
610
+ lazyModules.push(provider);
611
+ continue;
612
+ }
492
613
  const normalized = normalizeAppProvider(provider);
493
614
  container.provide(normalized.provider);
494
615
  if (normalized.moduleToken !== void 0) moduleTokens.push(normalized.moduleToken);
495
616
  }
496
- for (const override of options.overrides ?? []) container.override(normalizeAppProvider(override).provider);
617
+ for (const override of options.overrides ?? []) {
618
+ const normalized = normalizeAppProvider(override);
619
+ if (normalized.moduleToken !== void 0 && !moduleTokens.some((moduleToken) => moduleToken === normalized.moduleToken)) throw new CosystemError(`Cannot add ${tokenName(normalized.moduleToken)} as a new CoSystem module through overrides.`);
620
+ container.override(normalized.provider);
621
+ }
497
622
  container.freeze();
498
623
  const modules = instantiateModules(container, moduleTokens);
499
- const store = create(createRootState(modules), {
500
- name: "cosystem",
501
- sliceMode: "single"
502
- });
624
+ const store = create(createRootState(modules), createStoreOptions(options.engine, shouldEnablePatches(options)));
503
625
  const app = new RuntimeApp({
504
626
  container,
505
627
  devOptions: options.devOptions ?? {},
628
+ lazyModules,
506
629
  modules,
507
630
  plugins: options.plugins ?? [],
508
631
  state: { version: 0 },
@@ -510,26 +633,35 @@ function createAppInternal(options = {}) {
510
633
  ...options.testInspector === void 0 ? {} : { testInspector: options.testInspector }
511
634
  });
512
635
  app.bindModules();
636
+ app.attachRuntimeMetadata();
637
+ instantiateEagerProviders(container);
513
638
  app.runModuleCreatedHooks();
514
639
  app.init();
640
+ appContainerMap.set(app, container);
515
641
  return app;
516
642
  }
517
643
  var RuntimeApp = class {
518
- container;
519
644
  state;
520
645
  store;
646
+ #container;
521
647
  devOptions;
522
648
  modules;
649
+ pendingLazyModules;
523
650
  moduleByToken = /* @__PURE__ */ new Map();
524
651
  moduleByName = /* @__PURE__ */ new Map();
525
652
  plugins;
526
653
  testInspector;
654
+ effectDisposers = [];
655
+ pendingEffects = /* @__PURE__ */ new Set();
656
+ loadedLazyModules = /* @__PURE__ */ new WeakMap();
657
+ dynamicScopes = [];
527
658
  initPromise = Promise.resolve();
528
659
  isStarted = false;
529
660
  isDisposed = false;
530
661
  constructor(options) {
531
- this.container = options.container;
662
+ this.#container = options.container;
532
663
  this.devOptions = options.devOptions;
664
+ this.pendingLazyModules = [...options.lazyModules];
533
665
  this.modules = options.modules;
534
666
  this.plugins = options.plugins;
535
667
  this.state = options.state;
@@ -539,29 +671,35 @@ var RuntimeApp = class {
539
671
  this.moduleByToken.set(moduleBinding.token, moduleBinding);
540
672
  this.moduleByName.set(moduleBinding.name, moduleBinding);
541
673
  }
674
+ this.wrapStoreSetState();
542
675
  this.store.subscribe(() => {
543
676
  this.state.version += 1;
544
677
  const state = this.store.getPureState();
545
- this.testInspector?.recordPatch(state);
678
+ this.testInspector?.recordState(state);
546
679
  this.emitStateChange({ state });
547
680
  });
681
+ this.testInspector?.setFlushEffects(() => this.flushEffects());
548
682
  }
549
683
  get started() {
550
684
  return this.isStarted;
551
685
  }
552
686
  get(token) {
553
- return this.container.get(token);
687
+ const moduleBinding = this.moduleByToken.get(token);
688
+ if (moduleBinding !== void 0) return moduleBinding.instance;
689
+ return this.#container.get(token);
554
690
  }
555
691
  getAsync(token) {
556
- return this.container.getAsync(token);
692
+ const moduleBinding = this.moduleByToken.get(token);
693
+ if (moduleBinding !== void 0) return Promise.resolve(moduleBinding.instance);
694
+ return this.#container.getAsync(token);
557
695
  }
558
696
  getAll(token) {
559
- return this.container.getAll(token);
697
+ return this.#container.getAll(token);
560
698
  }
561
699
  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;
700
+ const moduleBinding = this.moduleByToken.get(token);
701
+ if (moduleBinding === void 0) throw new CosystemError(`${tokenName(token)} is not a CoSystem module.`);
702
+ return moduleBinding.instance;
565
703
  }
566
704
  getModuleByName(name) {
567
705
  const moduleBinding = this.moduleByName.get(name);
@@ -588,50 +726,129 @@ var RuntimeApp = class {
588
726
  tracker.dispose();
589
727
  };
590
728
  }
729
+ runInAction(module, callback, options = {}) {
730
+ const moduleBinding = this.resolveModuleBinding(module);
731
+ return this.runActionCallback(moduleBinding, options.name ?? "runInAction", options.args ?? [], callback);
732
+ }
591
733
  async start() {
592
734
  if (this.isStarted) return;
593
735
  await this.initPromise;
594
- await this.runLifecycle("onStart");
595
- this.isStarted = true;
736
+ try {
737
+ await this.runLifecycle("onStart");
738
+ this.isStarted = true;
739
+ } catch (error) {
740
+ this.emitError(error, { phase: "start" });
741
+ throw error;
742
+ }
596
743
  }
597
744
  async stop() {
598
745
  if (!this.isStarted) return;
599
- await this.runLifecycle("onStop", true);
600
- this.isStarted = false;
746
+ try {
747
+ await this.runLifecycle("onStop", true);
748
+ this.isStarted = false;
749
+ } catch (error) {
750
+ this.emitError(error, { phase: "stop" });
751
+ throw error;
752
+ }
601
753
  }
602
754
  async dispose() {
603
755
  if (this.isDisposed) return;
604
756
  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;
757
+ try {
758
+ this.stopEffects();
759
+ await this.waitForPendingEffects();
760
+ await this.runLifecycle("onDispose", true);
761
+ for (const scope of this.dynamicScopes.toReversed()) await scope.dispose();
762
+ await Promise.all(this.plugins.map((plugin) => plugin.dispose?.()));
763
+ await this.#container.dispose();
764
+ this.store.destroy();
765
+ this.isDisposed = true;
766
+ } catch (error) {
767
+ this.emitError(error, { phase: "dispose" });
768
+ throw error;
769
+ }
610
770
  }
611
771
  createScope(options) {
612
- return { container: this.container.createScope(options) };
772
+ return { container: this.#container.createScope(options) };
773
+ }
774
+ async load(module) {
775
+ if (module === void 0) {
776
+ const modules = this.pendingLazyModules.splice(0);
777
+ const results = [];
778
+ for (const pendingModule of modules) results.push(await this.load(pendingModule));
779
+ return results;
780
+ }
781
+ const existing = this.loadedLazyModules.get(module);
782
+ if (existing !== void 0) return existing;
783
+ if (this.isDisposed) throw new CosystemError("Cannot load a lazy module after app disposal.");
784
+ await this.initPromise;
785
+ try {
786
+ const providers = normalizeLazyModuleProviders(await module.load());
787
+ const scopeContainer = this.#container.createScope();
788
+ const moduleTokens = [];
789
+ for (const provider of providers) {
790
+ const normalized = normalizeAppProvider(provider);
791
+ scopeContainer.provide(normalized.provider);
792
+ if (normalized.moduleToken !== void 0) moduleTokens.push(normalized.moduleToken);
793
+ }
794
+ scopeContainer.freeze();
795
+ const loadedModules = instantiateModules(scopeContainer, moduleTokens, false);
796
+ this.assertNewModules(loadedModules);
797
+ this.installModuleState(loadedModules);
798
+ this.registerModules(loadedModules);
799
+ this.bindModules(loadedModules);
800
+ this.attachRuntimeMetadata(loadedModules);
801
+ instantiateEagerProviders(scopeContainer);
802
+ this.dynamicScopes.push(scopeContainer);
803
+ this.runModuleCreatedHooks(loadedModules);
804
+ await this.runLifecycle("onInit", false, loadedModules);
805
+ this.startEffects(loadedModules);
806
+ if (this.isStarted) await this.runLifecycle("onStart", false, loadedModules);
807
+ const result = {
808
+ modules: loadedModules.map(toModuleCreatedEvent),
809
+ scope: { container: scopeContainer }
810
+ };
811
+ this.loadedLazyModules.set(module, result);
812
+ return result;
813
+ } catch (error) {
814
+ this.emitError(error, { phase: "load" });
815
+ throw error;
816
+ }
613
817
  }
614
- bindModules() {
615
- for (const moduleBinding of this.modules) {
818
+ bindModules(modules = this.modules) {
819
+ for (const moduleBinding of modules) {
616
820
  this.bindState(moduleBinding);
617
821
  this.bindComputed(moduleBinding);
618
822
  this.bindActions(moduleBinding);
619
823
  }
620
824
  }
621
- runModuleCreatedHooks() {
622
- for (const moduleBinding of this.modules) {
623
- const event = {
825
+ attachRuntimeMetadata(modules = this.modules) {
826
+ for (const moduleBinding of modules) Object.defineProperty(moduleBinding.instance, runtimeModuleMetadataKey, {
827
+ configurable: false,
828
+ enumerable: false,
829
+ value: {
830
+ app: this,
624
831
  name: moduleBinding.name,
625
- token: moduleBinding.token,
626
- instance: moduleBinding.instance
627
- };
832
+ token: moduleBinding.token
833
+ }
834
+ });
835
+ }
836
+ runModuleCreatedHooks(modules = this.modules) {
837
+ for (const moduleBinding of modules) {
838
+ const event = toModuleCreatedEvent(moduleBinding);
628
839
  for (const plugin of this.plugins) plugin.onModuleCreated?.(event);
629
840
  }
630
841
  }
631
842
  init() {
632
843
  this.initPromise = (async () => {
633
- await Promise.all(this.plugins.map((plugin) => plugin.setup?.(this)));
634
- await this.runLifecycle("onInit");
844
+ try {
845
+ await Promise.all(this.plugins.map((plugin) => this.runWithAppInjectContext(() => plugin.setup?.(this))));
846
+ await this.runLifecycle("onInit");
847
+ this.startEffects();
848
+ } catch (error) {
849
+ this.emitError(error, { phase: "init" });
850
+ throw error;
851
+ }
635
852
  })();
636
853
  }
637
854
  bindState(moduleBinding) {
@@ -655,15 +872,21 @@ var RuntimeApp = class {
655
872
  bindComputed(moduleBinding) {
656
873
  for (const property of moduleBinding.metadata.computed) {
657
874
  const getter = getGetter(moduleBinding.instance, property);
875
+ const accessor = computed$1(() => getter.call(moduleBinding.instance));
658
876
  moduleBinding.originalComputed.set(property, getter);
877
+ moduleBinding.computedAccessors.set(property, accessor);
659
878
  Object.defineProperty(moduleBinding.instance, property, {
660
879
  configurable: true,
661
880
  enumerable: true,
662
- get: () => getter.call(moduleBinding.instance)
881
+ get: () => {
882
+ if (moduleBinding.activeDraft !== void 0 || !moduleBinding.reactiveSlice) return getter.call(moduleBinding.instance);
883
+ return accessor();
884
+ }
663
885
  });
664
886
  }
665
887
  }
666
888
  readModuleState(moduleBinding, property) {
889
+ if (!moduleBinding.reactiveSlice) return this.store.getPureState()[moduleBinding.name]?.[property];
667
890
  return this.store.getState()[moduleBinding.name]?.[property];
668
891
  }
669
892
  writeModuleState(moduleBinding, property, value) {
@@ -672,6 +895,18 @@ var RuntimeApp = class {
672
895
  return;
673
896
  }
674
897
  if (this.devOptions.strictActions === true && moduleBinding.actionDepth === 0) throw new CosystemError(`Cannot write ${moduleBinding.name}.${String(property)} outside an action.`);
898
+ if (!moduleBinding.reactiveSlice) {
899
+ const state = this.store.getPureState();
900
+ const slice = state[moduleBinding.name] ?? {};
901
+ this.store.setState({
902
+ ...state,
903
+ [moduleBinding.name]: {
904
+ ...slice,
905
+ [property]: value
906
+ }
907
+ });
908
+ return;
909
+ }
675
910
  this.store.setState((draft) => {
676
911
  draft[moduleBinding.name] ??= {};
677
912
  draft[moduleBinding.name][property] = value;
@@ -680,6 +915,9 @@ var RuntimeApp = class {
680
915
  runAction(moduleBinding, property, args) {
681
916
  const action = moduleBinding.originalActions.get(property);
682
917
  if (action === void 0) throw new CosystemError(`${moduleBinding.name}.${String(property)} is not an action.`);
918
+ return this.runActionCallback(moduleBinding, property, args, () => action.apply(moduleBinding.instance, [...args]));
919
+ }
920
+ runActionCallback(moduleBinding, property, args, callback) {
683
921
  const event = {
684
922
  args,
685
923
  method: String(property),
@@ -691,21 +929,76 @@ var RuntimeApp = class {
691
929
  let error;
692
930
  try {
693
931
  moduleBinding.actionDepth += 1;
694
- this.store.setState((draft) => {
932
+ if (moduleBinding.reactiveSlice) this.store.setState((draft) => {
695
933
  const previousDraft = moduleBinding.activeDraft;
696
934
  moduleBinding.activeDraft = draft[moduleBinding.name] ?? {};
697
935
  draft[moduleBinding.name] = moduleBinding.activeDraft;
698
936
  try {
699
- result = action.apply(moduleBinding.instance, [...args]);
937
+ result = callback();
700
938
  } finally {
701
939
  moduleBinding.activeDraft = previousDraft;
702
940
  }
703
941
  });
942
+ else {
943
+ const state = this.store.getPureState();
944
+ const slice = state[moduleBinding.name];
945
+ const previousDraft = moduleBinding.activeDraft;
946
+ moduleBinding.activeDraft = slice === void 0 ? {} : { ...slice };
947
+ try {
948
+ result = callback();
949
+ this.store.setState({
950
+ ...state,
951
+ [moduleBinding.name]: moduleBinding.activeDraft
952
+ });
953
+ } finally {
954
+ moduleBinding.activeDraft = previousDraft;
955
+ }
956
+ }
704
957
  } catch (caught) {
705
958
  error = caught;
959
+ this.emitError(caught, { phase: "action" });
706
960
  } finally {
707
961
  moduleBinding.actionDepth -= 1;
708
962
  }
963
+ if (error !== void 0) {
964
+ this.finishAction(event, error);
965
+ throw error;
966
+ }
967
+ if (isPromiseLike(result)) return Promise.resolve(result).then((value) => {
968
+ this.finishAction(event);
969
+ return value;
970
+ }, (caught) => {
971
+ this.emitError(caught, { phase: "action" });
972
+ this.finishAction(event, caught);
973
+ throw caught;
974
+ });
975
+ this.finishAction(event);
976
+ return result;
977
+ }
978
+ resolveModuleBinding(target) {
979
+ if (typeof target === "object" && target !== null) {
980
+ const metadata = getRuntimeModuleMetadata(target);
981
+ if (metadata !== void 0) {
982
+ if (metadata.app !== this) throw new CosystemError("runInAction() target belongs to another CoSystem app.");
983
+ const moduleBinding = this.moduleByToken.get(metadata.token);
984
+ if (moduleBinding !== void 0) return moduleBinding;
985
+ }
986
+ if (isTokenObject(target)) {
987
+ const moduleBinding = this.moduleByToken.get(target);
988
+ if (moduleBinding !== void 0) return moduleBinding;
989
+ }
990
+ throw new CosystemError("runInAction() target is not a CoSystem module.");
991
+ }
992
+ if (typeof target === "string") {
993
+ const moduleBinding = this.moduleByName.get(target) ?? this.moduleByToken.get(target);
994
+ if (moduleBinding !== void 0) return moduleBinding;
995
+ throw new CosystemError(`${target} is not a CoSystem module.`);
996
+ }
997
+ const moduleBinding = this.moduleByToken.get(target);
998
+ if (moduleBinding !== void 0) return moduleBinding;
999
+ throw new CosystemError(`${tokenName(target)} is not a CoSystem module.`);
1000
+ }
1001
+ finishAction(event, error) {
709
1002
  const endedEvent = {
710
1003
  ...event,
711
1004
  endedAt: Date.now(),
@@ -713,12 +1006,63 @@ var RuntimeApp = class {
713
1006
  };
714
1007
  this.testInspector?.recordAction(endedEvent);
715
1008
  this.emitActionEnd(endedEvent);
716
- if (error !== void 0) throw error;
717
- return result;
718
1009
  }
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]?.();
1010
+ async runLifecycle(method, reverse = false, modules = this.modules) {
1011
+ const orderedModules = reverse ? modules.toReversed() : modules;
1012
+ for (const moduleBinding of orderedModules) {
1013
+ const lifecycle = moduleBinding.instance;
1014
+ await this.runWithAppInjectContext(() => lifecycle[method]?.());
1015
+ }
1016
+ }
1017
+ startEffects(modules = this.modules) {
1018
+ for (const moduleBinding of modules) for (const property of moduleBinding.metadata.effects) this.startEffect(moduleBinding, property);
1019
+ }
1020
+ startEffect(moduleBinding, property) {
1021
+ const method = getMethod(moduleBinding.instance, property);
1022
+ const tracker = createReactiveTracker();
1023
+ let disposed = false;
1024
+ const run = () => {
1025
+ if (disposed) return;
1026
+ try {
1027
+ tracker.track(() => this.runEffect(moduleBinding, property, method));
1028
+ } catch (error) {
1029
+ this.emitError(error, { phase: "effect" });
1030
+ throw error;
1031
+ }
1032
+ };
1033
+ const unsubscribe = tracker.subscribe(() => {
1034
+ try {
1035
+ run();
1036
+ } catch {}
1037
+ });
1038
+ run();
1039
+ this.effectDisposers.push(() => {
1040
+ disposed = true;
1041
+ unsubscribe();
1042
+ tracker.dispose();
1043
+ });
1044
+ }
1045
+ runEffect(moduleBinding, property, method) {
1046
+ const result = this.runWithAppInjectContext(() => method.call(moduleBinding.instance));
1047
+ if (!isPromiseLike(result)) return;
1048
+ const pending = Promise.resolve(result).then(() => void 0).catch((error) => {
1049
+ this.emitError(error, { phase: `effect:${moduleBinding.name}.${String(property)}` });
1050
+ throw error;
1051
+ }).finally(() => {
1052
+ this.pendingEffects.delete(pending);
1053
+ });
1054
+ this.pendingEffects.add(pending);
1055
+ pending.catch(() => void 0);
1056
+ }
1057
+ stopEffects() {
1058
+ for (const dispose of this.effectDisposers.splice(0).toReversed()) dispose();
1059
+ }
1060
+ async flushEffects() {
1061
+ await this.initPromise;
1062
+ await this.waitForPendingEffects();
1063
+ }
1064
+ async waitForPendingEffects() {
1065
+ while (this.pendingEffects.size > 0) await Promise.all(this.pendingEffects);
722
1066
  }
723
1067
  emitActionStart(event) {
724
1068
  for (const plugin of this.plugins) plugin.onActionStart?.(event);
@@ -729,6 +1073,59 @@ var RuntimeApp = class {
729
1073
  emitStateChange(event) {
730
1074
  for (const plugin of this.plugins) plugin.onStateChange?.(event);
731
1075
  }
1076
+ emitPatch(event) {
1077
+ for (const plugin of this.plugins) plugin.onPatch?.(event);
1078
+ }
1079
+ emitError(error, context) {
1080
+ for (const plugin of this.plugins) plugin.onError?.(error, context);
1081
+ }
1082
+ runWithAppInjectContext(callback) {
1083
+ return runWithInjectContext({
1084
+ mode: "sync",
1085
+ requestContainer: this.#container,
1086
+ resolutionCache: /* @__PURE__ */ new Map(),
1087
+ stack: []
1088
+ }, callback);
1089
+ }
1090
+ wrapStoreSetState() {
1091
+ const originalSetState = this.store.setState.bind(this.store);
1092
+ this.store.setState = ((...args) => {
1093
+ const result = originalSetState(...args);
1094
+ this.recordMutationResult(result);
1095
+ return result;
1096
+ });
1097
+ }
1098
+ recordMutationResult(result) {
1099
+ if (!Array.isArray(result) || result.length < 3) return;
1100
+ const patches = result[1];
1101
+ if (patches.length === 0) return;
1102
+ this.testInspector?.recordPatch(patches);
1103
+ this.emitPatch({
1104
+ inversePatches: result[2],
1105
+ patches
1106
+ });
1107
+ }
1108
+ assertNewModules(modules) {
1109
+ for (const moduleBinding of modules) {
1110
+ if (this.moduleByToken.has(moduleBinding.token)) throw new DuplicateProviderError(tokenName(moduleBinding.token));
1111
+ if (this.moduleByName.has(moduleBinding.name)) throw new DuplicateProviderError(moduleBinding.name);
1112
+ }
1113
+ }
1114
+ installModuleState(modules) {
1115
+ if (modules.length === 0) return;
1116
+ const rootState = createRootState(modules);
1117
+ this.store.setState({
1118
+ ...this.store.getPureState(),
1119
+ ...rootState
1120
+ });
1121
+ }
1122
+ registerModules(modules) {
1123
+ for (const moduleBinding of modules) {
1124
+ this.modules.push(moduleBinding);
1125
+ this.moduleByToken.set(moduleBinding.token, moduleBinding);
1126
+ this.moduleByName.set(moduleBinding.name, moduleBinding);
1127
+ }
1128
+ }
732
1129
  };
733
1130
  function normalizeAppProvider(provider) {
734
1131
  if (typeof provider === "function") {
@@ -751,6 +1148,18 @@ function normalizeAppProvider(provider) {
751
1148
  }
752
1149
  return { provider };
753
1150
  }
1151
+ function createStoreOptions(engine, enablePatches) {
1152
+ return {
1153
+ name: "cosystem",
1154
+ sliceMode: "single",
1155
+ enablePatches,
1156
+ ...engine?.transport === void 0 ? {} : { transport: engine.transport }
1157
+ };
1158
+ }
1159
+ function shouldEnablePatches(options) {
1160
+ if (options.engine?.patches !== void 0) return options.engine.patches;
1161
+ return options.testInspector !== void 0 || (options.plugins ?? []).some((plugin) => plugin.onPatch !== void 0);
1162
+ }
754
1163
  function createModuleClassProviderOptions(useClass, metadata) {
755
1164
  return {
756
1165
  useClass,
@@ -764,7 +1173,7 @@ function mergeModuleClassProviderOptions(provider, metadata) {
764
1173
  ..."scope" in provider || metadata.scope === void 0 ? {} : { scope: metadata.scope }
765
1174
  };
766
1175
  }
767
- function instantiateModules(container, moduleTokens) {
1176
+ function instantiateModules(container, moduleTokens, reactiveSlice = true) {
768
1177
  const modules = [];
769
1178
  for (const moduleToken of moduleTokens) {
770
1179
  const instance = container.get(moduleToken);
@@ -775,16 +1184,36 @@ function instantiateModules(container, moduleTokens) {
775
1184
  modules.push({
776
1185
  actionDepth: 0,
777
1186
  activeDraft: void 0,
1187
+ computedAccessors: /* @__PURE__ */ new Map(),
778
1188
  instance,
779
1189
  metadata,
780
1190
  name,
781
1191
  originalActions: /* @__PURE__ */ new Map(),
782
1192
  originalComputed: /* @__PURE__ */ new Map(),
1193
+ reactiveSlice,
783
1194
  token: moduleToken
784
1195
  });
785
1196
  }
786
1197
  return modules;
787
1198
  }
1199
+ function toModuleCreatedEvent(moduleBinding) {
1200
+ return {
1201
+ instance: moduleBinding.instance,
1202
+ name: moduleBinding.name,
1203
+ token: moduleBinding.token
1204
+ };
1205
+ }
1206
+ function instantiateEagerProviders(container) {
1207
+ const internalContainer = container;
1208
+ for (const [token, records] of internalContainer.records) {
1209
+ if (!records.some((record) => record.eager)) continue;
1210
+ if (records.some((record) => record.multi)) {
1211
+ container.getAll(token);
1212
+ continue;
1213
+ }
1214
+ container.get(token);
1215
+ }
1216
+ }
788
1217
  function createRootState(modules) {
789
1218
  const rootState = {};
790
1219
  for (const moduleBinding of modules) {
@@ -808,6 +1237,12 @@ function getGetter(instance, property) {
808
1237
  if (descriptor?.get === void 0) throw new CosystemError(`${String(property)} is not a getter.`);
809
1238
  return descriptor.get;
810
1239
  }
1240
+ function getRuntimeModuleMetadata(module) {
1241
+ return module[runtimeModuleMetadataKey];
1242
+ }
1243
+ function isTokenObject(value) {
1244
+ return "id" in value && typeof value.id === "symbol";
1245
+ }
811
1246
  function getDescriptor(instance, property) {
812
1247
  let current = instance;
813
1248
  while (current !== null) {
@@ -819,32 +1254,71 @@ function getDescriptor(instance, property) {
819
1254
  function isApp(value) {
820
1255
  return typeof value === "object" && value !== null && "get" in value && "start" in value && "dispose" in value;
821
1256
  }
1257
+ function isPromiseLike(value) {
1258
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
1259
+ }
1260
+ function getAppContainer(app) {
1261
+ const container = appContainerMap.get(app);
1262
+ if (container === void 0) throw new CosystemError("Parent app was not created by CoSystem.");
1263
+ return container;
1264
+ }
822
1265
  //#endregion
823
1266
  //#region src/decorators.ts
824
1267
  function module(options = {}) {
825
- return function moduleDecorator(target, _context) {
826
- defineModule(target, options);
1268
+ return function moduleDecorator(target, context) {
1269
+ defineModule(target, options, context);
827
1270
  return target;
828
1271
  };
829
1272
  }
830
1273
  function state(_value, context) {
831
1274
  if (context.kind !== "accessor") throw new TypeError("@state only supports standard accessor decorators.");
1275
+ ensureContextModuleMetadata(context)?.state.add(context.name);
832
1276
  context.addInitializer(function initializeState() {
833
1277
  addModuleState(this.constructor, context.name);
834
1278
  });
835
1279
  }
836
1280
  function action(_value, context) {
837
1281
  if (context.kind !== "method") throw new TypeError("@action only supports method decorators.");
1282
+ ensureContextModuleMetadata(context)?.actions.add(context.name);
838
1283
  context.addInitializer(function initializeAction() {
839
1284
  addModuleAction(this.constructor, context.name);
840
1285
  });
841
1286
  }
842
1287
  function computed(_value, context) {
843
1288
  if (context.kind !== "getter") throw new TypeError("@computed only supports getter decorators.");
1289
+ ensureContextModuleMetadata(context)?.computed.add(context.name);
844
1290
  context.addInitializer(function initializeComputed() {
845
1291
  addModuleComputed(this.constructor, context.name);
846
1292
  });
847
1293
  }
1294
+ function effect(_value, context) {
1295
+ if (context.kind !== "method") throw new TypeError("@effect only supports method decorators.");
1296
+ ensureContextModuleMetadata(context)?.effects.add(context.name);
1297
+ context.addInitializer(function initializeEffect() {
1298
+ addModuleEffect(this.constructor, context.name);
1299
+ });
1300
+ }
1301
+ //#endregion
1302
+ //#region src/loggerPlugin.ts
1303
+ function createLoggerPlugin(options = {}) {
1304
+ const logger = options.logger ?? console;
1305
+ return {
1306
+ name: "cosystem:logger",
1307
+ onActionEnd(event) {
1308
+ if (event.error !== void 0) {
1309
+ logger.error(`Action failed: ${event.module}.${event.method}`, event);
1310
+ return;
1311
+ }
1312
+ logger.info(`Action completed: ${event.module}.${event.method}`, event);
1313
+ },
1314
+ onError(error, context) {
1315
+ logger.error(`Runtime error during ${context.phase}`, error);
1316
+ },
1317
+ onModuleCreated(event) {
1318
+ logger.info(`Module created: ${event.name}`, event);
1319
+ }
1320
+ };
1321
+ }
848
1322
  //#endregion
849
1323
  //#region src/testApp.ts
850
1324
  function testApp(options = {}) {
@@ -867,9 +1341,13 @@ function testApp(options = {}) {
867
1341
  if (autoStart === true) return app.start().then(() => app);
868
1342
  return app;
869
1343
  }
1344
+ function defaultFlushEffects() {
1345
+ return Promise.resolve();
1346
+ }
870
1347
  function createTestInspector() {
871
1348
  const actions = [];
872
1349
  const patches = [];
1350
+ let flushEffects = defaultFlushEffects;
873
1351
  let lastState;
874
1352
  return {
875
1353
  clearActions() {
@@ -879,7 +1357,7 @@ function createTestInspector() {
879
1357
  patches.length = 0;
880
1358
  },
881
1359
  flushEffects() {
882
- return Promise.resolve();
1360
+ return flushEffects();
883
1361
  },
884
1362
  getActions() {
885
1363
  return actions;
@@ -894,59 +1372,78 @@ function createTestInspector() {
894
1372
  actions.push(event);
895
1373
  },
896
1374
  recordPatch(patch) {
897
- lastState = patch;
898
1375
  patches.push(patch);
1376
+ },
1377
+ recordState(state) {
1378
+ lastState = state;
1379
+ },
1380
+ setFlushEffects(callback) {
1381
+ flushEffects = callback;
899
1382
  }
900
1383
  };
901
1384
  }
902
1385
  //#endregion
903
1386
  //#region src/worker.ts
904
1387
  function createWorkerApp(options) {
905
- const { transport, ...appOptions } = options;
906
- const app = createApp(appOptions);
1388
+ const { stateSections, sync = "snapshot", transport, ...appOptions } = options;
1389
+ let stateSyncVersion = 0;
1390
+ let publishPatches = false;
1391
+ const patchPlugin = {
1392
+ name: "cosystem:worker-patches",
1393
+ onPatch(event) {
1394
+ if (publishPatches) {
1395
+ const version = stateSections === void 0 ? app.state.version : stateSyncVersion + 1;
1396
+ if (publishState(app, transport, event.patches, sync, stateSections, version)) stateSyncVersion = version;
1397
+ }
1398
+ }
1399
+ };
1400
+ const app = createApp({
1401
+ ...appOptions,
1402
+ engine: {
1403
+ ...appOptions.engine,
1404
+ patches: true
1405
+ },
1406
+ plugins: [...appOptions.plugins ?? [], patchPlugin]
1407
+ });
1408
+ const ready = app.start().then(() => {
1409
+ publishPatches = true;
1410
+ transport.post({ type: "ready" });
1411
+ const version = stateSections === void 0 ? app.state.version : stateSyncVersion;
1412
+ if (publishState(app, transport, [], "snapshot", stateSections, version)) stateSyncVersion = version;
1413
+ });
907
1414
  const unsubscribeTransport = transport.subscribe((message) => {
908
1415
  if (message.type !== "call") return;
909
- handleCall(app, transport, message);
910
- });
911
- const unsubscribeStore = app.store.subscribe(() => {
912
- publishState(app, transport);
1416
+ handleCall(app, transport, message, ready);
913
1417
  });
914
- transport.post({ type: "ready" });
915
- publishState(app, transport);
916
1418
  return {
917
1419
  app,
1420
+ ready,
918
1421
  async dispose() {
919
1422
  unsubscribeTransport();
920
- unsubscribeStore();
1423
+ await ready.catch(() => void 0);
921
1424
  await app.dispose();
922
1425
  }
923
1426
  };
924
1427
  }
925
1428
  function createWorkerClient(options) {
926
- const { transport } = options;
1429
+ const { onConflict, transport } = options;
927
1430
  const listeners = /* @__PURE__ */ new Set();
1431
+ const selectorWatchers = /* @__PURE__ */ new Set();
928
1432
  const pending = /* @__PURE__ */ new Map();
929
1433
  const state = { version: 0 };
930
1434
  let nextId = 1;
931
1435
  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);
1436
+ let readySettled = false;
1437
+ let resolveReady;
1438
+ let rejectReady;
1439
+ const ready = new Promise((resolve, reject) => {
1440
+ resolveReady = resolve;
1441
+ rejectReady = reject;
948
1442
  });
1443
+ ready.catch(() => void 0);
1444
+ let unsubscribe = noop;
949
1445
  const client = {
1446
+ ready,
950
1447
  state,
951
1448
  call(module, method, ...args) {
952
1449
  const id = nextId;
@@ -967,9 +1464,14 @@ function createWorkerClient(options) {
967
1464
  },
968
1465
  dispose() {
969
1466
  unsubscribe();
1467
+ if (!readySettled) {
1468
+ readySettled = true;
1469
+ rejectReady(new CosystemError("Worker client disposed before initial state."));
1470
+ }
970
1471
  for (const entry of pending.values()) entry.reject(new CosystemError("Worker client disposed before response."));
971
1472
  pending.clear();
972
1473
  listeners.clear();
1474
+ selectorWatchers.clear();
973
1475
  },
974
1476
  getState() {
975
1477
  return snapshot;
@@ -980,22 +1482,256 @@ function createWorkerClient(options) {
980
1482
  return (...args) => client.call(name, property, ...args);
981
1483
  } });
982
1484
  },
1485
+ select(selector) {
1486
+ if (snapshot === void 0) throw new CosystemError("Worker client state is not ready.");
1487
+ return selector(snapshot, client);
1488
+ },
983
1489
  subscribe(listener) {
984
1490
  listeners.add(listener);
985
1491
  return () => {
986
1492
  listeners.delete(listener);
987
1493
  };
1494
+ },
1495
+ watch(selector, listener, watchOptions = {}) {
1496
+ const watcher = {
1497
+ equals: watchOptions.equals ?? Object.is,
1498
+ immediate: watchOptions.immediate ?? false,
1499
+ initialized: false,
1500
+ listener,
1501
+ previous: void 0,
1502
+ selector
1503
+ };
1504
+ if (snapshot !== void 0) {
1505
+ const value = selector(snapshot, client);
1506
+ watcher.initialized = true;
1507
+ watcher.previous = value;
1508
+ if (watcher.immediate) listener(value, value);
1509
+ }
1510
+ selectorWatchers.add(watcher);
1511
+ return () => {
1512
+ selectorWatchers.delete(watcher);
1513
+ };
1514
+ }
1515
+ };
1516
+ const publishSelectorWatchers = () => {
1517
+ if (snapshot === void 0) return;
1518
+ for (const watcher of selectorWatchers) {
1519
+ const value = watcher.selector(snapshot, client);
1520
+ if (!watcher.initialized) {
1521
+ watcher.initialized = true;
1522
+ watcher.previous = value;
1523
+ if (watcher.immediate) watcher.listener(value, value);
1524
+ continue;
1525
+ }
1526
+ const previous = watcher.previous;
1527
+ if (watcher.equals(value, previous)) continue;
1528
+ watcher.previous = value;
1529
+ watcher.listener(value, previous);
988
1530
  }
989
1531
  };
1532
+ unsubscribe = transport.subscribe((message) => {
1533
+ if (message.type === "state") {
1534
+ if (readySettled && message.version <= state.version) {
1535
+ reportWorkerConflict(onConflict, {
1536
+ currentVersion: state.version,
1537
+ incomingVersion: message.version,
1538
+ message,
1539
+ reason: "stale-message"
1540
+ });
1541
+ return;
1542
+ }
1543
+ const isPatchOnly = message.state === void 0 && message.sync === "patch";
1544
+ if (isPatchOnly && snapshot === void 0) {
1545
+ reportWorkerConflict(onConflict, {
1546
+ currentVersion: state.version,
1547
+ incomingVersion: message.version,
1548
+ message,
1549
+ reason: "missing-snapshot"
1550
+ });
1551
+ return;
1552
+ }
1553
+ if (isPatchOnly && readySettled && message.version !== state.version + 1) {
1554
+ reportWorkerConflict(onConflict, {
1555
+ currentVersion: state.version,
1556
+ incomingVersion: message.version,
1557
+ message,
1558
+ reason: "version-gap"
1559
+ });
1560
+ return;
1561
+ }
1562
+ try {
1563
+ snapshot = isPatchOnly ? applyWorkerPatches(snapshot, message.patches ?? []) : message.state;
1564
+ } catch (error) {
1565
+ reportWorkerConflict(onConflict, {
1566
+ currentVersion: state.version,
1567
+ error,
1568
+ incomingVersion: message.version,
1569
+ message,
1570
+ reason: "patch-apply-failed"
1571
+ });
1572
+ return;
1573
+ }
1574
+ state.version = message.version;
1575
+ if (!readySettled) {
1576
+ readySettled = true;
1577
+ resolveReady();
1578
+ }
1579
+ for (const listener of listeners) listener(message);
1580
+ publishSelectorWatchers();
1581
+ return;
1582
+ }
1583
+ if (message.type !== "result") return;
1584
+ const entry = pending.get(message.id);
1585
+ if (entry === void 0) return;
1586
+ pending.delete(message.id);
1587
+ if (message.error !== void 0) {
1588
+ entry.reject(createRemoteError(message.error));
1589
+ return;
1590
+ }
1591
+ entry.resolve(message.value);
1592
+ });
990
1593
  return client;
991
1594
  }
1595
+ function reportWorkerConflict(onConflict, event) {
1596
+ onConflict?.(event);
1597
+ }
992
1598
  function createMemoryWorkerTransportPair() {
993
1599
  const leftListeners = /* @__PURE__ */ new Set();
994
1600
  const rightListeners = /* @__PURE__ */ new Set();
995
1601
  return [createMemoryWorkerTransport(leftListeners, rightListeners), createMemoryWorkerTransport(rightListeners, leftListeners)];
996
1602
  }
997
- async function handleCall(app, transport, message) {
1603
+ function createPostMessageWorkerTransport(endpoint, options = {}) {
1604
+ const source = options.source ?? endpoint;
1605
+ const target = options.target ?? endpoint;
1606
+ return {
1607
+ post(message) {
1608
+ try {
1609
+ target.postMessage(message);
1610
+ } catch (error) {
1611
+ options.onError?.(error, message);
1612
+ }
1613
+ },
1614
+ subscribe(listener) {
1615
+ const handleMessage = (event) => {
1616
+ if (!isWorkerMessage(event.data)) return;
1617
+ listener(event.data);
1618
+ };
1619
+ source.addEventListener("message", handleMessage);
1620
+ return () => {
1621
+ source.removeEventListener("message", handleMessage);
1622
+ };
1623
+ }
1624
+ };
1625
+ }
1626
+ function createBroadcastWorkerTransport(broadcast, options = {}) {
1627
+ const channel = options.channel ?? defaultBroadcastWorkerChannel;
1628
+ const peerId = options.peerId ?? createWorkerPeerId();
1629
+ const callRoutes = /* @__PURE__ */ new Map();
1630
+ let nextRoutedCallId = 1;
1631
+ return {
1632
+ post(message) {
1633
+ try {
1634
+ const routed = routeBroadcastWorkerMessage(message, callRoutes);
1635
+ const target = routed.target ?? getBroadcastWorkerTarget(message, options);
1636
+ const envelope = {
1637
+ channel,
1638
+ message: routed.message,
1639
+ source: peerId,
1640
+ type: "cosystem:worker",
1641
+ ...target === void 0 ? {} : { target }
1642
+ };
1643
+ broadcast.postMessage(envelope);
1644
+ } catch (error) {
1645
+ options.onError?.(error, message);
1646
+ }
1647
+ },
1648
+ subscribe(listener) {
1649
+ const handleMessage = (event) => {
1650
+ const envelope = event.data;
1651
+ if (!isBroadcastWorkerMessageEnvelope(envelope) || envelope.channel !== channel || envelope.source === peerId || envelope.target !== void 0 && envelope.target !== peerId) return;
1652
+ if (envelope.message.type === "call") {
1653
+ const routedCallId = nextRoutedCallId;
1654
+ nextRoutedCallId += 1;
1655
+ callRoutes.set(routedCallId, {
1656
+ id: envelope.message.id,
1657
+ source: envelope.source
1658
+ });
1659
+ listener({
1660
+ ...envelope.message,
1661
+ id: routedCallId
1662
+ });
1663
+ return;
1664
+ }
1665
+ listener(envelope.message);
1666
+ };
1667
+ broadcast.addEventListener("message", handleMessage);
1668
+ return () => {
1669
+ broadcast.removeEventListener("message", handleMessage);
1670
+ };
1671
+ }
1672
+ };
1673
+ }
1674
+ function createMemoryBroadcastChannel(name = defaultBroadcastWorkerChannel) {
1675
+ return new MemoryBroadcastChannel(name);
1676
+ }
1677
+ function createDataTransportWorkerTransport(dataTransport, options = {}) {
1678
+ const listeners = /* @__PURE__ */ new Set();
1679
+ const disposers = [];
1680
+ let listening = false;
1681
+ const start = () => {
1682
+ if (listening) return;
1683
+ listening = true;
1684
+ for (const type of workerMessageTypes) {
1685
+ const dispose = dataTransport.listen(type, (message) => {
1686
+ if (!isWorkerMessage(message)) return;
1687
+ for (const listener of listeners) listener(message);
1688
+ });
1689
+ if (typeof dispose === "function") disposers.push(dispose);
1690
+ }
1691
+ };
1692
+ const stop = () => {
1693
+ if (!listening) return;
1694
+ listening = false;
1695
+ for (const dispose of disposers.splice(0)) dispose();
1696
+ };
1697
+ return {
1698
+ post(message) {
1699
+ dataTransport.emit({
1700
+ name: message.type,
1701
+ respond: false
1702
+ }, message).catch((error) => {
1703
+ options.onError?.(error, message);
1704
+ });
1705
+ },
1706
+ subscribe(listener) {
1707
+ start();
1708
+ listeners.add(listener);
1709
+ return () => {
1710
+ listeners.delete(listener);
1711
+ if (listeners.size === 0) stop();
1712
+ };
1713
+ }
1714
+ };
1715
+ }
1716
+ function getBroadcastWorkerTarget(message, options) {
1717
+ return message.type === "call" ? options.targetPeerId : void 0;
1718
+ }
1719
+ function routeBroadcastWorkerMessage(message, callRoutes) {
1720
+ if (message.type !== "result") return { message };
1721
+ const route = callRoutes.get(message.id);
1722
+ if (route === void 0) return { message };
1723
+ callRoutes.delete(message.id);
1724
+ return {
1725
+ message: {
1726
+ ...message,
1727
+ id: route.id
1728
+ },
1729
+ target: route.source
1730
+ };
1731
+ }
1732
+ async function handleCall(app, transport, message, ready) {
998
1733
  try {
1734
+ await ready;
999
1735
  const module = app.getModuleByName(message.module);
1000
1736
  const method = module[message.method];
1001
1737
  if (typeof method !== "function") throw new CosystemError(`${message.module}.${message.method} is not callable.`);
@@ -1013,13 +1749,112 @@ async function handleCall(app, transport, message) {
1013
1749
  });
1014
1750
  }
1015
1751
  }
1016
- function publishState(app, transport) {
1017
- transport.post({
1018
- state: app.store.getPureState(),
1752
+ function publishState(app, transport, patches = [], mode = "snapshot", sections, version = app.state.version) {
1753
+ const filteredPatches = filterWorkerPatches(patches, sections);
1754
+ const isPatch = filteredPatches.length > 0;
1755
+ if (patches.length > 0 && filteredPatches.length === 0) return false;
1756
+ const state = filterWorkerState(app.store.getPureState(), sections);
1757
+ const message = {
1758
+ ...isPatch && mode === "patch" ? {} : { state },
1759
+ ...sections === void 0 ? {} : { sections },
1760
+ sync: isPatch ? "patch" : "snapshot",
1019
1761
  type: "state",
1020
- version: app.state.version
1762
+ version,
1763
+ ...isPatch ? { patches: filteredPatches } : {}
1764
+ };
1765
+ transport.post(message);
1766
+ return true;
1767
+ }
1768
+ function filterWorkerState(state, sections) {
1769
+ if (sections === void 0 || !isRecord(state)) return state;
1770
+ const filtered = {};
1771
+ for (const section of sections) if (section in state) filtered[section] = state[section];
1772
+ return filtered;
1773
+ }
1774
+ function filterWorkerPatches(patches, sections) {
1775
+ if (sections === void 0) return patches;
1776
+ const sectionSet = new Set(sections);
1777
+ return patches.filter((patch) => {
1778
+ if (!isWorkerPatch(patch)) throw new CosystemError("Worker state patch is invalid.");
1779
+ const section = getWorkerPatchSection(patch);
1780
+ return section !== void 0 && sectionSet.has(String(section));
1021
1781
  });
1022
1782
  }
1783
+ function getWorkerPatchSection(patch) {
1784
+ return normalizePatchPath(patch.path)[0];
1785
+ }
1786
+ function applyWorkerPatches(state, patches) {
1787
+ let next = state;
1788
+ for (const patch of patches) next = applyWorkerPatch(next, patch);
1789
+ return next;
1790
+ }
1791
+ function applyWorkerPatch(state, patch) {
1792
+ if (!isWorkerPatch(patch)) throw new CosystemError("Worker state patch is invalid.");
1793
+ const path = normalizePatchPath(patch.path);
1794
+ if (path.length === 0) {
1795
+ if (patch.op === "remove") return;
1796
+ return patch.value;
1797
+ }
1798
+ return applyPatchAtPath(state, path, patch);
1799
+ }
1800
+ function applyPatchAtPath(state, path, patch) {
1801
+ const [segment, ...rest] = path;
1802
+ if (segment === void 0) throw new CosystemError("Worker state patch path is invalid.");
1803
+ const container = clonePatchContainer(state, segment);
1804
+ if (rest.length === 0) {
1805
+ if (patch.op === "remove") {
1806
+ removePatchValue(container, segment);
1807
+ return container;
1808
+ }
1809
+ setPatchValue(container, segment, patch.value);
1810
+ return container;
1811
+ }
1812
+ setPatchValue(container, segment, applyPatchAtPath(getPatchValue(container, segment), rest, patch));
1813
+ return container;
1814
+ }
1815
+ function clonePatchContainer(state, nextSegment) {
1816
+ if (Array.isArray(state)) return [...state];
1817
+ if (isRecord(state)) return { ...state };
1818
+ return typeof nextSegment === "number" ? [] : {};
1819
+ }
1820
+ function getPatchValue(container, segment) {
1821
+ if (Array.isArray(container)) return container[toArrayIndex(segment)];
1822
+ return container[String(segment)];
1823
+ }
1824
+ function setPatchValue(container, segment, value) {
1825
+ if (Array.isArray(container)) {
1826
+ container[toArrayIndex(segment)] = value;
1827
+ return;
1828
+ }
1829
+ container[String(segment)] = value;
1830
+ }
1831
+ function removePatchValue(container, segment) {
1832
+ if (Array.isArray(container)) {
1833
+ container.splice(toArrayIndex(segment), 1);
1834
+ return;
1835
+ }
1836
+ delete container[String(segment)];
1837
+ }
1838
+ function toArrayIndex(segment) {
1839
+ if (typeof segment === "number") return segment;
1840
+ return Number(segment);
1841
+ }
1842
+ function normalizePatchPath(path) {
1843
+ if (Array.isArray(path)) return path.map((segment) => normalizePatchPathSegment(segment));
1844
+ if (typeof path === "string") {
1845
+ if (path === "" || path === "/") return [];
1846
+ return path.split("/").slice(1).map((segment) => normalizePatchPathSegment(segment.replaceAll("~1", "/").replaceAll("~0", "~")));
1847
+ }
1848
+ throw new CosystemError("Worker state patch path is invalid.");
1849
+ }
1850
+ function normalizePatchPathSegment(segment) {
1851
+ if (typeof segment === "number" || typeof segment === "string") return segment;
1852
+ throw new CosystemError("Worker state patch path segment is invalid.");
1853
+ }
1854
+ function isWorkerPatch(value) {
1855
+ if (!isRecord(value)) return false;
1856
+ return (value.op === "add" || value.op === "replace" || value.op === "remove") && (Array.isArray(value.path) || typeof value.path === "string");
1857
+ }
1023
1858
  function createMemoryWorkerTransport(inbox, outbox) {
1024
1859
  return {
1025
1860
  post(message) {
@@ -1033,6 +1868,72 @@ function createMemoryWorkerTransport(inbox, outbox) {
1033
1868
  }
1034
1869
  };
1035
1870
  }
1871
+ const workerMessageTypes = [
1872
+ "call",
1873
+ "result",
1874
+ "state",
1875
+ "ready"
1876
+ ];
1877
+ const defaultBroadcastWorkerChannel = "cosystem:worker";
1878
+ const memoryBroadcastChannels = /* @__PURE__ */ new Map();
1879
+ let nextWorkerPeerId = 1;
1880
+ var MemoryBroadcastChannel = class {
1881
+ name;
1882
+ #listeners = /* @__PURE__ */ new Set();
1883
+ #closed = false;
1884
+ constructor(name) {
1885
+ this.name = name;
1886
+ let channels = memoryBroadcastChannels.get(name);
1887
+ if (channels === void 0) {
1888
+ channels = /* @__PURE__ */ new Set();
1889
+ memoryBroadcastChannels.set(name, channels);
1890
+ }
1891
+ channels.add(this);
1892
+ }
1893
+ postMessage(message) {
1894
+ if (this.#closed) return;
1895
+ const channels = memoryBroadcastChannels.get(this.name);
1896
+ if (channels === void 0) return;
1897
+ for (const channel of channels) {
1898
+ if (channel === this || channel.#closed) continue;
1899
+ channel.dispatch({ data: message });
1900
+ }
1901
+ }
1902
+ addEventListener(_type, listener) {
1903
+ if (!this.#closed) this.#listeners.add(listener);
1904
+ }
1905
+ removeEventListener(_type, listener) {
1906
+ this.#listeners.delete(listener);
1907
+ }
1908
+ close() {
1909
+ if (this.#closed) return;
1910
+ this.#closed = true;
1911
+ this.#listeners.clear();
1912
+ const channels = memoryBroadcastChannels.get(this.name);
1913
+ if (channels === void 0) return;
1914
+ channels.delete(this);
1915
+ if (channels.size === 0) memoryBroadcastChannels.delete(this.name);
1916
+ }
1917
+ dispatch(event) {
1918
+ for (const listener of this.#listeners) listener(event);
1919
+ }
1920
+ };
1921
+ function createWorkerPeerId() {
1922
+ const id = nextWorkerPeerId;
1923
+ nextWorkerPeerId += 1;
1924
+ return `peer:${id}`;
1925
+ }
1926
+ function isWorkerMessage(message) {
1927
+ if (typeof message !== "object" || message === null || !("type" in message)) return false;
1928
+ return workerMessageTypes.includes(message.type);
1929
+ }
1930
+ function isBroadcastWorkerMessageEnvelope(message) {
1931
+ if (!isRecord(message)) return false;
1932
+ return message.type === "cosystem:worker" && typeof message.channel === "string" && typeof message.source === "string" && (message.target === void 0 || typeof message.target === "string") && isWorkerMessage(message.message);
1933
+ }
1934
+ function isRecord(value) {
1935
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1936
+ }
1036
1937
  function serializeError(error) {
1037
1938
  if (error instanceof Error) return {
1038
1939
  message: error.message,
@@ -1050,7 +1951,8 @@ function createRemoteError(error) {
1050
1951
  if (error.stack !== void 0) remoteError.stack = error.stack;
1051
1952
  return remoteError;
1052
1953
  }
1954
+ function noop() {}
1053
1955
  //#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 };
1956
+ export { AmbiguousProviderError, AsyncProviderInSyncResolutionError, CircularDependencyError, CosystemError, DuplicateProviderError, FrozenContainerError, InjectContextError, LifetimeLeakError, MissingProviderError, action, computed, createApp, createBroadcastWorkerTransport, createContainer, createDataTransportWorkerTransport, createLoggerPlugin, createMemoryBroadcastChannel, createMemoryWorkerTransportPair, createPostMessageWorkerTransport, createWorkerApp, createWorkerClient, defineModule, effect, getModuleMetadata, inject, lazyModule, module, provide, runInAction, state, testApp, token, tokenName };
1055
1957
 
1056
1958
  //# sourceMappingURL=index.mjs.map