@shiftbloom-studio/symphony-state 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 (59) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +198 -0
  3. package/dist/adapters/atom.cjs +36 -0
  4. package/dist/adapters/atom.cjs.map +1 -0
  5. package/dist/adapters/atom.d.cts +8 -0
  6. package/dist/adapters/atom.d.ts +8 -0
  7. package/dist/adapters/atom.js +34 -0
  8. package/dist/adapters/atom.js.map +1 -0
  9. package/dist/adapters/external.cjs +16 -0
  10. package/dist/adapters/external.cjs.map +1 -0
  11. package/dist/adapters/external.d.cts +13 -0
  12. package/dist/adapters/external.d.ts +13 -0
  13. package/dist/adapters/external.js +14 -0
  14. package/dist/adapters/external.js.map +1 -0
  15. package/dist/adapters/query.cjs +17 -0
  16. package/dist/adapters/query.cjs.map +1 -0
  17. package/dist/adapters/query.d.cts +16 -0
  18. package/dist/adapters/query.d.ts +16 -0
  19. package/dist/adapters/query.js +15 -0
  20. package/dist/adapters/query.js.map +1 -0
  21. package/dist/adapters/redux.cjs +20 -0
  22. package/dist/adapters/redux.cjs.map +1 -0
  23. package/dist/adapters/redux.d.cts +15 -0
  24. package/dist/adapters/redux.d.ts +15 -0
  25. package/dist/adapters/redux.js +18 -0
  26. package/dist/adapters/redux.js.map +1 -0
  27. package/dist/adapters/url.cjs +39 -0
  28. package/dist/adapters/url.cjs.map +1 -0
  29. package/dist/adapters/url.d.cts +13 -0
  30. package/dist/adapters/url.d.ts +13 -0
  31. package/dist/adapters/url.js +37 -0
  32. package/dist/adapters/url.js.map +1 -0
  33. package/dist/adapters/zustand.cjs +16 -0
  34. package/dist/adapters/zustand.cjs.map +1 -0
  35. package/dist/adapters/zustand.d.cts +10 -0
  36. package/dist/adapters/zustand.d.ts +10 -0
  37. package/dist/adapters/zustand.js +14 -0
  38. package/dist/adapters/zustand.js.map +1 -0
  39. package/dist/devtools.cjs +159 -0
  40. package/dist/devtools.cjs.map +1 -0
  41. package/dist/devtools.d.cts +9 -0
  42. package/dist/devtools.d.ts +9 -0
  43. package/dist/devtools.js +157 -0
  44. package/dist/devtools.js.map +1 -0
  45. package/dist/index.cjs +560 -0
  46. package/dist/index.cjs.map +1 -0
  47. package/dist/index.d.cts +20 -0
  48. package/dist/index.d.ts +20 -0
  49. package/dist/index.js +552 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/react.cjs +576 -0
  52. package/dist/react.cjs.map +1 -0
  53. package/dist/react.d.cts +40 -0
  54. package/dist/react.d.ts +40 -0
  55. package/dist/react.js +569 -0
  56. package/dist/react.js.map +1 -0
  57. package/dist/types-B1uzdpYH.d.cts +67 -0
  58. package/dist/types-B1uzdpYH.d.ts +67 -0
  59. package/package.json +127 -0
