@opetope/core 0.1.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 (131) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/README.ru.md +246 -0
  5. package/dist/abort-compat.d.ts +10 -0
  6. package/dist/abort-compat.js +2 -0
  7. package/dist/abort-compat.js.map +1 -0
  8. package/dist/adapter-method.d.ts +27 -0
  9. package/dist/attachment-lifecycle-cleanup.d.ts +3 -0
  10. package/dist/attachment-lifecycle-cleanup.js +2 -0
  11. package/dist/attachment-lifecycle-cleanup.js.map +1 -0
  12. package/dist/attachment-lifecycle-contract.d.ts +260 -0
  13. package/dist/attachment-lifecycle-contract.js +2 -0
  14. package/dist/attachment-lifecycle-contract.js.map +1 -0
  15. package/dist/attachment-lifecycle-helpers.d.ts +34 -0
  16. package/dist/attachment-lifecycle-helpers.js +2 -0
  17. package/dist/attachment-lifecycle-helpers.js.map +1 -0
  18. package/dist/attachment-lifecycle-machine.d.ts +3 -0
  19. package/dist/attachment-lifecycle-machine.js +2 -0
  20. package/dist/attachment-lifecycle-machine.js.map +1 -0
  21. package/dist/attachment-lifecycle-mailbox.d.ts +10 -0
  22. package/dist/attachment-lifecycle-mailbox.js +2 -0
  23. package/dist/attachment-lifecycle-mailbox.js.map +1 -0
  24. package/dist/attachment-lifecycle-open.d.ts +3 -0
  25. package/dist/attachment-lifecycle-open.js +2 -0
  26. package/dist/attachment-lifecycle-open.js.map +1 -0
  27. package/dist/attachment-lifecycle.d.ts +4 -0
  28. package/dist/attachment-lifecycle.js +2 -0
  29. package/dist/attachment-lifecycle.js.map +1 -0
  30. package/dist/attachment.d.ts +49 -0
  31. package/dist/attachment.js +2 -0
  32. package/dist/attachment.js.map +1 -0
  33. package/dist/call-authority.d.ts +26 -0
  34. package/dist/call-controller-support.d.ts +77 -0
  35. package/dist/call-controller-support.js +2 -0
  36. package/dist/call-controller-support.js.map +1 -0
  37. package/dist/call-controller.d.ts +49 -0
  38. package/dist/call-controller.js +2 -0
  39. package/dist/call-controller.js.map +1 -0
  40. package/dist/call-execution-inspection.d.ts +26 -0
  41. package/dist/call-execution-inspection.js +2 -0
  42. package/dist/call-execution-inspection.js.map +1 -0
  43. package/dist/call-executor-factory.d.ts +5 -0
  44. package/dist/call-executor-factory.js +2 -0
  45. package/dist/call-executor-factory.js.map +1 -0
  46. package/dist/call-executor-record.d.ts +66 -0
  47. package/dist/call-executor-record.js +2 -0
  48. package/dist/call-executor-record.js.map +1 -0
  49. package/dist/call-executor-support.d.ts +25 -0
  50. package/dist/call-executor-support.js +2 -0
  51. package/dist/call-executor-support.js.map +1 -0
  52. package/dist/call-executor-types.d.ts +56 -0
  53. package/dist/call-executor-waiters.d.ts +23 -0
  54. package/dist/call-executor-waiters.js +2 -0
  55. package/dist/call-executor-waiters.js.map +1 -0
  56. package/dist/call-executor.d.ts +6 -0
  57. package/dist/call-executor.js +2 -0
  58. package/dist/call-executor.js.map +1 -0
  59. package/dist/call-invocation.d.ts +61 -0
  60. package/dist/call-invocation.js +2 -0
  61. package/dist/call-invocation.js.map +1 -0
  62. package/dist/call-lane.d.ts +28 -0
  63. package/dist/call-lane.js +2 -0
  64. package/dist/call-lane.js.map +1 -0
  65. package/dist/call-target-result.d.ts +22 -0
  66. package/dist/call-target-result.js +2 -0
  67. package/dist/call-target-result.js.map +1 -0
  68. package/dist/call-target.d.ts +42 -0
  69. package/dist/call-target.js +2 -0
  70. package/dist/call-target.js.map +1 -0
  71. package/dist/call.d.ts +11 -0
  72. package/dist/call.js +2 -0
  73. package/dist/call.js.map +1 -0
  74. package/dist/contribution-hub.d.ts +100 -0
  75. package/dist/contribution-hub.js +2 -0
  76. package/dist/contribution-hub.js.map +1 -0
  77. package/dist/contribution-transaction.d.ts +20 -0
  78. package/dist/contribution-transaction.js +2 -0
  79. package/dist/contribution-transaction.js.map +1 -0
  80. package/dist/contribution.d.ts +64 -0
  81. package/dist/contribution.js +2 -0
  82. package/dist/contribution.js.map +1 -0
  83. package/dist/data-scheduler.d.ts +4 -0
  84. package/dist/data-scheduler.js +2 -0
  85. package/dist/data-scheduler.js.map +1 -0
  86. package/dist/errors.d.ts +56 -0
  87. package/dist/errors.js +2 -0
  88. package/dist/errors.js.map +1 -0
  89. package/dist/identity.d.ts +6 -0
  90. package/dist/identity.js +2 -0
  91. package/dist/identity.js.map +1 -0
  92. package/dist/index.d.ts +17 -0
  93. package/dist/index.js +2 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/internal.d.ts +46 -0
  96. package/dist/internal.js +2 -0
  97. package/dist/internal.js.map +1 -0
  98. package/dist/lifetime.d.ts +19 -0
  99. package/dist/lifetime.js +2 -0
  100. package/dist/lifetime.js.map +1 -0
  101. package/dist/model-context.d.ts +155 -0
  102. package/dist/model.d.ts +21 -0
  103. package/dist/model.js +2 -0
  104. package/dist/model.js.map +1 -0
  105. package/dist/platform-compat.d.ts +9 -0
  106. package/dist/platform-compat.js +2 -0
  107. package/dist/platform-compat.js.map +1 -0
  108. package/dist/port.d.ts +26 -0
  109. package/dist/port.js +2 -0
  110. package/dist/port.js.map +1 -0
  111. package/dist/reactive-collection.d.ts +27 -0
  112. package/dist/reactive-collection.js +2 -0
  113. package/dist/reactive-collection.js.map +1 -0
  114. package/dist/reactive-node.d.ts +62 -0
  115. package/dist/reactive-node.js +2 -0
  116. package/dist/reactive-node.js.map +1 -0
  117. package/dist/reactive-scheduler-control.d.ts +34 -0
  118. package/dist/reactive-scheduler-control.js +2 -0
  119. package/dist/reactive-scheduler-control.js.map +1 -0
  120. package/dist/reactive-scheduler.d.ts +18 -0
  121. package/dist/reactive-scheduler.js +2 -0
  122. package/dist/reactive-scheduler.js.map +1 -0
  123. package/dist/reactive.d.ts +41 -0
  124. package/dist/reactive.js +2 -0
  125. package/dist/reactive.js.map +1 -0
  126. package/dist/readable.d.ts +13 -0
  127. package/dist/readable.js +2 -0
  128. package/dist/readable.js.map +1 -0
  129. package/dist/resource.d.ts +82 -0
  130. package/dist/type-helpers.d.ts +14 -0
  131. package/package.json +66 -0
