@getforma/core 1.0.10 → 1.2.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 (55) hide show
  1. package/dist/{chunk-T33QUD2Y.cjs → chunk-E3FV2VSU.cjs} +43 -43
  2. package/dist/{chunk-T33QUD2Y.cjs.map → chunk-E3FV2VSU.cjs.map} +1 -1
  3. package/dist/chunk-ETPJTPYD.js +103 -0
  4. package/dist/chunk-ETPJTPYD.js.map +1 -0
  5. package/dist/chunk-H7MDUHS5.cjs +110 -0
  6. package/dist/chunk-H7MDUHS5.cjs.map +1 -0
  7. package/dist/{chunk-6FW5E54W.cjs → chunk-KUXNZ5MG.cjs} +127 -103
  8. package/dist/chunk-KUXNZ5MG.cjs.map +1 -0
  9. package/dist/{chunk-3G7ET4O5.js → chunk-Y3A2EVVS.js} +121 -94
  10. package/dist/chunk-Y3A2EVVS.js.map +1 -0
  11. package/dist/{chunk-AFRFF7XL.js → chunk-YRNYOZF3.js} +4 -4
  12. package/dist/{chunk-AFRFF7XL.js.map → chunk-YRNYOZF3.js.map} +1 -1
  13. package/dist/forma-runtime-csp.js +52 -4
  14. package/dist/forma-runtime.js +98 -10
  15. package/dist/formajs-runtime-hardened.global.js +52 -4
  16. package/dist/formajs-runtime-hardened.global.js.map +1 -1
  17. package/dist/formajs-runtime.global.js +98 -10
  18. package/dist/formajs-runtime.global.js.map +1 -1
  19. package/dist/formajs.global.js +368 -147
  20. package/dist/formajs.global.js.map +1 -1
  21. package/dist/http.cjs +11 -11
  22. package/dist/http.js +2 -2
  23. package/dist/index.cjs +262 -141
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +32 -27
  26. package/dist/index.d.ts +32 -27
  27. package/dist/index.js +204 -83
  28. package/dist/index.js.map +1 -1
  29. package/dist/{resource-DeEzxUz6.d.cts → resource-D6HfVgiA.d.cts} +7 -2
  30. package/dist/{resource-BHsgURy0.d.ts → resource-ljs2X5NM.d.ts} +7 -2
  31. package/dist/runtime-hardened.cjs +53 -3
  32. package/dist/runtime-hardened.cjs.map +1 -1
  33. package/dist/runtime-hardened.js +53 -3
  34. package/dist/runtime-hardened.js.map +1 -1
  35. package/dist/runtime.cjs +29 -29
  36. package/dist/runtime.js +3 -3
  37. package/dist/server.cjs +7 -7
  38. package/dist/server.d.cts +2 -2
  39. package/dist/server.d.ts +2 -2
  40. package/dist/server.js +2 -2
  41. package/dist/{signal-C9v4akyJ.d.cts → signal-CRBJYQ6B.d.cts} +0 -8
  42. package/dist/{signal-C9v4akyJ.d.ts → signal-CRBJYQ6B.d.ts} +0 -8
  43. package/dist/tc39-compat.cjs +3 -6
  44. package/dist/tc39-compat.cjs.map +1 -1
  45. package/dist/tc39-compat.d.cts +1 -1
  46. package/dist/tc39-compat.d.ts +1 -1
  47. package/dist/tc39-compat.js +1 -4
  48. package/dist/tc39-compat.js.map +1 -1
  49. package/package.json +1 -1
  50. package/dist/chunk-3G7ET4O5.js.map +0 -1
  51. package/dist/chunk-6FW5E54W.cjs.map +0 -1
  52. package/dist/chunk-HLM5BZZQ.js +0 -35
  53. package/dist/chunk-HLM5BZZQ.js.map +0 -1
  54. package/dist/chunk-QLPCVK7C.cjs +0 -38
  55. package/dist/chunk-QLPCVK7C.cjs.map +0 -1