@@ -0,0 +1,40 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { C as Conductor, c as SectionHandle, S as SectionDefinition, D as DerivedSectionDefinition, a as Scheduler } from './types-B1uzdpYH.cjs';
4
+
5
+ type SymphonyProviderProps = {
6
+ conductor: Conductor;
7
+ children: React.ReactNode;
8
+ };
9
+ declare const SymphonyProvider: ({ conductor, children }: SymphonyProviderProps) => react_jsx_runtime.JSX.Element;
10
+ declare const useConductor: () => Conductor;
11
+
12
+ declare const useSection: <T>(key: string) => SectionHandle<T> & {
13
+ value: T;
14
+ };
15
+ declare const useSelector: <T, S>(key: string, selector: (value: T) => S, equality?: (a: S, b: S) => boolean) => S;
16
+
17
+ type SymphonyScriptProps = {
18
+ state: Record<string, unknown>;
19
+ globalKey?: string;
20
+ };
21
+ declare const SymphonyScript: ({ state, globalKey }: SymphonyScriptProps) => react_jsx_runtime.JSX.Element;
22
+
23
+ type AnySectionDefinition = SectionDefinition<unknown> | DerivedSectionDefinition<unknown>;
24
+ type SectionValues<Sections extends Record<string, AnySectionDefinition>> = {
25
+ [K in keyof Sections]: Sections[K] extends SectionDefinition<infer T> ? T : Sections[K] extends DerivedSectionDefinition<infer T> ? T : never;
26
+ };
27
+ type CreateSymphonyConfig<Sections extends Record<string, AnySectionDefinition>> = {
28
+ sections: Sections;
29
+ scheduler?: Scheduler;
30
+ bootstrap?: Record<string, unknown> | (() => Record<string, unknown> | undefined);
31
+ };
32
+ type SymphonyInstance<Sections extends Record<string, AnySectionDefinition>> = {
33
+ conductor: Conductor;
34
+ Provider: typeof SymphonyProvider;
35
+ useSection: <K extends keyof Sections>(key: K) => ReturnType<typeof useSection<SectionValues<Sections>[K]>>;
36
+ useSelector: <K extends keyof Sections, S>(key: K, selector: (value: SectionValues<Sections>[K]) => S, equality?: (a: S, b: S) => boolean) => S;
37
+ };
38
+ declare const createSymphony: <Sections extends Record<string, AnySectionDefinition>>(config: CreateSymphonyConfig<Sections>) => SymphonyInstance<Sections>;
39
+
40
+ export { SymphonyProvider, SymphonyScript, createSymphony, useConductor, useSection, useSelector };
@@ -0,0 +1,40 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { C as Conductor, c as SectionHandle, S as SectionDefinition, D as DerivedSectionDefinition, a as Scheduler } from './types-B1uzdpYH.js';
4
+
5
+ type SymphonyProviderProps = {
6
+ conductor: Conductor;
7
+ children: React.ReactNode;
8
+ };
9
+ declare const SymphonyProvider: ({ conductor, children }: SymphonyProviderProps) => react_jsx_runtime.JSX.Element;
10
+ declare const useConductor: () => Conductor;
11
+
12
+ declare const useSection: <T>(key: string) => SectionHandle<T> & {
13
+ value: T;
14
+ };
15
+ declare const useSelector: <T, S>(key: string, selector: (value: T) => S, equality?: (a: S, b: S) => boolean) => S;
16
+
17
+ type SymphonyScriptProps = {
18
+ state: Record<string, unknown>;
19
+ globalKey?: string;
20
+ };
21
+ declare const SymphonyScript: ({ state, globalKey }: SymphonyScriptProps) => react_jsx_runtime.JSX.Element;
22
+
23
+ type AnySectionDefinition = SectionDefinition<unknown> | DerivedSectionDefinition<unknown>;
24
+ type SectionValues<Sections extends Record<string, AnySectionDefinition>> = {
25
+ [K in keyof Sections]: Sections[K] extends SectionDefinition<infer T> ? T : Sections[K] extends DerivedSectionDefinition<infer T> ? T : never;
26
+ };
27
+ type CreateSymphonyConfig<Sections extends Record<string, AnySectionDefinition>> = {
28
+ sections: Sections;
29
+ scheduler?: Scheduler;
30
+ bootstrap?: Record<string, unknown> | (() => Record<string, unknown> | undefined);
31
+ };
32
+ type SymphonyInstance<Sections extends Record<string, AnySectionDefinition>> = {
33
+ conductor: Conductor;
34
+ Provider: typeof SymphonyProvider;
35
+ useSection: <K extends keyof Sections>(key: K) => ReturnType<typeof useSection<SectionValues<Sections>[K]>>;
36
+ useSelector: <K extends keyof Sections, S>(key: K, selector: (value: SectionValues<Sections>[K]) => S, equality?: (a: S, b: S) => boolean) => S;
37
+ };
38
+ declare const createSymphony: <Sections extends Record<string, AnySectionDefinition>>(config: CreateSymphonyConfig<Sections>) => SymphonyInstance<Sections>;
39
+
40
+ export { SymphonyProvider, SymphonyScript, createSymphony, useConductor, useSection, useSelector };
package/dist/react.js ADDED
@@ -0,0 +1,569 @@
1
+ import { createContext, useContext, useCallback, useSyncExternalStore, useMemo, useRef } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/react/SymphonyProvider.tsx
5
+ var SymphonyContext = createContext(null);
6
+ var SymphonyProvider = ({ conductor, children }) => {
7
+ return /* @__PURE__ */ jsx(SymphonyContext.Provider, { value: conductor, children });
8
+ };
9
+ var useConductor = () => {
10
+ const conductor = useContext(SymphonyContext);
11
+ if (!conductor) {
12
+ throw new Error("SymphonyProvider is missing in the React tree.");
13
+ }
14
+ return conductor;
15
+ };
16
+ var useSection = (key) => {
17
+ const conductor = useConductor();
18
+ const getSnapshot = useCallback(() => conductor.getSectionValue(key), [
19
+ conductor,
20
+ key
21
+ ]);
22
+ const subscribe = useCallback(
23
+ (cb) => conductor.subscribe(key, cb),
24
+ [conductor, key]
25
+ );
26
+ const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
27
+ const handle = useMemo(
28
+ () => ({
29
+ value,
30
+ get: () => value,
31
+ set: (next) => conductor.getSection(key).set(next),
32
+ patch: (partial) => conductor.getSection(key).patch(partial),
33
+ subscribe: (cb) => conductor.subscribe(key, cb)
34
+ }),
35
+ [conductor, key, value, subscribe]
36
+ );
37
+ return handle;
38
+ };
39
+ var useSelector = (key, selector, equality = Object.is) => {
40
+ const conductor = useConductor();
41
+ const selectedRef = useRef();
42
+ const getSnapshot = useCallback(() => {
43
+ const value = conductor.getSectionValue(key);
44
+ const nextSelected = selector(value);
45
+ if (selectedRef.current !== void 0 && equality(selectedRef.current, nextSelected)) {
46
+ return selectedRef.current;
47
+ }
48
+ selectedRef.current = nextSelected;
49
+ return nextSelected;
50
+ }, [conductor, key, selector, equality]);
51
+ const subscribe = useCallback(
52
+ (cb) => conductor.subscribe(key, cb),
53
+ [conductor, key]
54
+ );
55
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
56
+ };
57
+ var escapeJson = (value) => value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
58
+ var SymphonyScript = ({
59
+ state,
60
+ globalKey = "__SYMPHONY_STATE__"
61
+ }) => {
62
+ const serialized = escapeJson(JSON.stringify(state));
63
+ const script = `window[${JSON.stringify(globalKey)}]=${serialized};`;
64
+ return /* @__PURE__ */ jsx(
65
+ "script",
66
+ {
67
+ "data-symphony": true,
68
+ dangerouslySetInnerHTML: { __html: script }
69
+ }
70
+ );
71
+ };
72
+
73
+ // src/core/scheduler.ts
74
+ var scheduleMicrotask = (flush) => {
75
+ if (typeof queueMicrotask === "function") {
76
+ queueMicrotask(flush);
77
+ return;
78
+ }
79
+ Promise.resolve().then(flush);
80
+ };
81
+ var createScheduler = (scheduler) => {
82
+ if (!scheduler || scheduler === "microtask") {
83
+ return scheduleMicrotask;
84
+ }
85
+ if (scheduler === "sync") {
86
+ return (flush) => flush();
87
+ }
88
+ if (scheduler === "raf") {
89
+ return (flush) => {
90
+ if (typeof requestAnimationFrame === "function") {
91
+ requestAnimationFrame(() => flush());
92
+ return;
93
+ }
94
+ scheduleMicrotask(flush);
95
+ };
96
+ }
97
+ return scheduler;
98
+ };
99
+
100
+ // src/core/conductor.ts
101
+ var defaultPersist = (value, config) => {
102
+ if (!config.storage) {
103
+ return;
104
+ }
105
+ const serialize = config.serialize ?? JSON.stringify;
106
+ config.storage.setItem(config.key, serialize(value));
107
+ };
108
+ var readPersist = (config) => {
109
+ if (!config.storage) {
110
+ return void 0;
111
+ }
112
+ const raw = config.storage.getItem(config.key);
113
+ if (!raw) {
114
+ return void 0;
115
+ }
116
+ const deserialize = config.deserialize ?? JSON.parse;
117
+ return deserialize(raw);
118
+ };
119
+ var now = () => Date.now();
120
+ var isObject = (value) => typeof value === "object" && value !== null;
121
+ var createDependencyOrder = (dependencies) => {
122
+ const indegree = /* @__PURE__ */ new Map();
123
+ const outgoing = /* @__PURE__ */ new Map();
124
+ dependencies.forEach((deps, key) => {
125
+ indegree.set(key, deps.length);
126
+ deps.forEach((dep) => {
127
+ if (!outgoing.has(dep)) {
128
+ outgoing.set(dep, []);
129
+ }
130
+ outgoing.get(dep)?.push(key);
131
+ });
132
+ });
133
+ const queue = [];
134
+ indegree.forEach((count, key) => {
135
+ if (count === 0) {
136
+ queue.push(key);
137
+ }
138
+ });
139
+ const order = [];
140
+ while (queue.length > 0) {
141
+ const current = queue.shift();
142
+ if (!current) {
143
+ continue;
144
+ }
145
+ order.push(current);
146
+ const children = outgoing.get(current) ?? [];
147
+ for (const child of children) {
148
+ const next = (indegree.get(child) ?? 0) - 1;
149
+ indegree.set(child, next);
150
+ if (next === 0) {
151
+ queue.push(child);
152
+ }
153
+ }
154
+ }
155
+ if (order.length !== dependencies.size) {
156
+ const cycle = [...dependencies.keys()].filter((key) => !order.includes(key));
157
+ throw new Error(
158
+ `Dependency cycle detected in sections: ${cycle.join(", ")}`
159
+ );
160
+ }
161
+ return order;
162
+ };
163
+ var resolveStorage = (persist) => {
164
+ if (!persist) {
165
+ return void 0;
166
+ }
167
+ if (persist.storage) {
168
+ return persist;
169
+ }
170
+ if (typeof window === "undefined") {
171
+ return { ...persist, storage: null };
172
+ }
173
+ return { ...persist, storage: window.localStorage };
174
+ };
175
+ var createConductor = (config) => {
176
+ const sections = /* @__PURE__ */ new Map();
177
+ const updates = /* @__PURE__ */ new Map();
178
+ const effects = /* @__PURE__ */ new Set();
179
+ const dependencies = /* @__PURE__ */ new Map();
180
+ const scheduler = createScheduler(config.scheduler);
181
+ const transactionHistory = [];
182
+ const transactionHistoryLimit = config.transactionHistoryLimit ?? 20;
183
+ const persistValue = (state, value) => {
184
+ if (!state.persist) {
185
+ return;
186
+ }
187
+ const persist = resolveStorage(state.persist);
188
+ if (!persist?.storage) {
189
+ return;
190
+ }
191
+ if (persist.throttleMs && persist.throttleMs > 0) {
192
+ if (state.persistTimer) {
193
+ clearTimeout(state.persistTimer);
194
+ }
195
+ state.persistTimer = setTimeout(() => {
196
+ defaultPersist(value, persist);
197
+ }, persist.throttleMs);
198
+ return;
199
+ }
200
+ defaultPersist(value, persist);
201
+ };
202
+ const hydrateValue = (state) => {
203
+ const persist = resolveStorage(state.persist);
204
+ if (!persist?.storage) {
205
+ return;
206
+ }
207
+ const stored = readPersist(persist);
208
+ if (stored !== void 0) {
209
+ state.value = stored;
210
+ state.adapter?.set(stored);
211
+ }
212
+ };
213
+ const bootstrapValues = (() => {
214
+ if (!config.bootstrap) {
215
+ return void 0;
216
+ }
217
+ if (typeof config.bootstrap === "function") {
218
+ return config.bootstrap();
219
+ }
220
+ return config.bootstrap;
221
+ })();
222
+ for (const definition of config.sections) {
223
+ if (sections.has(definition.key)) {
224
+ throw new Error(`Section with key "${definition.key}" already exists.`);
225
+ }
226
+ const dependsOn = definition.dependsOn ?? [];
227
+ const state = {
228
+ key: definition.key,
229
+ adapter: definition.source,
230
+ subscribers: /* @__PURE__ */ new Set(),
231
+ dependsOn,
232
+ persist: definition.persist,
233
+ value: definition.source.get()
234
+ };
235
+ sections.set(definition.key, state);
236
+ dependencies.set(definition.key, dependsOn);
237
+ }
238
+ const derivedDefinitions = config.derived ?? [];
239
+ for (const definition of derivedDefinitions) {
240
+ if (sections.has(definition.key)) {
241
+ throw new Error(`Section with key "${definition.key}" already exists.`);
242
+ }
243
+ const state = {
244
+ key: definition.key,
245
+ subscribers: /* @__PURE__ */ new Set(),
246
+ dependsOn: definition.inputs,
247
+ value: void 0,
248
+ derived: {
249
+ inputs: definition.inputs,
250
+ compute: definition.compute,
251
+ lastInputs: void 0
252
+ }
253
+ };
254
+ sections.set(definition.key, state);
255
+ dependencies.set(definition.key, definition.inputs);
256
+ }
257
+ const order = createDependencyOrder(dependencies);
258
+ for (const [key, state] of sections) {
259
+ if (bootstrapValues && key in bootstrapValues) {
260
+ const value = bootstrapValues[key];
261
+ state.value = value;
262
+ state.adapter?.set(value);
263
+ continue;
264
+ }
265
+ if (state.persist) {
266
+ hydrateValue(state);
267
+ }
268
+ }
269
+ const stagedChanges = /* @__PURE__ */ new Set();
270
+ const pendingNotifications = /* @__PURE__ */ new Set();
271
+ let scheduled = false;
272
+ let transactionDepth = 0;
273
+ let lastTransactionLabel;
274
+ const notifySubscribers = () => {
275
+ scheduled = false;
276
+ for (const key of pendingNotifications) {
277
+ const state = sections.get(key);
278
+ if (!state) {
279
+ continue;
280
+ }
281
+ for (const subscriber of state.subscribers) {
282
+ subscriber();
283
+ }
284
+ }
285
+ pendingNotifications.clear();
286
+ };
287
+ const scheduleNotify = (keys) => {
288
+ for (const key of keys) {
289
+ pendingNotifications.add(key);
290
+ }
291
+ if (scheduled) {
292
+ return;
293
+ }
294
+ scheduled = true;
295
+ scheduler(notifySubscribers);
296
+ };
297
+ const markChanged = (key) => {
298
+ stagedChanges.add(key);
299
+ };
300
+ const applyUpdate = (state, update) => {
301
+ if (update.type === "set") {
302
+ if (Object.is(state.value, update.value)) {
303
+ return false;
304
+ }
305
+ state.adapter?.set(update.value);
306
+ state.value = update.value;
307
+ return true;
308
+ }
309
+ if (state.adapter?.patch) {
310
+ if (isObject(state.value) && isObject(update.value)) {
311
+ const next = { ...state.value, ...update.value };
312
+ const changed = Object.keys(update.value).some(
313
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
314
+ (k) => !Object.is(state.value[k], update.value[k])
315
+ );
316
+ if (!changed) {
317
+ return false;
318
+ }
319
+ state.adapter.patch(update.value);
320
+ state.value = next;
321
+ return true;
322
+ }
323
+ state.adapter.patch(update.value);
324
+ state.value = update.value;
325
+ return true;
326
+ }
327
+ if (isObject(state.value) && isObject(update.value)) {
328
+ const next = { ...state.value, ...update.value };
329
+ const changed = Object.keys(update.value).some(
330
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
+ (k) => !Object.is(state.value[k], update.value[k])
332
+ );
333
+ if (!changed) {
334
+ return false;
335
+ }
336
+ state.adapter?.set(next);
337
+ state.value = next;
338
+ return true;
339
+ }
340
+ throw new Error(`Section "${state.key}" does not support patch updates.`);
341
+ };
342
+ const computeDerived = (state) => {
343
+ if (!state.derived) {
344
+ return false;
345
+ }
346
+ const inputs = state.derived.inputs.map((key) => {
347
+ const inputState = sections.get(key);
348
+ if (!inputState) {
349
+ throw new Error(`Missing input section "${key}".`);
350
+ }
351
+ return inputState.value;
352
+ });
353
+ if (state.derived.lastInputs && inputs.every((value, index) => Object.is(value, state.derived?.lastInputs?.[index]))) {
354
+ return false;
355
+ }
356
+ const next = state.derived.compute(...inputs);
357
+ const changed = !Object.is(state.value, next);
358
+ state.value = next;
359
+ state.derived.lastInputs = inputs;
360
+ return changed;
361
+ };
362
+ for (const key of order) {
363
+ const state = sections.get(key);
364
+ if (!state?.derived) {
365
+ continue;
366
+ }
367
+ computeDerived(state);
368
+ }
369
+ const runEffects = (changedKeys) => {
370
+ if (effects.size === 0) {
371
+ return;
372
+ }
373
+ const api = {
374
+ get: (key) => getSectionValue(key),
375
+ set: (key, value) => setSectionValue(key, value),
376
+ patch: (key, value) => patchSectionValue(key, value),
377
+ transaction: (fn, label) => transaction(fn, label)
378
+ };
379
+ for (const effect2 of effects) {
380
+ if (!effect2.when.some((key) => changedKeys.includes(key))) {
381
+ continue;
382
+ }
383
+ const limit = effect2.maxDepth ?? 5;
384
+ if (transactionDepth > limit) {
385
+ throw new Error(
386
+ `Effect depth exceeded while running effect for sections: ${effect2.when.join(", ")}`
387
+ );
388
+ }
389
+ effect2.run(api);
390
+ }
391
+ };
392
+ const commit = () => {
393
+ if (updates.size === 0 && stagedChanges.size === 0) {
394
+ return;
395
+ }
396
+ const changedKeys = [];
397
+ for (const key of order) {
398
+ const state = sections.get(key);
399
+ if (!state) {
400
+ continue;
401
+ }
402
+ const update = updates.get(key);
403
+ if (update && !state.derived) {
404
+ const changed = applyUpdate(state, update);
405
+ if (changed) {
406
+ persistValue(state, state.value);
407
+ changedKeys.push(key);
408
+ }
409
+ updates.delete(key);
410
+ }
411
+ if (state.derived) {
412
+ const derivedChanged = computeDerived(state);
413
+ if (derivedChanged) {
414
+ changedKeys.push(key);
415
+ }
416
+ }
417
+ }
418
+ if (changedKeys.length > 0) {
419
+ scheduleNotify(changedKeys);
420
+ transactionHistory.unshift({
421
+ label: lastTransactionLabel,
422
+ touched: [...new Set(changedKeys)],
423
+ timestamp: now()
424
+ });
425
+ if (transactionHistory.length > transactionHistoryLimit) {
426
+ transactionHistory.length = transactionHistoryLimit;
427
+ }
428
+ }
429
+ stagedChanges.clear();
430
+ lastTransactionLabel = void 0;
431
+ runEffects(changedKeys);
432
+ };
433
+ const beginTransaction = (label) => {
434
+ transactionDepth += 1;
435
+ if (label) {
436
+ lastTransactionLabel = label;
437
+ }
438
+ };
439
+ const endTransaction = () => {
440
+ transactionDepth -= 1;
441
+ if (transactionDepth === 0) {
442
+ commit();
443
+ }
444
+ };
445
+ const transaction = (fn, label) => {
446
+ beginTransaction(label);
447
+ try {
448
+ fn();
449
+ } finally {
450
+ endTransaction();
451
+ }
452
+ };
453
+ const setSectionValue = (key, value) => {
454
+ if (!sections.has(key)) {
455
+ throw new Error(`Unknown section "${key}".`);
456
+ }
457
+ if (transactionDepth === 0) {
458
+ return transaction(() => setSectionValue(key, value));
459
+ }
460
+ const state = sections.get(key);
461
+ if (state?.derived) {
462
+ throw new Error(`Cannot set derived section "${key}".`);
463
+ }
464
+ updates.set(key, { type: "set", value });
465
+ markChanged(key);
466
+ };
467
+ const patchSectionValue = (key, value) => {
468
+ if (!sections.has(key)) {
469
+ throw new Error(`Unknown section "${key}".`);
470
+ }
471
+ if (transactionDepth === 0) {
472
+ return transaction(() => patchSectionValue(key, value));
473
+ }
474
+ const state = sections.get(key);
475
+ if (state?.derived) {
476
+ throw new Error(`Cannot patch derived section "${key}".`);
477
+ }
478
+ updates.set(key, { type: "patch", value });
479
+ markChanged(key);
480
+ };
481
+ const getSectionValue = (key) => {
482
+ const state = sections.get(key);
483
+ if (!state) {
484
+ throw new Error(`Unknown section "${key}".`);
485
+ }
486
+ return state.value;
487
+ };
488
+ const subscribe = (key, cb) => {
489
+ const state = sections.get(key);
490
+ if (!state) {
491
+ throw new Error(`Unknown section "${key}".`);
492
+ }
493
+ state.subscribers.add(cb);
494
+ return () => {
495
+ state.subscribers.delete(cb);
496
+ };
497
+ };
498
+ const getSection = (key) => {
499
+ return {
500
+ get: () => getSectionValue(key),
501
+ set: (next) => setSectionValue(key, next),
502
+ patch: (next) => patchSectionValue(key, next),
503
+ subscribe: (cb) => subscribe(key, cb)
504
+ };
505
+ };
506
+ const effect = (definition) => {
507
+ effects.add(definition);
508
+ return () => effects.delete(definition);
509
+ };
510
+ const getSnapshot = () => {
511
+ const snapshot = { sections: {}, transactions: [] };
512
+ for (const [key, state] of sections) {
513
+ snapshot.sections[key] = state.value;
514
+ }
515
+ snapshot.transactions = [...transactionHistory];
516
+ return snapshot;
517
+ };
518
+ const destroy = () => {
519
+ sections.forEach((state) => {
520
+ state.adapter?.destroy?.();
521
+ if (state.persistTimer) {
522
+ clearTimeout(state.persistTimer);
523
+ }
524
+ state.subscribers.clear();
525
+ });
526
+ sections.clear();
527
+ updates.clear();
528
+ effects.clear();
529
+ };
530
+ return {
531
+ getSection,
532
+ getSectionValue,
533
+ subscribe,
534
+ transaction,
535
+ effect,
536
+ getSnapshot,
537
+ destroy
538
+ };
539
+ };
540
+
541
+ // src/react/createSymphony.ts
542
+ var createSymphony = (config) => {
543
+ const sectionEntries = Object.values(config.sections);
544
+ const sections = [];
545
+ const derived = [];
546
+ for (const entry of sectionEntries) {
547
+ if ("source" in entry) {
548
+ sections.push(entry);
549
+ } else {
550
+ derived.push(entry);
551
+ }
552
+ }
553
+ const conductor = createConductor({
554
+ sections,
555
+ derived,
556
+ scheduler: config.scheduler,
557
+ bootstrap: config.bootstrap
558
+ });
559
+ return {
560
+ conductor,
561
+ Provider: SymphonyProvider,
562
+ useSection: (key) => useSection(String(key)),
563
+ useSelector: (key, selector, equality) => useSelector(String(key), selector, equality)
564
+ };
565
+ };
566
+
567
+ export { SymphonyProvider, SymphonyScript, createSymphony, useConductor, useSection, useSelector };
568
+ //# sourceMappingURL=react.js.map
569
+ //# sourceMappingURL=react.js.map