@earendil-works/chord 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +205 -0
  2. package/dist/api.d.ts +15 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +72 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/bundler.d.ts +6 -0
  7. package/dist/bundler.d.ts.map +1 -0
  8. package/dist/bundler.js +3 -0
  9. package/dist/bundler.js.map +1 -0
  10. package/dist/context/index.d.ts +24 -0
  11. package/dist/context/index.d.ts.map +1 -0
  12. package/dist/context/index.js +97 -0
  13. package/dist/context/index.js.map +1 -0
  14. package/dist/delta/index.d.ts +119 -0
  15. package/dist/delta/index.d.ts.map +1 -0
  16. package/dist/delta/index.js +1213 -0
  17. package/dist/delta/index.js.map +1 -0
  18. package/dist/facets/host.d.ts +12 -0
  19. package/dist/facets/host.d.ts.map +1 -0
  20. package/dist/facets/host.js +763 -0
  21. package/dist/facets/host.js.map +1 -0
  22. package/dist/facets/loader.d.ts +3 -0
  23. package/dist/facets/loader.d.ts.map +1 -0
  24. package/dist/facets/loader.js +5 -0
  25. package/dist/facets/loader.js.map +1 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +10 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json.d.ts +4 -0
  31. package/dist/json.d.ts.map +1 -0
  32. package/dist/json.js +57 -0
  33. package/dist/json.js.map +1 -0
  34. package/dist/node/bundle-loader.d.ts +30 -0
  35. package/dist/node/bundle-loader.d.ts.map +1 -0
  36. package/dist/node/bundle-loader.js +377 -0
  37. package/dist/node/bundle-loader.js.map +1 -0
  38. package/dist/node/bundle.d.ts +26 -0
  39. package/dist/node/bundle.d.ts.map +1 -0
  40. package/dist/node/bundle.js +183 -0
  41. package/dist/node/bundle.js.map +1 -0
  42. package/dist/node/manifest.d.ts +36 -0
  43. package/dist/node/manifest.d.ts.map +1 -0
  44. package/dist/node/manifest.js +6 -0
  45. package/dist/node/manifest.js.map +1 -0
  46. package/dist/node/package.d.ts +15 -0
  47. package/dist/node/package.d.ts.map +1 -0
  48. package/dist/node/package.js +199 -0
  49. package/dist/node/package.js.map +1 -0
  50. package/dist/node.d.ts +5 -0
  51. package/dist/node.d.ts.map +1 -0
  52. package/dist/node.js +3 -0
  53. package/dist/node.js.map +1 -0
  54. package/dist/services/consumer.d.ts +11 -0
  55. package/dist/services/consumer.d.ts.map +1 -0
  56. package/dist/services/consumer.js +537 -0
  57. package/dist/services/consumer.js.map +1 -0
  58. package/dist/services/errors.d.ts +8 -0
  59. package/dist/services/errors.d.ts.map +1 -0
  60. package/dist/services/errors.js +22 -0
  61. package/dist/services/errors.js.map +1 -0
  62. package/dist/services/handle.d.ts +15 -0
  63. package/dist/services/handle.d.ts.map +1 -0
  64. package/dist/services/handle.js +99 -0
  65. package/dist/services/handle.js.map +1 -0
  66. package/dist/services/instances.d.ts +25 -0
  67. package/dist/services/instances.d.ts.map +1 -0
  68. package/dist/services/instances.js +137 -0
  69. package/dist/services/instances.js.map +1 -0
  70. package/dist/services/loopback.d.ts +5 -0
  71. package/dist/services/loopback.d.ts.map +1 -0
  72. package/dist/services/loopback.js +15 -0
  73. package/dist/services/loopback.js.map +1 -0
  74. package/dist/services/provider.d.ts +37 -0
  75. package/dist/services/provider.d.ts.map +1 -0
  76. package/dist/services/provider.js +472 -0
  77. package/dist/services/provider.js.map +1 -0
  78. package/dist/services/state-codec.d.ts +15 -0
  79. package/dist/services/state-codec.d.ts.map +1 -0
  80. package/dist/services/state-codec.js +115 -0
  81. package/dist/services/state-codec.js.map +1 -0
  82. package/dist/services/state-internals.d.ts +11 -0
  83. package/dist/services/state-internals.d.ts.map +1 -0
  84. package/dist/services/state-internals.js +10 -0
  85. package/dist/services/state-internals.js.map +1 -0
  86. package/dist/services/state.d.ts +23 -0
  87. package/dist/services/state.d.ts.map +1 -0
  88. package/dist/services/state.js +124 -0
  89. package/dist/services/state.js.map +1 -0
  90. package/dist/services/wire.d.ts +60 -0
  91. package/dist/services/wire.d.ts.map +1 -0
  92. package/dist/services/wire.js +173 -0
  93. package/dist/services/wire.js.map +1 -0
  94. package/dist/types.d.ts +203 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +2 -0
  97. package/dist/types.js.map +1 -0
  98. package/package.json +72 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-codec.d.ts","sourceRoot":"","sources":["../../src/services/state-codec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGX,qBAAqB,EACrB,2BAA2B,EAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,MAAM,WAAW,CAAC;AAE5F,0FAA0F;AAC1F,MAAM,WAAW,mBAAmB;IACnC,cAAc,CAAC,QAAQ,EAAE,2BAA2B,GAAG,+BAA+B,CAAC;IACvF,YAAY,CAAC,MAAM,EAAE,qBAAqB,GAAG,yBAAyB,CAAC;CACvE;AAED,0FAA0F;AAC1F,MAAM,WAAW,mBAAmB;IACnC,cAAc,CAAC,QAAQ,EAAE,+BAA+B,GAAG,2BAA2B,CAAC;IACvF,YAAY,CAAC,MAAM,EAAE,yBAAyB,GAAG,qBAAqB,CAAC;CACvE;AAwCD,wBAAgB,yBAAyB,IAAI,mBAAmB,CA4B/D;AAED,wBAAgB,yBAAyB,IAAI,mBAAmB,CA4B/D","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 \"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 \"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,115 @@
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 "state":
45
+ return { ...update, ops: codecs.get(update.instance, update.member).encode(update.ops) };
46
+ case "replaced":
47
+ codecs.reset();
48
+ return { ...update, snapshot: encodeInstance(update.snapshot, codecs) };
49
+ case "spawned":
50
+ return { ...update, instance: encodeInstance(update.instance, codecs) };
51
+ case "unavailable":
52
+ codecs.reset();
53
+ return update;
54
+ case "closed":
55
+ codecs.removeInstance(update.instance);
56
+ return update;
57
+ }
58
+ },
59
+ };
60
+ }
61
+ export function createServiceStateDecoder() {
62
+ const codecs = new StateCodecRegistry(decoder);
63
+ return {
64
+ decodeSnapshot(snapshot) {
65
+ codecs.reset();
66
+ return {
67
+ ...snapshot,
68
+ instances: snapshot.instances.map((instance) => decodeInstance(instance, codecs)),
69
+ };
70
+ },
71
+ decodeUpdate(update) {
72
+ switch (update.type) {
73
+ case "state":
74
+ return { ...update, ops: codecs.get(update.instance, update.member).decode(update.ops) };
75
+ case "replaced":
76
+ codecs.reset();
77
+ return { ...update, snapshot: decodeInstance(update.snapshot, codecs) };
78
+ case "spawned":
79
+ return { ...update, instance: decodeInstance(update.instance, codecs) };
80
+ case "unavailable":
81
+ codecs.reset();
82
+ return update;
83
+ case "closed":
84
+ codecs.removeInstance(update.instance);
85
+ return update;
86
+ }
87
+ },
88
+ };
89
+ }
90
+ function encodeInstance(instance, codecs) {
91
+ return {
92
+ ...instance,
93
+ members: instance.members.map((member) => member.kind === "state"
94
+ ? { ...member, ops: codecs.add(instance.instance, member.name).encode(member.ops) }
95
+ : member),
96
+ };
97
+ }
98
+ function decodeInstance(instance, codecs) {
99
+ return {
100
+ ...instance,
101
+ members: instance.members.map((member) => member.kind === "state"
102
+ ? { ...member, ops: codecs.add(instance.instance, member.name).decode(member.ops) }
103
+ : member),
104
+ };
105
+ }
106
+ function stateKey(instance, member) {
107
+ return JSON.stringify([instance?.key ?? null, instance?.generation ?? null, member]);
108
+ }
109
+ function sameAddress(left, right) {
110
+ return left?.key === right.key && left.generation === right.generation;
111
+ }
112
+ function describeState(instance, member) {
113
+ return instance === undefined ? member : `${instance.key}@${instance.generation}.${member}`;
114
+ }
115
+ //# 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 \"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 \"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,11 @@
1
+ import type { Op } from "../delta/index.ts";
2
+ import type { Context } from "../types.ts";
3
+ export interface ReplicatedStateInternals {
4
+ readonly sequence: number;
5
+ readonly value: unknown;
6
+ publish(context: Context): void;
7
+ subscribe(listener: (ops: readonly Op[], sequence: number, context: Context) => void): () => void;
8
+ }
9
+ export declare function registerReplicatedStateInternals(value: object, internals: ReplicatedStateInternals): void;
10
+ export declare function getReplicatedStateInternals(value: unknown): ReplicatedStateInternals | undefined;
11
+ //# 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,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,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","sourcesContent":["import type { Op } from \"../delta/index.ts\";\nimport type { Context } from \"../types.ts\";\n\nexport interface ReplicatedStateInternals {\n\treadonly sequence: number;\n\treadonly value: unknown;\n\tpublish(context: Context): void;\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,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":"AAUA,MAAM,OAAO,GAAG,IAAI,OAAO,EAAoC,CAAC;AAEhE,MAAM,UAAU,gCAAgC,CAAC,KAAa,EAAE,SAAmC,EAAQ;IAC1G,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AAAA,CAC9B;AAED,MAAM,UAAU,2BAA2B,CAAC,KAAc,EAAwC;IACjG,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAAA,CAC1B","sourcesContent":["import type { Op } from \"../delta/index.ts\";\nimport type { Context } from \"../types.ts\";\n\nexport interface ReplicatedStateInternals {\n\treadonly sequence: number;\n\treadonly value: unknown;\n\tpublish(context: Context): void;\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,23 @@
1
+ import { type Op } from "../delta/index.ts";
2
+ import type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from "../types.ts";
3
+ export declare class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {
4
+ #private;
5
+ constructor(initial: T);
6
+ get value(): T;
7
+ get state(): T;
8
+ publish(context: Context): void;
9
+ subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void;
10
+ }
11
+ /** A cold read-only state used by service consumers until a complete snapshot arrives. */
12
+ export declare class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {
13
+ #private;
14
+ constructor(reportError: (error: Error) => void);
15
+ get value(): T | undefined;
16
+ subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void;
17
+ hydrate(sequence: number, ops: readonly Op[], context: Context): void;
18
+ update(sequence: number, ops: readonly Op[], context: Context): void;
19
+ clear(): void;
20
+ }
21
+ /** @internal Context for synthetic service deliveries without a caller. */
22
+ export declare function serviceDeliveryContext(): Context;
23
+ //# 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,EAAuB,MAAM,mBAAmB,CAAC;AACzF,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGxH,qBAAa,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,sBAAsB,CAAC,CAAC,CAAC;;IAO7F,YAAY,OAAO,EAAE,CAAC,EAiBrB;IAED,IAAI,KAAK,IAAI,CAAC,CAEb;IAED,IAAI,KAAK,IAAI,CAAC,CAEb;IAED,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAQ9B;IAED,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,KAAK,IAAI,GAAG,MAAM,IAAI,CAMvG;CACD;AAED,0FAA0F;AAC1F,qBAAa,sBAAsB,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;;IAMjG,YAAY,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAE9C;IAED,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAEzB;IAED,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,KAAK,IAAI,GAAG,MAAM,IAAI,CASvG;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAMpE;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAYnE;IAED,KAAK,IAAI,IAAI,CAGZ;CAmBD;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,IAAI,OAAO,CAGhD","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../context/index.ts\";\nimport { applyImmutable, isBase, type Op, type Tracker, track } from \"../delta/index.ts\";\nimport type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from \"../types.ts\";\nimport { registerReplicatedStateInternals } from \"./state-internals.ts\";\n\nexport class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #sourceListeners = new Set<(ops: readonly Op[], sequence: number, context: Context) => void>();\n\treadonly #tracker: Tracker<T>;\n\t#publishedValue: T;\n\t#sequence = 0;\n\n\tconstructor(initial: T) {\n\t\tthis.#tracker = track(initial);\n\t\tthis.#publishedValue = applyImmutable(undefined, this.#tracker.flush()) as unknown as T;\n\t\tconst thisSource = this;\n\t\tregisterReplicatedStateInternals(this, {\n\t\t\tget sequence() {\n\t\t\t\treturn thisSource.#sequence;\n\t\t\t},\n\t\t\tget value() {\n\t\t\t\treturn thisSource.#publishedValue;\n\t\t\t},\n\t\t\tpublish: (context) => thisSource.publish(context),\n\t\t\tsubscribe: (listener) => {\n\t\t\t\tthisSource.#sourceListeners.add(listener);\n\t\t\t\treturn () => thisSource.#sourceListeners.delete(listener);\n\t\t\t},\n\t\t});\n\t}\n\n\tget value(): T {\n\t\treturn this.#publishedValue;\n\t}\n\n\tget state(): T {\n\t\treturn this.#tracker.state;\n\t}\n\n\tpublish(context: Context): void {\n\t\tconst ops = this.#tracker.flush();\n\t\tif (ops.length === 0) return;\n\t\tthis.#sequence += 1;\n\t\tthis.#publishedValue = applyImmutable(this.#publishedValue as unknown as JsonValue, ops) as unknown as T;\n\t\tfor (const listener of [...this.#sourceListeners]) listener(ops, this.#sequence, context);\n\t\tconst delivery = { kind: \"update\", sequence: this.#sequence } as const;\n\t\tfor (const listener of [...this.#listeners]) listener(this.#publishedValue, context, delivery);\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tconst context = serviceDeliveryContext();\n\t\tthis.publish(context);\n\t\tthis.#listeners.add(listener);\n\t\tlistener(this.#publishedValue, context, { kind: \"hydrate\", sequence: this.#sequence });\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n}\n\n/** A cold read-only state used by service consumers until a complete snapshot arrives. */\nexport class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #reportError: (error: Error) => void;\n\t#value: T | undefined;\n\t#sequence: number | undefined;\n\n\tconstructor(reportError: (error: Error) => void) {\n\t\tthis.#reportError = reportError;\n\t}\n\n\tget value(): T | undefined {\n\t\treturn this.#value;\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tthis.#listeners.add(listener);\n\t\tif (this.#value !== undefined) {\n\t\t\tthis.#deliver(listener, this.#value, serviceDeliveryContext(), {\n\t\t\t\tkind: \"hydrate\",\n\t\t\t\tsequence: this.#sequence!,\n\t\t\t});\n\t\t}\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\thydrate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (!isBase(ops)) throw new Error(\"Replicated state snapshot is not a base operation batch\");\n\t\tconst value = applyImmutable<T>(undefined, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"hydrate\", sequence });\n\t}\n\n\tupdate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (this.#sequence === undefined || this.#value === undefined) {\n\t\t\tthrow new Error(\"Replicated state received an update before hydration\");\n\t\t}\n\t\tif (sequence !== this.#sequence + 1) {\n\t\t\tthis.clear();\n\t\t\tthrow new Error(\"Replicated state update sequence has a gap\");\n\t\t}\n\t\tconst value = applyImmutable(this.#value, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"update\", sequence });\n\t}\n\n\tclear(): void {\n\t\tthis.#value = undefined;\n\t\tthis.#sequence = undefined;\n\t}\n\n\t#deliverAll(context: Context, delivery: ReplicatedStateDelivery): void {\n\t\tif (this.#value === undefined) return;\n\t\tfor (const listener of this.#listeners) this.#deliver(listener, this.#value, context, delivery);\n\t}\n\n\t#deliver(\n\t\tlistener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void,\n\t\tvalue: T,\n\t\tcontext: Context,\n\t\tdelivery: ReplicatedStateDelivery,\n\t): void {\n\t\ttry {\n\t\t\tlistener(value, context, delivery);\n\t\t} catch (error) {\n\t\t\tthis.#reportError(toError(error));\n\t\t}\n\t}\n}\n\n/** @internal Context for synthetic service deliveries without a caller. */\nexport function serviceDeliveryContext(): Context {\n\t// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.\n\treturn BACKGROUND_CONTEXT;\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n"]}
@@ -0,0 +1,124 @@
1
+ import { BACKGROUND_CONTEXT } from "../context/index.js";
2
+ import { applyImmutable, isBase, track } from "../delta/index.js";
3
+ import { registerReplicatedStateInternals } from "./state-internals.js";
4
+ export class MutableReplicatedStateImpl {
5
+ #listeners = new Set();
6
+ #sourceListeners = new Set();
7
+ #tracker;
8
+ #publishedValue;
9
+ #sequence = 0;
10
+ constructor(initial) {
11
+ this.#tracker = track(initial);
12
+ this.#publishedValue = applyImmutable(undefined, this.#tracker.flush());
13
+ const thisSource = this;
14
+ registerReplicatedStateInternals(this, {
15
+ get sequence() {
16
+ return thisSource.#sequence;
17
+ },
18
+ get value() {
19
+ return thisSource.#publishedValue;
20
+ },
21
+ publish: (context) => thisSource.publish(context),
22
+ subscribe: (listener) => {
23
+ thisSource.#sourceListeners.add(listener);
24
+ return () => thisSource.#sourceListeners.delete(listener);
25
+ },
26
+ });
27
+ }
28
+ get value() {
29
+ return this.#publishedValue;
30
+ }
31
+ get state() {
32
+ return this.#tracker.state;
33
+ }
34
+ publish(context) {
35
+ const ops = this.#tracker.flush();
36
+ if (ops.length === 0)
37
+ return;
38
+ this.#sequence += 1;
39
+ this.#publishedValue = applyImmutable(this.#publishedValue, ops);
40
+ for (const listener of [...this.#sourceListeners])
41
+ listener(ops, this.#sequence, context);
42
+ const delivery = { kind: "update", sequence: this.#sequence };
43
+ for (const listener of [...this.#listeners])
44
+ listener(this.#publishedValue, context, delivery);
45
+ }
46
+ subscribe(listener) {
47
+ const context = serviceDeliveryContext();
48
+ this.publish(context);
49
+ this.#listeners.add(listener);
50
+ listener(this.#publishedValue, context, { kind: "hydrate", sequence: this.#sequence });
51
+ return () => this.#listeners.delete(listener);
52
+ }
53
+ }
54
+ /** A cold read-only state used by service consumers until a complete snapshot arrives. */
55
+ export class ReplicatedStateReplica {
56
+ #listeners = new Set();
57
+ #reportError;
58
+ #value;
59
+ #sequence;
60
+ constructor(reportError) {
61
+ this.#reportError = reportError;
62
+ }
63
+ get value() {
64
+ return this.#value;
65
+ }
66
+ subscribe(listener) {
67
+ this.#listeners.add(listener);
68
+ if (this.#value !== undefined) {
69
+ this.#deliver(listener, this.#value, serviceDeliveryContext(), {
70
+ kind: "hydrate",
71
+ sequence: this.#sequence,
72
+ });
73
+ }
74
+ return () => this.#listeners.delete(listener);
75
+ }
76
+ hydrate(sequence, ops, context) {
77
+ if (!isBase(ops))
78
+ throw new Error("Replicated state snapshot is not a base operation batch");
79
+ const value = applyImmutable(undefined, ops);
80
+ this.#sequence = sequence;
81
+ this.#value = value;
82
+ this.#deliverAll(context, { kind: "hydrate", sequence });
83
+ }
84
+ update(sequence, ops, context) {
85
+ if (this.#sequence === undefined || this.#value === undefined) {
86
+ throw new Error("Replicated state received an update before hydration");
87
+ }
88
+ if (sequence !== this.#sequence + 1) {
89
+ this.clear();
90
+ throw new Error("Replicated state update sequence has a gap");
91
+ }
92
+ const value = applyImmutable(this.#value, ops);
93
+ this.#sequence = sequence;
94
+ this.#value = value;
95
+ this.#deliverAll(context, { kind: "update", sequence });
96
+ }
97
+ clear() {
98
+ this.#value = undefined;
99
+ this.#sequence = undefined;
100
+ }
101
+ #deliverAll(context, delivery) {
102
+ if (this.#value === undefined)
103
+ return;
104
+ for (const listener of this.#listeners)
105
+ this.#deliver(listener, this.#value, context, delivery);
106
+ }
107
+ #deliver(listener, value, context, delivery) {
108
+ try {
109
+ listener(value, context, delivery);
110
+ }
111
+ catch (error) {
112
+ this.#reportError(toError(error));
113
+ }
114
+ }
115
+ }
116
+ /** @internal Context for synthetic service deliveries without a caller. */
117
+ export function serviceDeliveryContext() {
118
+ // TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.
119
+ return BACKGROUND_CONTEXT;
120
+ }
121
+ function toError(error) {
122
+ return error instanceof Error ? error : new Error(String(error));
123
+ }
124
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/services/state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../context/index.ts\";\nimport { applyImmutable, isBase, type Op, type Tracker, track } from \"../delta/index.ts\";\nimport type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from \"../types.ts\";\nimport { registerReplicatedStateInternals } from \"./state-internals.ts\";\n\nexport class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #sourceListeners = new Set<(ops: readonly Op[], sequence: number, context: Context) => void>();\n\treadonly #tracker: Tracker<T>;\n\t#publishedValue: T;\n\t#sequence = 0;\n\n\tconstructor(initial: T) {\n\t\tthis.#tracker = track(initial);\n\t\tthis.#publishedValue = applyImmutable(undefined, this.#tracker.flush()) as unknown as T;\n\t\tconst thisSource = this;\n\t\tregisterReplicatedStateInternals(this, {\n\t\t\tget sequence() {\n\t\t\t\treturn thisSource.#sequence;\n\t\t\t},\n\t\t\tget value() {\n\t\t\t\treturn thisSource.#publishedValue;\n\t\t\t},\n\t\t\tpublish: (context) => thisSource.publish(context),\n\t\t\tsubscribe: (listener) => {\n\t\t\t\tthisSource.#sourceListeners.add(listener);\n\t\t\t\treturn () => thisSource.#sourceListeners.delete(listener);\n\t\t\t},\n\t\t});\n\t}\n\n\tget value(): T {\n\t\treturn this.#publishedValue;\n\t}\n\n\tget state(): T {\n\t\treturn this.#tracker.state;\n\t}\n\n\tpublish(context: Context): void {\n\t\tconst ops = this.#tracker.flush();\n\t\tif (ops.length === 0) return;\n\t\tthis.#sequence += 1;\n\t\tthis.#publishedValue = applyImmutable(this.#publishedValue as unknown as JsonValue, ops) as unknown as T;\n\t\tfor (const listener of [...this.#sourceListeners]) listener(ops, this.#sequence, context);\n\t\tconst delivery = { kind: \"update\", sequence: this.#sequence } as const;\n\t\tfor (const listener of [...this.#listeners]) listener(this.#publishedValue, context, delivery);\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tconst context = serviceDeliveryContext();\n\t\tthis.publish(context);\n\t\tthis.#listeners.add(listener);\n\t\tlistener(this.#publishedValue, context, { kind: \"hydrate\", sequence: this.#sequence });\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n}\n\n/** A cold read-only state used by service consumers until a complete snapshot arrives. */\nexport class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #reportError: (error: Error) => void;\n\t#value: T | undefined;\n\t#sequence: number | undefined;\n\n\tconstructor(reportError: (error: Error) => void) {\n\t\tthis.#reportError = reportError;\n\t}\n\n\tget value(): T | undefined {\n\t\treturn this.#value;\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tthis.#listeners.add(listener);\n\t\tif (this.#value !== undefined) {\n\t\t\tthis.#deliver(listener, this.#value, serviceDeliveryContext(), {\n\t\t\t\tkind: \"hydrate\",\n\t\t\t\tsequence: this.#sequence!,\n\t\t\t});\n\t\t}\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\thydrate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (!isBase(ops)) throw new Error(\"Replicated state snapshot is not a base operation batch\");\n\t\tconst value = applyImmutable<T>(undefined, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"hydrate\", sequence });\n\t}\n\n\tupdate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (this.#sequence === undefined || this.#value === undefined) {\n\t\t\tthrow new Error(\"Replicated state received an update before hydration\");\n\t\t}\n\t\tif (sequence !== this.#sequence + 1) {\n\t\t\tthis.clear();\n\t\t\tthrow new Error(\"Replicated state update sequence has a gap\");\n\t\t}\n\t\tconst value = applyImmutable(this.#value, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"update\", sequence });\n\t}\n\n\tclear(): void {\n\t\tthis.#value = undefined;\n\t\tthis.#sequence = undefined;\n\t}\n\n\t#deliverAll(context: Context, delivery: ReplicatedStateDelivery): void {\n\t\tif (this.#value === undefined) return;\n\t\tfor (const listener of this.#listeners) this.#deliver(listener, this.#value, context, delivery);\n\t}\n\n\t#deliver(\n\t\tlistener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void,\n\t\tvalue: T,\n\t\tcontext: Context,\n\t\tdelivery: ReplicatedStateDelivery,\n\t): void {\n\t\ttry {\n\t\t\tlistener(value, context, delivery);\n\t\t} catch (error) {\n\t\t\tthis.#reportError(toError(error));\n\t\t}\n\t}\n}\n\n/** @internal Context for synthetic service deliveries without a caller. */\nexport function serviceDeliveryContext(): Context {\n\t// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.\n\treturn BACKGROUND_CONTEXT;\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n"]}
@@ -0,0 +1,60 @@
1
+ import { type WireOp } from "../delta/index.ts";
2
+ import type { ServiceCall, ServiceCatalogueEntry, ServiceInstanceAddress, ServiceMode, ServiceProviderUpdate, ServiceSubscriptionSnapshot } from "../types.ts";
3
+ export type WireServiceMemberSnapshot = {
4
+ readonly name: string;
5
+ readonly kind: "method";
6
+ } | {
7
+ readonly name: string;
8
+ readonly kind: "state";
9
+ readonly sequence: number;
10
+ readonly ops: readonly WireOp[];
11
+ };
12
+ export type WireServiceInstanceSnapshot = {
13
+ readonly instance?: ServiceInstanceAddress;
14
+ readonly members: readonly WireServiceMemberSnapshot[];
15
+ };
16
+ export type WireServiceSubscriptionSnapshot = {
17
+ readonly serviceId: string;
18
+ readonly mode: ServiceMode;
19
+ readonly instances: readonly WireServiceInstanceSnapshot[];
20
+ };
21
+ export type WireServiceProviderUpdate = {
22
+ readonly type: "state";
23
+ readonly instance?: ServiceInstanceAddress;
24
+ readonly member: string;
25
+ readonly sequence: number;
26
+ readonly ops: readonly WireOp[];
27
+ } | {
28
+ readonly type: "unavailable";
29
+ } | {
30
+ readonly type: "replaced";
31
+ readonly snapshot: WireServiceInstanceSnapshot;
32
+ } | {
33
+ readonly type: "spawned";
34
+ readonly instance: WireServiceInstanceSnapshot;
35
+ } | {
36
+ readonly type: "closed";
37
+ readonly instance: ServiceInstanceAddress;
38
+ };
39
+ export type ServiceControlCall = {
40
+ readonly type: "catalogue";
41
+ } | {
42
+ readonly type: "subscribe";
43
+ readonly subscriptionId: string;
44
+ readonly serviceId: string;
45
+ readonly mode: ServiceMode;
46
+ } | {
47
+ readonly type: "unsubscribe";
48
+ readonly subscriptionId: string;
49
+ };
50
+ export declare function createServiceCatalogueCall(): ServiceCall;
51
+ export declare function createServiceSubscribeCall(subscriptionId: string, serviceId: string, mode: ServiceMode): ServiceCall;
52
+ export declare function createServiceUnsubscribeCall(subscriptionId: string): ServiceCall;
53
+ export declare function decodeServiceControlCall(call: ServiceCall): ServiceControlCall | undefined;
54
+ export declare function parseServiceCall(value: unknown): ServiceCall;
55
+ export declare function parseServiceCatalogue(value: unknown): readonly ServiceCatalogueEntry[];
56
+ export declare function parseServiceSubscriptionSnapshot(value: unknown): ServiceSubscriptionSnapshot;
57
+ export declare function parseWireServiceSubscriptionSnapshot(value: unknown): WireServiceSubscriptionSnapshot;
58
+ export declare function parseServiceProviderUpdate(value: unknown): ServiceProviderUpdate;
59
+ export declare function parseWireServiceProviderUpdate(value: unknown): WireServiceProviderUpdate;
60
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/services/wire.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertValidOp, assertValidWireOp, type WireOp } from \"../delta/index.ts\";\nimport type {\n\tServiceCall,\n\tServiceCatalogueEntry,\n\tServiceInstanceAddress,\n\tServiceMode,\n\tServiceProviderUpdate,\n\tServiceSubscriptionSnapshot,\n} from \"../types.ts\";\n\nexport type WireServiceMemberSnapshot =\n\t| { readonly name: string; readonly kind: \"method\" }\n\t| { readonly name: string; readonly kind: \"state\"; readonly sequence: number; readonly ops: readonly WireOp[] };\n\nexport type WireServiceInstanceSnapshot = {\n\treadonly instance?: ServiceInstanceAddress;\n\treadonly members: readonly WireServiceMemberSnapshot[];\n};\n\nexport type WireServiceSubscriptionSnapshot = {\n\treadonly serviceId: string;\n\treadonly mode: ServiceMode;\n\treadonly instances: readonly WireServiceInstanceSnapshot[];\n};\n\nexport type WireServiceProviderUpdate =\n\t| {\n\t\t\treadonly type: \"state\";\n\t\t\treadonly instance?: ServiceInstanceAddress;\n\t\t\treadonly member: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly ops: readonly WireOp[];\n\t }\n\t| { readonly type: \"unavailable\" }\n\t| { readonly type: \"replaced\"; readonly snapshot: WireServiceInstanceSnapshot }\n\t| { readonly type: \"spawned\"; readonly instance: WireServiceInstanceSnapshot }\n\t| { readonly type: \"closed\"; readonly instance: ServiceInstanceAddress };\n\nconst SERVICE_CONTROL_ID = \"$chord.service\";\nconst SERVICE_CATALOGUE_MEMBER = \"catalogue\";\nconst SERVICE_SUBSCRIBE_MEMBER = \"subscribe\";\nconst SERVICE_UNSUBSCRIBE_MEMBER = \"unsubscribe\";\n\nexport type ServiceControlCall =\n\t| { readonly type: \"catalogue\" }\n\t| {\n\t\t\treadonly type: \"subscribe\";\n\t\t\treadonly subscriptionId: string;\n\t\t\treadonly serviceId: string;\n\t\t\treadonly mode: ServiceMode;\n\t }\n\t| { readonly type: \"unsubscribe\"; readonly subscriptionId: string };\n\nexport function createServiceCatalogueCall(): ServiceCall {\n\treturn { serviceId: SERVICE_CONTROL_ID, member: SERVICE_CATALOGUE_MEMBER, args: [] };\n}\n\nexport function createServiceSubscribeCall(subscriptionId: string, serviceId: string, mode: ServiceMode): ServiceCall {\n\treturn { serviceId: SERVICE_CONTROL_ID, member: SERVICE_SUBSCRIBE_MEMBER, args: [subscriptionId, serviceId, mode] };\n}\n\nexport function createServiceUnsubscribeCall(subscriptionId: string): ServiceCall {\n\treturn { serviceId: SERVICE_CONTROL_ID, member: SERVICE_UNSUBSCRIBE_MEMBER, args: [subscriptionId] };\n}\n\nexport function decodeServiceControlCall(call: ServiceCall): ServiceControlCall | undefined {\n\tif (call.serviceId !== SERVICE_CONTROL_ID || call.instance !== undefined) return undefined;\n\tif (call.member === SERVICE_CATALOGUE_MEMBER && call.args.length === 0) return { type: \"catalogue\" };\n\tif (\n\t\tcall.member === SERVICE_SUBSCRIBE_MEMBER &&\n\t\tcall.args.length === 3 &&\n\t\tisId(call.args[0]) &&\n\t\tisId(call.args[1]) &&\n\t\t(call.args[2] === \"singleton\" || call.args[2] === \"keyed\")\n\t) {\n\t\treturn {\n\t\t\ttype: \"subscribe\",\n\t\t\tsubscriptionId: call.args[0],\n\t\t\tserviceId: call.args[1],\n\t\t\tmode: call.args[2],\n\t\t};\n\t}\n\tif (call.member === SERVICE_UNSUBSCRIBE_MEMBER && call.args.length === 1 && isId(call.args[0])) {\n\t\treturn { type: \"unsubscribe\", subscriptionId: call.args[0] };\n\t}\n\treturn undefined;\n}\n\nexport function parseServiceCall(value: unknown): ServiceCall {\n\tconst call = record(value, \"service call\");\n\tassertKeys(call, [\"serviceId\", \"member\", \"args\"], [\"instance\"], \"service call\");\n\tif (!isId(call.serviceId) || !isId(call.member) || !Array.isArray(call.args)) {\n\t\tthrow new TypeError(\"Invalid service call\");\n\t}\n\tif (call.instance !== undefined) assertAddress(call.instance);\n\treturn value as ServiceCall;\n}\n\nexport function parseServiceCatalogue(value: unknown): readonly ServiceCatalogueEntry[] {\n\tif (!Array.isArray(value)) throw new TypeError(\"Invalid service catalogue\");\n\tconst ids = new Set<string>();\n\tfor (const candidate of value) {\n\t\tconst entry = record(candidate, \"service catalogue entry\");\n\t\tassertKeys(entry, [\"serviceId\", \"mode\"], [], \"service catalogue entry\");\n\t\tif (!isId(entry.serviceId) || !isMode(entry.mode) || ids.has(entry.serviceId)) {\n\t\t\tthrow new TypeError(\"Invalid service catalogue\");\n\t\t}\n\t\tids.add(entry.serviceId);\n\t}\n\treturn value as unknown as readonly ServiceCatalogueEntry[];\n}\n\nexport function parseServiceSubscriptionSnapshot(value: unknown): ServiceSubscriptionSnapshot {\n\tassertSubscriptionSnapshot(value, assertValidOp);\n\treturn value as ServiceSubscriptionSnapshot;\n}\n\nexport function parseWireServiceSubscriptionSnapshot(value: unknown): WireServiceSubscriptionSnapshot {\n\tassertSubscriptionSnapshot(value, assertValidWireOp);\n\treturn value as WireServiceSubscriptionSnapshot;\n}\n\nexport function parseServiceProviderUpdate(value: unknown): ServiceProviderUpdate {\n\tassertProviderUpdate(value, assertValidOp);\n\treturn value as ServiceProviderUpdate;\n}\n\nexport function parseWireServiceProviderUpdate(value: unknown): WireServiceProviderUpdate {\n\tassertProviderUpdate(value, assertValidWireOp);\n\treturn value as WireServiceProviderUpdate;\n}\n\nfunction assertSubscriptionSnapshot(value: unknown, assertOp: (value: unknown) => void): void {\n\tconst snapshot = record(value, \"service subscription snapshot\");\n\tassertKeys(snapshot, [\"serviceId\", \"mode\", \"instances\"], [], \"service subscription snapshot\");\n\tif (!isId(snapshot.serviceId) || !isMode(snapshot.mode) || !Array.isArray(snapshot.instances)) {\n\t\tthrow new TypeError(\"Invalid service subscription snapshot\");\n\t}\n\tfor (const instance of snapshot.instances) assertInstance(instance, assertOp);\n}\n\nfunction assertProviderUpdate(value: unknown, assertOp: (value: unknown) => void): void {\n\tconst update = record(value, \"service provider update\");\n\tswitch (update.type) {\n\t\tcase \"state\":\n\t\t\tassertKeys(update, [\"type\", \"member\", \"sequence\", \"ops\"], [\"instance\"], \"state update\");\n\t\t\tif (!isId(update.member) || !isInteger(update.sequence, 1) || !Array.isArray(update.ops)) {\n\t\t\t\tthrow new TypeError(\"Invalid service state update\");\n\t\t\t}\n\t\t\tif (update.instance !== undefined) assertAddress(update.instance);\n\t\t\tfor (const op of update.ops) assertOp(op);\n\t\t\treturn;\n\t\tcase \"unavailable\":\n\t\t\tassertKeys(update, [\"type\"], [], \"unavailable update\");\n\t\t\treturn;\n\t\tcase \"replaced\":\n\t\t\tassertKeys(update, [\"type\", \"snapshot\"], [], \"replacement update\");\n\t\t\tassertInstance(update.snapshot, assertOp);\n\t\t\treturn;\n\t\tcase \"spawned\":\n\t\t\tassertKeys(update, [\"type\", \"instance\"], [], \"spawn update\");\n\t\t\tassertInstance(update.instance, assertOp);\n\t\t\treturn;\n\t\tcase \"closed\":\n\t\t\tassertKeys(update, [\"type\", \"instance\"], [], \"close update\");\n\t\t\tassertAddress(update.instance);\n\t\t\treturn;\n\t\tdefault:\n\t\t\tthrow new TypeError(\"Invalid service provider update\");\n\t}\n}\n\nfunction assertInstance(value: unknown, assertOp: (value: unknown) => void): void {\n\tconst instance = record(value, \"service instance snapshot\");\n\tassertKeys(instance, [\"members\"], [\"instance\"], \"service instance snapshot\");\n\tif (instance.instance !== undefined) assertAddress(instance.instance);\n\tif (!Array.isArray(instance.members)) throw new TypeError(\"Invalid service instance snapshot\");\n\tfor (const candidate of instance.members) {\n\t\tconst member = record(candidate, \"service member snapshot\");\n\t\tif (member.kind === \"method\") {\n\t\t\tassertKeys(member, [\"name\", \"kind\"], [], \"service method snapshot\");\n\t\t\tif (!isId(member.name)) throw new TypeError(\"Invalid service method snapshot\");\n\t\t\tcontinue;\n\t\t}\n\t\tif (member.kind === \"state\") {\n\t\t\tassertKeys(member, [\"name\", \"kind\", \"sequence\", \"ops\"], [], \"service state snapshot\");\n\t\t\tif (!isId(member.name) || !isInteger(member.sequence, 0) || !Array.isArray(member.ops)) {\n\t\t\t\tthrow new TypeError(\"Invalid service state snapshot\");\n\t\t\t}\n\t\t\tfor (const op of member.ops) assertOp(op);\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new TypeError(\"Invalid service member snapshot\");\n\t}\n}\n\nfunction assertAddress(value: unknown): asserts value is ServiceInstanceAddress {\n\tconst address = record(value, \"service instance address\");\n\tassertKeys(address, [\"key\", \"generation\"], [], \"service instance address\");\n\tif (!isId(address.key) || !isInteger(address.generation, 1)) {\n\t\tthrow new TypeError(\"Invalid service instance address\");\n\t}\n}\n\nfunction record(value: unknown, description: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new TypeError(`Invalid ${description}`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction assertKeys(\n\tvalue: Record<string, unknown>,\n\trequired: readonly string[],\n\toptional: readonly string[],\n\tdescription: string,\n): void {\n\tconst allowed = new Set([...required, ...optional]);\n\tif (required.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !allowed.has(key))) {\n\t\tthrow new TypeError(`Invalid ${description}`);\n\t}\n}\n\nfunction isId(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction isMode(value: unknown): value is ServiceMode {\n\treturn value === \"singleton\" || value === \"keyed\";\n}\n\nfunction isInteger(value: unknown, minimum: number): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= minimum;\n}\n"]}
@@ -0,0 +1,173 @@
1
+ import { assertValidOp, assertValidWireOp } from "../delta/index.js";
2
+ const SERVICE_CONTROL_ID = "$chord.service";
3
+ const SERVICE_CATALOGUE_MEMBER = "catalogue";
4
+ const SERVICE_SUBSCRIBE_MEMBER = "subscribe";
5
+ const SERVICE_UNSUBSCRIBE_MEMBER = "unsubscribe";
6
+ export function createServiceCatalogueCall() {
7
+ return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_CATALOGUE_MEMBER, args: [] };
8
+ }
9
+ export function createServiceSubscribeCall(subscriptionId, serviceId, mode) {
10
+ return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_SUBSCRIBE_MEMBER, args: [subscriptionId, serviceId, mode] };
11
+ }
12
+ export function createServiceUnsubscribeCall(subscriptionId) {
13
+ return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_UNSUBSCRIBE_MEMBER, args: [subscriptionId] };
14
+ }
15
+ export function decodeServiceControlCall(call) {
16
+ if (call.serviceId !== SERVICE_CONTROL_ID || call.instance !== undefined)
17
+ return undefined;
18
+ if (call.member === SERVICE_CATALOGUE_MEMBER && call.args.length === 0)
19
+ return { type: "catalogue" };
20
+ if (call.member === SERVICE_SUBSCRIBE_MEMBER &&
21
+ call.args.length === 3 &&
22
+ isId(call.args[0]) &&
23
+ isId(call.args[1]) &&
24
+ (call.args[2] === "singleton" || call.args[2] === "keyed")) {
25
+ return {
26
+ type: "subscribe",
27
+ subscriptionId: call.args[0],
28
+ serviceId: call.args[1],
29
+ mode: call.args[2],
30
+ };
31
+ }
32
+ if (call.member === SERVICE_UNSUBSCRIBE_MEMBER && call.args.length === 1 && isId(call.args[0])) {
33
+ return { type: "unsubscribe", subscriptionId: call.args[0] };
34
+ }
35
+ return undefined;
36
+ }
37
+ export function parseServiceCall(value) {
38
+ const call = record(value, "service call");
39
+ assertKeys(call, ["serviceId", "member", "args"], ["instance"], "service call");
40
+ if (!isId(call.serviceId) || !isId(call.member) || !Array.isArray(call.args)) {
41
+ throw new TypeError("Invalid service call");
42
+ }
43
+ if (call.instance !== undefined)
44
+ assertAddress(call.instance);
45
+ return value;
46
+ }
47
+ export function parseServiceCatalogue(value) {
48
+ if (!Array.isArray(value))
49
+ throw new TypeError("Invalid service catalogue");
50
+ const ids = new Set();
51
+ for (const candidate of value) {
52
+ const entry = record(candidate, "service catalogue entry");
53
+ assertKeys(entry, ["serviceId", "mode"], [], "service catalogue entry");
54
+ if (!isId(entry.serviceId) || !isMode(entry.mode) || ids.has(entry.serviceId)) {
55
+ throw new TypeError("Invalid service catalogue");
56
+ }
57
+ ids.add(entry.serviceId);
58
+ }
59
+ return value;
60
+ }
61
+ export function parseServiceSubscriptionSnapshot(value) {
62
+ assertSubscriptionSnapshot(value, assertValidOp);
63
+ return value;
64
+ }
65
+ export function parseWireServiceSubscriptionSnapshot(value) {
66
+ assertSubscriptionSnapshot(value, assertValidWireOp);
67
+ return value;
68
+ }
69
+ export function parseServiceProviderUpdate(value) {
70
+ assertProviderUpdate(value, assertValidOp);
71
+ return value;
72
+ }
73
+ export function parseWireServiceProviderUpdate(value) {
74
+ assertProviderUpdate(value, assertValidWireOp);
75
+ return value;
76
+ }
77
+ function assertSubscriptionSnapshot(value, assertOp) {
78
+ const snapshot = record(value, "service subscription snapshot");
79
+ assertKeys(snapshot, ["serviceId", "mode", "instances"], [], "service subscription snapshot");
80
+ if (!isId(snapshot.serviceId) || !isMode(snapshot.mode) || !Array.isArray(snapshot.instances)) {
81
+ throw new TypeError("Invalid service subscription snapshot");
82
+ }
83
+ for (const instance of snapshot.instances)
84
+ assertInstance(instance, assertOp);
85
+ }
86
+ function assertProviderUpdate(value, assertOp) {
87
+ const update = record(value, "service provider update");
88
+ switch (update.type) {
89
+ case "state":
90
+ assertKeys(update, ["type", "member", "sequence", "ops"], ["instance"], "state update");
91
+ if (!isId(update.member) || !isInteger(update.sequence, 1) || !Array.isArray(update.ops)) {
92
+ throw new TypeError("Invalid service state update");
93
+ }
94
+ if (update.instance !== undefined)
95
+ assertAddress(update.instance);
96
+ for (const op of update.ops)
97
+ assertOp(op);
98
+ return;
99
+ case "unavailable":
100
+ assertKeys(update, ["type"], [], "unavailable update");
101
+ return;
102
+ case "replaced":
103
+ assertKeys(update, ["type", "snapshot"], [], "replacement update");
104
+ assertInstance(update.snapshot, assertOp);
105
+ return;
106
+ case "spawned":
107
+ assertKeys(update, ["type", "instance"], [], "spawn update");
108
+ assertInstance(update.instance, assertOp);
109
+ return;
110
+ case "closed":
111
+ assertKeys(update, ["type", "instance"], [], "close update");
112
+ assertAddress(update.instance);
113
+ return;
114
+ default:
115
+ throw new TypeError("Invalid service provider update");
116
+ }
117
+ }
118
+ function assertInstance(value, assertOp) {
119
+ const instance = record(value, "service instance snapshot");
120
+ assertKeys(instance, ["members"], ["instance"], "service instance snapshot");
121
+ if (instance.instance !== undefined)
122
+ assertAddress(instance.instance);
123
+ if (!Array.isArray(instance.members))
124
+ throw new TypeError("Invalid service instance snapshot");
125
+ for (const candidate of instance.members) {
126
+ const member = record(candidate, "service member snapshot");
127
+ if (member.kind === "method") {
128
+ assertKeys(member, ["name", "kind"], [], "service method snapshot");
129
+ if (!isId(member.name))
130
+ throw new TypeError("Invalid service method snapshot");
131
+ continue;
132
+ }
133
+ if (member.kind === "state") {
134
+ assertKeys(member, ["name", "kind", "sequence", "ops"], [], "service state snapshot");
135
+ if (!isId(member.name) || !isInteger(member.sequence, 0) || !Array.isArray(member.ops)) {
136
+ throw new TypeError("Invalid service state snapshot");
137
+ }
138
+ for (const op of member.ops)
139
+ assertOp(op);
140
+ continue;
141
+ }
142
+ throw new TypeError("Invalid service member snapshot");
143
+ }
144
+ }
145
+ function assertAddress(value) {
146
+ const address = record(value, "service instance address");
147
+ assertKeys(address, ["key", "generation"], [], "service instance address");
148
+ if (!isId(address.key) || !isInteger(address.generation, 1)) {
149
+ throw new TypeError("Invalid service instance address");
150
+ }
151
+ }
152
+ function record(value, description) {
153
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
154
+ throw new TypeError(`Invalid ${description}`);
155
+ }
156
+ return value;
157
+ }
158
+ function assertKeys(value, required, optional, description) {
159
+ const allowed = new Set([...required, ...optional]);
160
+ if (required.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !allowed.has(key))) {
161
+ throw new TypeError(`Invalid ${description}`);
162
+ }
163
+ }
164
+ function isId(value) {
165
+ return typeof value === "string" && value.length > 0;
166
+ }
167
+ function isMode(value) {
168
+ return value === "singleton" || value === "keyed";
169
+ }
170
+ function isInteger(value, minimum) {
171
+ return typeof value === "number" && Number.isInteger(value) && value >= minimum;
172
+ }
173
+ //# sourceMappingURL=wire.js.map