@cosystem/core 0.0.2 → 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
  }));
@@ -362,7 +368,7 @@ var RuntimeContainer = class RuntimeContainer {
362
368
  }
363
369
  resolveAllRecords(records, context) {
364
370
  const values = records.map((record) => this.resolveRecord(record, context));
365
- if (values.some(isPromiseLike)) return Promise.all(values);
371
+ if (values.some(isPromiseLike$1)) return Promise.all(values);
366
372
  return values;
367
373
  }
368
374
  getCached(record, context) {
@@ -415,7 +421,7 @@ var RuntimeContainer = class RuntimeContainer {
415
421
  function isDependencyObject(dep) {
416
422
  return typeof dep === "object" && dep !== null && "token" in dep;
417
423
  }
418
- function isPromiseLike(value) {
424
+ function isPromiseLike$1(value) {
419
425
  return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
420
426
  }
421
427
  function isLifetimeLeak(parentScope, childScope) {
@@ -442,6 +448,47 @@ async function disposeValue(value, record) {
442
448
  if (typeof maybeDisposable.destroy === "function") await maybeDisposable.destroy();
443
449
  }
444
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
445
492
  //#region src/metadata.ts
446
493
  const moduleMetadata = /* @__PURE__ */ new WeakMap();
447
494
  const moduleMetadataKey = Symbol.for("@cosystem/core/moduleMetadata");
@@ -452,8 +499,9 @@ function defineModule(target, options = {}, context) {
452
499
  applyModuleOptions(metadata, options);
453
500
  addProperties(metadata.state, options.state);
454
501
  addProperties(metadata.actions, options.actions);
455
- writeContextMetadata(context, metadata);
456
502
  addProperties(metadata.computed, options.computed);
503
+ addProperties(metadata.effects, options.effects);
504
+ writeContextMetadata(context, metadata);
457
505
  return target;
458
506
  }
459
507
  function getModuleMetadata(target) {
@@ -473,6 +521,9 @@ function addModuleAction(target, property) {
473
521
  function addModuleComputed(target, property) {
474
522
  ensureModuleMetadata(target).computed.add(property);
475
523
  }
524
+ function addModuleEffect(target, property) {
525
+ ensureModuleMetadata(target).effects.add(property);
526
+ }
476
527
  function applyModuleOptions(metadata, options) {
477
528
  if (options.name !== void 0) metadata.name = options.name;
478
529
  if (options.deps !== void 0) metadata.deps = options.deps;
@@ -485,7 +536,8 @@ function ensureModuleMetadata(target) {
485
536
  kind: "module",
486
537
  state: /* @__PURE__ */ new Set(),
487
538
  actions: /* @__PURE__ */ new Set(),
488
- computed: /* @__PURE__ */ new Set()
539
+ computed: /* @__PURE__ */ new Set(),
540
+ effects: /* @__PURE__ */ new Set()
489
541
  };
490
542
  moduleMetadata.set(target, metadata);
491
543
  return metadata;
@@ -506,7 +558,8 @@ function createModuleMetadata() {
506
558
  kind: "module",
507
559
  state: /* @__PURE__ */ new Set(),
508
560
  actions: /* @__PURE__ */ new Set(),
509
- computed: /* @__PURE__ */ new Set()
561
+ computed: /* @__PURE__ */ new Set(),
562
+ effects: /* @__PURE__ */ new Set()
510
563
  };
511
564
  }
512
565
  function readContextMetadata(context) {
@@ -530,9 +583,10 @@ function mergeModuleMetadata(target, source) {
530
583
  addProperties(target.state, [...source.state]);
531
584
  addProperties(target.actions, [...source.actions]);
532
585
  addProperties(target.computed, [...source.computed]);
586
+ addProperties(target.effects, [...source.effects]);
533
587
  }
534
588
  function isModuleMetadata(value) {
535
- return typeof value === "object" && value !== null && value.kind === "module" && value instanceof Object && value.state instanceof Set && value.actions instanceof Set && value.computed instanceof Set;
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;
536
590
  }
537
591
  //#endregion
538
592
  //#region src/app.ts
@@ -541,22 +595,37 @@ const appContainerMap = /* @__PURE__ */ new WeakMap();
541
595
  function createApp(options = {}) {
542
596
  return createAppInternal(options);
543
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
+ }
544
603
  function createAppInternal(options = {}) {
545
604
  const parent = isApp(options.parent) ? getAppContainer(options.parent) : options.parent;
546
605
  const container = parent === void 0 ? createContainer() : createContainer({ parent });
547
606
  const moduleTokens = [];
607
+ const lazyModules = [];
548
608
  for (const provider of options.providers ?? []) {
609
+ if (isLazyModule(provider)) {
610
+ lazyModules.push(provider);
611
+ continue;
612
+ }
549
613
  const normalized = normalizeAppProvider(provider);
550
614
  container.provide(normalized.provider);
551
615
  if (normalized.moduleToken !== void 0) moduleTokens.push(normalized.moduleToken);
552
616
  }
553
- 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
+ }
554
622
  container.freeze();
555
623
  const modules = instantiateModules(container, moduleTokens);
556
624
  const store = create(createRootState(modules), createStoreOptions(options.engine, shouldEnablePatches(options)));
557
625
  const app = new RuntimeApp({
558
626
  container,
559
627
  devOptions: options.devOptions ?? {},
628
+ lazyModules,
560
629
  modules,
561
630
  plugins: options.plugins ?? [],
562
631
  state: { version: 0 },
@@ -565,6 +634,7 @@ function createAppInternal(options = {}) {
565
634
  });
566
635
  app.bindModules();
567
636
  app.attachRuntimeMetadata();
637
+ instantiateEagerProviders(container);
568
638
  app.runModuleCreatedHooks();
569
639
  app.init();
570
640
  appContainerMap.set(app, container);
@@ -576,16 +646,22 @@ var RuntimeApp = class {
576
646
  #container;
577
647
  devOptions;
578
648
  modules;
649
+ pendingLazyModules;
579
650
  moduleByToken = /* @__PURE__ */ new Map();
580
651
  moduleByName = /* @__PURE__ */ new Map();
581
652
  plugins;
582
653
  testInspector;
654
+ effectDisposers = [];
655
+ pendingEffects = /* @__PURE__ */ new Set();
656
+ loadedLazyModules = /* @__PURE__ */ new WeakMap();
657
+ dynamicScopes = [];
583
658
  initPromise = Promise.resolve();
584
659
  isStarted = false;
585
660
  isDisposed = false;
586
661
  constructor(options) {
587
662
  this.#container = options.container;
588
663
  this.devOptions = options.devOptions;
664
+ this.pendingLazyModules = [...options.lazyModules];
589
665
  this.modules = options.modules;
590
666
  this.plugins = options.plugins;
591
667
  this.state = options.state;
@@ -602,23 +678,28 @@ var RuntimeApp = class {
602
678
  this.testInspector?.recordState(state);
603
679
  this.emitStateChange({ state });
604
680
  });
681
+ this.testInspector?.setFlushEffects(() => this.flushEffects());
605
682
  }
606
683
  get started() {
607
684
  return this.isStarted;
608
685
  }
609
686
  get(token) {
687
+ const moduleBinding = this.moduleByToken.get(token);
688
+ if (moduleBinding !== void 0) return moduleBinding.instance;
610
689
  return this.#container.get(token);
611
690
  }
612
691
  getAsync(token) {
692
+ const moduleBinding = this.moduleByToken.get(token);
693
+ if (moduleBinding !== void 0) return Promise.resolve(moduleBinding.instance);
613
694
  return this.#container.getAsync(token);
614
695
  }
615
696
  getAll(token) {
616
697
  return this.#container.getAll(token);
617
698
  }
618
699
  getModule(token) {
619
- const value = this.get(token);
620
- if (!this.moduleByToken.has(token)) throw new CosystemError(`${tokenName(token)} is not a CoSystem module.`);
621
- 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;
622
703
  }
623
704
  getModuleByName(name) {
624
705
  const moduleBinding = this.moduleByName.get(name);
@@ -645,6 +726,10 @@ var RuntimeApp = class {
645
726
  tracker.dispose();
646
727
  };
647
728
  }
729
+ runInAction(module, callback, options = {}) {
730
+ const moduleBinding = this.resolveModuleBinding(module);
731
+ return this.runActionCallback(moduleBinding, options.name ?? "runInAction", options.args ?? [], callback);
732
+ }
648
733
  async start() {
649
734
  if (this.isStarted) return;
650
735
  await this.initPromise;
@@ -670,7 +755,10 @@ var RuntimeApp = class {
670
755
  if (this.isDisposed) return;
671
756
  await this.stop();
672
757
  try {
758
+ this.stopEffects();
759
+ await this.waitForPendingEffects();
673
760
  await this.runLifecycle("onDispose", true);
761
+ for (const scope of this.dynamicScopes.toReversed()) await scope.dispose();
674
762
  await Promise.all(this.plugins.map((plugin) => plugin.dispose?.()));
675
763
  await this.#container.dispose();
676
764
  this.store.destroy();
@@ -683,15 +771,59 @@ var RuntimeApp = class {
683
771
  createScope(options) {
684
772
  return { container: this.#container.createScope(options) };
685
773
  }
686
- bindModules() {
687
- for (const moduleBinding of this.modules) {
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
+ }
817
+ }
818
+ bindModules(modules = this.modules) {
819
+ for (const moduleBinding of modules) {
688
820
  this.bindState(moduleBinding);
689
821
  this.bindComputed(moduleBinding);
690
822
  this.bindActions(moduleBinding);
691
823
  }
692
824
  }
693
- attachRuntimeMetadata() {
694
- for (const moduleBinding of this.modules) Object.defineProperty(moduleBinding.instance, runtimeModuleMetadataKey, {
825
+ attachRuntimeMetadata(modules = this.modules) {
826
+ for (const moduleBinding of modules) Object.defineProperty(moduleBinding.instance, runtimeModuleMetadataKey, {
695
827
  configurable: false,
696
828
  enumerable: false,
697
829
  value: {
@@ -701,13 +833,9 @@ var RuntimeApp = class {
701
833
  }
702
834
  });
703
835
  }
704
- runModuleCreatedHooks() {
705
- for (const moduleBinding of this.modules) {
706
- const event = {
707
- name: moduleBinding.name,
708
- token: moduleBinding.token,
709
- instance: moduleBinding.instance
710
- };
836
+ runModuleCreatedHooks(modules = this.modules) {
837
+ for (const moduleBinding of modules) {
838
+ const event = toModuleCreatedEvent(moduleBinding);
711
839
  for (const plugin of this.plugins) plugin.onModuleCreated?.(event);
712
840
  }
713
841
  }
@@ -716,6 +844,7 @@ var RuntimeApp = class {
716
844
  try {
717
845
  await Promise.all(this.plugins.map((plugin) => this.runWithAppInjectContext(() => plugin.setup?.(this))));
718
846
  await this.runLifecycle("onInit");
847
+ this.startEffects();
719
848
  } catch (error) {
720
849
  this.emitError(error, { phase: "init" });
721
850
  throw error;
@@ -743,15 +872,21 @@ var RuntimeApp = class {
743
872
  bindComputed(moduleBinding) {
744
873
  for (const property of moduleBinding.metadata.computed) {
745
874
  const getter = getGetter(moduleBinding.instance, property);
875
+ const accessor = computed$1(() => getter.call(moduleBinding.instance));
746
876
  moduleBinding.originalComputed.set(property, getter);
877
+ moduleBinding.computedAccessors.set(property, accessor);
747
878
  Object.defineProperty(moduleBinding.instance, property, {
748
879
  configurable: true,
749
880
  enumerable: true,
750
- get: () => getter.call(moduleBinding.instance)
881
+ get: () => {
882
+ if (moduleBinding.activeDraft !== void 0 || !moduleBinding.reactiveSlice) return getter.call(moduleBinding.instance);
883
+ return accessor();
884
+ }
751
885
  });
752
886
  }
753
887
  }
754
888
  readModuleState(moduleBinding, property) {
889
+ if (!moduleBinding.reactiveSlice) return this.store.getPureState()[moduleBinding.name]?.[property];
755
890
  return this.store.getState()[moduleBinding.name]?.[property];
756
891
  }
757
892
  writeModuleState(moduleBinding, property, value) {
@@ -760,6 +895,18 @@ var RuntimeApp = class {
760
895
  return;
761
896
  }
762
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
+ }
763
910
  this.store.setState((draft) => {
764
911
  draft[moduleBinding.name] ??= {};
765
912
  draft[moduleBinding.name][property] = value;
@@ -768,6 +915,9 @@ var RuntimeApp = class {
768
915
  runAction(moduleBinding, property, args) {
769
916
  const action = moduleBinding.originalActions.get(property);
770
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) {
771
921
  const event = {
772
922
  args,
773
923
  method: String(property),
@@ -779,22 +929,76 @@ var RuntimeApp = class {
779
929
  let error;
780
930
  try {
781
931
  moduleBinding.actionDepth += 1;
782
- this.store.setState((draft) => {
932
+ if (moduleBinding.reactiveSlice) this.store.setState((draft) => {
783
933
  const previousDraft = moduleBinding.activeDraft;
784
934
  moduleBinding.activeDraft = draft[moduleBinding.name] ?? {};
785
935
  draft[moduleBinding.name] = moduleBinding.activeDraft;
786
936
  try {
787
- result = action.apply(moduleBinding.instance, [...args]);
937
+ result = callback();
788
938
  } finally {
789
939
  moduleBinding.activeDraft = previousDraft;
790
940
  }
791
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
+ }
792
957
  } catch (caught) {
793
958
  error = caught;
794
959
  this.emitError(caught, { phase: "action" });
795
960
  } finally {
796
961
  moduleBinding.actionDepth -= 1;
797
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) {
798
1002
  const endedEvent = {
799
1003
  ...event,
800
1004
  endedAt: Date.now(),
@@ -802,16 +1006,64 @@ var RuntimeApp = class {
802
1006
  };
803
1007
  this.testInspector?.recordAction(endedEvent);
804
1008
  this.emitActionEnd(endedEvent);
805
- if (error !== void 0) throw error;
806
- return result;
807
1009
  }
808
- async runLifecycle(method, reverse = false) {
809
- const modules = reverse ? this.modules.toReversed() : this.modules;
810
- for (const moduleBinding of modules) {
1010
+ async runLifecycle(method, reverse = false, modules = this.modules) {
1011
+ const orderedModules = reverse ? modules.toReversed() : modules;
1012
+ for (const moduleBinding of orderedModules) {
811
1013
  const lifecycle = moduleBinding.instance;
812
1014
  await this.runWithAppInjectContext(() => lifecycle[method]?.());
813
1015
  }
814
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);
1066
+ }
815
1067
  emitActionStart(event) {
816
1068
  for (const plugin of this.plugins) plugin.onActionStart?.(event);
817
1069
  }
@@ -853,6 +1105,27 @@ var RuntimeApp = class {
853
1105
  patches
854
1106
  });
855
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
+ }
856
1129
  };
857
1130
  function normalizeAppProvider(provider) {
858
1131
  if (typeof provider === "function") {
@@ -900,7 +1173,7 @@ function mergeModuleClassProviderOptions(provider, metadata) {
900
1173
  ..."scope" in provider || metadata.scope === void 0 ? {} : { scope: metadata.scope }
901
1174
  };
902
1175
  }
903
- function instantiateModules(container, moduleTokens) {
1176
+ function instantiateModules(container, moduleTokens, reactiveSlice = true) {
904
1177
  const modules = [];
905
1178
  for (const moduleToken of moduleTokens) {
906
1179
  const instance = container.get(moduleToken);
@@ -911,16 +1184,36 @@ function instantiateModules(container, moduleTokens) {
911
1184
  modules.push({
912
1185
  actionDepth: 0,
913
1186
  activeDraft: void 0,
1187
+ computedAccessors: /* @__PURE__ */ new Map(),
914
1188
  instance,
915
1189
  metadata,
916
1190
  name,
917
1191
  originalActions: /* @__PURE__ */ new Map(),
918
1192
  originalComputed: /* @__PURE__ */ new Map(),
1193
+ reactiveSlice,
919
1194
  token: moduleToken
920
1195
  });
921
1196
  }
922
1197
  return modules;
923
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
+ }
924
1217
  function createRootState(modules) {
925
1218
  const rootState = {};
926
1219
  for (const moduleBinding of modules) {
@@ -944,6 +1237,12 @@ function getGetter(instance, property) {
944
1237
  if (descriptor?.get === void 0) throw new CosystemError(`${String(property)} is not a getter.`);
945
1238
  return descriptor.get;
946
1239
  }
1240
+ function getRuntimeModuleMetadata(module) {
1241
+ return module[runtimeModuleMetadataKey];
1242
+ }
1243
+ function isTokenObject(value) {
1244
+ return "id" in value && typeof value.id === "symbol";
1245
+ }
947
1246
  function getDescriptor(instance, property) {
948
1247
  let current = instance;
949
1248
  while (current !== null) {
@@ -955,6 +1254,9 @@ function getDescriptor(instance, property) {
955
1254
  function isApp(value) {
956
1255
  return typeof value === "object" && value !== null && "get" in value && "start" in value && "dispose" in value;
957
1256
  }
1257
+ function isPromiseLike(value) {
1258
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
1259
+ }
958
1260
  function getAppContainer(app) {
959
1261
  const container = appContainerMap.get(app);
960
1262
  if (container === void 0) throw new CosystemError("Parent app was not created by CoSystem.");
@@ -989,6 +1291,13 @@ function computed(_value, context) {
989
1291
  addModuleComputed(this.constructor, context.name);
990
1292
  });
991
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
+ }
992
1301
  //#endregion
993
1302
  //#region src/loggerPlugin.ts
994
1303
  function createLoggerPlugin(options = {}) {
@@ -1032,9 +1341,13 @@ function testApp(options = {}) {
1032
1341
  if (autoStart === true) return app.start().then(() => app);
1033
1342
  return app;
1034
1343
  }
1344
+ function defaultFlushEffects() {
1345
+ return Promise.resolve();
1346
+ }
1035
1347
  function createTestInspector() {
1036
1348
  const actions = [];
1037
1349
  const patches = [];
1350
+ let flushEffects = defaultFlushEffects;
1038
1351
  let lastState;
1039
1352
  return {
1040
1353
  clearActions() {
@@ -1044,7 +1357,7 @@ function createTestInspector() {
1044
1357
  patches.length = 0;
1045
1358
  },
1046
1359
  flushEffects() {
1047
- return Promise.resolve();
1360
+ return flushEffects();
1048
1361
  },
1049
1362
  getActions() {
1050
1363
  return actions;
@@ -1063,17 +1376,25 @@ function createTestInspector() {
1063
1376
  },
1064
1377
  recordState(state) {
1065
1378
  lastState = state;
1379
+ },
1380
+ setFlushEffects(callback) {
1381
+ flushEffects = callback;
1066
1382
  }
1067
1383
  };
1068
1384
  }
1069
1385
  //#endregion
1070
1386
  //#region src/worker.ts
1071
1387
  function createWorkerApp(options) {
1072
- const { transport, ...appOptions } = options;
1388
+ const { stateSections, sync = "snapshot", transport, ...appOptions } = options;
1389
+ let stateSyncVersion = 0;
1390
+ let publishPatches = false;
1073
1391
  const patchPlugin = {
1074
1392
  name: "cosystem:worker-patches",
1075
1393
  onPatch(event) {
1076
- publishState(app, transport, event.patches);
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
+ }
1077
1398
  }
1078
1399
  };
1079
1400
  const app = createApp({
@@ -1084,45 +1405,45 @@ function createWorkerApp(options) {
1084
1405
  },
1085
1406
  plugins: [...appOptions.plugins ?? [], patchPlugin]
1086
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
+ });
1087
1414
  const unsubscribeTransport = transport.subscribe((message) => {
1088
1415
  if (message.type !== "call") return;
1089
- handleCall(app, transport, message);
1416
+ handleCall(app, transport, message, ready);
1090
1417
  });
1091
- transport.post({ type: "ready" });
1092
- publishState(app, transport);
1093
1418
  return {
1094
1419
  app,
1420
+ ready,
1095
1421
  async dispose() {
1096
1422
  unsubscribeTransport();
1423
+ await ready.catch(() => void 0);
1097
1424
  await app.dispose();
1098
1425
  }
1099
1426
  };
1100
1427
  }
1101
1428
  function createWorkerClient(options) {
1102
- const { transport } = options;
1429
+ const { onConflict, transport } = options;
1103
1430
  const listeners = /* @__PURE__ */ new Set();
1431
+ const selectorWatchers = /* @__PURE__ */ new Set();
1104
1432
  const pending = /* @__PURE__ */ new Map();
1105
1433
  const state = { version: 0 };
1106
1434
  let nextId = 1;
1107
1435
  let snapshot;
1108
- const unsubscribe = transport.subscribe((message) => {
1109
- if (message.type === "state") {
1110
- state.version = message.version;
1111
- snapshot = message.state;
1112
- for (const listener of listeners) listener(message);
1113
- return;
1114
- }
1115
- if (message.type !== "result") return;
1116
- const entry = pending.get(message.id);
1117
- if (entry === void 0) return;
1118
- pending.delete(message.id);
1119
- if (message.error !== void 0) {
1120
- entry.reject(createRemoteError(message.error));
1121
- return;
1122
- }
1123
- 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;
1124
1442
  });
1443
+ ready.catch(() => void 0);
1444
+ let unsubscribe = noop;
1125
1445
  const client = {
1446
+ ready,
1126
1447
  state,
1127
1448
  call(module, method, ...args) {
1128
1449
  const id = nextId;
@@ -1143,9 +1464,14 @@ function createWorkerClient(options) {
1143
1464
  },
1144
1465
  dispose() {
1145
1466
  unsubscribe();
1467
+ if (!readySettled) {
1468
+ readySettled = true;
1469
+ rejectReady(new CosystemError("Worker client disposed before initial state."));
1470
+ }
1146
1471
  for (const entry of pending.values()) entry.reject(new CosystemError("Worker client disposed before response."));
1147
1472
  pending.clear();
1148
1473
  listeners.clear();
1474
+ selectorWatchers.clear();
1149
1475
  },
1150
1476
  getState() {
1151
1477
  return snapshot;
@@ -1156,22 +1482,256 @@ function createWorkerClient(options) {
1156
1482
  return (...args) => client.call(name, property, ...args);
1157
1483
  } });
1158
1484
  },
1485
+ select(selector) {
1486
+ if (snapshot === void 0) throw new CosystemError("Worker client state is not ready.");
1487
+ return selector(snapshot, client);
1488
+ },
1159
1489
  subscribe(listener) {
1160
1490
  listeners.add(listener);
1161
1491
  return () => {
1162
1492
  listeners.delete(listener);
1163
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);
1164
1530
  }
1165
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
+ });
1166
1593
  return client;
1167
1594
  }
1595
+ function reportWorkerConflict(onConflict, event) {
1596
+ onConflict?.(event);
1597
+ }
1168
1598
  function createMemoryWorkerTransportPair() {
1169
1599
  const leftListeners = /* @__PURE__ */ new Set();
1170
1600
  const rightListeners = /* @__PURE__ */ new Set();
1171
1601
  return [createMemoryWorkerTransport(leftListeners, rightListeners), createMemoryWorkerTransport(rightListeners, leftListeners)];
1172
1602
  }
1173
- 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) {
1174
1733
  try {
1734
+ await ready;
1175
1735
  const module = app.getModuleByName(message.module);
1176
1736
  const method = module[message.method];
1177
1737
  if (typeof method !== "function") throw new CosystemError(`${message.module}.${message.method} is not callable.`);
@@ -1189,15 +1749,111 @@ async function handleCall(app, transport, message) {
1189
1749
  });
1190
1750
  }
1191
1751
  }
1192
- function publishState(app, transport, patches = []) {
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);
1193
1757
  const message = {
1194
- state: app.store.getPureState(),
1195
- sync: patches.length > 0 ? "patch" : "snapshot",
1758
+ ...isPatch && mode === "patch" ? {} : { state },
1759
+ ...sections === void 0 ? {} : { sections },
1760
+ sync: isPatch ? "patch" : "snapshot",
1196
1761
  type: "state",
1197
- version: app.state.version,
1198
- ...patches.length === 0 ? {} : { patches }
1762
+ version,
1763
+ ...isPatch ? { patches: filteredPatches } : {}
1199
1764
  };
1200
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));
1781
+ });
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");
1201
1857
  }
1202
1858
  function createMemoryWorkerTransport(inbox, outbox) {
1203
1859
  return {
@@ -1212,6 +1868,72 @@ function createMemoryWorkerTransport(inbox, outbox) {
1212
1868
  }
1213
1869
  };
1214
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
+ }
1215
1937
  function serializeError(error) {
1216
1938
  if (error instanceof Error) return {
1217
1939
  message: error.message,
@@ -1229,7 +1951,8 @@ function createRemoteError(error) {
1229
1951
  if (error.stack !== void 0) remoteError.stack = error.stack;
1230
1952
  return remoteError;
1231
1953
  }
1954
+ function noop() {}
1232
1955
  //#endregion
1233
- export { AmbiguousProviderError, AsyncProviderInSyncResolutionError, CircularDependencyError, CosystemError, DuplicateProviderError, FrozenContainerError, InjectContextError, LifetimeLeakError, MissingProviderError, action, computed, createApp, createContainer, createLoggerPlugin, 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 };
1234
1957
 
1235
1958
  //# sourceMappingURL=index.mjs.map