preact-sigma 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -467
- package/dist/index.d.mts +207 -193
- package/dist/index.mjs +593 -265
- package/llms.txt +354 -238
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,301 +1,629 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
get(key) {
|
|
38
|
-
if (!key) return this._view;
|
|
39
|
-
return this._signals.get(key);
|
|
40
|
-
}
|
|
41
|
-
peek(key) {
|
|
42
|
-
const signal = this.get(key);
|
|
43
|
-
if (!signal) return;
|
|
44
|
-
return signal.peek();
|
|
45
|
-
}
|
|
46
|
-
subscribe(...args) {
|
|
47
|
-
if (args.length > 1) {
|
|
48
|
-
const [key, listener] = args;
|
|
49
|
-
const signal = this.get(key);
|
|
50
|
-
if (!signal) throw new Error(`Property ${key} is not a signal`);
|
|
51
|
-
return signal.subscribe(listener);
|
|
52
|
-
}
|
|
53
|
-
return this._view.subscribe(args[0]);
|
|
54
|
-
}
|
|
55
|
-
on(name, listener) {
|
|
56
|
-
const adapter = (event) => {
|
|
57
|
-
const detail = event.detail;
|
|
58
|
-
if (detail === void 0) listener();
|
|
59
|
-
else listener(detail);
|
|
60
|
-
};
|
|
61
|
-
this.addEventListener(name, adapter);
|
|
62
|
-
return () => {
|
|
63
|
-
this.removeEventListener(name, adapter);
|
|
64
|
-
};
|
|
1
|
+
import { action, batch, batch as batch$1, computed, computed as computed$1, effect, signal, untracked, untracked as untracked$1 } from "@preact/signals";
|
|
2
|
+
import * as immer from "immer";
|
|
3
|
+
import { freeze, immerable } from "immer";
|
|
4
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
5
|
+
//#region src/internal/context.ts
|
|
6
|
+
const disabledContextOptions = {
|
|
7
|
+
allowActions: false,
|
|
8
|
+
allowCommit: false,
|
|
9
|
+
allowEmit: false,
|
|
10
|
+
allowQueries: false,
|
|
11
|
+
allowWrites: false,
|
|
12
|
+
draftAware: false,
|
|
13
|
+
draftOnRead: false,
|
|
14
|
+
liveComputeds: false,
|
|
15
|
+
reactiveReads: false
|
|
16
|
+
};
|
|
17
|
+
const publicContextOptions = {
|
|
18
|
+
computedReadonly: {
|
|
19
|
+
...disabledContextOptions,
|
|
20
|
+
reactiveReads: true
|
|
21
|
+
},
|
|
22
|
+
observe: {
|
|
23
|
+
...disabledContextOptions,
|
|
24
|
+
allowQueries: true
|
|
25
|
+
},
|
|
26
|
+
queryCommitted: {
|
|
27
|
+
...disabledContextOptions,
|
|
28
|
+
allowQueries: true,
|
|
29
|
+
reactiveReads: true
|
|
30
|
+
},
|
|
31
|
+
setup: {
|
|
32
|
+
...disabledContextOptions,
|
|
33
|
+
allowActions: true,
|
|
34
|
+
allowEmit: true,
|
|
35
|
+
allowQueries: true
|
|
65
36
|
}
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
};
|
|
38
|
+
const ownerContextOptions = {
|
|
39
|
+
action: {
|
|
40
|
+
...disabledContextOptions,
|
|
41
|
+
allowActions: true,
|
|
42
|
+
allowCommit: true,
|
|
43
|
+
allowEmit: true,
|
|
44
|
+
allowQueries: true,
|
|
45
|
+
allowWrites: true,
|
|
46
|
+
draftAware: true,
|
|
47
|
+
draftOnRead: true,
|
|
48
|
+
liveComputeds: true
|
|
49
|
+
},
|
|
50
|
+
computedDraftAware: {
|
|
51
|
+
...disabledContextOptions,
|
|
52
|
+
draftAware: true,
|
|
53
|
+
liveComputeds: true
|
|
54
|
+
},
|
|
55
|
+
queryDraftAware: {
|
|
56
|
+
...disabledContextOptions,
|
|
57
|
+
allowQueries: true,
|
|
58
|
+
draftAware: true,
|
|
59
|
+
liveComputeds: true
|
|
68
60
|
}
|
|
69
61
|
};
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
const dirtyContexts = {
|
|
63
|
+
computedReadonly: /* @__PURE__ */ new Set(),
|
|
64
|
+
observe: /* @__PURE__ */ new Set(),
|
|
65
|
+
queryCommitted: /* @__PURE__ */ new Set(),
|
|
66
|
+
setup: /* @__PURE__ */ new Set()
|
|
67
|
+
};
|
|
68
|
+
const contextKinds = Object.keys(dirtyContexts);
|
|
69
|
+
const contextCache = {
|
|
70
|
+
computedReadonly: /* @__PURE__ */ new WeakMap(),
|
|
71
|
+
observe: /* @__PURE__ */ new WeakMap(),
|
|
72
|
+
queryCommitted: /* @__PURE__ */ new WeakMap(),
|
|
73
|
+
setup: /* @__PURE__ */ new WeakMap()
|
|
74
|
+
};
|
|
75
|
+
const contextOwnerMap = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
let contextCacheFlushScheduled = false;
|
|
77
|
+
function getContext(target, kind) {
|
|
78
|
+
if (isOwnerContextKind(kind)) return getOwnerContext(target, kind);
|
|
79
|
+
return getPublicContext(target, kind);
|
|
72
80
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
...Object.getOwnPropertyDescriptor(object, key),
|
|
76
|
-
enumerable: false
|
|
77
|
-
});
|
|
81
|
+
function getContextOwner(context) {
|
|
82
|
+
return contextOwnerMap.get(context);
|
|
78
83
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return Reflect.getOwnPropertyDescriptor(currentState, key);
|
|
106
|
-
};
|
|
107
|
-
const getLens = (key) => {
|
|
108
|
-
let lens = (lenses ||= /* @__PURE__ */ new Map()).get(key);
|
|
109
|
-
if (!lens) {
|
|
110
|
-
lens = {
|
|
111
|
-
get: () => handle.get()[key],
|
|
112
|
-
set: (update) => {
|
|
113
|
-
handle.set((draft) => {
|
|
114
|
-
draft[key] = isProducer(update) ? produce(draft[key], update) : update;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
lensKeys.set(lens, key);
|
|
119
|
-
lenses.set(key, lens);
|
|
120
|
-
}
|
|
121
|
-
return lens;
|
|
122
|
-
};
|
|
123
|
-
return new Proxy(handle, {
|
|
124
|
-
get(target, key, receiver) {
|
|
125
|
-
if (Reflect.has(target, key)) return Reflect.get(target, key, receiver);
|
|
126
|
-
if (!getLensDescriptor(key)) return;
|
|
127
|
-
return getLens(key);
|
|
84
|
+
function registerContextOwner(context, owner) {
|
|
85
|
+
contextOwnerMap.set(context, owner);
|
|
86
|
+
}
|
|
87
|
+
function createContext(instance, options, owner) {
|
|
88
|
+
const publicPrototype = Object.getPrototypeOf(instance.publicInstance);
|
|
89
|
+
return new Proxy(publicPrototype, {
|
|
90
|
+
get(_target, key) {
|
|
91
|
+
if (typeof key !== "string") return Reflect.get(publicPrototype, key, owner?.actionContext ?? instance.publicInstance);
|
|
92
|
+
if (key === "commit") return options.allowCommit && owner ? () => commitActionOwner(owner) : void 0;
|
|
93
|
+
if (key === "emit") return options.allowEmit && owner ? (name, detail) => {
|
|
94
|
+
handleActionBoundary(owner, "emit");
|
|
95
|
+
instance.publicInstance.dispatchEvent(new CustomEvent(name, { detail }));
|
|
96
|
+
} : void 0;
|
|
97
|
+
if (instance.stateKeys.has(key)) {
|
|
98
|
+
if (owner && options.draftAware) return readActionStateValue(owner, key, options);
|
|
99
|
+
const signal = getSignal(instance, key);
|
|
100
|
+
return options.reactiveReads ? signal.value : signal.peek();
|
|
101
|
+
}
|
|
102
|
+
if (key in instance.type.computeFunctions) {
|
|
103
|
+
if (owner && options.liveComputeds) return readActionComputedValue(owner, key);
|
|
104
|
+
const signal = getSignal(instance, key);
|
|
105
|
+
return options.reactiveReads ? signal.value : signal.peek();
|
|
106
|
+
}
|
|
107
|
+
if (options.allowQueries && key in instance.type.queryFunctions) return Reflect.get(instance.publicInstance, key);
|
|
108
|
+
if (options.allowActions && key in instance.type.actionFunctions) return Reflect.get(instance.publicInstance, key);
|
|
109
|
+
if (Reflect.has(publicPrototype, key)) return Reflect.get(publicPrototype, key, owner?.actionContext ?? instance.publicInstance);
|
|
128
110
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
111
|
+
set(_target, key, value) {
|
|
112
|
+
if (!owner || !options.allowWrites || typeof key !== "string" || !instance.stateKeys.has(key)) return false;
|
|
113
|
+
setActionStateValue(owner, key, value);
|
|
114
|
+
return true;
|
|
133
115
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
116
|
+
apply: unsupportedOperation,
|
|
117
|
+
construct: unsupportedOperation,
|
|
118
|
+
defineProperty: unsupportedOperation,
|
|
119
|
+
deleteProperty: unsupportedOperation,
|
|
120
|
+
getOwnPropertyDescriptor: unsupportedOperation,
|
|
121
|
+
has: unsupportedOperation,
|
|
122
|
+
isExtensible: unsupportedOperation,
|
|
123
|
+
ownKeys: unsupportedOperation,
|
|
124
|
+
preventExtensions: unsupportedOperation,
|
|
125
|
+
setPrototypeOf: unsupportedOperation
|
|
144
126
|
});
|
|
145
127
|
}
|
|
146
|
-
function
|
|
128
|
+
function unsupportedOperation() {
|
|
129
|
+
throw new Error("[preact-sigma] This operation is not supported by context proxies");
|
|
130
|
+
}
|
|
131
|
+
const kindToOwnerContextKey = {
|
|
132
|
+
action: "actionContext",
|
|
133
|
+
computedDraftAware: "computedContext",
|
|
134
|
+
queryDraftAware: "queryContext"
|
|
135
|
+
};
|
|
136
|
+
function isOwnerContextKind(kind) {
|
|
137
|
+
return kind in kindToOwnerContextKey;
|
|
138
|
+
}
|
|
139
|
+
function getOwnerContext(owner, kind) {
|
|
140
|
+
const contextKey = kindToOwnerContextKey[kind];
|
|
141
|
+
if (owner[contextKey]) return owner[contextKey];
|
|
142
|
+
const context = createContext(owner.instance, ownerContextOptions[kind], owner);
|
|
143
|
+
registerSigmaInternals(context, owner.instance);
|
|
144
|
+
registerContextOwner(context, owner);
|
|
145
|
+
owner[contextKey] = context;
|
|
146
|
+
return context;
|
|
147
|
+
}
|
|
148
|
+
function getPublicContext(instance, kind) {
|
|
149
|
+
const cachedContext = contextCache[kind].get(instance);
|
|
150
|
+
if (cachedContext) return cachedContext;
|
|
151
|
+
const context = createContext(instance, publicContextOptions[kind], void 0);
|
|
152
|
+
registerSigmaInternals(context, instance);
|
|
153
|
+
contextCache[kind].set(instance, context);
|
|
154
|
+
dirtyContexts[kind].add(instance);
|
|
155
|
+
if (!contextCacheFlushScheduled) {
|
|
156
|
+
contextCacheFlushScheduled = true;
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
for (const queuedKind of contextKinds) {
|
|
159
|
+
for (const queuedInstance of dirtyContexts[queuedKind]) contextCache[queuedKind].delete(queuedInstance);
|
|
160
|
+
dirtyContexts[queuedKind].clear();
|
|
161
|
+
}
|
|
162
|
+
contextCacheFlushScheduled = false;
|
|
163
|
+
}, 0);
|
|
164
|
+
}
|
|
165
|
+
return context;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/internal/symbols.ts
|
|
169
|
+
const sigmaStateBrand = Symbol("sigma.v2.state");
|
|
170
|
+
const reservedKeys = new Set([
|
|
171
|
+
"get",
|
|
172
|
+
"emit",
|
|
173
|
+
"commit",
|
|
174
|
+
"on",
|
|
175
|
+
"setup"
|
|
176
|
+
]);
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/internal/runtime.ts
|
|
179
|
+
const sigmaInternalsMap = /* @__PURE__ */ new WeakMap();
|
|
180
|
+
let autoFreezeEnabled = true;
|
|
181
|
+
let nextActionOwnerId = 1;
|
|
182
|
+
let currentDraftOwner;
|
|
183
|
+
function registerSigmaInternals(context, instance) {
|
|
184
|
+
sigmaInternalsMap.set(context, instance);
|
|
185
|
+
}
|
|
186
|
+
function getSigmaInternals(context) {
|
|
187
|
+
const instance = sigmaInternalsMap.get(context);
|
|
188
|
+
if (!instance) throw new Error("[preact-sigma] Invalid sigma context");
|
|
189
|
+
return instance;
|
|
190
|
+
}
|
|
191
|
+
function isAutoFreeze() {
|
|
192
|
+
return autoFreezeEnabled;
|
|
193
|
+
}
|
|
194
|
+
/** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled. */
|
|
195
|
+
function setAutoFreeze(autoFreeze) {
|
|
196
|
+
autoFreezeEnabled = autoFreeze;
|
|
197
|
+
immer.setAutoFreeze(autoFreeze);
|
|
198
|
+
}
|
|
199
|
+
function isPlainObject(value) {
|
|
147
200
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
148
201
|
const prototype = Object.getPrototypeOf(value);
|
|
149
202
|
return prototype === Object.prototype || prototype === null;
|
|
150
203
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
204
|
+
function getSignal(instance, key) {
|
|
205
|
+
return instance.publicInstance["#" + key];
|
|
206
|
+
}
|
|
207
|
+
function initializeSigmaInstance(publicInstance, type, initialState) {
|
|
208
|
+
const stateKeys = new Set(type.defaultStateKeys);
|
|
209
|
+
if (initialState) for (const key in initialState) stateKeys.add(key);
|
|
210
|
+
const instance = {
|
|
211
|
+
currentSetupCleanup: void 0,
|
|
212
|
+
publicInstance,
|
|
213
|
+
stateKeys,
|
|
214
|
+
type,
|
|
215
|
+
disposed: false
|
|
216
|
+
};
|
|
217
|
+
for (const key of stateKeys) {
|
|
218
|
+
if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
|
|
219
|
+
let value = initialState?.[key];
|
|
220
|
+
if (value === void 0) value = typeof type.defaultState[key] === "function" ? type.defaultState[key].call(void 0) : type.defaultState[key];
|
|
221
|
+
const container = signal(value);
|
|
222
|
+
Object.defineProperty(publicInstance, "#" + key, { value: container });
|
|
223
|
+
Object.defineProperty(publicInstance, key, {
|
|
224
|
+
get: () => container.value,
|
|
225
|
+
enumerable: true
|
|
226
|
+
});
|
|
170
227
|
}
|
|
171
|
-
|
|
228
|
+
for (const key in type.computeFunctions) Object.defineProperty(publicInstance, "#" + key, { value: computed$1(() => type.computeFunctions[key].call(getContext(instance, "computedReadonly"))) });
|
|
229
|
+
registerSigmaInternals(publicInstance, instance);
|
|
172
230
|
}
|
|
173
|
-
|
|
174
|
-
function
|
|
175
|
-
|
|
231
|
+
function buildQueryMethod(queryFunction) {
|
|
232
|
+
return function(...args) {
|
|
233
|
+
const instance = getSigmaInternals(this);
|
|
234
|
+
const owner = getContextOwner(this);
|
|
235
|
+
if (owner) return queryFunction.apply(getContext(owner, "queryDraftAware"), args);
|
|
236
|
+
return computed$1(() => queryFunction.apply(getContext(instance, "queryCommitted"), args)).value;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function buildActionMethod(actionName, actionFn) {
|
|
240
|
+
const actionIsAsync = isAsyncFunction(actionFn);
|
|
241
|
+
return function(...args) {
|
|
242
|
+
const instance = getSigmaInternals(this);
|
|
243
|
+
if (instance.disposed) throw new Error("[preact-sigma] Cannot run an action on a disposed sigma state");
|
|
244
|
+
return untracked$1(() => {
|
|
245
|
+
let owner;
|
|
246
|
+
const callerOwner = getContextOwner(this);
|
|
247
|
+
if (callerOwner && callerOwner.instance === instance && !actionIsAsync) owner = callerOwner;
|
|
248
|
+
else {
|
|
249
|
+
handleActionBoundary(callerOwner, "action", actionName);
|
|
250
|
+
owner = createActionOwner(instance, actionName, actionFn, args);
|
|
251
|
+
}
|
|
252
|
+
let result;
|
|
253
|
+
try {
|
|
254
|
+
result = actionFn.apply(owner.actionContext, [...args]);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
clearCurrentDraft(owner);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
if (!actionIsAsync && isPromiseLike(result)) {
|
|
260
|
+
clearCurrentDraft(owner);
|
|
261
|
+
Promise.resolve(result).catch(() => {});
|
|
262
|
+
throw new Error(`[preact-sigma] Action "${actionName}" must use native async-await syntax to return a promise.`);
|
|
263
|
+
}
|
|
264
|
+
if (owner === callerOwner) return result;
|
|
265
|
+
const finalized = finalizeOwnerDraft(owner);
|
|
266
|
+
if (finalized?.changed) publishState(instance, finalized);
|
|
267
|
+
if (isPromiseLike(result)) return resolveAsyncActionResult(owner, result);
|
|
268
|
+
return result;
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function readActionStateValue(owner, key, options) {
|
|
273
|
+
if (owner.currentDraft) return owner.currentDraft[key];
|
|
274
|
+
const signal = getSignal(owner.instance, key);
|
|
275
|
+
const committedValue = options.reactiveReads ? signal.value : signal.peek();
|
|
276
|
+
if (options.draftOnRead && immer.isDraftable(committedValue)) return ensureOwnerDraft(owner)[key];
|
|
277
|
+
return committedValue;
|
|
278
|
+
}
|
|
279
|
+
function readActionComputedValue(owner, key) {
|
|
280
|
+
return owner.instance.type.computeFunctions[key].call(getContext(owner, "computedDraftAware"));
|
|
281
|
+
}
|
|
282
|
+
function setActionStateValue(owner, key, value) {
|
|
283
|
+
ensureOwnerDraft(owner)[key] = value;
|
|
284
|
+
}
|
|
285
|
+
function commitActionOwner(owner) {
|
|
286
|
+
const finalized = finalizeOwnerDraft(owner);
|
|
287
|
+
if (finalized?.changed) publishState(owner.instance, finalized);
|
|
288
|
+
}
|
|
289
|
+
function handleActionBoundary(owner, boundary, actionName) {
|
|
290
|
+
const draftOwner = currentDraftOwner;
|
|
291
|
+
if (!draftOwner?.currentDraft) return;
|
|
292
|
+
if (!finalizeOwnerDraft(draftOwner)?.changed) return;
|
|
293
|
+
if (draftOwner === owner) {
|
|
294
|
+
const message = boundary === "emit" ? `[preact-sigma] Action "${draftOwner.actionName}" has unpublished changes. Call this.commit() before emit().` : `[preact-sigma] Action "${draftOwner.actionName}" has unpublished changes. Call this.commit() before calling another action.`;
|
|
295
|
+
throw new Error(message);
|
|
296
|
+
}
|
|
297
|
+
if (boundary === "emit") throw new Error("[preact-sigma] Unexpected emit boundary. This is a bug.");
|
|
298
|
+
console.warn(`[preact-sigma] Discarded unpublished action changes from "${draftOwner.actionName}" before running "${actionName ?? "another action"}".`, createDraftMetadata(draftOwner));
|
|
299
|
+
}
|
|
300
|
+
function assertDefinitionKeyAvailable(builder, key, kind) {
|
|
301
|
+
if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
|
|
302
|
+
if (key in builder.computeFunctions || key in builder.queryFunctions || key in builder.actionFunctions) throw new Error(`[preact-sigma] Duplicate key for ${kind}: ${key}`);
|
|
303
|
+
}
|
|
304
|
+
function shouldSetup(publicInstance) {
|
|
305
|
+
return getSigmaInternals(publicInstance).type.setupFunctions.length > 0;
|
|
306
|
+
}
|
|
307
|
+
function clearCurrentDraft(owner) {
|
|
308
|
+
owner.currentDraft = void 0;
|
|
309
|
+
owner.currentBase = void 0;
|
|
310
|
+
if (currentDraftOwner === owner) currentDraftOwner = void 0;
|
|
311
|
+
}
|
|
312
|
+
function createActionOwner(instance, actionName, actionFn, args) {
|
|
313
|
+
const owner = {
|
|
314
|
+
actionFn,
|
|
315
|
+
actionName,
|
|
316
|
+
args,
|
|
317
|
+
id: nextActionOwnerId++,
|
|
318
|
+
instance,
|
|
319
|
+
publicInstance: instance.publicInstance
|
|
320
|
+
};
|
|
321
|
+
owner.actionContext = getContext(owner, "action");
|
|
322
|
+
registerSigmaInternals(owner.actionContext, instance);
|
|
323
|
+
registerContextOwner(owner.actionContext, owner);
|
|
324
|
+
return owner;
|
|
325
|
+
}
|
|
326
|
+
function createCommitError(owner) {
|
|
327
|
+
return /* @__PURE__ */ new Error(`[preact-sigma] Async action "${owner.actionName}" finished with unpublished changes. Call this.commit() before await or return.`);
|
|
328
|
+
}
|
|
329
|
+
function createDraftMetadata(owner) {
|
|
330
|
+
return {
|
|
331
|
+
action: owner.actionFn,
|
|
332
|
+
actionArgs: owner.args,
|
|
333
|
+
actionId: owner.id,
|
|
334
|
+
actionName: owner.actionName,
|
|
335
|
+
draftedInstance: currentDraftOwner?.publicInstance ?? owner.publicInstance,
|
|
336
|
+
instance: owner.instance.publicInstance
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function disposeCleanupResource(resource) {
|
|
340
|
+
if (typeof resource === "function") resource();
|
|
341
|
+
else if (resource instanceof AbortController) resource.abort();
|
|
342
|
+
else resource[Symbol.dispose]();
|
|
343
|
+
}
|
|
344
|
+
function assertExactStateKeys(instance, nextState) {
|
|
345
|
+
const extraKeys = Object.keys(nextState).filter((key) => !instance.stateKeys.has(key));
|
|
346
|
+
const missingKeys = [...instance.stateKeys].filter((key) => !Object.prototype.hasOwnProperty.call(nextState, key));
|
|
347
|
+
if (!extraKeys.length && !missingKeys.length) return;
|
|
348
|
+
let message = "[preact-sigma] replaceState() requires exactly the instance's state keys";
|
|
349
|
+
if (missingKeys.length) message += `. Missing: ${missingKeys.join(", ")}`;
|
|
350
|
+
if (extraKeys.length) message += `. Extra: ${extraKeys.join(", ")}`;
|
|
351
|
+
throw new Error(message);
|
|
352
|
+
}
|
|
353
|
+
function assertNoPendingDraft(operationName) {
|
|
354
|
+
const owner = currentDraftOwner;
|
|
355
|
+
if (!owner?.currentDraft) return;
|
|
356
|
+
throw new Error(`[preact-sigma] ${operationName}() cannot run while action "${owner.actionName}" has unpublished changes. Call this.commit() before ${operationName}().`);
|
|
357
|
+
}
|
|
358
|
+
function snapshotState(instance) {
|
|
359
|
+
const snapshot = Object.create(null);
|
|
360
|
+
for (const key of instance.stateKeys) snapshot[key] = getSignal(instance, key).peek();
|
|
361
|
+
return snapshot;
|
|
362
|
+
}
|
|
363
|
+
function ensureOwnerDraft(owner) {
|
|
364
|
+
if (owner.currentDraft) return owner.currentDraft;
|
|
365
|
+
handleActionBoundary(owner, "action", owner.actionName);
|
|
366
|
+
owner.currentBase = snapshotState(owner.instance);
|
|
367
|
+
owner.currentDraft = immer.createDraft(owner.currentBase);
|
|
368
|
+
currentDraftOwner = owner;
|
|
369
|
+
return owner.currentDraft;
|
|
370
|
+
}
|
|
371
|
+
function finalizeOwnerDraft(owner) {
|
|
372
|
+
const currentDraft = owner.currentDraft;
|
|
373
|
+
const oldState = owner.currentBase;
|
|
374
|
+
if (!currentDraft || !oldState) return;
|
|
375
|
+
clearCurrentDraft(owner);
|
|
376
|
+
let patches;
|
|
377
|
+
let inversePatches;
|
|
378
|
+
const newState = owner.instance.type.patchesEnabled ? immer.finishDraft(currentDraft, (nextPatches, nextInversePatches) => {
|
|
379
|
+
patches = nextPatches;
|
|
380
|
+
inversePatches = nextInversePatches;
|
|
381
|
+
}) : immer.finishDraft(currentDraft);
|
|
382
|
+
return {
|
|
383
|
+
changed: newState !== oldState,
|
|
384
|
+
inversePatches,
|
|
385
|
+
newState,
|
|
386
|
+
oldState,
|
|
387
|
+
patches
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function finalizeReplacementState(instance, oldState, nextState) {
|
|
391
|
+
const draft = immer.createDraft(oldState);
|
|
392
|
+
for (const key of instance.stateKeys) draft[key] = nextState[key];
|
|
393
|
+
let patches;
|
|
394
|
+
let inversePatches;
|
|
395
|
+
const newState = instance.type.patchesEnabled ? immer.finishDraft(draft, (nextPatches, nextInversePatches) => {
|
|
396
|
+
patches = nextPatches;
|
|
397
|
+
inversePatches = nextInversePatches;
|
|
398
|
+
}) : immer.finishDraft(draft);
|
|
399
|
+
return {
|
|
400
|
+
changed: newState !== oldState,
|
|
401
|
+
inversePatches,
|
|
402
|
+
newState,
|
|
403
|
+
oldState,
|
|
404
|
+
patches
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function isAsyncFunction(fn) {
|
|
408
|
+
return fn.constructor.name === "AsyncFunction";
|
|
409
|
+
}
|
|
410
|
+
function isPromiseLike(value) {
|
|
411
|
+
return value != null && typeof value.then === "function";
|
|
412
|
+
}
|
|
413
|
+
function publishState(instance, finalized) {
|
|
414
|
+
batch$1(() => {
|
|
415
|
+
for (const key of instance.stateKeys) {
|
|
416
|
+
const nextValue = finalized.newState[key];
|
|
417
|
+
if (isAutoFreeze()) immer.freeze(nextValue, true);
|
|
418
|
+
const signal = getSignal(instance, key);
|
|
419
|
+
signal.value = nextValue;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
for (const observer of instance.type.observeFunctions) observer.call(getContext(instance, "observe"), finalized);
|
|
176
423
|
}
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/framework.ts
|
|
179
424
|
/**
|
|
180
|
-
*
|
|
425
|
+
* Returns a shallow snapshot of an instance's committed public state.
|
|
181
426
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* instance receiver. Tagged query methods also skip the default `action()`
|
|
186
|
-
* wrapping step.
|
|
427
|
+
* The snapshot includes one own property for each top-level state key and reads
|
|
428
|
+
* the current committed value for that key. Its type is inferred from the
|
|
429
|
+
* instance's sigma-state definition.
|
|
187
430
|
*/
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
queryMethods.add(wrapped);
|
|
191
|
-
return wrapped;
|
|
431
|
+
function snapshot(publicInstance) {
|
|
432
|
+
return snapshotState(getSigmaInternals(publicInstance));
|
|
192
433
|
}
|
|
193
434
|
/**
|
|
194
|
-
*
|
|
195
|
-
* immutable public surface.
|
|
196
|
-
*
|
|
197
|
-
* `TState` may be any non-function value, including primitives.
|
|
198
|
-
*
|
|
199
|
-
* The constructor function's explicitly typed `StateHandle` parameter is what
|
|
200
|
-
* the library uses to infer the internal state and event types.
|
|
201
|
-
*
|
|
202
|
-
* Methods are automatically wrapped with `action()` from `@preact/signals`, so
|
|
203
|
-
* they are untracked and batched unless you opt into tracked reads with
|
|
204
|
-
* `query()`.
|
|
435
|
+
* Replaces an instance's committed public state from a snapshot object.
|
|
205
436
|
*
|
|
206
|
-
* The
|
|
207
|
-
* -
|
|
208
|
-
*
|
|
209
|
-
* - A top-level lens from the state handle (to expose one reactive property)
|
|
210
|
-
* - The state handle (to expose the reactive immutable base state directly)
|
|
211
|
-
* - A managed state instance (to compose another managed state as a property)
|
|
212
|
-
*
|
|
213
|
-
* Returned signals and top-level lenses are turned into getter properties, so
|
|
214
|
-
* reads are tracked by the `@preact/signals` runtime. When the base state is
|
|
215
|
-
* object-shaped, spreading the `StateHandle` into the returned object exposes
|
|
216
|
-
* its current top-level lenses at once.
|
|
217
|
-
*
|
|
218
|
-
* Events can carry at most one argument.
|
|
219
|
-
*
|
|
220
|
-
* The state constructor should be side-effect free.
|
|
437
|
+
* The replacement snapshot must be a plain object with exactly the instance's
|
|
438
|
+
* top-level state keys. Its type is inferred from the instance's sigma-state
|
|
439
|
+
* definition.
|
|
221
440
|
*/
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
441
|
+
function replaceState(publicInstance, nextState) {
|
|
442
|
+
const instance = getSigmaInternals(publicInstance);
|
|
443
|
+
if (!isPlainObject(nextState)) throw new Error("[preact-sigma] replaceState() requires a plain object snapshot");
|
|
444
|
+
assertNoPendingDraft("replaceState");
|
|
445
|
+
assertExactStateKeys(instance, nextState);
|
|
446
|
+
const finalized = finalizeReplacementState(instance, snapshot(publicInstance), nextState);
|
|
447
|
+
if (finalized.changed) publishState(instance, finalized);
|
|
448
|
+
}
|
|
449
|
+
async function resolveAsyncActionResult(owner, result) {
|
|
450
|
+
let settledValue;
|
|
451
|
+
let settledError;
|
|
452
|
+
let rejected = false;
|
|
453
|
+
try {
|
|
454
|
+
settledValue = await result;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
rejected = true;
|
|
457
|
+
settledError = error;
|
|
458
|
+
}
|
|
459
|
+
if (currentDraftOwner === owner && owner.currentDraft) {
|
|
460
|
+
if (finalizeOwnerDraft(owner)?.changed) {
|
|
461
|
+
const commitError = createCommitError(owner);
|
|
462
|
+
if (rejected) throw new AggregateError([settledError, commitError], `[preact-sigma] Async action "${owner.actionName}" rejected and left unpublished changes`);
|
|
463
|
+
throw commitError;
|
|
245
464
|
}
|
|
246
|
-
}
|
|
465
|
+
}
|
|
466
|
+
if (rejected) throw settledError;
|
|
467
|
+
return settledValue;
|
|
247
468
|
}
|
|
469
|
+
var Sigma = class extends EventTarget {
|
|
470
|
+
setup(...args) {
|
|
471
|
+
const instance = getSigmaInternals(this);
|
|
472
|
+
if (!instance.type.setupFunctions.length) throw new Error("[preact-sigma] Setup is undefined for this sigma state");
|
|
473
|
+
if (instance.disposed) throw new Error("[preact-sigma] Cannot set up a disposed sigma state");
|
|
474
|
+
instance.currentSetupCleanup?.();
|
|
475
|
+
instance.currentSetupCleanup = void 0;
|
|
476
|
+
const resources = instance.type.setupFunctions.flatMap((setup) => {
|
|
477
|
+
const result = setup.apply(getContext(instance, "setup"), args);
|
|
478
|
+
if (!Array.isArray(result)) throw new Error("[preact-sigma] Sigma setup handlers must return an array");
|
|
479
|
+
return result;
|
|
480
|
+
});
|
|
481
|
+
let cleanup;
|
|
482
|
+
if (resources.length) {
|
|
483
|
+
let cleaned = false;
|
|
484
|
+
cleanup = () => {
|
|
485
|
+
if (instance.currentSetupCleanup === cleanup) instance.currentSetupCleanup = void 0;
|
|
486
|
+
if (cleaned) return;
|
|
487
|
+
cleaned = true;
|
|
488
|
+
let errors;
|
|
489
|
+
for (let index = resources.length - 1; index >= 0; index -= 1) try {
|
|
490
|
+
disposeCleanupResource(resources[index]);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
errors ||= [];
|
|
493
|
+
errors.push(error);
|
|
494
|
+
}
|
|
495
|
+
if (errors) throw new AggregateError(errors, "Failed to dispose one or more sigma resources");
|
|
496
|
+
};
|
|
497
|
+
instance.currentSetupCleanup = cleanup;
|
|
498
|
+
} else cleanup = () => {};
|
|
499
|
+
return cleanup;
|
|
500
|
+
}
|
|
501
|
+
get(key) {
|
|
502
|
+
return getSignal(getSigmaInternals(this), key);
|
|
503
|
+
}
|
|
504
|
+
on(name, listener) {
|
|
505
|
+
const adapter = (event) => {
|
|
506
|
+
const payload = event.detail;
|
|
507
|
+
if (payload === void 0) listener();
|
|
508
|
+
else listener(payload);
|
|
509
|
+
};
|
|
510
|
+
this.addEventListener(name, adapter);
|
|
511
|
+
return () => {
|
|
512
|
+
this.removeEventListener(name, adapter);
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
Object.defineProperty(Sigma.prototype, sigmaStateBrand, { value: true });
|
|
248
517
|
//#endregion
|
|
249
|
-
//#region src/
|
|
250
|
-
|
|
251
|
-
|
|
518
|
+
//#region src/framework.ts
|
|
519
|
+
/** Checks whether a value is a sigma-state instance. */
|
|
520
|
+
function isSigmaState(value) {
|
|
521
|
+
return Boolean(value && typeof value === "object" && value[sigmaStateBrand]);
|
|
252
522
|
}
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
* Use this when a component needs the same managed-state API without defining a
|
|
257
|
-
* separate class. The constructor follows the same rules as
|
|
258
|
-
* `defineManagedState()`, including explicit typing of the `StateHandle`
|
|
259
|
-
* parameter for state and event inference.
|
|
260
|
-
*/
|
|
261
|
-
function useManagedState(constructor, initialState) {
|
|
262
|
-
const managedState = useState(() => new (defineManagedState(constructor, isFunction(initialState) ? initialState() : initialState))())[0];
|
|
263
|
-
useEffect(() => () => managedState.dispose(), [managedState]);
|
|
264
|
-
return managedState;
|
|
523
|
+
/** Creates a standalone tracked query function with the same signature as `fn`. */
|
|
524
|
+
function query(fn) {
|
|
525
|
+
return ((...args) => computed$1(() => fn(...args)).value);
|
|
265
526
|
}
|
|
266
527
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
* The listener is kept fresh automatically, so a dependency array is not part
|
|
270
|
-
* of this API. The listener receives the current value immediately and then
|
|
271
|
-
* future updates. Pass `null` to disable the subscription temporarily.
|
|
528
|
+
* Builds sigma-state constructors by accumulating default state, computeds, queries,
|
|
529
|
+
* observers, actions, and setup handlers.
|
|
272
530
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
531
|
+
var SigmaType = class extends Function {
|
|
532
|
+
constructor(name = "Sigma") {
|
|
533
|
+
super();
|
|
534
|
+
const type = {
|
|
535
|
+
actionFunctions: Object.create(null),
|
|
536
|
+
computeFunctions: Object.create(null),
|
|
537
|
+
defaultState: Object.create(null),
|
|
538
|
+
defaultStateKeys: [],
|
|
539
|
+
observeFunctions: [],
|
|
540
|
+
patchesEnabled: false,
|
|
541
|
+
queryFunctions: Object.create(null),
|
|
542
|
+
setupFunctions: []
|
|
543
|
+
};
|
|
544
|
+
const { [name]: SigmaTypeBuilder } = { [name]: class extends Sigma {
|
|
545
|
+
constructor(initialState) {
|
|
546
|
+
super();
|
|
547
|
+
initializeSigmaInstance(this, type, initialState);
|
|
548
|
+
}
|
|
549
|
+
} };
|
|
550
|
+
SigmaTypeBuilder.defaultState = function(defaultState) {
|
|
551
|
+
for (const key in defaultState) {
|
|
552
|
+
if (defaultState[key] === void 0) continue;
|
|
553
|
+
type.defaultState[key] = defaultState[key];
|
|
554
|
+
type.defaultStateKeys.push(key);
|
|
555
|
+
}
|
|
556
|
+
return this;
|
|
557
|
+
};
|
|
558
|
+
SigmaTypeBuilder.computed = function(computeFunctions) {
|
|
559
|
+
for (const key in computeFunctions) {
|
|
560
|
+
assertDefinitionKeyAvailable(type, key, "computed");
|
|
561
|
+
type.computeFunctions[key] = computeFunctions[key];
|
|
562
|
+
Object.defineProperty(this.prototype, key, { get: function() {
|
|
563
|
+
return this["#" + key].value;
|
|
564
|
+
} });
|
|
565
|
+
}
|
|
566
|
+
return this;
|
|
567
|
+
};
|
|
568
|
+
SigmaTypeBuilder.queries = function(queryFunctions) {
|
|
569
|
+
for (const key in queryFunctions) {
|
|
570
|
+
assertDefinitionKeyAvailable(type, key, "query");
|
|
571
|
+
const queryFunction = queryFunctions[key];
|
|
572
|
+
type.queryFunctions[key] = queryFunction;
|
|
573
|
+
Object.defineProperty(this.prototype, key, { value: buildQueryMethod(queryFunction) });
|
|
574
|
+
}
|
|
575
|
+
return this;
|
|
576
|
+
};
|
|
577
|
+
SigmaTypeBuilder.observe = function(listener, options) {
|
|
578
|
+
type.observeFunctions.push(listener);
|
|
579
|
+
if (options?.patches) type.patchesEnabled = true;
|
|
580
|
+
return this;
|
|
581
|
+
};
|
|
582
|
+
SigmaTypeBuilder.actions = function(actionFunctions) {
|
|
583
|
+
for (const key in actionFunctions) {
|
|
584
|
+
assertDefinitionKeyAvailable(type, key, "action");
|
|
585
|
+
const actionFunction = actionFunctions[key];
|
|
586
|
+
type.actionFunctions[key] = actionFunction;
|
|
587
|
+
Object.defineProperty(this.prototype, key, { value: buildActionMethod(key, actionFunction) });
|
|
588
|
+
}
|
|
589
|
+
return this;
|
|
590
|
+
};
|
|
591
|
+
SigmaTypeBuilder.setup = function(setup) {
|
|
592
|
+
type.setupFunctions.push(setup);
|
|
593
|
+
return this;
|
|
594
|
+
};
|
|
595
|
+
return SigmaTypeBuilder;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/listener.ts
|
|
600
|
+
/** Adds an event listener and returns a cleanup function that removes it. */
|
|
601
|
+
function listen(target, name, fn) {
|
|
602
|
+
const listener = isSigmaState(target) ? (event) => fn(event.detail) : fn;
|
|
603
|
+
target.addEventListener(name, listener);
|
|
604
|
+
return () => {
|
|
605
|
+
target.removeEventListener(name, listener);
|
|
606
|
+
};
|
|
276
607
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/hooks.ts
|
|
610
|
+
/** Creates one sigma-state instance for a component and manages its setup cleanup. */
|
|
611
|
+
function useSigma(create, setupArgs) {
|
|
612
|
+
const sigmaState = useState(create)[0];
|
|
613
|
+
if (shouldSetup(sigmaState)) {
|
|
614
|
+
const args = setupArgs ?? [];
|
|
615
|
+
useEffect(() => sigmaState.setup(...args), [sigmaState, ...args]);
|
|
616
|
+
}
|
|
617
|
+
return sigmaState;
|
|
618
|
+
}
|
|
619
|
+
/** Attaches an event listener in a component and cleans it up when dependencies change. */
|
|
620
|
+
function useListener(target, name, listener) {
|
|
621
|
+
const listenerRef = useRef(listener);
|
|
622
|
+
listenerRef.current = listener;
|
|
288
623
|
useEffect(() => {
|
|
289
624
|
if (!target) return;
|
|
290
|
-
|
|
291
|
-
target.addEventListener(name, listener);
|
|
292
|
-
return () => target.removeEventListener(name, listener);
|
|
625
|
+
return listen(target, name, ((event) => listenerRef.current(event)));
|
|
293
626
|
}, [target, name]);
|
|
294
627
|
}
|
|
295
|
-
function useStableCallback(callback) {
|
|
296
|
-
const ref = useRef(callback);
|
|
297
|
-
ref.current = callback;
|
|
298
|
-
return useCallback((...params) => (0, ref.current)(...params), []);
|
|
299
|
-
}
|
|
300
628
|
//#endregion
|
|
301
|
-
export { batch, computed,
|
|
629
|
+
export { SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|