@ny4n/chord 0.0.0-stage → 1.0.1

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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -2
  3. package/dist/api.d.ts +20 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +77 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/bundler.d.ts +6 -0
  8. package/dist/bundler.d.ts.map +1 -0
  9. package/dist/bundler.js +3 -0
  10. package/dist/bundler.js.map +1 -0
  11. package/dist/context/index.d.ts +24 -0
  12. package/dist/context/index.d.ts.map +1 -0
  13. package/dist/context/index.js +97 -0
  14. package/dist/context/index.js.map +1 -0
  15. package/dist/delta/apply-immutable-trusted.d.ts +4 -0
  16. package/dist/delta/apply-immutable-trusted.d.ts.map +1 -0
  17. package/dist/delta/apply-immutable-trusted.js +127 -0
  18. package/dist/delta/apply-immutable-trusted.js.map +1 -0
  19. package/dist/delta/core.d.ts +108 -0
  20. package/dist/delta/core.d.ts.map +1 -0
  21. package/dist/delta/core.js +645 -0
  22. package/dist/delta/core.js.map +1 -0
  23. package/dist/delta/diff.d.ts +5 -0
  24. package/dist/delta/diff.d.ts.map +1 -0
  25. package/dist/delta/diff.js +433 -0
  26. package/dist/delta/diff.js.map +1 -0
  27. package/dist/delta/draft.d.ts +5 -0
  28. package/dist/delta/draft.d.ts.map +1 -0
  29. package/dist/delta/draft.js +2 -0
  30. package/dist/delta/draft.js.map +1 -0
  31. package/dist/delta/index.d.ts +5 -0
  32. package/dist/delta/index.d.ts.map +1 -0
  33. package/dist/delta/index.js +6 -0
  34. package/dist/delta/index.js.map +1 -0
  35. package/dist/delta/revision-validator.d.ts +7 -0
  36. package/dist/delta/revision-validator.d.ts.map +1 -0
  37. package/dist/delta/revision-validator.js +71 -0
  38. package/dist/delta/revision-validator.js.map +1 -0
  39. package/dist/delta/tracker.d.ts +26 -0
  40. package/dist/delta/tracker.d.ts.map +1 -0
  41. package/dist/delta/tracker.js +2069 -0
  42. package/dist/delta/tracker.js.map +1 -0
  43. package/dist/facets/host.d.ts +12 -0
  44. package/dist/facets/host.d.ts.map +1 -0
  45. package/dist/facets/host.js +763 -0
  46. package/dist/facets/host.js.map +1 -0
  47. package/dist/facets/loader.d.ts +3 -0
  48. package/dist/facets/loader.d.ts.map +1 -0
  49. package/dist/facets/loader.js +5 -0
  50. package/dist/facets/loader.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +10 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/json.d.ts +10 -0
  56. package/dist/json.d.ts.map +1 -0
  57. package/dist/json.js +124 -0
  58. package/dist/json.js.map +1 -0
  59. package/dist/node/bundle-loader.d.ts +30 -0
  60. package/dist/node/bundle-loader.d.ts.map +1 -0
  61. package/dist/node/bundle-loader.js +377 -0
  62. package/dist/node/bundle-loader.js.map +1 -0
  63. package/dist/node/bundle.d.ts +26 -0
  64. package/dist/node/bundle.d.ts.map +1 -0
  65. package/dist/node/bundle.js +183 -0
  66. package/dist/node/bundle.js.map +1 -0
  67. package/dist/node/manifest.d.ts +36 -0
  68. package/dist/node/manifest.d.ts.map +1 -0
  69. package/dist/node/manifest.js +6 -0
  70. package/dist/node/manifest.js.map +1 -0
  71. package/dist/node/package.d.ts +15 -0
  72. package/dist/node/package.d.ts.map +1 -0
  73. package/dist/node/package.js +199 -0
  74. package/dist/node/package.js.map +1 -0
  75. package/dist/node.d.ts +5 -0
  76. package/dist/node.d.ts.map +1 -0
  77. package/dist/node.js +3 -0
  78. package/dist/node.js.map +1 -0
  79. package/dist/services/consumer.d.ts +11 -0
  80. package/dist/services/consumer.d.ts.map +1 -0
  81. package/dist/services/consumer.js +585 -0
  82. package/dist/services/consumer.js.map +1 -0
  83. package/dist/services/errors.d.ts +8 -0
  84. package/dist/services/errors.d.ts.map +1 -0
  85. package/dist/services/errors.js +22 -0
  86. package/dist/services/errors.js.map +1 -0
  87. package/dist/services/handle.d.ts +15 -0
  88. package/dist/services/handle.d.ts.map +1 -0
  89. package/dist/services/handle.js +101 -0
  90. package/dist/services/handle.js.map +1 -0
  91. package/dist/services/instances.d.ts +26 -0
  92. package/dist/services/instances.d.ts.map +1 -0
  93. package/dist/services/instances.js +140 -0
  94. package/dist/services/instances.js.map +1 -0
  95. package/dist/services/loopback.d.ts +5 -0
  96. package/dist/services/loopback.d.ts.map +1 -0
  97. package/dist/services/loopback.js +15 -0
  98. package/dist/services/loopback.js.map +1 -0
  99. package/dist/services/provider.d.ts +37 -0
  100. package/dist/services/provider.d.ts.map +1 -0
  101. package/dist/services/provider.js +521 -0
  102. package/dist/services/provider.js.map +1 -0
  103. package/dist/services/state-codec.d.ts +15 -0
  104. package/dist/services/state-codec.d.ts.map +1 -0
  105. package/dist/services/state-codec.js +133 -0
  106. package/dist/services/state-codec.js.map +1 -0
  107. package/dist/services/state-internals.d.ts +13 -0
  108. package/dist/services/state-internals.d.ts.map +1 -0
  109. package/dist/services/state-internals.js +10 -0
  110. package/dist/services/state-internals.js.map +1 -0
  111. package/dist/services/state.d.ts +28 -0
  112. package/dist/services/state.d.ts.map +1 -0
  113. package/dist/services/state.js +397 -0
  114. package/dist/services/state.js.map +1 -0
  115. package/dist/services/wire.d.ts +63 -0
  116. package/dist/services/wire.d.ts.map +1 -0
  117. package/dist/services/wire.js +186 -0
  118. package/dist/services/wire.js.map +1 -0
  119. package/dist/types.d.ts +277 -0
  120. package/dist/types.d.ts.map +1 -0
  121. package/dist/types.js +2 -0
  122. package/dist/types.js.map +1 -0
  123. package/package.json +71 -5
  124. package/src/delta/README.md +191 -0