@@ -0,0 +1,103 @@
1
+ import { signal, computed, setActiveSub, trigger } from 'alien-signals';
2
+
3
+ // src/reactive/dev.ts
4
+ function isDev() {
5
+ if (typeof process !== "undefined") {
6
+ return process.env?.NODE_ENV !== "production";
7
+ }
8
+ return typeof __FORMA_DEV__ !== "undefined" ? !!__FORMA_DEV__ : false;
9
+ }
10
+ var __DEV__ = isDev();
11
+ var _errorHandlers = /* @__PURE__ */ new Set();
12
+ function onError(handler) {
13
+ _errorHandlers.add(handler);
14
+ return () => {
15
+ _errorHandlers.delete(handler);
16
+ };
17
+ }
18
+ function reportError(error, source) {
19
+ for (const handler of _errorHandlers) {
20
+ try {
21
+ handler(error, source ? { source } : {});
22
+ } catch {
23
+ }
24
+ }
25
+ if (isDev()) {
26
+ console.error(`[forma] ${source ?? "Unknown"} error:`, error);
27
+ }
28
+ }
29
+ var signalNames = /* @__PURE__ */ new WeakMap();
30
+ function value(v) {
31
+ return () => v;
32
+ }
33
+ function applySignalSet(s, v, equals) {
34
+ if (typeof v !== "function") {
35
+ if (!equals) {
36
+ s(v);
37
+ return;
38
+ }
39
+ const prevSub2 = setActiveSub(void 0);
40
+ const prev2 = s();
41
+ setActiveSub(prevSub2);
42
+ if (equals(prev2, v)) return;
43
+ s(v);
44
+ if (Object.is(prev2, v)) forceNotify(s);
45
+ return;
46
+ }
47
+ const prevSub = setActiveSub(void 0);
48
+ const prev = s();
49
+ setActiveSub(prevSub);
50
+ const next = v(prev);
51
+ if (equals && equals(prev, next)) return;
52
+ s(next);
53
+ if (equals && Object.is(prev, next)) forceNotify(s);
54
+ }
55
+ function forceNotify(s) {
56
+ trigger(() => {
57
+ s();
58
+ });
59
+ }
60
+ function createSignal(initialValue, options) {
61
+ const s = signal(initialValue);
62
+ const getter = s;
63
+ if (__DEV__ && options?.name) {
64
+ signalNames.set(getter, options.name);
65
+ }
66
+ const eq = options?.equals;
67
+ const setter = (v) => applySignalSet(s, v, eq);
68
+ return [getter, setter];
69
+ }
70
+ var ERR = /* @__PURE__ */ Symbol("formaComputedError");
71
+ function isErrBox(v) {
72
+ return typeof v === "object" && v !== null && ERR in v;
73
+ }
74
+ function createComputed(fn) {
75
+ let errored = false;
76
+ let error;
77
+ let lastGood;
78
+ const raw = computed(() => {
79
+ try {
80
+ const v = fn(lastGood);
81
+ errored = false;
82
+ error = void 0;
83
+ lastGood = v;
84
+ return v;
85
+ } catch (e) {
86
+ errored = true;
87
+ error = e;
88
+ reportError(e, "computed");
89
+ return { [ERR]: e };
90
+ }
91
+ });
92
+ const reader = () => {
93
+ const v = raw();
94
+ if (errored || isErrBox(v)) throw error;
95
+ return v;
96
+ };
97
+ Object.defineProperty(reader, "name", { value: raw.name, configurable: true });
98
+ return reader;
99
+ }
100
+
101
+ export { __DEV__, createComputed, createSignal, onError, reportError, value };
102
+ //# sourceMappingURL=chunk-ETPJTPYD.js.map
103
+ //# sourceMappingURL=chunk-ETPJTPYD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reactive/dev.ts","../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","prev","createRawSignal","rawComputed"],"mappings":"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","file":"chunk-ETPJTPYD.js","sourcesContent":["/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by the bundler (tsup define) for dead-code elimination in\n// dist. For unbundled/raw-source usage it derives from NODE_ENV, and — when the\n// environment is indeterminate — defaults to *false* (production-safe: no console\n// noise) unless a consumer opts in via a global __FORMA_DEV__.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\ndeclare const __FORMA_DEV__: boolean | undefined;\n\n/**\n * Lazily evaluate whether we're in a dev environment. Read per-call (not frozen\n * at import) so runtime NODE_ENV changes are honored and so official prod dist\n * builds — where tsup hard-defines __DEV__ = false — stay quiet regardless.\n */\nfunction isDev(): boolean {\n if (typeof process !== 'undefined') {\n return process!.env?.NODE_ENV !== 'production';\n }\n return typeof __FORMA_DEV__ !== 'undefined' ? !!__FORMA_DEV__ : false;\n}\n\nexport const __DEV__: boolean = isDev();\n\n// ---------------------------------------------------------------------------\n// Global error handlers\n// ---------------------------------------------------------------------------\n\n/** Callback signature for a global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nconst _errorHandlers = new Set<ErrorHandler>();\n\n/**\n * Install a global error handler for FormaJS reactive errors. Called when\n * effects, computeds, or event handlers throw. Multiple handlers may be\n * registered; returns an unsubscribe function that removes only this handler.\n *\n * ```ts\n * const off = onError((err, info) => {\n * Sentry.captureException(err, { tags: { source: info?.source } });\n * });\n * // later: off();\n * ```\n */\nexport function onError(handler: ErrorHandler): () => void {\n _errorHandlers.add(handler);\n return () => { _errorHandlers.delete(handler); };\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n for (const handler of _errorHandlers) {\n try { handler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (isDev()) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub, trigger } from 'alien-signals';\nimport { __DEV__ } from './dev.js';\n\n// Debug names live in a side table rather than on the getter's `.name`, so\n// alien-signals' isSignal() (which keys off the bound function name) still\n// recognizes named signals.\nconst signalNames = new WeakMap<object, string>();\n\n/** Debug name of a signal getter, if one was provided (dev-only). */\nexport function getSignalName(fn: unknown): string | undefined {\n return typeof fn === 'function' ? signalNames.get(fn as object) : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n // Fast path: plain value, no equals — no prev read needed. (A running effect\n // writing its own dependency is detected at the effect level via the Pending\n // flag, so signal.ts does not need to do anything special here.)\n if (typeof v !== 'function') {\n if (!equals) {\n s(v);\n return;\n }\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // suppressed by custom equality\n s(v);\n if (Object.is(prev, v)) forceNotify(s);\n return;\n }\n\n // Functional update: read prev without tracking.\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // suppressed by custom equality\n s(next);\n if (equals && Object.is(prev, next)) forceNotify(s);\n}\n\n/**\n * When a custom `equals` decided to APPLY (returned false) but the value is the\n * identical reference, alien-signals suppresses propagation (its guard is\n * `pendingValue !== value`). Force-notify subscribers so `equals: () => false`\n * (\"always notify\") works for mutate-in-place patterns.\n */\nfunction forceNotify<T>(s: RawSignal<T>): void {\n trigger(() => { s(); });\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n if (__DEV__ && options?.name) {\n signalNames.set(getter as object, options.name);\n }\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\nimport { reportError } from './dev.js';\n\n/** Unique per-throw marker so alien-signals sees a value change and propagates. */\nconst ERR = Symbol('formaComputedError');\ninterface ErrBox { [ERR]: unknown; }\nfunction isErrBox(v: unknown): v is ErrBox {\n return typeof v === 'object' && v !== null && ERR in (v as object);\n}\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n // alien-signals' updateComputed leaves a throwing computed flagged clean with a\n // stale value, so a later read silently returns the stale value and never\n // rethrows. We cache the error in closure and rethrow on read until a\n // dependency changes (TC39/Solid). On throw we return a FRESH sentinel object so\n // alien-signals sees a value change and propagates the error to subscribers; the\n // user getter only ever sees the last GOOD value as `previousValue`.\n let errored = false;\n let error: unknown;\n let lastGood: T | undefined;\n\n const raw = rawComputed<T | ErrBox>(() => {\n try {\n const v = fn(lastGood);\n errored = false;\n error = undefined;\n lastGood = v;\n return v;\n } catch (e) {\n errored = true;\n error = e;\n reportError(e, 'computed');\n return { [ERR]: e };\n }\n });\n\n const reader = () => {\n const v = raw();\n if (errored || isErrBox(v)) throw error;\n return v as T;\n };\n // Preserve alien-signals' isComputed() recognition, which keys off the bound\n // function name (`bound computedOper`). Our reader wraps the raw computed, so\n // copy the raw name onto it.\n Object.defineProperty(reader, 'name', { value: raw.name, configurable: true });\n return reader;\n}\n"]}
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ var alienSignals = require('alien-signals');
4
+
5
+ // src/reactive/dev.ts
6
+ function isDev() {
7
+ if (typeof process !== "undefined") {
8
+ return process.env?.NODE_ENV !== "production";
9
+ }
10
+ return typeof __FORMA_DEV__ !== "undefined" ? !!__FORMA_DEV__ : false;
11
+ }
12
+ var __DEV__ = isDev();
13
+ var _errorHandlers = /* @__PURE__ */ new Set();
14
+ function onError(handler) {
15
+ _errorHandlers.add(handler);
16
+ return () => {
17
+ _errorHandlers.delete(handler);
18
+ };
19
+ }
20
+ function reportError(error, source) {
21
+ for (const handler of _errorHandlers) {
22
+ try {
23
+ handler(error, source ? { source } : {});
24
+ } catch {
25
+ }
26
+ }
27
+ if (isDev()) {
28
+ console.error(`[forma] ${source ?? "Unknown"} error:`, error);
29
+ }
30
+ }
31
+ var signalNames = /* @__PURE__ */ new WeakMap();
32
+ function value(v) {
33
+ return () => v;
34
+ }
35
+ function applySignalSet(s, v, equals) {
36
+ if (typeof v !== "function") {
37
+ if (!equals) {
38
+ s(v);
39
+ return;
40
+ }
41
+ const prevSub2 = alienSignals.setActiveSub(void 0);
42
+ const prev2 = s();
43
+ alienSignals.setActiveSub(prevSub2);
44
+ if (equals(prev2, v)) return;
45
+ s(v);
46
+ if (Object.is(prev2, v)) forceNotify(s);
47
+ return;
48
+ }
49
+ const prevSub = alienSignals.setActiveSub(void 0);
50
+ const prev = s();
51
+ alienSignals.setActiveSub(prevSub);
52
+ const next = v(prev);
53
+ if (equals && equals(prev, next)) return;
54
+ s(next);
55
+ if (equals && Object.is(prev, next)) forceNotify(s);
56
+ }
57
+ function forceNotify(s) {
58
+ alienSignals.trigger(() => {
59
+ s();
60
+ });
61
+ }
62
+ function createSignal(initialValue, options) {
63
+ const s = alienSignals.signal(initialValue);
64
+ const getter = s;
65
+ if (__DEV__ && options?.name) {
66
+ signalNames.set(getter, options.name);
67
+ }
68
+ const eq = options?.equals;
69
+ const setter = (v) => applySignalSet(s, v, eq);
70
+ return [getter, setter];
71
+ }
72
+ var ERR = /* @__PURE__ */ Symbol("formaComputedError");
73
+ function isErrBox(v) {
74
+ return typeof v === "object" && v !== null && ERR in v;
75
+ }
76
+ function createComputed(fn) {
77
+ let errored = false;
78
+ let error;
79
+ let lastGood;
80
+ const raw = alienSignals.computed(() => {
81
+ try {
82
+ const v = fn(lastGood);
83
+ errored = false;
84
+ error = void 0;
85
+ lastGood = v;
86
+ return v;
87
+ } catch (e) {
88
+ errored = true;
89
+ error = e;
90
+ reportError(e, "computed");
91
+ return { [ERR]: e };
92
+ }
93
+ });
94
+ const reader = () => {
95
+ const v = raw();
96
+ if (errored || isErrBox(v)) throw error;
97
+ return v;
98
+ };
99
+ Object.defineProperty(reader, "name", { value: raw.name, configurable: true });
100
+ return reader;
101
+ }
102
+
103
+ exports.__DEV__ = __DEV__;
104
+ exports.createComputed = createComputed;
105
+ exports.createSignal = createSignal;
106
+ exports.onError = onError;
107
+ exports.reportError = reportError;
108
+ exports.value = value;
109
+ //# sourceMappingURL=chunk-H7MDUHS5.cjs.map
110
+ //# sourceMappingURL=chunk-H7MDUHS5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reactive/dev.ts","../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","setActiveSub","prev","trigger","createRawSignal","rawComputed"],"mappings":"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","file":"chunk-H7MDUHS5.cjs","sourcesContent":["/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by the bundler (tsup define) for dead-code elimination in\n// dist. For unbundled/raw-source usage it derives from NODE_ENV, and — when the\n// environment is indeterminate — defaults to *false* (production-safe: no console\n// noise) unless a consumer opts in via a global __FORMA_DEV__.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\ndeclare const __FORMA_DEV__: boolean | undefined;\n\n/**\n * Lazily evaluate whether we're in a dev environment. Read per-call (not frozen\n * at import) so runtime NODE_ENV changes are honored and so official prod dist\n * builds — where tsup hard-defines __DEV__ = false — stay quiet regardless.\n */\nfunction isDev(): boolean {\n if (typeof process !== 'undefined') {\n return process!.env?.NODE_ENV !== 'production';\n }\n return typeof __FORMA_DEV__ !== 'undefined' ? !!__FORMA_DEV__ : false;\n}\n\nexport const __DEV__: boolean = isDev();\n\n// ---------------------------------------------------------------------------\n// Global error handlers\n// ---------------------------------------------------------------------------\n\n/** Callback signature for a global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nconst _errorHandlers = new Set<ErrorHandler>();\n\n/**\n * Install a global error handler for FormaJS reactive errors. Called when\n * effects, computeds, or event handlers throw. Multiple handlers may be\n * registered; returns an unsubscribe function that removes only this handler.\n *\n * ```ts\n * const off = onError((err, info) => {\n * Sentry.captureException(err, { tags: { source: info?.source } });\n * });\n * // later: off();\n * ```\n */\nexport function onError(handler: ErrorHandler): () => void {\n _errorHandlers.add(handler);\n return () => { _errorHandlers.delete(handler); };\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n for (const handler of _errorHandlers) {\n try { handler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (isDev()) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub, trigger } from 'alien-signals';\nimport { __DEV__ } from './dev.js';\n\n// Debug names live in a side table rather than on the getter's `.name`, so\n// alien-signals' isSignal() (which keys off the bound function name) still\n// recognizes named signals.\nconst signalNames = new WeakMap<object, string>();\n\n/** Debug name of a signal getter, if one was provided (dev-only). */\nexport function getSignalName(fn: unknown): string | undefined {\n return typeof fn === 'function' ? signalNames.get(fn as object) : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n // Fast path: plain value, no equals — no prev read needed. (A running effect\n // writing its own dependency is detected at the effect level via the Pending\n // flag, so signal.ts does not need to do anything special here.)\n if (typeof v !== 'function') {\n if (!equals) {\n s(v);\n return;\n }\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // suppressed by custom equality\n s(v);\n if (Object.is(prev, v)) forceNotify(s);\n return;\n }\n\n // Functional update: read prev without tracking.\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // suppressed by custom equality\n s(next);\n if (equals && Object.is(prev, next)) forceNotify(s);\n}\n\n/**\n * When a custom `equals` decided to APPLY (returned false) but the value is the\n * identical reference, alien-signals suppresses propagation (its guard is\n * `pendingValue !== value`). Force-notify subscribers so `equals: () => false`\n * (\"always notify\") works for mutate-in-place patterns.\n */\nfunction forceNotify<T>(s: RawSignal<T>): void {\n trigger(() => { s(); });\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n if (__DEV__ && options?.name) {\n signalNames.set(getter as object, options.name);\n }\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\nimport { reportError } from './dev.js';\n\n/** Unique per-throw marker so alien-signals sees a value change and propagates. */\nconst ERR = Symbol('formaComputedError');\ninterface ErrBox { [ERR]: unknown; }\nfunction isErrBox(v: unknown): v is ErrBox {\n return typeof v === 'object' && v !== null && ERR in (v as object);\n}\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n // alien-signals' updateComputed leaves a throwing computed flagged clean with a\n // stale value, so a later read silently returns the stale value and never\n // rethrows. We cache the error in closure and rethrow on read until a\n // dependency changes (TC39/Solid). On throw we return a FRESH sentinel object so\n // alien-signals sees a value change and propagates the error to subscribers; the\n // user getter only ever sees the last GOOD value as `previousValue`.\n let errored = false;\n let error: unknown;\n let lastGood: T | undefined;\n\n const raw = rawComputed<T | ErrBox>(() => {\n try {\n const v = fn(lastGood);\n errored = false;\n error = undefined;\n lastGood = v;\n return v;\n } catch (e) {\n errored = true;\n error = e;\n reportError(e, 'computed');\n return { [ERR]: e };\n }\n });\n\n const reader = () => {\n const v = raw();\n if (errored || isErrBox(v)) throw error;\n return v as T;\n };\n // Preserve alien-signals' isComputed() recognition, which keys off the bound\n // function name (`bound computedOper`). Our reader wraps the raw computed, so\n // copy the raw name onto it.\n Object.defineProperty(reader, 'name', { value: raw.name, configurable: true });\n return reader;\n}\n"]}
@@ -1,33 +1,77 @@
1
1
  'use strict';