@@ -0,0 +1,155 @@
1
+ import type { AdapterMethodKey, AdapterMethodSignature, UniqueExposeKeys } from './adapter-method.js';
2
+ import type { Call } from './call-target.js';
3
+ import type { Readable } from './readable.js';
4
+ import type { Resource, ResourceRequestKey, ResourceRetention, ResourceRetry, StreamBackpressure } from './resource.js';
5
+ import type { Awaitable } from './type-helpers.js';
6
+ declare const modelLaneBrand: unique symbol;
7
+ declare const ownedStateBrand: unique symbol;
8
+ type Disposer = () => Awaitable<void>;
9
+ type ScopeKey = number | string;
10
+ /**
11
+ * State a model owns. It is a `Readable` for every consumer and carries a brand only its creator can produce, so
12
+ * `update` accepts what this context created and rejects a derived or foreign `Readable` at compile time (D72).
13
+ */
14
+ interface OwnedState<Value> extends Readable<Value> {
15
+ readonly [ownedStateBrand]: true;
16
+ }
17
+ /** A queue the model shares between its own calls; `call({ lane })` serialises everything that names it. */
18
+ interface ModelLane {
19
+ readonly [modelLaneBrand]: true;
20
+ }
21
+ interface ModelTimers {
22
+ delay(milliseconds: number, run: (signal: AbortSignal) => unknown): () => void;
23
+ interval(milliseconds: number, run: (signal: AbortSignal) => unknown): () => void;
24
+ }
25
+ /** Inside a model call: nested calls travel through `invoke`, so cancellation and the lane are inherited. */
26
+ interface ModelCallContext {
27
+ invoke<Input, Output>(target: Call<Input, Output>, input: Input): Promise<Output>;
28
+ readonly signal: AbortSignal;
29
+ throwIfAborted(): void;
30
+ }
31
+ type ModelCallScheduling = {
32
+ readonly lane?: ModelLane;
33
+ readonly policy?: 'latest' | 'queue';
34
+ } | {
35
+ readonly lane?: never;
36
+ readonly policy: 'parallel';
37
+ };
38
+ /** What `calls(source, keys)` hands back: one `Call` per selected method, input and output taken from the source. */
39
+ type ModelSelectedCalls<Source, Keys extends readonly PropertyKey[]> = {
40
+ readonly [Key in Keys[number]]: AdapterMethodSignature<Source[Key & keyof Source]> extends {
41
+ readonly input: infer Input;
42
+ readonly output: infer Output;
43
+ } ? Call<Input, Output> : never;
44
+ };
45
+ type ModelCallOptions<Input, Output> = {
46
+ readonly once?: ((input: Input) => ScopeKey) | true;
47
+ readonly run: (input: Input, context: ModelCallContext) => Awaitable<Output>;
48
+ readonly singleFlight?: (input: Input) => ScopeKey;
49
+ } & ModelCallScheduling;
50
+ type ModelEffectContext<Value> = {
51
+ readonly current: Value;
52
+ readonly previous: Value | undefined;
53
+ readonly signal: AbortSignal;
54
+ readonly source: Readable<Value>;
55
+ readonly timers: ModelTimers;
56
+ };
57
+ type ModelEffectOptions<Value> = {
58
+ readonly from: Readable<Value>;
59
+ readonly onDispose?: (context: {
60
+ readonly source: Readable<Value>;
61
+ }) => Awaitable<void>;
62
+ readonly run: (context: ModelEffectContext<Value>) => Awaitable<Disposer | void>;
63
+ readonly when?: (current: Value, previous: Value | undefined) => boolean;
64
+ };
65
+ type ModelEventSubscribeContext<Source, Payload> = {
66
+ readonly emit: (payload: Payload) => void;
67
+ readonly signal: AbortSignal;
68
+ readonly source: Source;
69
+ readonly timers: ModelTimers;
70
+ };
71
+ type ModelEventOptions<Source, Payload> = {
72
+ readonly run: (context: {
73
+ readonly payload: Payload;
74
+ readonly signal: AbortSignal;
75
+ readonly source: Source;
76
+ }) => Awaitable<void>;
77
+ };
78
+ type ModelResourceOptions<Source, Target, Data, Key extends ResourceRequestKey> = {
79
+ readonly key: (target: Target) => Key;
80
+ readonly load: (target: Target, context: {
81
+ readonly signal: AbortSignal;
82
+ readonly source: Source;
83
+ }) => Awaitable<Data>;
84
+ readonly retention?: ResourceRetention;
85
+ readonly retry?: ResourceRetry;
86
+ };
87
+ type ModelStreamOptions<Source, Target, Data, Key extends ResourceRequestKey> = {
88
+ /** Required, exactly as the runtime demands it: a stream without a backpressure policy has no defined law (D154). */
89
+ readonly backpressure: StreamBackpressure;
90
+ readonly connect: (target: Target, context: {
91
+ readonly emit: (data: Data) => void;
92
+ readonly signal: AbortSignal;
93
+ readonly source: Source;
94
+ }) => Awaitable<Disposer | void>;
95
+ readonly consume?: (context: {
96
+ readonly data: Data;
97
+ readonly signal: AbortSignal;
98
+ readonly source: Source;
99
+ }) => Awaitable<void>;
100
+ readonly key: (target: Target) => Key;
101
+ readonly retention?: ResourceRetention;
102
+ readonly retry?: ResourceRetry;
103
+ };
104
+ type ModelScopeOpen<Value> = (value: Value, context: {
105
+ readonly signal: AbortSignal;
106
+ readonly timers: ModelTimers;
107
+ }) => Awaitable<Disposer | void>;
108
+ interface ModelScope {
109
+ keyed<Value>(options: {
110
+ readonly from: Readable<readonly Value[]>;
111
+ readonly key: (value: Value) => ScopeKey;
112
+ readonly open: ModelScopeOpen<Value>;
113
+ }): void;
114
+ switch<Value>(options: {
115
+ readonly from: Readable<Value>;
116
+ readonly key?: (value: NonNullable<Value>) => ScopeKey;
117
+ readonly open: ModelScopeOpen<NonNullable<Value>>;
118
+ }): void;
119
+ while<Value>(options: {
120
+ readonly from: Readable<Value>;
121
+ readonly open: ModelScopeOpen<Value>;
122
+ readonly when?: (value: Value) => boolean;
123
+ }): void;
124
+ }
125
+ /**
126
+ * What a model receives from the feature that owns it. The context is not generic: a model declares its own
127
+ * dependencies as a plain interface and the feature supplies them, so the model never learns about imports,
128
+ * ports or the feature that hosts it. Every node here is created when the generation opens and dies with it:
129
+ * state is writable only through `update`, calls are fenced on retire, and `timers`, `cleanup`, `effect`, `event`,
130
+ * `resource`, `stream` and `scope` release their work in the same drain (D73, D95).
131
+ */
132
+ interface ModelContext {
133
+ call<Input, Output>(options: ModelCallOptions<Input, Output>): Call<Input, Output>;
134
+ /**
135
+ * The same selection `own.calls` makes, on the model's own dependencies: every named method becomes a `Call` of
136
+ * this model, and one `lane` serialises the whole set (D143).
137
+ */
138
+ calls<Source, const Keys extends readonly [AdapterMethodKey<Source>, ...AdapterMethodKey<Source>[]]>(source: Source, keys: Keys & UniqueExposeKeys<Keys>, options?: {
139
+ readonly lane?: ModelLane;
140
+ readonly policy?: 'latest' | 'queue';
141
+ }): ModelSelectedCalls<Source, Keys>;
142
+ /** Owned cleanup: the disposer runs in the same drain as `effect`, `event`, `resource`, `stream` and `scope`. */
143
+ cleanup(disposer: () => unknown): void;
144
+ effect<Value>(options: ModelEffectOptions<Value>): void;
145
+ event<Source, Payload>(from: Source, subscribe: (context: ModelEventSubscribeContext<Source, Payload>) => Awaitable<Disposer | void>, options: ModelEventOptions<Source, Payload>): void;
146
+ lane(): ModelLane;
147
+ resource<Source, Target, Data, Key extends ResourceRequestKey>(from: Source, target: (source: Source) => Readable<Target | null>, options: ModelResourceOptions<Source, Target, Data, Key>): Resource<Data, Key>;
148
+ readonly scope: ModelScope;
149
+ readonly signal: AbortSignal;
150
+ state<Value>(initial: Value): OwnedState<Value>;
151
+ stream<Source, Target, Data, Key extends ResourceRequestKey>(from: Source, target: (source: Source) => Readable<Target | null>, options: ModelStreamOptions<Source, Target, Data, Key>): Resource<Data, Key>;
152
+ readonly timers: ModelTimers;
153
+ update<Value>(state: OwnedState<Value>, value: Value): void;
154
+ }
155
+ export type { ModelContext, ModelLane, OwnedState };
@@ -0,0 +1,21 @@
1
+ import type { Call } from './call-target.js';
2
+ import type { DeclarationId } from './identity.js';
3
+ import type { Readable } from './readable.js';
4
+ declare const modelBrand: unique symbol;
5
+ declare const modelIdentityBrand: unique symbol;
6
+ interface ModelIdentity {
7
+ readonly id: DeclarationId;
8
+ readonly [modelIdentityBrand]: true;
9
+ }
10
+ type ModelReadable = Readable<unknown>;
11
+ type ModelReadableFactory = (...arguments_: never[]) => ModelReadable;
12
+ type ModelField = Call<never, unknown> | ModelReadable | ModelReadableFactory;
13
+ type ModelShape = Readonly<Record<string, ModelField>>;
14
+ interface Model<T extends ModelShape> extends ModelIdentity {
15
+ readonly [modelBrand]: (value: T) => T;
16
+ }
17
+ type ModelOf<Declaration> = Declaration extends Model<infer Shape> ? Shape : never;
18
+ declare function defineModel<Shape extends ModelShape>(id: string): Model<Shape>;
19
+ declare function isModel(value: unknown): value is ModelIdentity;
20
+ export { defineModel, isModel };
21
+ export type { Model, ModelField, ModelIdentity, ModelOf, ModelReadable, ModelShape };
package/dist/model.js ADDED
@@ -0,0 +1,2 @@
1
+ import{declarationId as d}from"./identity.js";const n=Symbol("opetope.model"),r=Symbol("opetope.model-identity"),l=Object.freeze({}),t=new WeakSet;function c(e){const o=Object.freeze({id:d(e),[n]:l,[r]:!0});return t.add(o),o}function m(e){return typeof e=="object"&&e!==null&&t.has(e)}export{c as defineModel,m as isModel};
2
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sources":["../src/model.ts"],"sourcesContent":["import type { Call } from './call-target';\nimport { declarationId } from './identity';\nimport type { DeclarationId } from './identity';\nimport type { Readable } from './readable';\n\nconst modelBrand = Symbol('opetope.model');\nconst modelIdentityBrand = Symbol('opetope.model-identity');\nconst modelMarker = Object.freeze({});\nconst models = new WeakSet<ModelIdentity>();\n\ninterface ModelIdentity {\n readonly id: DeclarationId;\n readonly [modelIdentityBrand]: true;\n}\n\ntype ModelReadable = Readable<unknown>;\ntype ModelReadableFactory = (...arguments_: never[]) => ModelReadable;\ntype ModelField = Call<never, unknown> | ModelReadable | ModelReadableFactory;\ntype ModelShape = Readonly<Record<string, ModelField>>;\n\ninterface Model<T extends ModelShape> extends ModelIdentity {\n readonly [modelBrand]: (value: T) => T;\n}\n\ntype ModelOf<Declaration> = Declaration extends Model<infer Shape> ? Shape : never;\n\nfunction defineModel<Shape extends ModelShape>(id: string): Model<Shape> {\n const model = Object.freeze({\n id: declarationId(id),\n [modelBrand]: modelMarker as unknown as (value: Shape) => Shape,\n [modelIdentityBrand]: true as const,\n }) as unknown as Model<Shape>;\n models.add(model);\n\n return model;\n}\n\nfunction isModel(value: unknown): value is ModelIdentity {\n return typeof value === 'object' && value !== null && models.has(value as ModelIdentity);\n}\n\nexport { defineModel, isModel };\nexport type { Model, ModelField, ModelIdentity, ModelOf, ModelReadable, ModelShape };\n"],"names":["modelBrand","modelIdentityBrand","modelMarker","models","defineModel","id","model","declarationId","isModel","value"],"mappings":"8CAKA,MAAMA,EAAa,OAAO,eAAe,EACnCC,EAAqB,OAAO,wBAAwB,EACpDC,EAAc,OAAO,OAAO,EAAE,EAC9BC,EAAS,IAAI,QAkBnB,SAASC,EAAsCC,EAAU,CACvD,MAAMC,EAAQ,OAAO,OAAO,CAC1B,GAAIC,EAAcF,CAAE,EACpB,CAACL,CAAU,EAAGE,EACd,CAACD,CAAkB,EAAG,EACvB,CAAA,EACD,OAAAE,EAAO,IAAIG,CAAK,EAETA,CACT,CAEA,SAASE,EAAQC,EAAc,CAC7B,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQN,EAAO,IAAIM,CAAsB,CACzF"}
@@ -0,0 +1,9 @@
1
+ type AggregateErrorOptions = {
2
+ readonly cause?: unknown;
3
+ };
4
+ /** `Array.prototype.at` is above the supported browser floor, so index arithmetic stays the canonical read. */
5
+ declare function lastOf<Value>(values: readonly Value[]): Value | undefined;
6
+ declare function hasOwn(value: object, propertyKey: PropertyKey): boolean;
7
+ declare function createErrorWithCause(message: string, cause: unknown): Error;
8
+ declare function createAggregateError(errors: readonly unknown[], message: string, options?: AggregateErrorOptions): AggregateError;
9
+ export { createAggregateError, createErrorWithCause, hasOwn, lastOf };
@@ -0,0 +1,2 @@
1
+ function c(e){return e.length===0?void 0:e[e.length-1]}function a(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function o(e,t){const r=new Error(e);return Object.defineProperty(r,"cause",{configurable:!0,enumerable:!1,value:t}),r}function f(e,t,r){const u=Reflect.get(globalThis,"AggregateError");if(typeof u=="function")return new u(e,t,r);const n=new Error(t);return n.name="AggregateError",Object.defineProperty(n,"errors",{configurable:!0,enumerable:!1,value:[...e]}),r!==void 0&&a(r,"cause")&&Object.defineProperty(n,"cause",{configurable:!0,enumerable:!1,value:r.cause}),n}export{f as createAggregateError,o as createErrorWithCause,a as hasOwn,c as lastOf};
2
+ //# sourceMappingURL=platform-compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-compat.js","sources":["../src/platform-compat.ts"],"sourcesContent":["type AggregateErrorOptions = { readonly cause?: unknown };\n\ntype AggregateErrorConstructorLike = new (\n errors: Iterable<unknown>,\n message?: string,\n options?: AggregateErrorOptions,\n) => AggregateError;\n\n/** `Array.prototype.at` is above the supported browser floor, so index arithmetic stays the canonical read. */\nfunction lastOf<Value>(values: readonly Value[]): Value | undefined {\n return values.length === 0 ? undefined : values[values.length - 1];\n}\n\nfunction hasOwn(value: object, propertyKey: PropertyKey): boolean {\n return Object.prototype.hasOwnProperty.call(value, propertyKey);\n}\n\nfunction createErrorWithCause(message: string, cause: unknown): Error {\n const error = new Error(message);\n Object.defineProperty(error, 'cause', { configurable: true, enumerable: false, value: cause });\n\n return error;\n}\n\nfunction createAggregateError(\n errors: readonly unknown[],\n message: string,\n options?: AggregateErrorOptions,\n): AggregateError {\n const constructor = Reflect.get(globalThis, 'AggregateError') as AggregateErrorConstructorLike | undefined;\n\n if (typeof constructor === 'function') return new constructor(errors, message, options);\n\n const error = new Error(message) as AggregateError;\n error.name = 'AggregateError';\n Object.defineProperty(error, 'errors', { configurable: true, enumerable: false, value: [...errors] });\n\n if (options !== undefined && hasOwn(options, 'cause')) {\n Object.defineProperty(error, 'cause', { configurable: true, enumerable: false, value: options.cause });\n }\n\n return error;\n}\n\nexport { createAggregateError, createErrorWithCause, hasOwn, lastOf };\n"],"names":["lastOf","values","hasOwn","value","propertyKey","createErrorWithCause","message","cause","error","createAggregateError","errors","options","constructor"],"mappings":"AASA,SAASA,EAAcC,EAAwB,CAC7C,OAAOA,EAAO,SAAW,EAAI,OAAYA,EAAOA,EAAO,OAAS,CAAC,CACnE,CAEA,SAASC,EAAOC,EAAeC,EAAwB,CACrD,OAAO,OAAO,UAAU,eAAe,KAAKD,EAAOC,CAAW,CAChE,CAEA,SAASC,EAAqBC,EAAiBC,EAAc,CAC3D,MAAMC,EAAQ,IAAI,MAAMF,CAAO,EAC/B,cAAO,eAAeE,EAAO,QAAS,CAAE,aAAc,GAAM,WAAY,GAAO,MAAOD,CAAK,CAAE,EAEtFC,CACT,CAEA,SAASC,EACPC,EACAJ,EACAK,EAA+B,CAE/B,MAAMC,EAAc,QAAQ,IAAI,WAAY,gBAAgB,EAE5D,GAAI,OAAOA,GAAgB,WAAY,OAAO,IAAIA,EAAYF,EAAQJ,EAASK,CAAO,EAEtF,MAAMH,EAAQ,IAAI,MAAMF,CAAO,EAC/B,OAAAE,EAAM,KAAO,iBACb,OAAO,eAAeA,EAAO,SAAU,CAAE,aAAc,GAAM,WAAY,GAAO,MAAO,CAAC,GAAGE,CAAM,CAAC,CAAE,EAEhGC,IAAY,QAAaT,EAAOS,EAAS,OAAO,GAClD,OAAO,eAAeH,EAAO,QAAS,CAAE,aAAc,GAAM,WAAY,GAAO,MAAOG,EAAQ,KAAK,CAAE,EAGhGH,CACT"}
package/dist/port.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { Call } from './call-target.js';
2
+ import type { DeclarationId } from './identity.js';
3
+ declare const portBrand: unique symbol;
4
+ declare const portRefBrand: unique symbol;
5
+ interface PortIdentity {
6
+ readonly id: DeclarationId;
7
+ }
8
+ interface Port<Input, Output> extends PortIdentity {
9
+ readonly [portBrand]: (input: Input) => Output;
10
+ }
11
+ interface PortRef<Input, Output> extends Call<Input, Output> {
12
+ readonly [portRefBrand]: (input: Input) => Output;
13
+ }
14
+ type PortSignature<Value> = Value extends Port<infer Input, infer Output> ? {
15
+ readonly input: Input;
16
+ readonly output: Output;
17
+ } : never;
18
+ type PortInput<Value> = PortSignature<Value>['input'];
19
+ type PortOutput<Value> = PortSignature<Value>['output'];
20
+ type PortRefOf<Value> = PortRef<PortInput<Value>, PortOutput<Value>>;
21
+ declare function definePort<Input, Output>(id: string): Port<Input, Output>;
22
+ declare function isPort(value: unknown): value is PortIdentity;
23
+ declare function bindPortRef<Input, Output>(port: Port<Input, Output>, target: Call<Input, Output>): PortRef<Input, Output>;
24
+ declare function isPortRefFor<Input, Output>(port: Port<Input, Output>, value: unknown): value is PortRef<Input, Output>;
25
+ export { bindPortRef, definePort, isPort, isPortRefFor };
26
+ export type { Port, PortIdentity, PortInput, PortOutput, PortRef, PortRefOf };
package/dist/port.js ADDED
@@ -0,0 +1,2 @@
1
+ import{isCallTarget as f}from"./call-target.js";import{declarationId as c}from"./identity.js";const s=new WeakSet,r=new WeakMap;function d(t){const e=Object.freeze({id:c(t)});return s.add(e),e}function o(t){return typeof t=="object"&&t!==null&&s.has(t)}function a(t,e){if(!o(t))throw new TypeError("Port is not authentic.");if(!f(e))throw new TypeError("Port target is not authentic.");const n=e,i=r.get(n);return i===void 0?r.set(n,new Set([t])):i.add(t),e}function u(t,e){var n;return o(t)&&typeof e=="object"&&e!==null&&((n=r.get(e))==null?void 0:n.has(t))===!0}export{a as bindPortRef,d as definePort,o as isPort,u as isPortRefFor};
2
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sources":["../src/port.ts"],"sourcesContent":["import { isCallTarget } from './call-target';\nimport type { Call } from './call-target';\nimport { declarationId } from './identity';\nimport type { DeclarationId } from './identity';\n\ndeclare const portBrand: unique symbol;\ndeclare const portRefBrand: unique symbol;\n\ninterface PortIdentity {\n readonly id: DeclarationId;\n}\n\ninterface Port<Input, Output> extends PortIdentity {\n readonly [portBrand]: (input: Input) => Output;\n}\n\ninterface PortRef<Input, Output> extends Call<Input, Output> {\n readonly [portRefBrand]: (input: Input) => Output;\n}\n\ntype PortSignature<Value> =\n Value extends Port<infer Input, infer Output> ? { readonly input: Input; readonly output: Output } : never;\ntype PortInput<Value> = PortSignature<Value>['input'];\ntype PortOutput<Value> = PortSignature<Value>['output'];\ntype PortRefOf<Value> = PortRef<PortInput<Value>, PortOutput<Value>>;\n\nconst ports = new WeakSet<object>();\nconst portRefs = new WeakMap<object, Set<object>>();\n\nfunction definePort<Input, Output>(id: string): Port<Input, Output> {\n const port = Object.freeze({ id: declarationId(id) }) as Port<Input, Output>;\n ports.add(port);\n\n return port;\n}\n\nfunction isPort(value: unknown): value is PortIdentity {\n return typeof value === 'object' && value !== null && ports.has(value);\n}\n\nfunction bindPortRef<Input, Output>(port: Port<Input, Output>, target: Call<Input, Output>): PortRef<Input, Output> {\n if (!isPort(port)) throw new TypeError('Port is not authentic.');\n\n if (!isCallTarget(target)) throw new TypeError('Port target is not authentic.');\n\n const erasedTarget = target as object;\n const bindings = portRefs.get(erasedTarget);\n\n if (bindings === undefined) portRefs.set(erasedTarget, new Set([port]));\n else bindings.add(port);\n\n return target as PortRef<Input, Output>;\n}\n\nfunction isPortRefFor<Input, Output>(port: Port<Input, Output>, value: unknown): value is PortRef<Input, Output> {\n return isPort(port) && typeof value === 'object' && value !== null && portRefs.get(value)?.has(port) === true;\n}\n\nexport { bindPortRef, definePort, isPort, isPortRefFor };\nexport type { Port, PortIdentity, PortInput, PortOutput, PortRef, PortRefOf };\n"],"names":["ports","portRefs","definePort","id","port","declarationId","isPort","value","bindPortRef","target","isCallTarget","erasedTarget","bindings","isPortRefFor","_a"],"mappings":"8FA0BA,MAAMA,EAAQ,IAAI,QACZC,EAAW,IAAI,QAErB,SAASC,EAA0BC,EAAU,CAC3C,MAAMC,EAAO,OAAO,OAAO,CAAE,GAAIC,EAAcF,CAAE,EAAG,EACpD,OAAAH,EAAM,IAAII,CAAI,EAEPA,CACT,CAEA,SAASE,EAAOC,EAAc,CAC5B,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQP,EAAM,IAAIO,CAAK,CACvE,CAEA,SAASC,EAA2BJ,EAA2BK,EAA2B,CACxF,GAAI,CAACH,EAAOF,CAAI,EAAG,MAAM,IAAI,UAAU,wBAAwB,EAE/D,GAAI,CAACM,EAAaD,CAAM,EAAG,MAAM,IAAI,UAAU,+BAA+B,EAE9E,MAAME,EAAeF,EACfG,EAAWX,EAAS,IAAIU,CAAY,EAE1C,OAAIC,IAAa,OAAWX,EAAS,IAAIU,EAAc,IAAI,IAAI,CAACP,CAAI,CAAC,CAAC,EACjEQ,EAAS,IAAIR,CAAI,EAEfK,CACT,CAEA,SAASI,EAA4BT,EAA2BG,EAAc,OAC5E,OAAOD,EAAOF,CAAI,GAAK,OAAOG,GAAU,UAAYA,IAAU,QAAQO,EAAAb,EAAS,IAAIM,CAAK,IAAlB,YAAAO,EAAqB,IAAIV,MAAU,EAC3G"}
@@ -0,0 +1,27 @@
1
+ import type { Readable } from './readable.js';
2
+ type Lookup<Value> = {
3
+ readonly kind: 'found';
4
+ readonly value: Value;
5
+ } | {
6
+ readonly kind: 'missing';
7
+ };
8
+ interface CollectionSnapshot<Key, Value> {
9
+ readonly byId: ReadonlyMap<Key, Value>;
10
+ readonly list: readonly Value[];
11
+ }
12
+ interface Collection<Key, Value> extends Readable<CollectionSnapshot<Key, Value>> {
13
+ readonly byId: Readable<ReadonlyMap<Key, Value>>;
14
+ readonly list: Readable<readonly Value[]>;
15
+ }
16
+ interface CollectionOptions<Item, Key, Value = Item> {
17
+ readonly from: Readable<readonly Item[]>;
18
+ readonly key: (item: Item) => Key;
19
+ readonly project?: (item: Item) => Value;
20
+ }
21
+ interface ProjectedCollectionOptions<Item, Key, Value> extends CollectionOptions<Item, Key, Value> {
22
+ readonly project: (item: Item) => Value;
23
+ }
24
+ declare function collection<Item, Key, Value = Item>(options: CollectionOptions<Item, Key, Value>): Collection<Key, Value>;
25
+ declare function selectByKey<Key, Value>(source: Collection<Key, Value>, key: Key): Readable<Lookup<Value>>;
26
+ export { collection, selectByKey };
27
+ export type { Collection, CollectionOptions, CollectionSnapshot, Lookup, ProjectedCollectionOptions };
@@ -0,0 +1,2 @@
1
+ import{createAggregateError as S}from"./platform-compat.js";import{derive as h}from"./reactive.js";import{registerReactiveAlias as d,reactiveDerived as j,reactiveKeyed as w,isReactiveObserved as f,defaultEquality as K}from"./reactive-scheduler.js";const L=Object.freeze({kind:"missing"}),b=new Set,p=new WeakMap,l={allKeys:!1,keys:b},y={allKeys:!0,keys:b};function g(r,e){if(e===void 0)return r;const t=r??[];return t.push(...e),t}function O(r){return typeof r=="object"&&r!==null||typeof r=="function"}function I(r,e){if(r.length!==e.length)return!1;for(let t=0;t<e.length;t+=1)if(!Object.is(r[t],e[t]))return!1;return!0}function m(r,e){if(r.size!==e.size)return!1;for(const[t,s]of e)if(!r.has(t)||!Object.is(r.get(t),s))return!1;return!0}function k(r){if(r===void 0)return;let e;for(const{listener:t}of[...r])try{t()}catch(s){(e??(e=[])).push(s)}return e}function E(r){let e=!0;return()=>{e&&(e=!1,r())}}function C(r,e){return I(r.value.list,e.value.list)&&m(r.value.byId,e.value.byId)}class z{readable;hasSourceSnapshot=!1;key;keyListeners=new Map;projection;projectionCache=new WeakMap;selections=new Map;source;sourceSnapshot;sourceUnsubscribe;state={kind:"uninitialized"};constructor(e){this.key=e.key,this.projection=e.project,this.source=e.from,this.readable=j([this.source],t=>this.readIndex(t),K)}select(e){const t=this.selections.get(e);if(t!==void 0)return t;const s=w({getSnapshot:()=>this.getLookup(e),subscribe:i=>this.subscribeKey(e,i)},this.readable),n=this.cacheSelection(e,s);return this.selections.set(e,n),n}buildSnapshot(e){const t=this.state.kind==="value"?this.state.snapshot:void 0,s=new Map,n=[],i=new Map;for(const o of e){const[c,a,u]=this.projectEntry(o,s,t);s.set(c,a),n.push(a),i.set(c,u)}return{lookups:i,value:{byId:s,list:n}}}cacheSelection(e,t){let s=0,n=0;const i=()=>{const c=++n;queueMicrotask(()=>{var a;s>0||f(t)||c!==n||((a=this.selections.get(e))==null?void 0:a.getSnapshot)!==t.getSnapshot||this.selections.delete(e)})},o=Object.freeze({getSnapshot:t.getSnapshot,subscribe:c=>{s+=1,n+=1;let a;try{a=t.subscribe(c)}catch(v){throw s-=1,i(),v}let u=!0;return()=>{u&&(u=!1,s-=1,a(),i())}}});return d(o,t),i(),o}canReuseSnapshot(e){return this.hasSourceSnapshot&&this.state.kind==="value"&&Object.is(this.sourceSnapshot,e)}changedKeys(e,t){const s=new Set;for(const[n,i]of t.value.byId)(!e.value.byId.has(n)||!Object.is(e.value.byId.get(n),i))&&s.add(n);for(const n of e.value.byId.keys())t.value.byId.has(n)||s.add(n);return s}connect(){this.sourceUnsubscribe=this.readable.subscribe(()=>{})}disconnect(){if(this.isObserved())return;const e=this.sourceUnsubscribe;this.sourceUnsubscribe=void 0,e==null||e()}getLookup(e){return this.readable.getSnapshot().byId.has(e)?this.state.kind==="value"&&this.state.snapshot.lookups.get(e):L}isObserved(){return this.keyListeners.size>0}project(e){const t=this.projection;if(t===void 0)return e;if(!O(e))return t(e);const s=this.projectionCache.get(e);if(s!==void 0)return s.value;const n=t(e);return this.projectionCache.set(e,{value:n}),n}projectEntry(e,t,s){const n=this.key(e);if(t.has(n))throw new TypeError("Collection keys must be unique.");const i=this.project(e),o=s==null?void 0:s.lookups.get(n),c=(o==null?void 0:o.kind)==="found"&&Object.is(o.value,i)?o:{kind:"found",value:i};return[n,i,c]}publish(e){let t;if(e.allKeys){for(const s of this.keyListeners.values())t=g(t,k(s));return t}for(const s of e.keys)t=g(t,k(this.keyListeners.get(s)));return t}readIndex(e){const t=this.publish(this.refresh(e));if(t!==void 0)throw S(t,"Collection index invalidation failed.");if(this.state.kind==="error")throw this.state.error;if(this.state.kind==="uninitialized")throw new TypeError("Collection did not produce a snapshot.");return this.state.snapshot.value}refresh(e){try{const t=e(this.source);return this.canReuseSnapshot(t)?l:(this.hasSourceSnapshot=!0,this.sourceSnapshot=t,this.setValue(this.buildSnapshot(t)))}catch(t){return this.setError(t)}}removeKeyListener(e,t){const s=this.keyListeners.get(e);s==null||s.delete(t),(s==null?void 0:s.size)===0&&this.keyListeners.delete(e)}scheduleSelectionRelease(e){const t=this.selections.get(e);t!==void 0&&queueMicrotask(()=>{this.selections.get(e)===t&&!f(t)&&this.selections.delete(e)})}setError(e){const t=this.state;return this.sourceSnapshot=void 0,this.hasSourceSnapshot=!1,this.state={error:e,kind:"error"},t.kind==="error"&&Object.is(t.error,e)?l:y}setValue(e){const t=this.state;return t.kind==="value"&&C(t.snapshot,e)?l:(this.state={kind:"value",snapshot:e},t.kind==="value"?{allKeys:!1,keys:this.changedKeys(t.snapshot,e)}:y)}subscribeKey(e,t){const s={listener:t},n=this.keyListeners.get(e)??new Set,i=this.isObserved();n.add(s),this.keyListeners.set(e,n);try{i||this.connect()}catch(o){throw this.removeKeyListener(e,s),o}return E(()=>{this.removeKeyListener(e,s),this.disconnect(),this.scheduleSelectionRelease(e)})}}function q(r){const e=new z(r),t=h({from:e.readable,select:i=>i.byId}),s=h({from:e.readable,select:i=>i.list}),n=Object.freeze({byId:t,getSnapshot:e.readable.getSnapshot,list:s,subscribe:e.readable.subscribe});return p.set(n,e),d(n,e.readable),n}function M(r,e){const t=p.get(r);if(t===void 0)throw new TypeError("selectByKey requires a collection created by collection().");return t.select(e)}export{q as collection,M as selectByKey};
2
+ //# sourceMappingURL=reactive-collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive-collection.js","sources":["../src/reactive-collection.ts"],"sourcesContent":["import { createAggregateError } from './platform-compat';\nimport { derive } from './reactive';\nimport {\n defaultEquality,\n isReactiveObserved,\n reactiveDerived,\n reactiveKeyed,\n registerReactiveAlias,\n} from './reactive-scheduler';\nimport type { ReactiveRead } from './reactive-scheduler';\nimport type { Readable, ReadableListener } from './readable';\n\ntype Lookup<Value> = { readonly kind: 'found'; readonly value: Value } | { readonly kind: 'missing' };\n\ninterface CollectionSnapshot<Key, Value> {\n readonly byId: ReadonlyMap<Key, Value>;\n readonly list: readonly Value[];\n}\n\ninterface Collection<Key, Value> extends Readable<CollectionSnapshot<Key, Value>> {\n readonly byId: Readable<ReadonlyMap<Key, Value>>;\n readonly list: Readable<readonly Value[]>;\n}\n\ninterface CollectionOptions<Item, Key, Value = Item> {\n readonly from: Readable<readonly Item[]>;\n readonly key: (item: Item) => Key;\n readonly project?: (item: Item) => Value;\n}\n\ninterface ProjectedCollectionOptions<Item, Key, Value> extends CollectionOptions<Item, Key, Value> {\n readonly project: (item: Item) => Value;\n}\n\ninterface InternalCollectionSnapshot<Key, Value> {\n readonly lookups: ReadonlyMap<Key, Lookup<Value>>;\n readonly value: CollectionSnapshot<Key, Value>;\n}\n\ntype CollectionState<Key, Value> =\n | { readonly error: unknown; readonly kind: 'error' }\n | { readonly kind: 'uninitialized' }\n | { readonly kind: 'value'; readonly snapshot: InternalCollectionSnapshot<Key, Value> };\n\ninterface CollectionChange<Key> {\n readonly allKeys: boolean;\n readonly keys: ReadonlySet<Key>;\n}\n\ntype ListenerRegistration = { readonly listener: ReadableListener };\ntype ProjectionCell<Value> = { readonly value: Value };\n\nconst missingLookup = Object.freeze({ kind: 'missing' } as const);\nconst noKeys = new Set<never>();\nconst collectionHubs = new WeakMap<object, CollectionHub<unknown, unknown, unknown>>();\nconst unchangedCollection: CollectionChange<never> = { allKeys: false, keys: noKeys };\nconst allCollectionChanged: CollectionChange<never> = { allKeys: true, keys: noKeys };\n\nfunction mergeFailures(first: unknown[] | undefined, second: unknown[] | undefined): unknown[] | undefined {\n if (second === undefined) return first;\n\n const failures = first ?? [];\n failures.push(...second);\n\n return failures;\n}\n\nfunction isWeakKey(value: unknown): value is object {\n return (typeof value === 'object' && value !== null) || typeof value === 'function';\n}\n\nfunction listsEqual<Value>(previous: readonly Value[], next: readonly Value[]): boolean {\n if (previous.length !== next.length) return false;\n\n for (let index = 0; index < next.length; index += 1) {\n if (!Object.is(previous[index], next[index])) return false;\n }\n\n return true;\n}\n\nfunction mapsEqual<Key, Value>(previous: ReadonlyMap<Key, Value>, next: ReadonlyMap<Key, Value>): boolean {\n if (previous.size !== next.size) return false;\n\n for (const [key, value] of next) {\n if (!previous.has(key) || !Object.is(previous.get(key), value)) return false;\n }\n\n return true;\n}\n\nfunction notifyListeners(listeners: ReadonlySet<ListenerRegistration> | undefined): unknown[] | undefined {\n if (listeners === undefined) return undefined;\n\n let failures: unknown[] | undefined;\n\n for (const { listener } of [...listeners]) {\n try {\n listener();\n } catch (error) {\n (failures ??= []).push(error);\n }\n }\n\n return failures;\n}\n\nfunction once(release: () => void): () => void {\n let active = true;\n\n return (): void => {\n if (!active) return;\n\n active = false;\n release();\n };\n}\n\nfunction snapshotsEqual<Key, Value>(\n previous: InternalCollectionSnapshot<Key, Value>,\n next: InternalCollectionSnapshot<Key, Value>,\n): boolean {\n return listsEqual(previous.value.list, next.value.list) && mapsEqual(previous.value.byId, next.value.byId);\n}\n\nclass CollectionHub<Item, Key, Value> {\n readonly readable: Readable<CollectionSnapshot<Key, Value>>;\n private hasSourceSnapshot = false;\n private readonly key: (item: Item) => Key;\n private readonly keyListeners = new Map<Key, Set<ListenerRegistration>>();\n private readonly projection: ((item: Item) => Value) | undefined;\n private readonly projectionCache = new WeakMap<object, ProjectionCell<Value>>();\n private readonly selections = new Map<Key, Readable<Lookup<Value>>>();\n private readonly source: Readable<readonly Item[]>;\n private sourceSnapshot: readonly Item[] | undefined;\n private sourceUnsubscribe: (() => void) | undefined;\n private state: CollectionState<Key, Value> = { kind: 'uninitialized' };\n\n constructor(options: CollectionOptions<Item, Key, Value>) {\n this.key = options.key;\n this.projection = options.project;\n this.source = options.from;\n this.readable = reactiveDerived([this.source], get => this.readIndex(get), defaultEquality);\n }\n\n select(key: Key): Readable<Lookup<Value>> {\n const cached = this.selections.get(key);\n\n if (cached !== undefined) return cached;\n\n const selected = reactiveKeyed(\n {\n getSnapshot: (): Lookup<Value> => this.getLookup(key),\n subscribe: (listener): (() => void) => this.subscribeKey(key, listener),\n },\n this.readable,\n );\n const selection = this.cacheSelection(key, selected);\n this.selections.set(key, selection);\n\n return selection;\n }\n\n private buildSnapshot(items: readonly Item[]): InternalCollectionSnapshot<Key, Value> {\n const previous = this.state.kind === 'value' ? this.state.snapshot : undefined;\n const byId = new Map<Key, Value>();\n const list: Value[] = [];\n const lookups = new Map<Key, Lookup<Value>>();\n\n for (const item of items) {\n const [key, value, lookup] = this.projectEntry(item, byId, previous);\n byId.set(key, value);\n list.push(value);\n lookups.set(key, lookup);\n }\n\n return { lookups, value: { byId, list } };\n }\n\n private cacheSelection(key: Key, selected: Readable<Lookup<Value>>): Readable<Lookup<Value>> {\n let observers = 0;\n let releaseRevision = 0;\n const scheduleRelease = (): void => {\n const revision = ++releaseRevision;\n\n queueMicrotask(() => {\n if (\n observers > 0 ||\n isReactiveObserved(selected) ||\n revision !== releaseRevision ||\n this.selections.get(key)?.getSnapshot !== selected.getSnapshot\n ) {\n return;\n }\n\n this.selections.delete(key);\n });\n };\n const readable = Object.freeze({\n getSnapshot: selected.getSnapshot,\n subscribe: (listener: ReadableListener): (() => void) => {\n observers += 1;\n releaseRevision += 1;\n let unsubscribe: () => void;\n\n try {\n unsubscribe = selected.subscribe(listener);\n } catch (error) {\n observers -= 1;\n scheduleRelease();\n throw error;\n }\n\n let subscribed = true;\n\n return (): void => {\n if (!subscribed) return;\n\n subscribed = false;\n observers -= 1;\n unsubscribe();\n scheduleRelease();\n };\n },\n });\n registerReactiveAlias(readable, selected);\n\n scheduleRelease();\n\n return readable;\n }\n\n private canReuseSnapshot(sourceSnapshot: readonly Item[]): boolean {\n return this.hasSourceSnapshot && this.state.kind === 'value' && Object.is(this.sourceSnapshot, sourceSnapshot);\n }\n\n private changedKeys(\n previous: InternalCollectionSnapshot<Key, Value>,\n next: InternalCollectionSnapshot<Key, Value>,\n ): ReadonlySet<Key> {\n const changed = new Set<Key>();\n\n for (const [key, value] of next.value.byId) {\n if (!previous.value.byId.has(key) || !Object.is(previous.value.byId.get(key), value)) changed.add(key);\n }\n\n for (const key of previous.value.byId.keys()) {\n if (!next.value.byId.has(key)) changed.add(key);\n }\n\n return changed;\n }\n\n private connect(): void {\n this.sourceUnsubscribe = this.readable.subscribe(() => undefined);\n }\n\n private disconnect(): void {\n if (this.isObserved()) return;\n\n const unsubscribe = this.sourceUnsubscribe;\n this.sourceUnsubscribe = undefined;\n unsubscribe?.();\n }\n\n private getLookup(key: Key): Lookup<Value> {\n const snapshot = this.readable.getSnapshot();\n\n return snapshot.byId.has(key)\n ? ((this.state.kind === 'value' && this.state.snapshot.lookups.get(key)) as Lookup<Value>)\n : missingLookup;\n }\n\n private isObserved(): boolean {\n return this.keyListeners.size > 0;\n }\n\n private project(item: Item): Value {\n const projection = this.projection;\n\n if (projection === undefined) return item as unknown as Value;\n\n if (!isWeakKey(item)) return projection(item);\n\n const cached = this.projectionCache.get(item);\n\n if (cached !== undefined) return cached.value;\n\n const value = projection(item);\n this.projectionCache.set(item, { value });\n\n return value;\n }\n\n private projectEntry(\n item: Item,\n byId: ReadonlyMap<Key, Value>,\n previous: InternalCollectionSnapshot<Key, Value> | undefined,\n ): readonly [Key, Value, Lookup<Value>] {\n const key = this.key(item);\n\n if (byId.has(key)) throw new TypeError('Collection keys must be unique.');\n\n const value = this.project(item);\n const previousLookup = previous?.lookups.get(key);\n const lookup =\n previousLookup?.kind === 'found' && Object.is(previousLookup.value, value)\n ? previousLookup\n : { kind: 'found' as const, value };\n\n return [key, value, lookup];\n }\n\n private publish(change: CollectionChange<Key>): unknown[] | undefined {\n let failures: unknown[] | undefined;\n\n if (change.allKeys) {\n for (const listeners of this.keyListeners.values()) {\n failures = mergeFailures(failures, notifyListeners(listeners));\n }\n\n return failures;\n }\n\n for (const key of change.keys) failures = mergeFailures(failures, notifyListeners(this.keyListeners.get(key)));\n\n return failures;\n }\n\n private readIndex(get: ReactiveRead): CollectionSnapshot<Key, Value> {\n const failures = this.publish(this.refresh(get));\n\n if (failures !== undefined) throw createAggregateError(failures, 'Collection index invalidation failed.');\n\n if (this.state.kind === 'error') throw this.state.error;\n\n if (this.state.kind === 'uninitialized') throw new TypeError('Collection did not produce a snapshot.');\n\n return this.state.snapshot.value;\n }\n\n private refresh(get: ReactiveRead): CollectionChange<Key> {\n try {\n const sourceSnapshot = get(this.source);\n\n if (this.canReuseSnapshot(sourceSnapshot)) return unchangedCollection;\n\n this.hasSourceSnapshot = true;\n this.sourceSnapshot = sourceSnapshot;\n\n return this.setValue(this.buildSnapshot(sourceSnapshot));\n } catch (error) {\n return this.setError(error);\n }\n }\n\n private removeKeyListener(key: Key, registration: ListenerRegistration): void {\n const listeners = this.keyListeners.get(key);\n listeners?.delete(registration);\n\n if (listeners?.size === 0) this.keyListeners.delete(key);\n }\n\n private scheduleSelectionRelease(key: Key): void {\n const selected = this.selections.get(key);\n\n if (selected === undefined) return;\n\n queueMicrotask(() => {\n if (this.selections.get(key) === selected && !isReactiveObserved(selected)) this.selections.delete(key);\n });\n }\n\n private setError(error: unknown): CollectionChange<Key> {\n const previous = this.state;\n this.sourceSnapshot = undefined;\n this.hasSourceSnapshot = false;\n this.state = { error, kind: 'error' };\n\n return previous.kind === 'error' && Object.is(previous.error, error) ? unchangedCollection : allCollectionChanged;\n }\n\n private setValue(next: InternalCollectionSnapshot<Key, Value>): CollectionChange<Key> {\n const previous = this.state;\n\n if (previous.kind === 'value' && snapshotsEqual(previous.snapshot, next)) return unchangedCollection;\n\n this.state = { kind: 'value', snapshot: next };\n\n return previous.kind === 'value'\n ? { allKeys: false, keys: this.changedKeys(previous.snapshot, next) }\n : allCollectionChanged;\n }\n\n private subscribeKey(key: Key, listener: ReadableListener): () => void {\n const registration = { listener };\n const listeners = this.keyListeners.get(key) ?? new Set<ListenerRegistration>();\n const wasObserved = this.isObserved();\n listeners.add(registration);\n this.keyListeners.set(key, listeners);\n\n try {\n if (!wasObserved) this.connect();\n } catch (error) {\n this.removeKeyListener(key, registration);\n throw error;\n }\n\n return once(() => {\n this.removeKeyListener(key, registration);\n this.disconnect();\n this.scheduleSelectionRelease(key);\n });\n }\n}\n\nfunction collection<Item, Key, Value = Item>(options: CollectionOptions<Item, Key, Value>): Collection<Key, Value> {\n const hub = new CollectionHub(options);\n const byId = derive({ from: hub.readable, select: value => value.byId });\n const list = derive({ from: hub.readable, select: value => value.list });\n const readable = Object.freeze({\n byId,\n getSnapshot: hub.readable.getSnapshot,\n list,\n subscribe: hub.readable.subscribe,\n });\n collectionHubs.set(readable, hub as CollectionHub<unknown, unknown, unknown>);\n registerReactiveAlias(readable, hub.readable);\n\n return readable;\n}\n\nfunction selectByKey<Key, Value>(source: Collection<Key, Value>, key: Key): Readable<Lookup<Value>> {\n const hub = collectionHubs.get(source) as CollectionHub<unknown, Key, Value> | undefined;\n\n if (hub === undefined) throw new TypeError('selectByKey requires a collection created by collection().');\n\n return hub.select(key);\n}\n\nexport { collection, selectByKey };\nexport type { Collection, CollectionOptions, CollectionSnapshot, Lookup, ProjectedCollectionOptions };\n"],"names":["missingLookup","noKeys","collectionHubs","unchangedCollection","allCollectionChanged","mergeFailures","first","second","failures","isWeakKey","value","listsEqual","previous","next","index","mapsEqual","key","notifyListeners","listeners","listener","error","once","release","active","snapshotsEqual","CollectionHub","options","reactiveDerived","get","defaultEquality","cached","selected","reactiveKeyed","selection","items","byId","list","lookups","item","lookup","observers","releaseRevision","scheduleRelease","revision","isReactiveObserved","_a","readable","unsubscribe","subscribed","registerReactiveAlias","sourceSnapshot","changed","projection","previousLookup","change","createAggregateError","registration","wasObserved","collection","hub","derive","selectByKey","source"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { ReactiveScheduler } from './reactive-scheduler-control.js';
2
+ import type { Readable } from './readable.js';
3
+ type Equality<Value> = (previous: Value, next: Value) => boolean;
4
+ type NodeState<Value> = {
5
+ readonly error: unknown;
6
+ readonly kind: 'error';
7
+ readonly previous: typeof noPreviousValue | Value;
8
+ } | {
9
+ readonly kind: 'uninitialized';
10
+ } | {
11
+ readonly kind: 'value';
12
+ readonly value: Value;
13
+ };
14
+ type Freshness = 'check' | 'clean' | 'dirty';
15
+ declare const noPreviousValue: unique symbol;
16
+ declare const reactiveNodes: WeakMap<object, ReactiveNode<unknown>>;
17
+ declare const scheduler: ReactiveScheduler;
18
+ declare abstract class ReactiveNode<Value> {
19
+ readonly readable: Readable<Value>;
20
+ revision: number;
21
+ protected freshness: Freshness;
22
+ protected state: NodeState<Value>;
23
+ private active;
24
+ private deactivationRevision;
25
+ private readonly dependents;
26
+ private readonly listeners;
27
+ constructor();
28
+ activateNow(): void;
29
+ addDependent(dependent: ReactiveNode<unknown>): void;
30
+ getSnapshot(): Value;
31
+ invalidate(checkOnly?: boolean): void;
32
+ invalidateNow(checkOnly: boolean): boolean;
33
+ isActive(): boolean;
34
+ isObserved(): boolean;
35
+ publish(): unknown[] | undefined;
36
+ readDependencySnapshot(): Value;
37
+ removeDependent(dependent: ReactiveNode<unknown>): void;
38
+ /** The synchronous twin of the scheduled deactivation: same law, one implementation. */
39
+ rollbackActivationNow(): void;
40
+ schedulingDependencies(): Iterable<ReactiveNode<unknown>>;
41
+ schedulingDependents(): Iterable<ReactiveNode<unknown>>;
42
+ stabilizeNow(force: boolean): void;
43
+ protected abstract activateNode(): void;
44
+ protected commitError(error: unknown): void;
45
+ protected commitValue(value: Value, equals: Equality<Value>): void;
46
+ protected abstract deactivateNode(): void;
47
+ protected preparePull(): void;
48
+ protected abstract recompute(): void;
49
+ private commitChange;
50
+ private commitEqualValue;
51
+ private compareValues;
52
+ private deactivateIfUnobserved;
53
+ private isInvalidationRedundant;
54
+ private previousValue;
55
+ private readSettledSnapshot;
56
+ private recomputeUntilStable;
57
+ private resume;
58
+ private scheduleDeactivation;
59
+ private subscribe;
60
+ }
61
+ export { ReactiveNode, reactiveNodes, scheduler };
62
+ export type { Equality };
@@ -0,0 +1,2 @@
1
+ import{ReadableError as d}from"./errors.js";import{ReactiveScheduler as c}from"./reactive-scheduler-control.js";const h=Symbol("opetope.reactive-comparison-failed"),a=Symbol("opetope.reactive-no-previous-value"),o=new WeakMap,s=new c;class u{readable;revision=0;freshness="dirty";state={kind:"uninitialized"};active=!1;deactivationRevision=0;dependents=new Set;listeners=new Set;constructor(){const e=Object.freeze({getSnapshot:()=>this.getSnapshot(),subscribe:t=>this.subscribe(t)});this.readable=e,o.set(e,this)}activateNow(){this.deactivationRevision+=1,this.active=!0;try{this.activateNode()}catch(e){throw this.active=!1,e}}addDependent(e){const t=this.isObserved();this.dependents.add(e);try{t||this.resume()}catch(i){throw this.dependents.delete(e),i}}getSnapshot(){return this.freshness==="clean"&&(this.active||s.isPrepared(this))?this.readSettledSnapshot():(s.stabilize(this,!this.active),this.readSettledSnapshot())}invalidate(e=!1){s.invalidate(this,e)}invalidateNow(e){return this.isInvalidationRedundant(e)?!1:(this.freshness=e?"check":"dirty",this.active&&s.pending(this),!0)}isActive(){return this.active}isObserved(){return this.dependents.size>0||this.listeners.size>0}publish(){if(this.listeners.size===0)return;let e;for(const{listener:t}of[...this.listeners])try{t()}catch(i){(e??(e=[])).push(i)}return e}readDependencySnapshot(){return this.freshness!=="clean"||!this.active&&!s.isPrepared(this)?this.getSnapshot():this.readSettledSnapshot()}removeDependent(e){this.dependents.delete(e),this.scheduleDeactivation()}rollbackActivationNow(){this.deactivateIfUnobserved()}schedulingDependencies(){return[]}schedulingDependents(){return this.dependents}stabilizeNow(e){e&&this.preparePull(),this.recomputeUntilStable()}commitError(e){const t=this.state;this.state={error:e,kind:"error",previous:e instanceof d?a:this.previousValue()},this.freshness="clean",!(t.kind==="error"&&Object.is(t.error,e))&&this.commitChange(t.kind!=="uninitialized")}commitValue(e,t){const i=this.state,r=this.previousValue();if(r!==a){const n=this.compareValues(r,e,t);if(n===h)return;if(n){this.commitEqualValue(i,r);return}}this.state={kind:"value",value:e},this.freshness="clean",this.commitChange(i.kind!=="uninitialized")}preparePull(){this.freshness="dirty"}commitChange(e){this.revision+=1,s.invalidateDependents(this,!1),this.active&&e&&s.changed(this)}commitEqualValue(e,t){this.freshness="clean",e.kind==="error"&&(this.state={kind:"value",value:t},this.commitChange(!0))}compareValues(e,t,i){try{return i(e,t)}catch(r){return this.commitError(r),h}}deactivateIfUnobserved(){!this.active||this.isObserved()||(this.active=!1,this.deactivateNode())}isInvalidationRedundant(e){return this.freshness==="dirty"||e&&this.freshness==="check"}previousValue(){return this.state.kind==="value"?this.state.value:this.state.kind==="error"?this.state.previous:a}readSettledSnapshot(){if(this.state.kind==="error")throw this.state.error;if(this.state.kind==="uninitialized")throw new TypeError("Reactive node did not produce a snapshot.");return this.state.value}recomputeUntilStable(){let e=0;for(;this.freshness!=="clean"&&e<100;)e+=1,this.recompute();this.freshness!=="clean"&&s.pending(this)}resume(){if(this.active){this.deactivationRevision+=1;return}s.activate(this)}scheduleDeactivation(){if(!this.active||this.isObserved())return;const e=++this.deactivationRevision;queueMicrotask(()=>{e===this.deactivationRevision&&this.deactivateIfUnobserved()})}subscribe(e){const t={listener:e},i=this.isObserved();this.listeners.add(t);try{i||this.resume()}catch(n){throw this.listeners.delete(t),n}let r=!0;return()=>{r&&(r=!1,this.listeners.delete(t),this.scheduleDeactivation())}}}export{u as ReactiveNode,o as reactiveNodes,s as scheduler};
2
+ //# sourceMappingURL=reactive-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive-node.js","sources":["../src/reactive-node.ts"],"sourcesContent":["import { ReadableError } from './errors';\nimport { ReactiveScheduler } from './reactive-scheduler-control';\nimport type { Readable, ReadableListener } from './readable';\n\ntype Equality<Value> = (previous: Value, next: Value) => boolean;\n\ntype NodeState<Value> =\n | { readonly error: unknown; readonly kind: 'error'; readonly previous: typeof noPreviousValue | Value }\n | { readonly kind: 'uninitialized' }\n | { readonly kind: 'value'; readonly value: Value };\n\ntype Freshness = 'check' | 'clean' | 'dirty';\ntype ListenerRegistration = { readonly listener: ReadableListener };\n\nconst comparisonFailed = Symbol('opetope.reactive-comparison-failed');\nconst noPreviousValue = Symbol('opetope.reactive-no-previous-value');\nconst reactiveNodes = new WeakMap<object, ReactiveNode<unknown>>();\n\nconst scheduler = new ReactiveScheduler();\n\nabstract class ReactiveNode<Value> {\n readonly readable: Readable<Value>;\n revision = 0;\n protected freshness: Freshness = 'dirty';\n protected state: NodeState<Value> = { kind: 'uninitialized' };\n private active = false;\n private deactivationRevision = 0;\n private readonly dependents = new Set<ReactiveNode<unknown>>();\n private readonly listeners = new Set<ListenerRegistration>();\n\n constructor() {\n const readable = Object.freeze({\n getSnapshot: (): Value => this.getSnapshot(),\n subscribe: (listener: ReadableListener): (() => void) => this.subscribe(listener),\n });\n\n this.readable = readable;\n reactiveNodes.set(readable, this);\n }\n\n activateNow(): void {\n this.deactivationRevision += 1;\n this.active = true;\n\n try {\n this.activateNode();\n } catch (error) {\n this.active = false;\n throw error;\n }\n }\n\n addDependent(dependent: ReactiveNode<unknown>): void {\n const wasObserved = this.isObserved();\n this.dependents.add(dependent);\n\n try {\n if (!wasObserved) this.resume();\n } catch (error) {\n this.dependents.delete(dependent);\n throw error;\n }\n }\n\n getSnapshot(): Value {\n // An active clean node has nothing to stabilize: everything upstream published into it, and an invalidation\n // would have marked it. Walking the dependency graph on such a read would make every read O(graph).\n if (this.freshness === 'clean' && (this.active || scheduler.isPrepared(this))) return this.readSettledSnapshot();\n\n scheduler.stabilize(this, !this.active);\n\n return this.readSettledSnapshot();\n }\n\n invalidate(checkOnly = false): void {\n scheduler.invalidate(this, checkOnly);\n }\n\n invalidateNow(checkOnly: boolean): boolean {\n if (this.isInvalidationRedundant(checkOnly)) return false;\n\n this.freshness = checkOnly ? 'check' : 'dirty';\n\n if (this.active) scheduler.pending(this);\n\n return true;\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n isObserved(): boolean {\n return this.dependents.size > 0 || this.listeners.size > 0;\n }\n\n publish(): unknown[] | undefined {\n if (this.listeners.size === 0) return undefined;\n\n let failures: unknown[] | undefined;\n\n for (const { listener } of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n (failures ??= []).push(error);\n }\n }\n\n return failures;\n }\n\n readDependencySnapshot(): Value {\n if (this.freshness !== 'clean' || (!this.active && !scheduler.isPrepared(this))) return this.getSnapshot();\n\n return this.readSettledSnapshot();\n }\n\n removeDependent(dependent: ReactiveNode<unknown>): void {\n this.dependents.delete(dependent);\n this.scheduleDeactivation();\n }\n\n /** The synchronous twin of the scheduled deactivation: same law, one implementation. */\n rollbackActivationNow(): void {\n this.deactivateIfUnobserved();\n }\n\n schedulingDependencies(): Iterable<ReactiveNode<unknown>> {\n return [];\n }\n\n schedulingDependents(): Iterable<ReactiveNode<unknown>> {\n return this.dependents;\n }\n\n stabilizeNow(force: boolean): void {\n if (force) this.preparePull();\n\n this.recomputeUntilStable();\n }\n protected abstract activateNode(): void;\n protected commitError(error: unknown): void {\n const previous = this.state;\n this.state = {\n error,\n kind: 'error',\n previous: error instanceof ReadableError ? noPreviousValue : this.previousValue(),\n };\n this.freshness = 'clean';\n\n if (previous.kind === 'error' && Object.is(previous.error, error)) return;\n\n this.commitChange(previous.kind !== 'uninitialized');\n }\n protected commitValue(value: Value, equals: Equality<Value>): void {\n const previous = this.state;\n const previousValue = this.previousValue();\n\n if (previousValue !== noPreviousValue) {\n const equal = this.compareValues(previousValue, value, equals);\n\n if (equal === comparisonFailed) return;\n\n if (equal) {\n this.commitEqualValue(previous, previousValue);\n\n return;\n }\n }\n\n this.state = { kind: 'value', value };\n this.freshness = 'clean';\n this.commitChange(previous.kind !== 'uninitialized');\n }\n\n protected abstract deactivateNode(): void;\n\n protected preparePull(): void {\n this.freshness = 'dirty';\n }\n\n protected abstract recompute(): void;\n\n private commitChange(notifyListeners: boolean): void {\n this.revision += 1;\n scheduler.invalidateDependents(this, false);\n\n if (this.active && notifyListeners) scheduler.changed(this);\n }\n\n private commitEqualValue(previous: NodeState<Value>, value: Value): void {\n this.freshness = 'clean';\n\n if (previous.kind !== 'error') return;\n\n this.state = { kind: 'value', value };\n this.commitChange(true);\n }\n\n private compareValues(previous: Value, next: Value, equals: Equality<Value>): typeof comparisonFailed | boolean {\n try {\n return equals(previous, next);\n } catch (error) {\n this.commitError(error);\n\n return comparisonFailed;\n }\n }\n\n private deactivateIfUnobserved(): void {\n if (!this.active || this.isObserved()) return;\n\n this.active = false;\n this.deactivateNode();\n }\n\n private isInvalidationRedundant(checkOnly: boolean): boolean {\n return this.freshness === 'dirty' || (checkOnly && this.freshness === 'check');\n }\n\n private previousValue(): typeof noPreviousValue | Value {\n if (this.state.kind === 'value') return this.state.value;\n\n if (this.state.kind === 'error') return this.state.previous;\n\n return noPreviousValue;\n }\n\n private readSettledSnapshot(): Value {\n if (this.state.kind === 'error') throw this.state.error;\n\n if (this.state.kind === 'uninitialized') throw new TypeError('Reactive node did not produce a snapshot.');\n\n return this.state.value;\n }\n\n private recomputeUntilStable(): void {\n let attempts = 0;\n\n while (this.freshness !== 'clean' && attempts < 100) {\n attempts += 1;\n this.recompute();\n }\n\n if (this.freshness !== 'clean') scheduler.pending(this);\n }\n\n private resume(): void {\n if (this.active) {\n this.deactivationRevision += 1;\n\n return;\n }\n\n scheduler.activate(this);\n }\n\n private scheduleDeactivation(): void {\n if (!this.active || this.isObserved()) return;\n\n const revision = ++this.deactivationRevision;\n\n queueMicrotask(() => {\n // `activateNow` bumps the revision, so a node that came back to life cancels this pass.\n if (revision === this.deactivationRevision) this.deactivateIfUnobserved();\n });\n }\n\n private subscribe(listener: ReadableListener): () => void {\n const registration = { listener };\n const wasObserved = this.isObserved();\n this.listeners.add(registration);\n\n try {\n if (!wasObserved) this.resume();\n } catch (error) {\n this.listeners.delete(registration);\n throw error;\n }\n\n let subscribed = true;\n\n return (): void => {\n if (!subscribed) return;\n\n subscribed = false;\n this.listeners.delete(registration);\n this.scheduleDeactivation();\n };\n }\n}\n\nexport { ReactiveNode, reactiveNodes, scheduler };\nexport type { Equality };\n"],"names":["comparisonFailed","noPreviousValue","reactiveNodes","scheduler","ReactiveScheduler","ReactiveNode","readable","listener","error","dependent","wasObserved","checkOnly","failures","force","previous","ReadableError","value","equals","previousValue","equal","notifyListeners","next","attempts","revision","registration","subscribed"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ interface ScheduledReactiveNode {
2
+ activateNow(): void;
3
+ invalidateNow(checkOnly: boolean): boolean;
4
+ isActive(): boolean;
5
+ publish(): unknown[] | undefined;
6
+ rollbackActivationNow(): void;
7
+ schedulingDependencies(): Iterable<ScheduledReactiveNode>;
8
+ schedulingDependents(): Iterable<ScheduledReactiveNode>;
9
+ stabilizeNow(force: boolean): void;
10
+ }
11
+ declare class ReactiveScheduler {
12
+ private readonly changedNodes;
13
+ private readonly pendingNodes;
14
+ private readonly preparedNodes;
15
+ private stabilizing;
16
+ activate(node: ScheduledReactiveNode): void;
17
+ changed(node: ScheduledReactiveNode): void;
18
+ invalidate(node: ScheduledReactiveNode, checkOnly: boolean): void;
19
+ invalidateDependents(node: ScheduledReactiveNode, checkOnly: boolean): void;
20
+ isPrepared(node: ScheduledReactiveNode): boolean;
21
+ pending(node: ScheduledReactiveNode): void;
22
+ rollbackDependencies(nodes: readonly ScheduledReactiveNode[], cause: unknown): never;
23
+ stabilize(node: ScheduledReactiveNode, force: boolean): void;
24
+ private flush;
25
+ private readonly flushCall;
26
+ private invalidateNodes;
27
+ private orderDependencies;
28
+ private publishChanged;
29
+ private rollbackActivation;
30
+ private schedule;
31
+ private stabilizeOrdered;
32
+ private stabilizePending;
33
+ }
34
+ export { ReactiveScheduler };
@@ -0,0 +1,2 @@
1
+ import{scheduleDataWork as r}from"./data-scheduler.js";import{createAggregateError as d}from"./platform-compat.js";class h{changedNodes=new Set;pendingNodes=new Set;preparedNodes=new Set;stabilizing=0;activate(e){let n;try{for(const i of this.orderDependencies([e]))i.isActive()||(i.activateNow(),(n??(n=[])).push(i))}catch(i){this.rollbackActivation(n??[],i)}}changed(e){this.changedNodes.add(e),this.schedule()}invalidate(e,n){this.invalidateNodes([{checkOnly:n,node:e}])}invalidateDependents(e,n){this.invalidateNodes([...e.schedulingDependents()].map(i=>({checkOnly:n,node:i})))}isPrepared(e){return this.preparedNodes.has(e)}pending(e){this.pendingNodes.add(e),this.schedule()}rollbackDependencies(e,n){this.rollbackActivation(this.orderDependencies(e),n)}stabilize(e,n){this.stabilizeOrdered(this.orderDependencies([e]),i=>i===e?n:!i.isActive())}flush(){this.stabilizePending();const e=this.publishChanged();if((this.pendingNodes.size>0||this.changedNodes.size>0)&&this.schedule(),e!==void 0)throw d(e,"Reactive listeners failed after graph stabilization.")}flushCall=()=>this.flush();invalidateNodes(e){const n=[...e];for(;n.length>0;){const i=n.pop();if(i.node.invalidateNow(i.checkOnly))for(const s of i.node.schedulingDependents())n.push({checkOnly:!0,node:s})}}orderDependencies(e){const n=[],i=new Set,s=e.map(t=>({expanded:!1,node:t}));for(;s.length>0;){const t=s.pop();if(t.expanded){n.push(t.node);continue}if(i.has(t.node))continue;i.add(t.node),s.push({expanded:!0,node:t.node});const o=[...t.node.schedulingDependencies()];for(const a of o.reverse())s.push({expanded:!1,node:a})}return n}publishChanged(){const e=[...this.changedNodes];this.changedNodes.clear();let n;for(const i of e){const s=i.publish();s!==void 0&&(n??(n=[])).push(...s)}return n}rollbackActivation(e,n){let i;for(let s=e.length-1;s>=0;s-=1)try{e[s].rollbackActivationNow()}catch(t){(i??(i=[])).push(t)}throw i!==void 0?d(i,"Reactive activation rollback failed.",{cause:n}):n}schedule(){r(this.flushCall)}stabilizeOrdered(e,n){this.stabilizing+=1;try{for(const i of e)i.stabilizeNow(n(i)),this.preparedNodes.add(i)}finally{this.stabilizing-=1,this.stabilizing===0&&this.preparedNodes.clear()}}stabilizePending(){for(;this.pendingNodes.size>0;){const e=[...this.pendingNodes];this.pendingNodes.clear(),this.stabilizeOrdered(this.orderDependencies(e),()=>!1)}}}export{h as ReactiveScheduler};
2
+ //# sourceMappingURL=reactive-scheduler-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive-scheduler-control.js","sources":["../src/reactive-scheduler-control.ts"],"sourcesContent":["import { scheduleDataWork } from './data-scheduler';\nimport { createAggregateError } from './platform-compat';\n\ninterface ScheduledReactiveNode {\n activateNow(): void;\n invalidateNow(checkOnly: boolean): boolean;\n isActive(): boolean;\n publish(): unknown[] | undefined;\n rollbackActivationNow(): void;\n schedulingDependencies(): Iterable<ScheduledReactiveNode>;\n schedulingDependents(): Iterable<ScheduledReactiveNode>;\n stabilizeNow(force: boolean): void;\n}\n\nclass ReactiveScheduler {\n private readonly changedNodes = new Set<ScheduledReactiveNode>();\n private readonly pendingNodes = new Set<ScheduledReactiveNode>();\n private readonly preparedNodes = new Set<ScheduledReactiveNode>();\n private stabilizing = 0;\n\n activate(node: ScheduledReactiveNode): void {\n // Activating an already-active graph is the common case, so the rollback list is born only when something\n // actually changed state.\n let activated: ScheduledReactiveNode[] | undefined;\n\n try {\n for (const dependency of this.orderDependencies([node])) {\n if (dependency.isActive()) continue;\n\n dependency.activateNow();\n (activated ??= []).push(dependency);\n }\n } catch (error) {\n this.rollbackActivation(activated ?? [], error);\n }\n }\n\n changed(node: ScheduledReactiveNode): void {\n this.changedNodes.add(node);\n this.schedule();\n }\n\n invalidate(node: ScheduledReactiveNode, checkOnly: boolean): void {\n this.invalidateNodes([{ checkOnly, node }]);\n }\n\n invalidateDependents(node: ScheduledReactiveNode, checkOnly: boolean): void {\n this.invalidateNodes([...node.schedulingDependents()].map(dependent => ({ checkOnly, node: dependent })));\n }\n\n isPrepared(node: ScheduledReactiveNode): boolean {\n return this.preparedNodes.has(node);\n }\n\n pending(node: ScheduledReactiveNode): void {\n this.pendingNodes.add(node);\n this.schedule();\n }\n\n rollbackDependencies(nodes: readonly ScheduledReactiveNode[], cause: unknown): never {\n this.rollbackActivation(this.orderDependencies(nodes), cause);\n }\n\n stabilize(node: ScheduledReactiveNode, force: boolean): void {\n this.stabilizeOrdered(this.orderDependencies([node]), dependency =>\n dependency === node ? force : !dependency.isActive(),\n );\n }\n\n private flush(): void {\n this.stabilizePending();\n const failures = this.publishChanged();\n\n if (this.pendingNodes.size > 0 || this.changedNodes.size > 0) this.schedule();\n\n if (failures !== undefined) {\n throw createAggregateError(failures, 'Reactive listeners failed after graph stabilization.');\n }\n }\n\n private readonly flushCall = (): void => this.flush();\n\n private invalidateNodes(\n initial: readonly { readonly checkOnly: boolean; readonly node: ScheduledReactiveNode }[],\n ): void {\n const pending = [...initial];\n\n while (pending.length > 0) {\n const current = pending.pop()!;\n\n if (!current.node.invalidateNow(current.checkOnly)) continue;\n\n for (const dependent of current.node.schedulingDependents()) {\n pending.push({ checkOnly: true, node: dependent });\n }\n }\n }\n\n private orderDependencies(roots: readonly ScheduledReactiveNode[]): readonly ScheduledReactiveNode[] {\n const ordered: ScheduledReactiveNode[] = [];\n const visited = new Set<ScheduledReactiveNode>();\n const pending = roots.map(node => ({ expanded: false, node }));\n\n while (pending.length > 0) {\n const current = pending.pop()!;\n\n if (current.expanded) {\n ordered.push(current.node);\n\n continue;\n }\n\n if (visited.has(current.node)) continue;\n\n visited.add(current.node);\n pending.push({ expanded: true, node: current.node });\n\n const dependencies = [...current.node.schedulingDependencies()];\n\n for (const dependency of dependencies.reverse()) pending.push({ expanded: false, node: dependency });\n }\n\n return ordered;\n }\n\n private publishChanged(): unknown[] | undefined {\n const changed = [...this.changedNodes];\n this.changedNodes.clear();\n let failures: unknown[] | undefined;\n\n for (const node of changed) {\n const nodeFailures = node.publish();\n\n if (nodeFailures !== undefined) (failures ??= []).push(...nodeFailures);\n }\n\n return failures;\n }\n\n private rollbackActivation(activated: readonly ScheduledReactiveNode[], cause: unknown): never {\n let failures: unknown[] | undefined;\n\n for (let index = activated.length - 1; index >= 0; index -= 1) {\n try {\n activated[index]!.rollbackActivationNow();\n } catch (error) {\n (failures ??= []).push(error);\n }\n }\n\n if (failures !== undefined) {\n throw createAggregateError(failures, 'Reactive activation rollback failed.', { cause });\n }\n\n throw cause;\n }\n\n private schedule(): void {\n scheduleDataWork(this.flushCall);\n }\n\n private stabilizeOrdered(\n nodes: readonly ScheduledReactiveNode[],\n force: (node: ScheduledReactiveNode) => boolean,\n ): void {\n this.stabilizing += 1;\n\n try {\n for (const node of nodes) {\n node.stabilizeNow(force(node));\n this.preparedNodes.add(node);\n }\n } finally {\n this.stabilizing -= 1;\n\n if (this.stabilizing === 0) this.preparedNodes.clear();\n }\n }\n\n private stabilizePending(): void {\n while (this.pendingNodes.size > 0) {\n const nodes = [...this.pendingNodes];\n this.pendingNodes.clear();\n\n this.stabilizeOrdered(this.orderDependencies(nodes), () => false);\n }\n }\n}\n\nexport { ReactiveScheduler };\n"],"names":["ReactiveScheduler","node","activated","dependency","error","checkOnly","dependent","nodes","cause","force","failures","createAggregateError","initial","pending","current","roots","ordered","visited","dependencies","changed","nodeFailures","index","scheduleDataWork"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Equality } from './reactive-node.js';
2
+ import type { Readable, ReadableListener } from './readable.js';
3
+ type ReactiveRead = <Value>(readable: Readable<Value>) => Value;
4
+ interface ExternalReadableSource<Value> {
5
+ readonly getSnapshot: () => Value;
6
+ readonly subscribe: (listener: ReadableListener) => () => void;
7
+ }
8
+ declare const defaultEquality: <Value>(previous: Value, next: Value) => boolean;
9
+ declare function reactiveComputed<Value>(evaluate: (read: ReactiveRead) => Value, equals: Equality<Value>): Readable<Value>;
10
+ declare function reactiveDerived<Value>(dependencies: readonly Readable<unknown>[], evaluate: (read: ReactiveRead) => Value, equals: Equality<Value>): Readable<Value>;
11
+ declare function reactiveExternal<Source, Value>(source: ExternalReadableSource<Source>, select: (source: Source) => Value, equals: Equality<Value>): Readable<Value>;
12
+ /** A keyed source is invalidated selectively by its index, but must always stabilize after that index. */
13
+ declare function reactiveKeyed<Value>(source: ExternalReadableSource<Value>, index: Readable<unknown>): Readable<Value>;
14
+ /** Facades keep their underlying graph identity instead of introducing a late subscription boundary. */
15
+ declare function registerReactiveAlias(alias: object, readable: Readable<unknown>): void;
16
+ declare function isReactiveObserved(readable: Readable<unknown>): boolean;
17
+ export { defaultEquality, isReactiveObserved, reactiveComputed, reactiveDerived, reactiveExternal, reactiveKeyed, registerReactiveAlias, };
18
+ export type { Equality, ExternalReadableSource, ReactiveRead };
@@ -0,0 +1,2 @@
1
+ import{endDataTransaction as p,beginDataTransaction as v}from"./data-scheduler.js";import{createAggregateError as y}from"./platform-compat.js";import{reactiveNodes as o,ReactiveNode as d,scheduler as u}from"./reactive-node.js";const g=100,a=(s,e)=>Object.is(s,e),l=s=>s,m=()=>{};class h extends d{orderingDependencies;authoritativeSignal=!1;equals;hasSourceSnapshot=!1;select;selectorWrites=0;source;sourceRevision=0;sourceSnapshot;unsubscribe;constructor(e,t,i,n=[]){super(),this.orderingDependencies=n,this.equals=i,this.select=t,this.source=e}schedulingDependencies(){return this.orderingDependencies}activateNode(){const e=this.source.subscribe(()=>this.sourceChanged());if(this.unsubscribe=e,this.authoritativeSignal=!0,this.invalidate(),this.stabilizeNow(!1),this.state.kind==="error"){const t=this.state.error;this.unsubscribe=void 0;try{e()}catch(i){throw y([i],"Reactive connection rollback failed.",{cause:t})}throw t}}deactivateNode(){const e=this.unsubscribe??m;this.unsubscribe=void 0,e()}recompute(){const e=this.sourceRevision;try{const t=this.source.getSnapshot();if(this.canReuseProjection(t)){this.authoritativeSignal=!1,this.selectorWrites=0,this.freshness="clean";return}const i=this.select(t);if(this.sourceRevision!==e){this.restartAfterSelectorWrite();return}if(this.authoritativeSignal=!1,this.hasSourceSnapshot=!0,this.sourceSnapshot=t,this.commitValue(i,this.equals),this.sourceRevision!==e){this.restartAfterSelectorWrite();return}this.selectorWrites=0}catch(t){this.authoritativeSignal=!1,this.selectorWrites=0,this.hasSourceSnapshot=!1,this.sourceSnapshot=void 0,this.commitError(t)}}canReuseProjection(e){return!this.authoritativeSignal&&this.hasSourceSnapshot&&this.state.kind==="value"&&Object.is(this.sourceSnapshot,e)}restartAfterSelectorWrite(){if(this.selectorWrites+=1,this.selectorWrites>g){this.authoritativeSignal=!1,this.selectorWrites=0,this.hasSourceSnapshot=!1,this.sourceSnapshot=void 0,this.commitError(new TypeError("External readable selector keeps writing to its own source."));return}this.freshness="dirty",u.pending(this)}sourceChanged(){if(this.sourceRevision+=1,this.orderingDependencies.length>0){this.authoritativeSignal=!0,this.invalidate();return}v();try{this.authoritativeSignal=!0,this.invalidate()}finally{p()}}}function r(s){const e=o.get(s);if(e!==void 0)return e;const t=new h(s,l,a);return o.set(s,t),t}class f extends d{activating=!1;dependencies=new Map;equals;evaluate;evaluating=!1;scratchDependencies=new Map;constructor(e,t,i=[]){super(),this.equals=t,this.evaluate=e;for(const n of i){const c=r(n);this.dependencies.set(c,c.revision)}}schedulingDependencies(){return this.dependencies.keys()}activateNode(){this.activating=!0;try{(this.state.kind==="uninitialized"||this.freshness==="dirty")&&this.recompute();for(const e of this.dependencies.keys())e.addDependent(this);this.freshness!=="dirty"&&(this.freshness="check"),this.stabilizeNow(!1)}catch(e){throw this.rollbackActivationDependencies(),this.freshness="dirty",e}finally{this.activating=!1}}deactivateNode(){for(const e of this.dependencies.keys())e.removeDependent(this)}preparePull(){this.freshness!=="dirty"&&(this.freshness=this.state.kind==="uninitialized"?"dirty":"check")}recompute(){if(this.freshness==="check"&&!this.dependenciesChanged()){this.freshness="clean";return}if(this.evaluating){this.commitError(new TypeError("Reactive graph contains a computation cycle."));return}this.evaluating=!0;const e=this.scratchDependencies;e.clear();try{this.commitValue(this.evaluate(this.read),this.equals)}catch(t){this.commitError(t)}finally{this.evaluating=!1}try{this.replaceDependencies(e)}catch(t){if(this.commitError(t),this.activating)throw t}}connectNewDependencies(e){let t;try{for(const i of e.keys())this.dependencies.has(i)||(i.addDependent(this),(t??(t=[])).push(i))}catch(i){const n=t??[];for(const c of n)c.removeDependent(this);u.rollbackDependencies(n,i)}}dependenciesChanged(){for(const[e,t]of this.dependencies)if(e.revision!==t)return!0;return!1}disconnectOldDependencies(e){for(const t of this.dependencies.keys())e.has(t)||t.removeDependent(this)}read=e=>{const t=r(e);try{return t.readDependencySnapshot()}finally{this.scratchDependencies.set(t,t.revision)}};replaceDependencies(e){this.isActive()&&this.connectNewDependencies(e),this.isActive()&&this.disconnectOldDependencies(e),this.dependencies.clear();for(const[t,i]of e)this.dependencies.set(t,i);this.isActive()&&this.dependenciesChanged()&&(this.freshness="dirty")}rollbackActivationDependencies(){for(const e of this.dependencies.keys())e.removeDependent(this)}}function b(s,e){return new f(s,e).readable}function S(s,e,t){return new f(e,t,s).readable}function D(s,e,t){return new h(s,e,t).readable}function w(s,e){return new h(s,l,a,[r(e)]).readable}function k(s,e){o.set(s,r(e))}function R(s){return r(s).isObserved()}export{a as defaultEquality,R as isReactiveObserved,b as reactiveComputed,S as reactiveDerived,D as reactiveExternal,w as reactiveKeyed,k as registerReactiveAlias};
2
+ //# sourceMappingURL=reactive-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive-scheduler.js","sources":["../src/reactive-scheduler.ts"],"sourcesContent":["import { beginDataTransaction, endDataTransaction } from './data-scheduler';\nimport { createAggregateError } from './platform-compat';\nimport { ReactiveNode, reactiveNodes, scheduler } from './reactive-node';\nimport type { Equality } from './reactive-node';\nimport type { Readable, ReadableListener } from './readable';\n\ntype ReactiveRead = <Value>(readable: Readable<Value>) => Value;\n\ninterface ExternalReadableSource<Value> {\n readonly getSnapshot: () => Value;\n readonly subscribe: (listener: ReadableListener) => () => void;\n}\n\n/**\n * How many times one recompute may restart because its own selector wrote to the source it reads. A selector that\n * writes on every call would otherwise spin inside stabilization, so the node fails loud instead of hanging (D130).\n */\nconst maxSelectorWrites = 100;\nconst defaultEquality = <Value>(previous: Value, next: Value): boolean => Object.is(previous, next);\nconst identity = <Value>(value: Value): Value => value;\nconst noop = (): void => undefined;\n\nclass ExternalNode<Source, Value> extends ReactiveNode<Value> {\n private authoritativeSignal = false;\n private readonly equals: Equality<Value>;\n private hasSourceSnapshot = false;\n private readonly select: (source: Source) => Value;\n /** Consecutive restarts of this recompute caused by the selector's own writes. */\n private selectorWrites = 0;\n private readonly source: ExternalReadableSource<Source>;\n /** Every signal this source sent, counted even while the node is already dirty (D130). */\n private sourceRevision = 0;\n private sourceSnapshot: Source | undefined;\n private unsubscribe: (() => void) | undefined;\n\n constructor(\n source: ExternalReadableSource<Source>,\n select: (source: Source) => Value,\n equals: Equality<Value>,\n private readonly orderingDependencies: readonly ReactiveNode<unknown>[] = [],\n ) {\n super();\n this.equals = equals;\n this.select = select;\n this.source = source;\n }\n\n override schedulingDependencies(): Iterable<ReactiveNode<unknown>> {\n return this.orderingDependencies;\n }\n\n protected activateNode(): void {\n const unsubscribe = this.source.subscribe(() => this.sourceChanged());\n this.unsubscribe = unsubscribe;\n this.authoritativeSignal = true;\n this.invalidate();\n this.stabilizeNow(false);\n\n if (this.state.kind === 'error') {\n const cause = this.state.error;\n this.unsubscribe = undefined;\n\n try {\n unsubscribe();\n } catch (error) {\n throw createAggregateError([error], 'Reactive connection rollback failed.', { cause });\n }\n\n throw cause;\n }\n }\n\n protected deactivateNode(): void {\n const unsubscribe = this.unsubscribe ?? noop;\n this.unsubscribe = undefined;\n unsubscribe();\n }\n\n protected recompute(): void {\n const revision = this.sourceRevision;\n\n try {\n const sourceSnapshot = this.source.getSnapshot();\n\n if (this.canReuseProjection(sourceSnapshot)) {\n this.authoritativeSignal = false;\n this.selectorWrites = 0;\n this.freshness = 'clean';\n\n return;\n }\n\n const value = this.select(sourceSnapshot);\n\n // D130: `select` is foreign code that may write to the very source it reads. A projection of a snapshot the\n // source has already replaced never settles as clean: the node stays dirty and recomputes from the last write.\n if (this.sourceRevision !== revision) {\n this.restartAfterSelectorWrite();\n\n return;\n }\n\n this.authoritativeSignal = false;\n this.hasSourceSnapshot = true;\n this.sourceSnapshot = sourceSnapshot;\n this.commitValue(value, this.equals);\n\n // D130: `equals` is foreign code as well, and a write it makes leaves this clean value behind its source, so\n // the same restart limiter counts it. Only a recompute nobody wrote through settles the restart counter.\n if (this.sourceRevision !== revision) {\n this.restartAfterSelectorWrite();\n\n return;\n }\n\n this.selectorWrites = 0;\n } catch (error) {\n this.authoritativeSignal = false;\n this.selectorWrites = 0;\n this.hasSourceSnapshot = false;\n this.sourceSnapshot = undefined;\n this.commitError(error);\n }\n }\n\n private canReuseProjection(sourceSnapshot: Source): boolean {\n return (\n !this.authoritativeSignal &&\n this.hasSourceSnapshot &&\n this.state.kind === 'value' &&\n Object.is(this.sourceSnapshot, sourceSnapshot)\n );\n }\n\n /** A selector that writes on every call never settles, so the node stops trying and says why (D130). */\n private restartAfterSelectorWrite(): void {\n this.selectorWrites += 1;\n\n if (this.selectorWrites > maxSelectorWrites) {\n this.authoritativeSignal = false;\n this.selectorWrites = 0;\n this.hasSourceSnapshot = false;\n this.sourceSnapshot = undefined;\n this.commitError(new TypeError('External readable selector keeps writing to its own source.'));\n\n return;\n }\n\n this.freshness = 'dirty';\n scheduler.pending(this);\n }\n\n private sourceChanged(): void {\n this.sourceRevision += 1;\n\n // Keyed index signals already run inside stabilization. They invalidate only the affected keys, without\n // starting a nested transaction while the index is still evaluating.\n if (this.orderingDependencies.length > 0) {\n this.authoritativeSignal = true;\n this.invalidate();\n\n return;\n }\n\n beginDataTransaction();\n\n try {\n this.authoritativeSignal = true;\n this.invalidate();\n } finally {\n endDataTransaction();\n }\n }\n}\n\nfunction nodeFromReadable<Value>(readable: Readable<Value>): ReactiveNode<Value> {\n const existing = reactiveNodes.get(readable) as ReactiveNode<Value> | undefined;\n\n if (existing !== undefined) return existing;\n\n const node = new ExternalNode(readable, identity, defaultEquality<Value>);\n reactiveNodes.set(readable, node);\n\n return node;\n}\n\nclass FormulaNode<Value> extends ReactiveNode<Value> {\n private activating = false;\n private readonly dependencies = new Map<ReactiveNode<unknown>, number>();\n private readonly equals: Equality<Value>;\n private readonly evaluate: (read: ReactiveRead) => Value;\n private evaluating = false;\n private readonly scratchDependencies = new Map<ReactiveNode<unknown>, number>();\n\n constructor(\n evaluate: (read: ReactiveRead) => Value,\n equals: Equality<Value>,\n dependencies: readonly Readable<unknown>[] = [],\n ) {\n super();\n this.equals = equals;\n this.evaluate = evaluate;\n\n for (const dependency of dependencies) {\n const node = nodeFromReadable(dependency);\n this.dependencies.set(node, node.revision);\n }\n }\n\n override schedulingDependencies(): Iterable<ReactiveNode<unknown>> {\n return this.dependencies.keys();\n }\n\n protected activateNode(): void {\n this.activating = true;\n\n try {\n if (this.state.kind === 'uninitialized' || this.freshness === 'dirty') this.recompute();\n\n for (const dependency of this.dependencies.keys()) dependency.addDependent(this);\n\n if (this.freshness !== 'dirty') this.freshness = 'check';\n\n this.stabilizeNow(false);\n } catch (error) {\n this.rollbackActivationDependencies();\n\n this.freshness = 'dirty';\n\n throw error;\n } finally {\n this.activating = false;\n }\n }\n\n protected deactivateNode(): void {\n for (const dependency of this.dependencies.keys()) dependency.removeDependent(this);\n }\n\n protected override preparePull(): void {\n if (this.freshness !== 'dirty') {\n this.freshness = this.state.kind === 'uninitialized' ? 'dirty' : 'check';\n }\n }\n\n protected recompute(): void {\n if (this.freshness === 'check' && !this.dependenciesChanged()) {\n this.freshness = 'clean';\n\n return;\n }\n\n if (this.evaluating) {\n this.commitError(new TypeError('Reactive graph contains a computation cycle.'));\n\n return;\n }\n\n this.evaluating = true;\n // One scratch map and one `read` closure per node, not per evaluation: a recompute of a settled graph runs on\n // every update, and the hot-path rule forbids per-call allocations.\n const nextDependencies = this.scratchDependencies;\n nextDependencies.clear();\n\n try {\n this.commitValue(this.evaluate(this.read), this.equals);\n } catch (error) {\n this.commitError(error);\n } finally {\n this.evaluating = false;\n }\n\n try {\n this.replaceDependencies(nextDependencies);\n } catch (error) {\n this.commitError(error);\n\n if (this.activating) throw error;\n }\n }\n\n private connectNewDependencies(next: Map<ReactiveNode<unknown>, number>): void {\n let connected: ReactiveNode<unknown>[] | undefined;\n\n try {\n for (const dependency of next.keys()) {\n if (this.dependencies.has(dependency)) continue;\n\n dependency.addDependent(this);\n (connected ??= []).push(dependency);\n }\n } catch (error) {\n const enrolled = connected ?? [];\n\n for (const dependency of enrolled) dependency.removeDependent(this);\n\n scheduler.rollbackDependencies(enrolled, error);\n }\n }\n\n private dependenciesChanged(): boolean {\n for (const [dependency, revision] of this.dependencies) {\n if (dependency.revision !== revision) return true;\n }\n\n return false;\n }\n\n private disconnectOldDependencies(next: Map<ReactiveNode<unknown>, number>): void {\n for (const dependency of this.dependencies.keys()) {\n if (!next.has(dependency)) dependency.removeDependent(this);\n }\n }\n\n private readonly read: ReactiveRead = <Dependency>(readable: Readable<Dependency>): Dependency => {\n const dependency = nodeFromReadable(readable);\n\n try {\n return dependency.readDependencySnapshot();\n } finally {\n this.scratchDependencies.set(dependency, dependency.revision);\n }\n };\n\n private replaceDependencies(next: Map<ReactiveNode<unknown>, number>): void {\n if (this.isActive()) this.connectNewDependencies(next);\n\n if (this.isActive()) this.disconnectOldDependencies(next);\n\n this.dependencies.clear();\n\n for (const [dependency, revision] of next) this.dependencies.set(dependency, revision);\n\n if (this.isActive() && this.dependenciesChanged()) this.freshness = 'dirty';\n }\n\n /** New edges were rolled back before this point; the committed dependency map remains authoritative. */\n private rollbackActivationDependencies(): void {\n for (const dependency of this.dependencies.keys()) dependency.removeDependent(this);\n }\n}\n\nfunction reactiveComputed<Value>(evaluate: (read: ReactiveRead) => Value, equals: Equality<Value>): Readable<Value> {\n return new FormulaNode(evaluate, equals).readable;\n}\n\nfunction reactiveDerived<Value>(\n dependencies: readonly Readable<unknown>[],\n evaluate: (read: ReactiveRead) => Value,\n equals: Equality<Value>,\n): Readable<Value> {\n return new FormulaNode(evaluate, equals, dependencies).readable;\n}\n\nfunction reactiveExternal<Source, Value>(\n source: ExternalReadableSource<Source>,\n select: (source: Source) => Value,\n equals: Equality<Value>,\n): Readable<Value> {\n return new ExternalNode(source, select, equals).readable;\n}\n\n/** A keyed source is invalidated selectively by its index, but must always stabilize after that index. */\nfunction reactiveKeyed<Value>(source: ExternalReadableSource<Value>, index: Readable<unknown>): Readable<Value> {\n return new ExternalNode(source, identity, defaultEquality, [nodeFromReadable(index)]).readable;\n}\n\n/** Facades keep their underlying graph identity instead of introducing a late subscription boundary. */\nfunction registerReactiveAlias(alias: object, readable: Readable<unknown>): void {\n reactiveNodes.set(alias, nodeFromReadable(readable));\n}\n\nfunction isReactiveObserved(readable: Readable<unknown>): boolean {\n return nodeFromReadable(readable).isObserved();\n}\n\nexport {\n defaultEquality,\n isReactiveObserved,\n reactiveComputed,\n reactiveDerived,\n reactiveExternal,\n reactiveKeyed,\n registerReactiveAlias,\n};\nexport type { Equality, ExternalReadableSource, ReactiveRead };\n"],"names":["maxSelectorWrites","defaultEquality","previous","next","identity","value","noop","ExternalNode","ReactiveNode","source","select","equals","orderingDependencies","unsubscribe","cause","error","createAggregateError","revision","sourceSnapshot","scheduler","beginDataTransaction","endDataTransaction","nodeFromReadable","readable","existing","reactiveNodes","node","FormulaNode","evaluate","dependencies","dependency","nextDependencies","connected","enrolled","reactiveComputed","reactiveDerived","reactiveExternal","reactiveKeyed","index","registerReactiveAlias","alias","isReactiveObserved"],"mappings":"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"}