@@ -0,0 +1,133 @@
1
+ import { decoder, encoder } from "../delta/index.js";
2
+ class StateCodecRegistry {
3
+ #create;
4
+ #entries = new Map();
5
+ constructor(create) {
6
+ this.#create = create;
7
+ }
8
+ reset() {
9
+ this.#entries.clear();
10
+ }
11
+ add(instance, member) {
12
+ const key = stateKey(instance, member);
13
+ if (this.#entries.has(key))
14
+ throw new Error(`Duplicate service state ${describeState(instance, member)}`);
15
+ const codec = this.#create();
16
+ this.#entries.set(key, { instance, codec });
17
+ return codec;
18
+ }
19
+ get(instance, member) {
20
+ const codec = this.#entries.get(stateKey(instance, member))?.codec;
21
+ if (codec === undefined)
22
+ throw new Error(`Unknown service state ${describeState(instance, member)}`);
23
+ return codec;
24
+ }
25
+ removeInstance(instance) {
26
+ for (const [key, entry] of this.#entries) {
27
+ if (sameAddress(entry.instance, instance))
28
+ this.#entries.delete(key);
29
+ }
30
+ }
31
+ }
32
+ export function createServiceStateEncoder() {
33
+ const codecs = new StateCodecRegistry(encoder);
34
+ return {
35
+ encodeSnapshot(snapshot) {
36
+ codecs.reset();
37
+ return {
38
+ ...snapshot,
39
+ instances: snapshot.instances.map((instance) => encodeInstance(instance, codecs)),
40
+ };
41
+ },
42
+ encodeUpdate(update) {
43
+ switch (update.type) {
44
+ case "reset":
45
+ codecs.reset();
46
+ return {
47
+ ...update,
48
+ snapshot: {
49
+ ...update.snapshot,
50
+ instances: update.snapshot.instances.map((instance) => encodeInstance(instance, codecs)),
51
+ },
52
+ };
53
+ case "state":
54
+ return { ...update, ops: codecs.get(update.instance, update.member).encode(update.ops) };
55
+ case "replaced":
56
+ codecs.reset();
57
+ return { ...update, snapshot: encodeInstance(update.snapshot, codecs) };
58
+ case "spawned":
59
+ return { ...update, instance: encodeInstance(update.instance, codecs) };
60
+ case "unavailable":
61
+ codecs.reset();
62
+ return update;
63
+ case "closed":
64
+ codecs.removeInstance(update.instance);
65
+ return update;
66
+ }
67
+ },
68
+ };
69
+ }
70
+ export function createServiceStateDecoder() {
71
+ const codecs = new StateCodecRegistry(decoder);
72
+ return {
73
+ decodeSnapshot(snapshot) {
74
+ codecs.reset();
75
+ return {
76
+ ...snapshot,
77
+ instances: snapshot.instances.map((instance) => decodeInstance(instance, codecs)),
78
+ };
79
+ },
80
+ decodeUpdate(update) {
81
+ switch (update.type) {
82
+ case "reset":
83
+ codecs.reset();
84
+ return {
85
+ ...update,
86
+ snapshot: {
87
+ ...update.snapshot,
88
+ instances: update.snapshot.instances.map((instance) => decodeInstance(instance, codecs)),
89
+ },
90
+ };
91
+ case "state":
92
+ return { ...update, ops: codecs.get(update.instance, update.member).decode(update.ops) };
93
+ case "replaced":
94
+ codecs.reset();
95
+ return { ...update, snapshot: decodeInstance(update.snapshot, codecs) };
96
+ case "spawned":
97
+ return { ...update, instance: decodeInstance(update.instance, codecs) };
98
+ case "unavailable":
99
+ codecs.reset();
100
+ return update;
101
+ case "closed":
102
+ codecs.removeInstance(update.instance);
103
+ return update;
104
+ }
105
+ },
106
+ };
107
+ }
108
+ function encodeInstance(instance, codecs) {
109
+ return {
110
+ ...instance,
111
+ members: instance.members.map((member) => member.kind === "state"
112
+ ? { ...member, ops: codecs.add(instance.instance, member.name).encode(member.ops) }
113
+ : member),
114
+ };
115
+ }
116
+ function decodeInstance(instance, codecs) {
117
+ return {
118
+ ...instance,
119
+ members: instance.members.map((member) => member.kind === "state"
120
+ ? { ...member, ops: codecs.add(instance.instance, member.name).decode(member.ops) }
121
+ : member),
122
+ };
123
+ }
124
+ function stateKey(instance, member) {
125
+ return JSON.stringify([instance?.key ?? null, instance?.generation ?? null, member]);
126
+ }
127
+ function sameAddress(left, right) {
128
+ return left?.key === right.key && left.generation === right.generation;
129
+ }
130
+ function describeState(instance, member) {
131
+ return instance === undefined ? member : `${instance.key}@${instance.generation}.${member}`;
132
+ }
133
+ //# sourceMappingURL=state-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-codec.js","sourceRoot":"","sources":["../../src/services/state-codec.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Decoder, decoder, type Encoder, encoder } from \"../delta/index.ts\";\nimport type {\n\tServiceInstanceAddress,\n\tServiceInstanceSnapshot,\n\tServiceProviderUpdate,\n\tServiceSubscriptionSnapshot,\n} from \"../types.ts\";\nimport type { WireServiceProviderUpdate, WireServiceSubscriptionSnapshot } from \"./wire.ts\";\n\n/** Stateful operation encoders for every replicated state in one service subscription. */\nexport interface ServiceStateEncoder {\n\tencodeSnapshot(snapshot: ServiceSubscriptionSnapshot): WireServiceSubscriptionSnapshot;\n\tencodeUpdate(update: ServiceProviderUpdate): WireServiceProviderUpdate;\n}\n\n/** Stateful operation decoders for every replicated state in one service subscription. */\nexport interface ServiceStateDecoder {\n\tdecodeSnapshot(snapshot: WireServiceSubscriptionSnapshot): ServiceSubscriptionSnapshot;\n\tdecodeUpdate(update: WireServiceProviderUpdate): ServiceProviderUpdate;\n}\n\ninterface CodecEntry<C> {\n\treadonly instance: ServiceInstanceAddress | undefined;\n\treadonly codec: C;\n}\n\nclass StateCodecRegistry<C> {\n\treadonly #create: () => C;\n\treadonly #entries = new Map<string, CodecEntry<C>>();\n\n\tconstructor(create: () => C) {\n\t\tthis.#create = create;\n\t}\n\n\treset(): void {\n\t\tthis.#entries.clear();\n\t}\n\n\tadd(instance: ServiceInstanceAddress | undefined, member: string): C {\n\t\tconst key = stateKey(instance, member);\n\t\tif (this.#entries.has(key)) throw new Error(`Duplicate service state ${describeState(instance, member)}`);\n\t\tconst codec = this.#create();\n\t\tthis.#entries.set(key, { instance, codec });\n\t\treturn codec;\n\t}\n\n\tget(instance: ServiceInstanceAddress | undefined, member: string): C {\n\t\tconst codec = this.#entries.get(stateKey(instance, member))?.codec;\n\t\tif (codec === undefined) throw new Error(`Unknown service state ${describeState(instance, member)}`);\n\t\treturn codec;\n\t}\n\n\tremoveInstance(instance: ServiceInstanceAddress): void {\n\t\tfor (const [key, entry] of this.#entries) {\n\t\t\tif (sameAddress(entry.instance, instance)) this.#entries.delete(key);\n\t\t}\n\t}\n}\n\nexport function createServiceStateEncoder(): ServiceStateEncoder {\n\tconst codecs = new StateCodecRegistry<Encoder>(encoder);\n\treturn {\n\t\tencodeSnapshot(snapshot) {\n\t\t\tcodecs.reset();\n\t\t\treturn {\n\t\t\t\t...snapshot,\n\t\t\t\tinstances: snapshot.instances.map((instance) => encodeInstance(instance, codecs)),\n\t\t\t};\n\t\t},\n\t\tencodeUpdate(update) {\n\t\t\tswitch (update.type) {\n\t\t\t\tcase \"reset\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...update,\n\t\t\t\t\t\tsnapshot: {\n\t\t\t\t\t\t\t...update.snapshot,\n\t\t\t\t\t\t\tinstances: update.snapshot.instances.map((instance) => encodeInstance(instance, codecs)),\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\tcase \"state\":\n\t\t\t\t\treturn { ...update, ops: codecs.get(update.instance, update.member).encode(update.ops) };\n\t\t\t\tcase \"replaced\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn { ...update, snapshot: encodeInstance(update.snapshot, codecs) };\n\t\t\t\tcase \"spawned\":\n\t\t\t\t\treturn { ...update, instance: encodeInstance(update.instance, codecs) };\n\t\t\t\tcase \"unavailable\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn update;\n\t\t\t\tcase \"closed\":\n\t\t\t\t\tcodecs.removeInstance(update.instance);\n\t\t\t\t\treturn update;\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function createServiceStateDecoder(): ServiceStateDecoder {\n\tconst codecs = new StateCodecRegistry<Decoder>(decoder);\n\treturn {\n\t\tdecodeSnapshot(snapshot) {\n\t\t\tcodecs.reset();\n\t\t\treturn {\n\t\t\t\t...snapshot,\n\t\t\t\tinstances: snapshot.instances.map((instance) => decodeInstance(instance, codecs)),\n\t\t\t};\n\t\t},\n\t\tdecodeUpdate(update) {\n\t\t\tswitch (update.type) {\n\t\t\t\tcase \"reset\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...update,\n\t\t\t\t\t\tsnapshot: {\n\t\t\t\t\t\t\t...update.snapshot,\n\t\t\t\t\t\t\tinstances: update.snapshot.instances.map((instance) => decodeInstance(instance, codecs)),\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\tcase \"state\":\n\t\t\t\t\treturn { ...update, ops: codecs.get(update.instance, update.member).decode(update.ops) };\n\t\t\t\tcase \"replaced\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn { ...update, snapshot: decodeInstance(update.snapshot, codecs) };\n\t\t\t\tcase \"spawned\":\n\t\t\t\t\treturn { ...update, instance: decodeInstance(update.instance, codecs) };\n\t\t\t\tcase \"unavailable\":\n\t\t\t\t\tcodecs.reset();\n\t\t\t\t\treturn update;\n\t\t\t\tcase \"closed\":\n\t\t\t\t\tcodecs.removeInstance(update.instance);\n\t\t\t\t\treturn update;\n\t\t\t}\n\t\t},\n\t};\n}\n\nfunction encodeInstance(\n\tinstance: ServiceInstanceSnapshot,\n\tcodecs: StateCodecRegistry<Encoder>,\n): WireServiceSubscriptionSnapshot[\"instances\"][number] {\n\treturn {\n\t\t...instance,\n\t\tmembers: instance.members.map((member) =>\n\t\t\tmember.kind === \"state\"\n\t\t\t\t? { ...member, ops: codecs.add(instance.instance, member.name).encode(member.ops) }\n\t\t\t\t: member,\n\t\t),\n\t};\n}\n\nfunction decodeInstance(\n\tinstance: WireServiceSubscriptionSnapshot[\"instances\"][number],\n\tcodecs: StateCodecRegistry<Decoder>,\n): ServiceInstanceSnapshot {\n\treturn {\n\t\t...instance,\n\t\tmembers: instance.members.map((member) =>\n\t\t\tmember.kind === \"state\"\n\t\t\t\t? { ...member, ops: codecs.add(instance.instance, member.name).decode(member.ops) }\n\t\t\t\t: member,\n\t\t),\n\t};\n}\n\nfunction stateKey(instance: ServiceInstanceAddress | undefined, member: string): string {\n\treturn JSON.stringify([instance?.key ?? null, instance?.generation ?? null, member]);\n}\n\nfunction sameAddress(left: ServiceInstanceAddress | undefined, right: ServiceInstanceAddress): boolean {\n\treturn left?.key === right.key && left.generation === right.generation;\n}\n\nfunction describeState(instance: ServiceInstanceAddress | undefined, member: string): string {\n\treturn instance === undefined ? member : `${instance.key}@${instance.generation}.${member}`;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { Op } from "../delta/index.ts";
2
+ import type { Context } from "../types.ts";
3
+ export interface ReplicatedStateInternals {
4
+ /** Atomically capture the immutable value and its matching publication sequence. */
5
+ snapshot(): {
6
+ readonly value: unknown;
7
+ readonly sequence: number;
8
+ };
9
+ subscribe(listener: (ops: readonly Op[], sequence: number, context: Context) => void): () => void;
10
+ }
11
+ export declare function registerReplicatedStateInternals(value: object, internals: ReplicatedStateInternals): void;
12
+ export declare function getReplicatedStateInternals(value: unknown): ReplicatedStateInternals | undefined;
13
+ //# sourceMappingURL=state-internals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-internals.d.ts","sourceRoot":"","sources":["../../src/services/state-internals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,wBAAwB;IACxC,oFAAoF;IACpF,QAAQ,IAAI;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAClG;AAID,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,wBAAwB,GAAG,IAAI,CAEzG;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,GAAG,SAAS,CAGhG"}
@@ -0,0 +1,10 @@
1
+ const sources = new WeakMap();
2
+ export function registerReplicatedStateInternals(value, internals) {
3
+ sources.set(value, internals);
4
+ }
5
+ export function getReplicatedStateInternals(value) {
6
+ if (typeof value !== "object" || value === null)
7
+ return undefined;
8
+ return sources.get(value);
9
+ }
10
+ //# sourceMappingURL=state-internals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-internals.js","sourceRoot":"","sources":["../../src/services/state-internals.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,GAAG,IAAI,OAAO,EAAoC,CAAC;AAEhE,MAAM,UAAU,gCAAgC,CAAC,KAAa,EAAE,SAAmC;IAClG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,KAAc;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import type { Op } from \"../delta/index.ts\";\nimport type { Context } from \"../types.ts\";\n\nexport interface ReplicatedStateInternals {\n\t/** Atomically capture the immutable value and its matching publication sequence. */\n\tsnapshot(): { readonly value: unknown; readonly sequence: number };\n\tsubscribe(listener: (ops: readonly Op[], sequence: number, context: Context) => void): () => void;\n}\n\nconst sources = new WeakMap<object, ReplicatedStateInternals>();\n\nexport function registerReplicatedStateInternals(value: object, internals: ReplicatedStateInternals): void {\n\tsources.set(value, internals);\n}\n\nexport function getReplicatedStateInternals(value: unknown): ReplicatedStateInternals | undefined {\n\tif (typeof value !== \"object\" || value === null) return undefined;\n\treturn sources.get(value);\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { type Op } from "../delta/index.ts";
2
+ import type { AttachedReplicatedState, Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery, ReplicatedStateSource, ReplicatedStateSourceOptions } from "../types.ts";
3
+ /** The void signature also accepts synchronous callbacks that return an ignored value. */
4
+ type StateListener<T> = (value: T, context: Context, delivery: ReplicatedStateDelivery) => void;
5
+ export declare class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {
6
+ #private;
7
+ constructor(initial: T);
8
+ get value(): T;
9
+ change(context: Context, mutate: Parameters<MutableReplicatedState<T>["change"]>[1]): void;
10
+ replace(context: Context, value: T): void;
11
+ subscribe(listener: StateListener<T>): () => void;
12
+ }
13
+ /** Attach a publication-only replicated state to one authoritative immutable source stream. */
14
+ export declare function attachReplicatedStateSource<T>(source: ReplicatedStateSource<T>, options?: ReplicatedStateSourceOptions): AttachedReplicatedState<T>;
15
+ /** A cold read-only state used by service consumers until a complete snapshot arrives. */
16
+ export declare class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {
17
+ #private;
18
+ constructor(reportError: (error: Error) => void);
19
+ get value(): T | undefined;
20
+ subscribe(listener: StateListener<T>): () => void;
21
+ hydrate(sequence: number, ops: readonly Op[], context: Context): void;
22
+ update(sequence: number, ops: readonly Op[], context: Context): void;
23
+ clear(): void;
24
+ }
25
+ /** @internal Context for synthetic service deliveries without a caller. */
26
+ export declare function serviceDeliveryContext(): Context;
27
+ export {};
28
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/services/state.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,EAAE,EAAsC,MAAM,mBAAmB,CAAC;AAExG,OAAO,KAAK,EACX,uBAAuB,EACvB,OAAO,EACP,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EAGrB,4BAA4B,EAC5B,MAAM,aAAa,CAAC;AAGrB,0FAA0F;AAC1F,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,KAAK,IAAI,CAAC;AAqKhG,qBAAa,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,sBAAsB,CAAC,CAAC,CAAC;;IAK7F,YAAY,OAAO,EAAE,CAAC,EAOrB;IAED,IAAI,KAAK,IAAI,CAAC,CAEb;IAED,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CA2BzF;IAED,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CASxC;IAED,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAEhD;CACD;AA8ED,+FAA+F;AAC/F,wBAAgB,2BAA2B,CAAC,CAAC,EAC5C,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,EAChC,OAAO,GAAE,4BAAiC,GACxC,uBAAuB,CAAC,CAAC,CAAC,CAc5B;AAED,0FAA0F;AAC1F,qBAAa,sBAAsB,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;;IAOjG,YAAY,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAE9C;IAED,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAEzB;IAED,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAehD;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAYpE;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAkBnE;IAED,KAAK,IAAI,IAAI,CAIZ;CAUD;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,IAAI,OAAO,CAGhD"}
@@ -0,0 +1,397 @@
1
+ import { BACKGROUND_CONTEXT } from "../context/index.js";
2
+ import { applyImmutable, isBase, track } from "../delta/index.js";
3
+ import { JsonRevisionValidator } from "../delta/revision-validator.js";
4
+ import { registerReplicatedStateInternals } from "./state-internals.js";
5
+ /** One public subscription, independent of producer and other subscriber progress. */
6
+ class StateSubscriber {
7
+ #listener;
8
+ #reportError;
9
+ #pending = [];
10
+ #running = false;
11
+ #started = false;
12
+ #closed = false;
13
+ constructor(listener, reportError) {
14
+ this.#listener = listener;
15
+ this.#reportError = reportError;
16
+ }
17
+ push(frame) {
18
+ if (this.#closed)
19
+ return;
20
+ if (this.#pending.length === 100) {
21
+ // A cold replica can queue updates reentrantly before this subscriber's first hydration starts.
22
+ const hydration = this.#started ? undefined : this.#pending[0];
23
+ this.#pending.length = 0;
24
+ if (hydration !== undefined)
25
+ this.#pending.push(hydration);
26
+ }
27
+ this.#pending.push(frame);
28
+ }
29
+ drain() {
30
+ if (this.#running || this.#closed)
31
+ return;
32
+ this.#running = true;
33
+ for (let frame = this.#pending.shift(); frame !== undefined; frame = this.#pending.shift()) {
34
+ this.#started = true;
35
+ try {
36
+ const result = this.#listener(frame.value, frame.context, frame.delivery);
37
+ if (isPromiseLike(result)) {
38
+ void Promise.resolve(result).then(() => this.#resume(), (error) => {
39
+ this.#report(error);
40
+ this.#resume();
41
+ });
42
+ return;
43
+ }
44
+ }
45
+ catch (error) {
46
+ this.#report(error);
47
+ }
48
+ }
49
+ this.#running = false;
50
+ }
51
+ clear() {
52
+ this.#pending.length = 0;
53
+ }
54
+ close() {
55
+ this.#closed = true;
56
+ this.clear();
57
+ }
58
+ #resume() {
59
+ this.#running = false;
60
+ this.drain();
61
+ }
62
+ #report(error) {
63
+ try {
64
+ this.#reportError(toError(error));
65
+ }
66
+ catch (reportError) {
67
+ reportErrorAsync(toError(reportError));
68
+ }
69
+ }
70
+ }
71
+ /** Maintains local publication order independently of how revisions are produced. */
72
+ class ReplicatedStatePublisher {
73
+ #listeners = new Map();
74
+ #reportError;
75
+ #sourceListeners = new Set();
76
+ #publications = [];
77
+ #value;
78
+ #sequence = 0;
79
+ #delivering = false;
80
+ constructor(initial, reportError = reportErrorAsync) {
81
+ this.#value = initial;
82
+ this.#reportError = reportError;
83
+ }
84
+ get value() {
85
+ return this.#value;
86
+ }
87
+ snapshot() {
88
+ return { value: this.#value, sequence: this.#sequence };
89
+ }
90
+ subscribe(listener) {
91
+ const { value, sequence } = this.snapshot();
92
+ const subscriber = new StateSubscriber(listener, this.#reportError);
93
+ this.#listeners.set(subscriber, sequence);
94
+ subscriber.push({ value, context: serviceDeliveryContext(), delivery: { kind: "hydrate", sequence } });
95
+ subscriber.drain();
96
+ return () => {
97
+ subscriber.close();
98
+ this.#listeners.delete(subscriber);
99
+ };
100
+ }
101
+ subscribeSource(listener) {
102
+ this.#sourceListeners.add(listener);
103
+ return () => this.#sourceListeners.delete(listener);
104
+ }
105
+ /** Publish an already-prepared immutable revision and return isolated listener failures. */
106
+ publish(value, ops, context) {
107
+ this.#value = value;
108
+ this.#sequence += 1;
109
+ this.#publications.push({ value, ops, sequence: this.#sequence, context });
110
+ if (this.#delivering)
111
+ return [];
112
+ this.#delivering = true;
113
+ const errors = [];
114
+ try {
115
+ for (let publication = this.#publications.shift(); publication !== undefined; publication = this.#publications.shift()) {
116
+ for (const listener of [...this.#sourceListeners]) {
117
+ try {
118
+ listener(publication.ops, publication.sequence, publication.context);
119
+ }
120
+ catch (error) {
121
+ errors.push(error);
122
+ }
123
+ }
124
+ const delivery = { kind: "update", sequence: publication.sequence };
125
+ for (const [subscriber, hydratedSequence] of [...this.#listeners]) {
126
+ if (publication.sequence <= hydratedSequence)
127
+ continue;
128
+ subscriber.push({ value: publication.value, context: publication.context, delivery });
129
+ subscriber.drain();
130
+ }
131
+ }
132
+ }
133
+ finally {
134
+ this.#delivering = false;
135
+ }
136
+ return errors;
137
+ }
138
+ }
139
+ export class MutableReplicatedStateImpl {
140
+ #tracker;
141
+ #publisher;
142
+ #changing = false;
143
+ constructor(initial) {
144
+ this.#tracker = track(initial);
145
+ this.#publisher = new ReplicatedStatePublisher(this.#tracker.value);
146
+ registerReplicatedStateInternals(this, {
147
+ snapshot: () => this.#publisher.snapshot(),
148
+ subscribe: (listener) => this.#publisher.subscribeSource(listener),
149
+ });
150
+ }
151
+ get value() {
152
+ return this.#tracker.value;
153
+ }
154
+ change(context, mutate) {
155
+ if (this.#changing)
156
+ throw new Error("Replicated state cannot be changed reentrantly from a change callback");
157
+ this.#changing = true;
158
+ let prepared;
159
+ try {
160
+ const change = this.#tracker.beginChange();
161
+ try {
162
+ const outcome = mutate(change.state);
163
+ if (isPromiseLike(outcome)) {
164
+ void Promise.resolve(outcome).catch(() => undefined);
165
+ throw new TypeError("Replicated state change callbacks must be synchronous");
166
+ }
167
+ prepared = change.prepare();
168
+ }
169
+ catch (error) {
170
+ change.abort();
171
+ throw error;
172
+ }
173
+ }
174
+ finally {
175
+ this.#changing = false;
176
+ }
177
+ this.#tracker.adopt(prepared);
178
+ if (prepared.ops.length === 0)
179
+ return;
180
+ throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
181
+ }
182
+ replace(context, value) {
183
+ if (this.#changing)
184
+ throw new Error("Replicated state cannot be replaced from a change callback");
185
+ const prepared = this.#tracker.prepareReplace(value);
186
+ this.#tracker.adopt(prepared);
187
+ if (prepared.ops.length === 0)
188
+ return;
189
+ throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
190
+ }
191
+ subscribe(listener) {
192
+ return this.#publisher.subscribe(listener);
193
+ }
194
+ }
195
+ class AttachedReplicatedStateImpl {
196
+ #publisher;
197
+ #attachment;
198
+ #reportError;
199
+ #cursor;
200
+ #disposed = false;
201
+ constructor(attachment, options) {
202
+ const { snapshot } = attachment;
203
+ assertCursor(snapshot.cursor, "snapshot");
204
+ this.#attachment = attachment;
205
+ this.#cursor = snapshot.cursor;
206
+ this.#reportError = options.onError ?? reportErrorAsync;
207
+ this.#publisher = new ReplicatedStatePublisher(snapshot.value, (error) => this.#report(error));
208
+ registerReplicatedStateInternals(this, {
209
+ snapshot: () => this.#publisher.snapshot(),
210
+ subscribe: (listener) => this.#publisher.subscribeSource(listener),
211
+ });
212
+ }
213
+ get value() {
214
+ return this.#publisher.value;
215
+ }
216
+ subscribe(listener) {
217
+ return this.#publisher.subscribe(listener);
218
+ }
219
+ activate() {
220
+ this.#attachment.activate((frame) => this.#receive(frame));
221
+ }
222
+ dispose() {
223
+ if (this.#disposed)
224
+ return;
225
+ this.#disposed = true;
226
+ this.#attachment.dispose();
227
+ }
228
+ #receive(frame) {
229
+ if (this.#disposed)
230
+ return;
231
+ try {
232
+ assertCursor(frame.cursor, "frame");
233
+ const expected = this.#cursor + 1;
234
+ if (frame.cursor !== expected) {
235
+ throw new Error(`Replicated state source cursor has a gap: expected ${expected}, received ${frame.cursor}`);
236
+ }
237
+ this.#cursor = frame.cursor;
238
+ const errors = this.#publisher.publish(frame.value, frame.ops, frame.context);
239
+ if (errors.length === 1)
240
+ this.#report(errors[0]);
241
+ else if (errors.length > 1)
242
+ this.#report(new AggregateError(errors, "Replicated state listeners failed"));
243
+ }
244
+ catch (error) {
245
+ this.#fail(toError(error));
246
+ }
247
+ }
248
+ #fail(error) {
249
+ if (this.#disposed)
250
+ return;
251
+ this.#disposed = true;
252
+ try {
253
+ this.#attachment.dispose();
254
+ }
255
+ catch (disposeError) {
256
+ this.#report(new AggregateError([error, disposeError], "Replicated state source contract failed"));
257
+ return;
258
+ }
259
+ this.#report(error);
260
+ }
261
+ #report(error) {
262
+ try {
263
+ this.#reportError(toError(error));
264
+ }
265
+ catch (reportError) {
266
+ reportErrorAsync(toError(reportError));
267
+ }
268
+ }
269
+ }
270
+ /** Attach a publication-only replicated state to one authoritative immutable source stream. */
271
+ export function attachReplicatedStateSource(source, options = {}) {
272
+ const attachment = source.attach();
273
+ try {
274
+ const state = new AttachedReplicatedStateImpl(attachment, options);
275
+ state.activate();
276
+ return state;
277
+ }
278
+ catch (error) {
279
+ try {
280
+ attachment.dispose();
281
+ }
282
+ catch (disposeError) {
283
+ throw new AggregateError([error, disposeError], "Failed to attach replicated state source");
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+ /** A cold read-only state used by service consumers until a complete snapshot arrives. */
289
+ export class ReplicatedStateReplica {
290
+ #listeners = new Set();
291
+ #reportError;
292
+ #validator = new JsonRevisionValidator();
293
+ #value;
294
+ #sequence;
295
+ constructor(reportError) {
296
+ this.#reportError = reportError;
297
+ }
298
+ get value() {
299
+ return this.#value;
300
+ }
301
+ subscribe(listener) {
302
+ const subscriber = new StateSubscriber(listener, this.#reportError);
303
+ this.#listeners.add(subscriber);
304
+ if (this.#value !== undefined) {
305
+ subscriber.push({
306
+ value: this.#value,
307
+ context: serviceDeliveryContext(),
308
+ delivery: { kind: "hydrate", sequence: this.#sequence },
309
+ });
310
+ subscriber.drain();
311
+ }
312
+ return () => {
313
+ subscriber.close();
314
+ this.#listeners.delete(subscriber);
315
+ };
316
+ }
317
+ hydrate(sequence, ops, context) {
318
+ let next;
319
+ try {
320
+ if (!isBase(ops))
321
+ throw new Error("Replicated state snapshot is not a base operation batch");
322
+ next = this.#validator.validate(applyImmutable(undefined, ops));
323
+ }
324
+ catch (error) {
325
+ this.clear();
326
+ throw error;
327
+ }
328
+ this.#sequence = sequence;
329
+ this.#value = next;
330
+ this.#deliverAll(context, { kind: "hydrate", sequence });
331
+ }
332
+ update(sequence, ops, context) {
333
+ if (this.#sequence === undefined || this.#value === undefined) {
334
+ throw new Error("Replicated state received an update before hydration");
335
+ }
336
+ if (sequence !== this.#sequence + 1) {
337
+ this.clear();
338
+ throw new Error("Replicated state update sequence has a gap");
339
+ }
340
+ let next;
341
+ try {
342
+ next = this.#validator.validate(applyImmutable(this.#value, ops));
343
+ }
344
+ catch (error) {
345
+ this.clear();
346
+ throw error;
347
+ }
348
+ this.#sequence = sequence;
349
+ this.#value = next;
350
+ this.#deliverAll(context, { kind: "update", sequence });
351
+ }
352
+ clear() {
353
+ this.#value = undefined;
354
+ this.#sequence = undefined;
355
+ for (const subscriber of this.#listeners)
356
+ subscriber.clear();
357
+ }
358
+ #deliverAll(context, delivery) {
359
+ if (this.#value === undefined)
360
+ return;
361
+ const frame = { value: this.#value, context, delivery };
362
+ const subscribers = [...this.#listeners];
363
+ // Enqueue for everyone before user code can publish another revision reentrantly.
364
+ for (const subscriber of subscribers)
365
+ subscriber.push(frame);
366
+ for (const subscriber of subscribers)
367
+ subscriber.drain();
368
+ }
369
+ }
370
+ /** @internal Context for synthetic service deliveries without a caller. */
371
+ export function serviceDeliveryContext() {
372
+ // TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.
373
+ return BACKGROUND_CONTEXT;
374
+ }
375
+ function assertCursor(cursor, kind) {
376
+ if (!Number.isSafeInteger(cursor))
377
+ throw new TypeError(`Replicated state source ${kind} cursor must be a safe integer`);
378
+ }
379
+ function isPromiseLike(value) {
380
+ return (((typeof value === "object" && value !== null) || typeof value === "function") &&
381
+ typeof value.then === "function");
382
+ }
383
+ function throwCollectedErrors(errors, message) {
384
+ if (errors.length === 1)
385
+ throw errors[0];
386
+ if (errors.length > 1)
387
+ throw new AggregateError(errors, message);
388
+ }
389
+ function reportErrorAsync(error) {
390
+ queueMicrotask(() => {
391
+ throw error;
392
+ });
393
+ }
394
+ function toError(error) {
395
+ return error instanceof Error ? error : new Error(String(error));
396
+ }
397
+ //# sourceMappingURL=state.js.map