2
2
 
3
- var chunkQLPCVK7C_cjs = require('./chunk-QLPCVK7C.cjs');
3
+ var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
4
4
  var alienSignals = require('alien-signals');
5
5
 
6
6
  var currentRoot = null;
7
7
  var rootStack = [];
8
+ var currentOwner = null;
9
+ function getOwner() {
10
+ return currentOwner;
11
+ }
12
+ function runWithOwner(owner, fn) {
13
+ const prev = currentOwner;
14
+ currentOwner = owner;
15
+ try {
16
+ return fn();
17
+ } finally {
18
+ currentOwner = prev;
19
+ }
20
+ }
21
+ function registerOwnerDisposer(owner, dispose) {
22
+ owner.disposers.push(dispose);
23
+ }
24
+ function createChildOwner() {
25
+ return { disposers: [] };
26
+ }
27
+ function disposeOwner(owner) {
28
+ const ds = owner.disposers;
29
+ for (const d of ds) {
30
+ try {
31
+ d();
32
+ } catch {
33
+ }
34
+ }
35
+ ds.length = 0;
36
+ }
8
37
  function createRootImpl(fn, owned) {
9
38
  const scope = { disposers: [], scopeDispose: null };
10
39
  const parentRoot = owned ? currentRoot : null;
11
40
  rootStack.push(currentRoot);
12
41
  currentRoot = scope;
42
+ const prevOwner = currentOwner;
43
+ currentOwner = scope;
13
44
  let disposed = false;
14
- const dispose = () => {
15
- if (disposed) return;
16
- disposed = true;
17
- if (scope.scopeDispose) {
45
+ let setupComplete = false;
46
+ let disposeRequestedDuringSetup = false;
47
+ const runDisposers = () => {
48
+ for (const d of scope.disposers) {
18
49
  try {
19
- scope.scopeDispose();
50
+ d();
20
51
  } catch {
21
52
  }
22
- scope.scopeDispose = null;
23
53
  }
24
- for (const d of scope.disposers) {
54
+ scope.disposers.length = 0;
55
+ };
56
+ const runTeardown = () => {
57
+ if (scope.scopeDispose) {
25
58
  try {
26
- d();
59
+ scope.scopeDispose();
27
60
  } catch {
28
61
  }
62
+ scope.scopeDispose = null;
29
63
  }
30
- scope.disposers.length = 0;
64
+ runDisposers();
65
+ };
66
+ const dispose = () => {
67
+ if (disposed) return;
68
+ disposed = true;
69
+ if (!setupComplete) {
70
+ disposeRequestedDuringSetup = true;
71
+ runDisposers();
72
+ return;
73
+ }
74
+ runTeardown();
31
75
  };
32
76
  if (parentRoot) {
33
77
  parentRoot.disposers.push(dispose);
@@ -48,8 +92,13 @@ function createRootImpl(fn, owned) {
48
92
  alienSignals.setActiveSub(prevSub);
49
93
  }
50
94
  }
95
+ setupComplete = true;
96
+ if (disposeRequestedDuringSetup) {
97
+ runTeardown();
98
+ }
51
99
  } finally {
52
100
  currentRoot = rootStack.pop() ?? null;
101
+ currentOwner = prevOwner;
53
102
  }
54
103
  return result;
55
104
  }
@@ -60,12 +109,12 @@ function createUnownedRoot(fn) {
60
109
  return createRootImpl(fn, false);
61
110
  }
62
111
  function registerDisposer(dispose) {
63
- if (currentRoot) {
64
- currentRoot.disposers.push(dispose);
112
+ if (currentOwner) {
113
+ currentOwner.disposers.push(dispose);
65
114
  }
66
115
  }
67
116
  function hasActiveRoot() {
68
- return currentRoot !== null;
117
+ return currentOwner !== null;
69
118
  }
70
119
 
71
120
  // src/reactive/cleanup.ts
@@ -78,29 +127,12 @@ function setCleanupCollector(collector) {
78
127
  currentCleanupCollector = collector;
79
128
  return prev;
80
129
  }
81
-
82
- // src/reactive/dev.ts
83
- var __DEV__ = typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true;
84
- var _errorHandler = null;
85
- function onError(handler) {
86
- _errorHandler = handler;
87
- }
88
- function reportError(error, source) {
89
- if (_errorHandler) {
90
- try {
91
- _errorHandler(error, source ? { source } : {});
92
- } catch {
93
- }
94
- }
95
- if (__DEV__) {
96
- console.error(`[forma] ${source ?? "Unknown"} error:`, error);
97
- }
98
- }
99
130
  var POOL_SIZE = 32;
100
131
  var MAX_REENTRANT_RUNS = 100;
101
132
  var pool = [];
102
133
  for (let i = 0; i < POOL_SIZE; i++) pool.push([]);
103
134
  var poolIdx = POOL_SIZE;
135
+ var PENDING = 32;
104
136
  function acquireArray() {
105
137
  if (poolIdx > 0) {
106
138
  const arr = pool[--poolIdx];
@@ -120,7 +152,7 @@ function runCleanup(fn) {
120
152
  try {
121
153
  fn();
122
154
  } catch (e) {
123
- reportError(e, "effect cleanup");
155
+ chunkH7MDUHS5_cjs.reportError(e, "effect cleanup");
124
156
  }
125
157
  }
126
158
  function runCleanups(bag) {
@@ -129,7 +161,7 @@ function runCleanups(bag) {
129
161
  try {
130
162
  bag[i]();
131
163
  } catch (e) {
132
- reportError(e, "effect cleanup");
164
+ chunkH7MDUHS5_cjs.reportError(e, "effect cleanup");
133
165
  }
134
166
  }
135
167
  }
@@ -141,7 +173,8 @@ function internalEffect(fn) {
141
173
  return dispose;
142
174
  }
143
175
  function createEffect(fn) {
144
- const shouldRegister = hasActiveRoot();
176
+ const ownerAtCreate = getOwner();
177
+ const childOwner = createChildOwner();
145
178
  let cleanup;
146
179
  let cleanupBag;
147
180
  let nextCleanup;
@@ -160,11 +193,8 @@ function createEffect(fn) {
160
193
  }
161
194
  nextCleanup = cb;
162
195
  };
163
- let skipCleanupInfra = false;
164
- let firstRun = true;
165
- let running = false;
166
- let rerunRequested = false;
167
196
  const runOnce = () => {
197
+ disposeOwner(childOwner);
168
198
  if (cleanup !== void 0) {
169
199
  runCleanup(cleanup);
170
200
  cleanup = void 0;
@@ -174,68 +204,46 @@ function createEffect(fn) {
174
204
  releaseArray(cleanupBag);
175
205
  cleanupBag = void 0;
176
206
  }
177
- if (skipCleanupInfra) {
178
- try {
179
- fn();
180
- } catch (e) {
181
- reportError(e, "effect");
182
- }
183
- return;
184
- }
185
207
  nextCleanup = void 0;
186
208
  nextCleanupBag = void 0;
187
209
  const prevCollector = setCleanupCollector(addCleanup);
188
210
  try {
189
- const result = fn();
211
+ const result = runWithOwner(childOwner, fn);
190
212
  if (typeof result === "function") {
191
213
  addCleanup(result);
192
214
  }
193
- if (nextCleanup === void 0 && nextCleanupBag === void 0) {
194
- if (firstRun) skipCleanupInfra = true;
195
- return;
196
- }
197
215
  if (nextCleanupBag !== void 0) {
198
216
  cleanupBag = nextCleanupBag;
199
- } else {
217
+ } else if (nextCleanup !== void 0) {
200
218
  cleanup = nextCleanup;
201
219
  }
202
220
  } catch (e) {
203
- reportError(e, "effect");
221
+ chunkH7MDUHS5_cjs.reportError(e, "effect");
204
222
  if (nextCleanupBag !== void 0) {
205
223
  cleanupBag = nextCleanupBag;
206
- } else {
224
+ } else if (nextCleanup !== void 0) {
207
225
  cleanup = nextCleanup;
208
226
  }
209
227
  } finally {
210
228
  setCleanupCollector(prevCollector);
211
- firstRun = false;
212
229
  }
213
230
  };
214
231
  const safeFn = () => {
215
- if (running) {
216
- rerunRequested = true;
217
- return;
218
- }
219
- running = true;
220
- try {
221
- let reentrantRuns = 0;
222
- do {
223
- rerunRequested = false;
224
- runOnce();
225
- if (rerunRequested) {
226
- reentrantRuns++;
227
- if (reentrantRuns >= MAX_REENTRANT_RUNS) {
228
- reportError(
229
- new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} re-entrant runs`),
230
- "effect"
231
- );
232
- rerunRequested = false;
233
- }
234
- }
235
- } while (rerunRequested);
236
- } finally {
237
- running = false;
238
- }
232
+ const node = alienSignals.getActiveSub();
233
+ let reentrantRuns = 0;
234
+ do {
235
+ if (node) node.flags &= ~PENDING;
236
+ runOnce();
237
+ if (node === void 0 || (node.flags & PENDING) === 0) break;
238
+ if (++reentrantRuns >= MAX_REENTRANT_RUNS) {
239
+ node.flags &= ~PENDING;
240
+ chunkH7MDUHS5_cjs.reportError(
241
+ new Error(`createEffect exceeded ${MAX_REENTRANT_RUNS} self-triggered re-runs (cycle?)`),
242
+ "effect"
243
+ );
244
+ break;
245
+ }
246
+ } while (true);
239
247
  };
240
248
  const dispose = alienSignals.effect(safeFn);
241
249
  let disposed = false;
@@ -243,6 +251,7 @@ function createEffect(fn) {
243
251
  if (disposed) return;
244
252
  disposed = true;
245
253
  dispose();
254
+ disposeOwner(childOwner);
246
255
  if (cleanup !== void 0) {
247
256
  runCleanup(cleanup);
248
257
  cleanup = void 0;
@@ -253,14 +262,14 @@ function createEffect(fn) {
253
262
  cleanupBag = void 0;
254
263
  }
255
264
  };
256
- if (shouldRegister) {
257
- registerDisposer(wrappedDispose);
265
+ if (ownerAtCreate) {
266
+ registerOwnerDisposer(ownerAtCreate, wrappedDispose);
258
267
  }
259
268
  return wrappedDispose;
260
269
  }
261
270
 
262
271
  // src/reactive/memo.ts
263
- var createMemo = chunkQLPCVK7C_cjs.createComputed;
272
+ var createMemo = chunkH7MDUHS5_cjs.createComputed;
264
273
  function batch(fn) {
265
274
  alienSignals.startBatch();
266
275
  try {
@@ -302,7 +311,7 @@ function createRef(initialValue) {
302
311
 
303
312
  // src/reactive/reducer.ts
304
313
  function createReducer(reducer, initialState) {
305
- const [state, setState] = chunkQLPCVK7C_cjs.createSignal(initialState);
314
+ const [state, setState] = chunkH7MDUHS5_cjs.createSignal(initialState);
306
315
  const dispatch = (action) => {
307
316
  setState((prev) => reducer(prev, action));
308
317
  };
@@ -325,9 +334,9 @@ function getSuspenseContext() {
325
334
 
326
335
  // src/reactive/resource.ts
327
336
  function createResource(source, fetcher, options) {
328
- const [data, setData] = chunkQLPCVK7C_cjs.createSignal(options?.initialValue);
329
- const [loading, setLoading] = chunkQLPCVK7C_cjs.createSignal(false);
330
- const [error, setError] = chunkQLPCVK7C_cjs.createSignal(void 0);
337
+ const [data, setData] = chunkH7MDUHS5_cjs.createSignal(options?.initialValue);
338
+ const [loading, setLoading] = chunkH7MDUHS5_cjs.createSignal(false);
339
+ const [error, setError] = chunkH7MDUHS5_cjs.createSignal(void 0);
331
340
  const suspenseCtx = getSuspenseContext();
332
341
  let abortController = null;
333
342
  let fetchVersion = 0;
@@ -345,25 +354,34 @@ function createResource(source, fetcher, options) {
345
354
  suspenseCtx.increment();
346
355
  suspensePending = true;
347
356
  }
348
- setLoading(true);
349
- setError(void 0);
350
- Promise.resolve(fetcher(sourceValue)).then((result) => {
357
+ batch(() => {
358
+ setLoading(true);
359
+ setError(void 0);
360
+ });
361
+ let promise;
362
+ try {
363
+ promise = Promise.resolve(fetcher(sourceValue, { signal: controller.signal }));
364
+ } catch (err) {
365
+ promise = Promise.reject(err);
366
+ }
367
+ promise.then((result) => {
351
368
  if (isLatest() && !controller.signal.aborted) {
352
- setData(() => result);
369
+ batch(() => {
370
+ setData(() => result);
371
+ setLoading(false);
372
+ });
353
373
  }
354
374
  }).catch((err) => {
355
- if (isLatest() && !controller.signal.aborted) {
356
- if (err?.name !== "AbortError") {
375
+ if (isLatest() && !controller.signal.aborted && err?.name !== "AbortError") {
376
+ batch(() => {
357
377
  setError(err);
358
- }
378
+ setLoading(false);
379
+ });
359
380
  }
360
381
  }).finally(() => {
361
382
  if (suspensePending) suspenseCtx?.decrement();
362
- if (isLatest()) {
363
- setLoading(false);
364
- if (abortController === controller) {
365
- abortController = null;
366
- }
383
+ if (isLatest() && abortController === controller) {
384
+ abortController = null;
367
385
  }
368
386
  });
369
387
  };
@@ -371,6 +389,15 @@ function createResource(source, fetcher, options) {
371
389
  source();
372
390
  doFetch();
373
391
  });
392
+ if (hasActiveRoot()) {
393
+ registerDisposer(() => {
394
+ fetchVersion++;
395
+ if (abortController) {
396
+ abortController.abort();
397
+ abortController = null;
398
+ }
399
+ });
400
+ }
374
401
  const resource = (() => data());
375
402
  resource.loading = loading;
376
403
  resource.error = error;
@@ -403,7 +430,6 @@ Object.defineProperty(exports, "trigger", {
403
430
  enumerable: true,
404
431
  get: function () { return alienSignals.trigger; }
405
432
  });
406
- exports.__DEV__ = __DEV__;
407
433
  exports.batch = batch;
408
434
  exports.createEffect = createEffect;
409
435
  exports.createMemo = createMemo;
@@ -415,11 +441,9 @@ exports.createUnownedRoot = createUnownedRoot;
415
441
  exports.internalEffect = internalEffect;
416
442
  exports.on = on;
417
443
  exports.onCleanup = onCleanup;
418
- exports.onError = onError;
419
444
  exports.popSuspenseContext = popSuspenseContext;
420
445
  exports.pushSuspenseContext = pushSuspenseContext;
421
446
  exports.registerDisposer = registerDisposer;
422
- exports.reportError = reportError;
423
447
  exports.untrack = untrack;
424
- //# sourceMappingURL=chunk-6FW5E54W.cjs.map
425
- //# sourceMappingURL=chunk-6FW5E54W.cjs.map
448
+ //# sourceMappingURL=chunk-KUXNZ5MG.cjs.map
449
+ //# sourceMappingURL=chunk-KUXNZ5MG.cjs.map