@getforma/core 1.4.0 → 1.5.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 (43) hide show
  1. package/dist/{chunk-AJP4OVCN.js → chunk-3U2IQIKB.js} +4 -4
  2. package/dist/{chunk-AJP4OVCN.js.map → chunk-3U2IQIKB.js.map} +1 -1
  3. package/dist/{chunk-FBM7V4NE.cjs → chunk-D46EJ3VS.cjs} +43 -43
  4. package/dist/{chunk-FBM7V4NE.cjs.map → chunk-D46EJ3VS.cjs.map} +1 -1
  5. package/dist/{chunk-Y3A2EVVS.js → chunk-F5QUU76U.js} +4 -4
  6. package/dist/{chunk-Y3A2EVVS.js.map → chunk-F5QUU76U.js.map} +1 -1
  7. package/dist/{chunk-ETPJTPYD.js → chunk-FAIWRZKJ.js} +6 -3
  8. package/dist/chunk-FAIWRZKJ.js.map +1 -0
  9. package/dist/{chunk-H7MDUHS5.cjs → chunk-HAULTMNX.cjs} +6 -2
  10. package/dist/chunk-HAULTMNX.cjs.map +1 -0
  11. package/dist/{chunk-KUXNZ5MG.cjs → chunk-PAKPQRVE.cjs} +14 -12
  12. package/dist/{chunk-KUXNZ5MG.cjs.map → chunk-PAKPQRVE.cjs.map} +1 -1
  13. package/dist/formajs.global.js +69 -11
  14. package/dist/formajs.global.js.map +1 -1
  15. package/dist/http.cjs +11 -11
  16. package/dist/http.js +2 -2
  17. package/dist/index.cjs +143 -76
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +39 -5
  20. package/dist/index.d.ts +39 -5
  21. package/dist/index.js +67 -16
  22. package/dist/index.js.map +1 -1
  23. package/dist/{resource-D6HfVgiA.d.cts → resource-CBTcorBJ.d.ts} +2 -2
  24. package/dist/{resource-ljs2X5NM.d.ts → resource-CaFfIB5i.d.cts} +2 -2
  25. package/dist/runtime.cjs +28 -28
  26. package/dist/runtime.js +3 -3
  27. package/dist/server.cjs +7 -7
  28. package/dist/server.d.cts +2 -2
  29. package/dist/server.d.ts +2 -2
  30. package/dist/server.js +2 -2
  31. package/dist/{signal-CRBJYQ6B.d.cts → signal-CIXTR4Qz.d.cts} +29 -1
  32. package/dist/{signal-CRBJYQ6B.d.ts → signal-CIXTR4Qz.d.ts} +29 -1
  33. package/dist/storage.cjs +66 -16
  34. package/dist/storage.cjs.map +1 -1
  35. package/dist/storage.js +66 -16
  36. package/dist/storage.js.map +1 -1
  37. package/dist/tc39-compat.cjs +3 -3
  38. package/dist/tc39-compat.d.cts +1 -1
  39. package/dist/tc39-compat.d.ts +1 -1
  40. package/dist/tc39-compat.js +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-ETPJTPYD.js.map +0 -1
  43. package/dist/chunk-H7MDUHS5.cjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { S as SignalGetter } from './signal-CRBJYQ6B.cjs';
1
+ import { S as SignalGetter } from './signal-CIXTR4Qz.js';
2
2
 
3
3
  /**
4
4
  * Forma Reactive - Resource
@@ -66,4 +66,4 @@ interface ResourceOptions<T> {
66
66
  */
67
67
  declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S, info: ResourceFetcherInfo) => Promise<T>, options?: ResourceOptions<T>): Resource<T>;
68
68
 
69
- export { type Resource as R, type ResourceOptions as a, createResource as c };
69
+ export { type Resource as R, type ResourceFetcherInfo as a, type ResourceOptions as b, createResource as c };
@@ -1,4 +1,4 @@
1
- import { S as SignalGetter } from './signal-CRBJYQ6B.js';
1
+ import { S as SignalGetter } from './signal-CIXTR4Qz.cjs';
2
2
 
3
3
  /**
4
4
  * Forma Reactive - Resource
@@ -66,4 +66,4 @@ interface ResourceOptions<T> {
66
66
  */
67
67
  declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S, info: ResourceFetcherInfo) => Promise<T>, options?: ResourceOptions<T>): Resource<T>;
68
68
 
69
- export { type Resource as R, type ResourceOptions as a, createResource as c };
69
+ export { type Resource as R, type ResourceFetcherInfo as a, type ResourceOptions as b, createResource as c };
package/dist/runtime.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkFBM7V4NE_cjs = require('./chunk-FBM7V4NE.cjs');
3
+ var chunkD46EJ3VS_cjs = require('./chunk-D46EJ3VS.cjs');
4
4
  var chunk263HW3KN_cjs = require('./chunk-263HW3KN.cjs');
5
- var chunkKUXNZ5MG_cjs = require('./chunk-KUXNZ5MG.cjs');
6
- var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
5
+ var chunkPAKPQRVE_cjs = require('./chunk-PAKPQRVE.cjs');
6
+ var chunkHAULTMNX_cjs = require('./chunk-HAULTMNX.cjs');
7
7
 
8
8
  // src/dom/reconcile.ts
9
9
  function getBindTargets(el) {
@@ -1062,7 +1062,7 @@ function parseIfHandler(expr, scope) {
1062
1062
  }
1063
1063
  if (rest.length > 0) return null;
1064
1064
  return (e) => {
1065
- chunkKUXNZ5MG_cjs.batch(() => {
1065
+ chunkPAKPQRVE_cjs.batch(() => {
1066
1066
  if (condExpr()) thenHandler(e);
1067
1067
  else elseHandler?.(e);
1068
1068
  });
@@ -1816,7 +1816,7 @@ function parseHandler(expr, scope) {
1816
1816
  const handlers = stmts.map((s) => parseHandler(s, scope));
1817
1817
  if (handlers.every((h) => h !== null)) {
1818
1818
  return (e) => {
1819
- chunkKUXNZ5MG_cjs.batch(() => {
1819
+ chunkPAKPQRVE_cjs.batch(() => {
1820
1820
  for (const h of handlers) h(e);
1821
1821
  });
1822
1822
  };
@@ -1829,7 +1829,7 @@ function parseHandler(expr, scope) {
1829
1829
  const name = incrMatch[1];
1830
1830
  const op = incrMatch[2];
1831
1831
  return () => {
1832
- chunkKUXNZ5MG_cjs.batch(() => {
1832
+ chunkPAKPQRVE_cjs.batch(() => {
1833
1833
  const val = scope.getters[name]?.() ?? 0;
1834
1834
  scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
1835
1835
  });
@@ -1840,7 +1840,7 @@ function parseHandler(expr, scope) {
1840
1840
  const op = preIncrMatch[1];
1841
1841
  const name = preIncrMatch[2];
1842
1842
  return () => {
1843
- chunkKUXNZ5MG_cjs.batch(() => {
1843
+ chunkPAKPQRVE_cjs.batch(() => {
1844
1844
  const val = scope.getters[name]?.() ?? 0;
1845
1845
  scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
1846
1846
  });
@@ -1850,7 +1850,7 @@ function parseHandler(expr, scope) {
1850
1850
  if (toggleMatch && toggleMatch[1] === toggleMatch[2]) {
1851
1851
  const name = toggleMatch[1];
1852
1852
  return () => {
1853
- chunkKUXNZ5MG_cjs.batch(() => {
1853
+ chunkPAKPQRVE_cjs.batch(() => {
1854
1854
  scope.setters[name]?.(!scope.getters[name]?.());
1855
1855
  });
1856
1856
  };
@@ -1862,7 +1862,7 @@ function parseHandler(expr, scope) {
1862
1862
  if (valExpr) {
1863
1863
  if (_debug) dbg(`parseHandler: assignment "${name} = ..." \u2014 setter exists:`, !!scope.setters[name], ", getter exists:", !!scope.getters[name]);
1864
1864
  return () => {
1865
- chunkKUXNZ5MG_cjs.batch(() => {
1865
+ chunkPAKPQRVE_cjs.batch(() => {
1866
1866
  const val = valExpr();
1867
1867
  if (_debug) dbg(`SETTER: ${name} = ${val} (was: ${scope.getters[name]?.()})`);
1868
1868
  scope.setters[name]?.(val);
@@ -1877,7 +1877,7 @@ function parseHandler(expr, scope) {
1877
1877
  const valExpr = parseExpression(compoundMatch[3].trim(), scope);
1878
1878
  if (valExpr) {
1879
1879
  return () => {
1880
- chunkKUXNZ5MG_cjs.batch(() => {
1880
+ chunkPAKPQRVE_cjs.batch(() => {
1881
1881
  const current = scope.getters[name]?.() ?? 0;
1882
1882
  const val = valExpr();
1883
1883
  switch (op) {
@@ -1958,7 +1958,7 @@ function buildHandler(expr, scope) {
1958
1958
  }
1959
1959
  });
1960
1960
  const unsafeHandler = (e) => {
1961
- chunkKUXNZ5MG_cjs.batch(() => fn(proxy, e, e));
1961
+ chunkPAKPQRVE_cjs.batch(() => fn(proxy, e, e));
1962
1962
  };
1963
1963
  const result = {
1964
1964
  handler: unsafeHandler,
@@ -2006,7 +2006,7 @@ function initScope(stateEl) {
2006
2006
  const getters = {};
2007
2007
  const setters = {};
2008
2008
  for (const [key, initial] of Object.entries(state)) {
2009
- const [get, set] = chunkH7MDUHS5_cjs.createSignal(initial);
2009
+ const [get, set] = chunkHAULTMNX_cjs.createSignal(initial);
2010
2010
  getters[key] = get;
2011
2011
  setters[key] = set;
2012
2012
  }
@@ -2118,7 +2118,7 @@ function bindElement(el, scope, disposers) {
2118
2118
  const prevGetter = scope.getters[name];
2119
2119
  delete scope.getters[name];
2120
2120
  const evaluate = buildEvaluator(`{${expr}}`, scope);
2121
- const getter = chunkH7MDUHS5_cjs.createComputed(evaluate);
2121
+ const getter = chunkHAULTMNX_cjs.createComputed(evaluate);
2122
2122
  scope.getters[name] = getter;
2123
2123
  if (!prevGetter) {
2124
2124
  delete scope.setters[name];
@@ -2129,7 +2129,7 @@ function bindElement(el, scope, disposers) {
2129
2129
  const textExpr = !known || known.has("data-text") ? el.getAttribute("data-text") : null;
2130
2130
  if (textExpr) {
2131
2131
  const evaluate = buildEvaluator(textExpr, scope);
2132
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2132
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2133
2133
  setElementTextFast(el, toTextValue(evaluate()));
2134
2134
  });
2135
2135
  disposers.push(dispose);
@@ -2144,7 +2144,7 @@ function bindElement(el, scope, disposers) {
2144
2144
  dbg(`bindElement: data-show="${showExpr}" on <${tag}${cls}>`);
2145
2145
  }
2146
2146
  let initialized = false;
2147
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2147
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2148
2148
  const visible = !!evaluate();
2149
2149
  if (_debug) dbg(`data-show effect: "${showExpr}" \u2192 ${visible}`);
2150
2150
  applyShowVisibility(el, visible, transition, !initialized);
@@ -2163,7 +2163,7 @@ function bindElement(el, scope, disposers) {
2163
2163
  const parent = el.parentNode;
2164
2164
  let inserted = true;
2165
2165
  let initialized = false;
2166
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2166
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2167
2167
  const show = !!evaluate();
2168
2168
  if (show && !inserted) {
2169
2169
  clearTransitionState(el);
@@ -2214,7 +2214,7 @@ function bindElement(el, scope, disposers) {
2214
2214
  if (getter && setter) {
2215
2215
  const input = el;
2216
2216
  const tag = input.tagName;
2217
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2217
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2218
2218
  const val = getter();
2219
2219
  const type = input.type;
2220
2220
  if (type === "checkbox") {
@@ -2302,14 +2302,14 @@ function bindElement(el, scope, disposers) {
2302
2302
  } else if (name.startsWith("data-class:")) {
2303
2303
  const cls = name.slice(11);
2304
2304
  const evaluate = buildEvaluator(attr.value, scope);
2305
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2305
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2306
2306
  el.classList.toggle(cls, !!evaluate());
2307
2307
  });
2308
2308
  disposers.push(dispose);
2309
2309
  } else if (name.startsWith("data-bind:")) {
2310
2310
  const attrName = name.slice(10);
2311
2311
  const evaluate = buildEvaluator(attr.value, scope);
2312
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2312
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2313
2313
  const val = evaluate();
2314
2314
  if (val == null || val === false) {
2315
2315
  el.removeAttribute(attrName);
@@ -2337,7 +2337,7 @@ function bindElement(el, scope, disposers) {
2337
2337
  if (saved !== null) setter(JSON.parse(saved));
2338
2338
  } catch {
2339
2339
  }
2340
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2340
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2341
2341
  try {
2342
2342
  localStorage.setItem(key, JSON.stringify(getter()));
2343
2343
  } catch {
@@ -2424,7 +2424,7 @@ function bindElement(el, scope, disposers) {
2424
2424
  });
2425
2425
  }
2426
2426
  } : void 0;
2427
- const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
2427
+ const dispose = chunkPAKPQRVE_cjs.internalEffect(() => {
2428
2428
  const rawItems = evaluate();
2429
2429
  if (!Array.isArray(rawItems)) {
2430
2430
  for (const n of oldNodes) {
@@ -2447,7 +2447,7 @@ function bindElement(el, scope, disposers) {
2447
2447
  const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
2448
2448
  const oldWrapped = oldItems;
2449
2449
  const keyFn = keyProp ? (w) => String(w.__item?.[keyProp] ?? "") : (w) => w.__idx;
2450
- const result = chunkFBM7V4NE_cjs.reconcileList(
2450
+ const result = chunkD46EJ3VS_cjs.reconcileList(
2451
2451
  el,
2452
2452
  oldWrapped,
2453
2453
  wrapped,
@@ -2495,16 +2495,16 @@ function bindElement(el, scope, disposers) {
2495
2495
  else if (k === "error") errorTarget = v;
2496
2496
  else if (k === "poll") interval = parseInt(v ?? "0", 10);
2497
2497
  }
2498
- const [getTarget, setTarget] = chunkH7MDUHS5_cjs.createSignal(null);
2498
+ const [getTarget, setTarget] = chunkHAULTMNX_cjs.createSignal(null);
2499
2499
  scope.getters[target] = getTarget;
2500
2500
  scope.setters[target] = setTarget;
2501
2501
  if (loadingTarget) {
2502
- const [gl, sl] = chunkH7MDUHS5_cjs.createSignal(false);
2502
+ const [gl, sl] = chunkHAULTMNX_cjs.createSignal(false);
2503
2503
  scope.getters[loadingTarget] = gl;
2504
2504
  scope.setters[loadingTarget] = sl;
2505
2505
  }
2506
2506
  if (errorTarget) {
2507
- const [ge, se] = chunkH7MDUHS5_cjs.createSignal(null);
2507
+ const [ge, se] = chunkHAULTMNX_cjs.createSignal(null);
2508
2508
  scope.getters[errorTarget] = ge;
2509
2509
  scope.setters[errorTarget] = se;
2510
2510
  }
@@ -2833,7 +2833,7 @@ function getScopes() {
2833
2833
  function setScopeValue(element, key, value) {
2834
2834
  const scope = element.__formaScope;
2835
2835
  if (!scope?.setters[key]) return;
2836
- chunkKUXNZ5MG_cjs.batch(() => {
2836
+ chunkPAKPQRVE_cjs.batch(() => {
2837
2837
  scope.setters[key](value);
2838
2838
  });
2839
2839
  }
@@ -2842,7 +2842,7 @@ function resetScope(element) {
2842
2842
  const initialJSON = element.__formaInitialState;
2843
2843
  if (!scope || !initialJSON) return;
2844
2844
  const initial = parseState(initialJSON);
2845
- chunkKUXNZ5MG_cjs.batch(() => {
2845
+ chunkPAKPQRVE_cjs.batch(() => {
2846
2846
  for (const [key, val] of Object.entries(initial)) {
2847
2847
  scope.setters[key]?.(val);
2848
2848
  }
@@ -2872,7 +2872,7 @@ function getReconciler() {
2872
2872
  }
2873
2873
  }
2874
2874
  },
2875
- batch: chunkKUXNZ5MG_cjs.batch
2875
+ batch: chunkPAKPQRVE_cjs.batch
2876
2876
  });
2877
2877
  }
2878
2878
  return _reconciler;
package/dist/runtime.js CHANGED
@@ -1,7 +1,7 @@
1
- import { reconcileList } from './chunk-AJP4OVCN.js';
1
+ import { reconcileList } from './chunk-3U2IQIKB.js';
2
2
  import { isEventHandlerAttr, isUrlAttr, isDangerousUrl } from './chunk-2QQBKQIF.js';
3
- import { internalEffect, batch } from './chunk-Y3A2EVVS.js';
4
- import { createSignal, createComputed } from './chunk-ETPJTPYD.js';
3
+ import { internalEffect, batch } from './chunk-F5QUU76U.js';
4
+ import { createSignal, createComputed } from './chunk-FAIWRZKJ.js';
5
5
 
6
6
  // src/dom/reconcile.ts
7
7
  function getBindTargets(el) {
package/dist/server.cjs CHANGED
@@ -1,25 +1,25 @@
1
1
  'use strict';
2
2
 
3
- var chunkKUXNZ5MG_cjs = require('./chunk-KUXNZ5MG.cjs');
4
- var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
3
+ var chunkPAKPQRVE_cjs = require('./chunk-PAKPQRVE.cjs');
4
+ var chunkHAULTMNX_cjs = require('./chunk-HAULTMNX.cjs');
5
5
 
6
6
  // src/server/action.ts
7
7
  function createAction(serverFn, options) {
8
- const [pending, setPending] = chunkH7MDUHS5_cjs.createSignal(false);
9
- const [error, setError] = chunkH7MDUHS5_cjs.createSignal(void 0);
8
+ const [pending, setPending] = chunkHAULTMNX_cjs.createSignal(false);
9
+ const [error, setError] = chunkHAULTMNX_cjs.createSignal(void 0);
10
10
  const action = async (...args) => {
11
11
  setPending(true);
12
12
  setError(void 0);
13
13
  if (options?.optimistic) {
14
14
  try {
15
- chunkKUXNZ5MG_cjs.batch(() => options.optimistic(...args));
15
+ chunkPAKPQRVE_cjs.batch(() => options.optimistic(...args));
16
16
  } catch {
17
17
  }
18
18
  }
19
19
  try {
20
20
  const result = await serverFn(...args);
21
21
  if (options?.onSuccess) {
22
- chunkKUXNZ5MG_cjs.batch(() => options.onSuccess(result, ...args));
22
+ chunkPAKPQRVE_cjs.batch(() => options.onSuccess(result, ...args));
23
23
  }
24
24
  if (options?.invalidates) {
25
25
  for (const resource of options.invalidates) {
@@ -31,7 +31,7 @@ function createAction(serverFn, options) {
31
31
  } catch (err) {
32
32
  if (options?.onError) {
33
33
  try {
34
- chunkKUXNZ5MG_cjs.batch(() => options.onError(err, ...args));
34
+ chunkPAKPQRVE_cjs.batch(() => options.onError(err, ...args));
35
35
  } catch {
36
36
  }
37
37
  }
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as Resource } from './resource-D6HfVgiA.cjs';
2
- import './signal-CRBJYQ6B.cjs';
1
+ import { R as Resource } from './resource-CaFfIB5i.cjs';
2
+ import './signal-CIXTR4Qz.cjs';
3
3
 
4
4
  /**
5
5
  * FormaJS Server - Action
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as Resource } from './resource-ljs2X5NM.js';
2
- import './signal-CRBJYQ6B.js';
1
+ import { R as Resource } from './resource-CBTcorBJ.js';
2
+ import './signal-CIXTR4Qz.js';
3
3
 
4
4
  /**
5
5
  * FormaJS Server - Action
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { batch } from './chunk-Y3A2EVVS.js';
2
- import { createSignal } from './chunk-ETPJTPYD.js';
1
+ import { batch } from './chunk-F5QUU76U.js';
2
+ import { createSignal } from './chunk-FAIWRZKJ.js';
3
3
 
4
4
  // src/server/action.ts
5
5
  function createAction(serverFn, options) {
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Forma Reactive - Signal
3
+ *
4
+ * Fine-grained reactive primitive backed by alien-signals.
5
+ * API: createSignal returns [getter, setter] tuple following SolidJS conventions.
6
+ *
7
+ * TC39 Signals equivalent: Signal.State
8
+ */
9
+ /** Debug name of a signal getter, if one was provided (dev-only). */
10
+ declare function getSignalName(fn: unknown): string | undefined;
1
11
  /** A function that reads the current value of a signal. */
2
12
  type SignalGetter<T> = () => T;
3
13
  /** A function that updates a signal — accepts a value or an updater function. */
@@ -23,6 +33,24 @@ interface SignalOptions<T> {
23
33
  */
24
34
  equals?: (prev: T, next: T) => boolean;
25
35
  }
36
+ /**
37
+ * Wrap a value so the setter treats it as a literal value, not a functional updater.
38
+ *
39
+ * When `T` is itself a function type, passing a function to the setter is
40
+ * ambiguous -- it looks like a functional update (`prev => next`). Use
41
+ * `value()` to disambiguate:
42
+ *
43
+ * ```ts
44
+ * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));
45
+ *
46
+ * // BUG: interpreted as a functional update -- calls the arrow with prev
47
+ * // setFn(() => console.log('b'));
48
+ *
49
+ * // Correct: wraps in a thunk so the setter stores it as-is
50
+ * setFn(value(() => console.log('b')));
51
+ * ```
52
+ */
53
+ declare function value<T>(v: T): () => T;
26
54
  /**
27
55
  * Create a reactive signal.
28
56
  *
@@ -44,4 +72,4 @@ interface SignalOptions<T> {
44
72
  */
45
73
  declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>];
46
74
 
47
- export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c };
75
+ export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c, getSignalName as g, value as v };
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Forma Reactive - Signal
3
+ *
4
+ * Fine-grained reactive primitive backed by alien-signals.
5
+ * API: createSignal returns [getter, setter] tuple following SolidJS conventions.
6
+ *
7
+ * TC39 Signals equivalent: Signal.State
8
+ */
9
+ /** Debug name of a signal getter, if one was provided (dev-only). */
10
+ declare function getSignalName(fn: unknown): string | undefined;
1
11
  /** A function that reads the current value of a signal. */
2
12
  type SignalGetter<T> = () => T;
3
13
  /** A function that updates a signal — accepts a value or an updater function. */
@@ -23,6 +33,24 @@ interface SignalOptions<T> {
23
33
  */
24
34
  equals?: (prev: T, next: T) => boolean;
25
35
  }
36
+ /**
37
+ * Wrap a value so the setter treats it as a literal value, not a functional updater.
38
+ *
39
+ * When `T` is itself a function type, passing a function to the setter is
40
+ * ambiguous -- it looks like a functional update (`prev => next`). Use
41
+ * `value()` to disambiguate:
42
+ *
43
+ * ```ts
44
+ * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));
45
+ *
46
+ * // BUG: interpreted as a functional update -- calls the arrow with prev
47
+ * // setFn(() => console.log('b'));
48
+ *
49
+ * // Correct: wraps in a thunk so the setter stores it as-is
50
+ * setFn(value(() => console.log('b')));
51
+ * ```
52
+ */
53
+ declare function value<T>(v: T): () => T;
26
54
  /**
27
55
  * Create a reactive signal.
28
56
  *
@@ -44,4 +72,4 @@ interface SignalOptions<T> {
44
72
  */
45
73
  declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>];
46
74
 
47
- export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c };
75
+ export { type SignalGetter as S, type SignalOptions as a, type SignalSetter as b, createSignal as c, getSignalName as g, value as v };
package/dist/storage.cjs CHANGED
@@ -26,7 +26,10 @@ function createLocalStorage(key, options) {
26
26
  }
27
27
  },
28
28
  remove() {
29
- localStorage.removeItem(key);
29
+ try {
30
+ localStorage.removeItem(key);
31
+ } catch {
32
+ }
30
33
  }
31
34
  };
32
35
  }
@@ -57,37 +60,70 @@ function createSessionStorage(key, options) {
57
60
  }
58
61
  },
59
62
  remove() {
60
- sessionStorage.removeItem(key);
63
+ try {
64
+ sessionStorage.removeItem(key);
65
+ } catch {
66
+ }
61
67
  }
62
68
  };
63
69
  }
64
70
 
65
71
  // src/storage/indexed.ts
66
72
  var dbCache = /* @__PURE__ */ new Map();
73
+ var openLocks = /* @__PURE__ */ new Map();
67
74
  function openDB(dbName, storeName) {
68
75
  const cacheKey = `${dbName}::${storeName}`;
69
76
  const cached = dbCache.get(cacheKey);
70
77
  if (cached) return cached;
71
- const promise = new Promise((resolve, reject) => {
78
+ const prevLock = openLocks.get(dbName) ?? Promise.resolve();
79
+ const promise = prevLock.then(() => doOpen(dbName, storeName, cacheKey), () => doOpen(dbName, storeName, cacheKey));
80
+ dbCache.set(cacheKey, promise);
81
+ openLocks.set(dbName, promise.then(() => void 0, () => void 0));
82
+ promise.catch(() => {
83
+ dbCache.delete(cacheKey);
84
+ });
85
+ return promise;
86
+ }
87
+ function doOpen(dbName, storeName, cacheKey) {
88
+ return new Promise((resolve, reject) => {
89
+ const wire = (db) => {
90
+ db.onversionchange = () => {
91
+ db.close();
92
+ dbCache.delete(cacheKey);
93
+ };
94
+ db.onclose = () => {
95
+ dbCache.delete(cacheKey);
96
+ };
97
+ resolve(db);
98
+ };
72
99
  const probe = indexedDB.open(dbName);
73
100
  probe.onerror = () => reject(probe.error);
74
101
  probe.onsuccess = () => {
75
102
  const db = probe.result;
76
103
  if (db.objectStoreNames.contains(storeName)) {
77
- resolve(db);
104
+ wire(db);
78
105
  return;
79
106
  }
80
107
  const nextVersion = db.version + 1;
81
108
  db.close();
82
109
  const upgrade = indexedDB.open(dbName, nextVersion);
83
110
  upgrade.onerror = () => reject(upgrade.error);
111
+ upgrade.onblocked = () => reject(upgrade.error ?? new DOMException("Upgrade blocked: another connection is open", "AbortError"));
84
112
  upgrade.onupgradeneeded = () => {
85
113
  const upgradedDB = upgrade.result;
86
114
  if (!upgradedDB.objectStoreNames.contains(storeName)) {
87
115
  upgradedDB.createObjectStore(storeName);
88
116
  }
89
117
  };
90
- upgrade.onsuccess = () => resolve(upgrade.result);
118
+ upgrade.onsuccess = () => {
119
+ const upgradedDB = upgrade.result;
120
+ if (!upgradedDB.objectStoreNames.contains(storeName)) {
121
+ upgradedDB.close();
122
+ reject(new DOMException(`Object store "${storeName}" was not created`, "NotFoundError"));
123
+ return;
124
+ }
125
+ wire(upgradedDB);
126
+ };
91
127
  };
92
128
  probe.onupgradeneeded = () => {
93
129
  const db = probe.result;
@@ -96,23 +132,37 @@ function openDB(dbName, storeName) {
96
132
  }
97
133
  };
98
134
  });
99
- dbCache.set(cacheKey, promise);
100
- promise.catch(() => {
101
- dbCache.delete(cacheKey);
102
- });
103
- return promise;
104
135
  }
105
136
  function createIndexedDB(dbName, storeName = "default") {
137
+ const cacheKey = `${dbName}::${storeName}`;
106
138
  function withStore(mode, fn) {
107
139
  return openDB(dbName, storeName).then(
108
140
  (db) => new Promise((resolve, reject) => {
109
- const tx = db.transaction(storeName, mode);
110
- const store = tx.objectStore(storeName);
111
- const request = fn(store);
112
- request.onsuccess = () => resolve(request.result);
113
- request.onerror = () => reject(request.error);
141
+ let request;
142
+ let tx;
143
+ try {
144
+ tx = db.transaction(storeName, mode);
145
+ request = fn(tx.objectStore(storeName));
146
+ } catch (err) {
147
+ reject(err);
148
+ return;
149
+ }
150
+ let result;
151
+ request.onsuccess = () => {
152
+ result = request.result;
153
+ if (mode === "readonly") resolve(result);
154
+ };
155
+ tx.oncomplete = () => resolve(result);
156
+ tx.onerror = () => reject(tx.error ?? request.error);
157
+ tx.onabort = () => reject(tx.error ?? request.error ?? new DOMException("Transaction aborted", "AbortError"));
114
158
  })
115
- );
159
+ ).catch((err) => {
160
+ const name = err?.name;
161
+ if (name === "InvalidStateError" || name === "AbortError" || name === "NotFoundError") {
162
+ dbCache.delete(cacheKey);
163
+ }
164
+ throw err;
165
+ });
116
166
  }
117
167
  return {
118
168
  get(key) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/storage/local.ts","../src/storage/session.ts","../src/storage/indexed.ts"],"names":[],"mappings":"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","file":"storage.cjs","sourcesContent":["/**\n * Forma Storage - Local\n *\n * Typed localStorage wrapper with graceful error handling.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { TypedStorage, StorageOptions } from './types.js';\n\n/**\n * Create a typed localStorage wrapper for the given key.\n *\n * ```ts\n * const store = createLocalStorage<{ name: string }>('user');\n * store.set({ name: 'Alice' });\n * store.get(); // { name: 'Alice' }\n * store.remove();\n * ```\n */\nexport function createLocalStorage<T>(\n key: string,\n options?: StorageOptions<T>,\n): TypedStorage<T> {\n const serialize = options?.serialize ?? JSON.stringify;\n const deserialize = options?.deserialize ?? JSON.parse;\n const validate = options?.validate;\n\n return {\n key,\n\n get(): T | null {\n try {\n const raw = localStorage.getItem(key);\n if (raw === null) return null;\n const value = deserialize(raw);\n if (validate && !validate(value)) return null;\n return value as T;\n } catch {\n return null;\n }\n },\n\n set(value: T): void {\n try {\n const serialized = serialize(value);\n localStorage.setItem(key, serialized);\n } catch { /* QuotaExceededError or storage disabled — silently ignore */ }\n },\n\n remove(): void {\n localStorage.removeItem(key);\n },\n };\n}\n","/**\n * Forma Storage - Session\n *\n * Typed sessionStorage wrapper with graceful error handling.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { TypedStorage, StorageOptions } from './types.js';\n\n/**\n * Create a typed sessionStorage wrapper for the given key.\n *\n * ```ts\n * const store = createSessionStorage<{ token: string }>('auth');\n * store.set({ token: 'abc123' });\n * store.get(); // { token: 'abc123' }\n * store.remove();\n * ```\n */\nexport function createSessionStorage<T>(\n key: string,\n options?: StorageOptions<T>,\n): TypedStorage<T> {\n const serialize = options?.serialize ?? JSON.stringify;\n const deserialize = options?.deserialize ?? JSON.parse;\n const validate = options?.validate;\n\n return {\n key,\n\n get(): T | null {\n try {\n const raw = sessionStorage.getItem(key);\n if (raw === null) return null;\n const value = deserialize(raw);\n if (validate && !validate(value)) return null;\n return value as T;\n } catch {\n return null;\n }\n },\n\n set(value: T): void {\n try {\n const serialized = serialize(value);\n sessionStorage.setItem(key, serialized);\n } catch { /* QuotaExceededError or storage disabled — silently ignore */ }\n },\n\n remove(): void {\n sessionStorage.removeItem(key);\n },\n };\n}\n","/**\n * Forma Storage - IndexedDB\n *\n * Simplified IndexedDB wrapper with lazy connection and promise-based API.\n * Zero dependencies — native browser APIs only.\n */\n\nexport interface IDBStore<T> {\n get(key: string): Promise<T | undefined>;\n set(key: string, value: T): Promise<void>;\n delete(key: string): Promise<void>;\n getAll(): Promise<T[]>;\n keys(): Promise<string[]>;\n clear(): Promise<void>;\n}\n\n/** Cached database connections keyed by \"dbName::storeName\". */\nconst dbCache = new Map<string, Promise<IDBDatabase>>();\n\n/**\n * Open (or retrieve a cached) IndexedDB database, ensuring the\n * requested object store exists.\n */\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n const cacheKey = `${dbName}::${storeName}`;\n const cached = dbCache.get(cacheKey);\n if (cached) return cached;\n\n const promise = new Promise<IDBDatabase>((resolve, reject) => {\n // First, try opening at the current (or default) version.\n const probe = indexedDB.open(dbName);\n\n probe.onerror = () => reject(probe.error);\n\n probe.onsuccess = () => {\n const db = probe.result;\n if (db.objectStoreNames.contains(storeName)) {\n resolve(db);\n return;\n }\n // Store doesn't exist yet — close and reopen with a version bump.\n const nextVersion = db.version + 1;\n db.close();\n\n const upgrade = indexedDB.open(dbName, nextVersion);\n upgrade.onerror = () => reject(upgrade.error);\n upgrade.onupgradeneeded = () => {\n const upgradedDB = upgrade.result;\n if (!upgradedDB.objectStoreNames.contains(storeName)) {\n upgradedDB.createObjectStore(storeName);\n }\n };\n upgrade.onsuccess = () => resolve(upgrade.result);\n };\n\n probe.onupgradeneeded = () => {\n const db = probe.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n };\n });\n\n dbCache.set(cacheKey, promise);\n\n // If the open fails, remove from cache so the next call retries.\n promise.catch(() => {\n dbCache.delete(cacheKey);\n });\n\n return promise;\n}\n\n/**\n * Create a simplified IndexedDB store.\n *\n * ```ts\n * const store = createIndexedDB<User>('myApp', 'users');\n * await store.set('u1', { name: 'Alice' });\n * const user = await store.get('u1'); // { name: 'Alice' }\n * ```\n */\nexport function createIndexedDB<T>(\n dbName: string,\n storeName: string = 'default',\n): IDBStore<T> {\n /** Helper: run a single read/write transaction and return the request result. */\n function withStore<R>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<R>,\n ): Promise<R> {\n return openDB(dbName, storeName).then(\n (db) =>\n new Promise<R>((resolve, reject) => {\n const tx = db.transaction(storeName, mode);\n const store = tx.objectStore(storeName);\n const request = fn(store);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n }),\n );\n }\n\n return {\n get(key: string): Promise<T | undefined> {\n return withStore('readonly', (store) => store.get(key)) as Promise<T | undefined>;\n },\n\n set(key: string, value: T): Promise<void> {\n return withStore('readwrite', (store) => store.put(value, key)).then(\n () => undefined,\n );\n },\n\n delete(key: string): Promise<void> {\n return withStore('readwrite', (store) => store.delete(key)).then(\n () => undefined,\n );\n },\n\n getAll(): Promise<T[]> {\n return withStore('readonly', (store) => store.getAll()) as Promise<T[]>;\n },\n\n keys(): Promise<string[]> {\n return withStore('readonly', (store) => store.getAllKeys()).then(\n (keys) => keys.map(String),\n );\n },\n\n clear(): Promise<void> {\n return withStore('readwrite', (store) => store.clear()).then(\n () => undefined,\n );\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/storage/local.ts","../src/storage/session.ts","../src/storage/indexed.ts"],"names":[],"mappings":"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","file":"storage.cjs","sourcesContent":["/**\n * Forma Storage - Local\n *\n * Typed localStorage wrapper with graceful error handling.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { TypedStorage, StorageOptions } from './types.js';\n\n/**\n * Create a typed localStorage wrapper for the given key.\n *\n * ```ts\n * const store = createLocalStorage<{ name: string }>('user');\n * store.set({ name: 'Alice' });\n * store.get(); // { name: 'Alice' }\n * store.remove();\n * ```\n */\nexport function createLocalStorage<T>(\n key: string,\n options?: StorageOptions<T>,\n): TypedStorage<T> {\n const serialize = options?.serialize ?? JSON.stringify;\n const deserialize = options?.deserialize ?? JSON.parse;\n const validate = options?.validate;\n\n return {\n key,\n\n get(): T | null {\n try {\n const raw = localStorage.getItem(key);\n if (raw === null) return null;\n const value = deserialize(raw);\n if (validate && !validate(value)) return null;\n return value as T;\n } catch {\n return null;\n }\n },\n\n set(value: T): void {\n try {\n const serialized = serialize(value);\n localStorage.setItem(key, serialized);\n } catch { /* QuotaExceededError or storage disabled — silently ignore */ }\n },\n\n remove(): void {\n try {\n localStorage.removeItem(key);\n } catch { /* storage disabled — silently ignore, matching get/set */ }\n },\n };\n}\n","/**\n * Forma Storage - Session\n *\n * Typed sessionStorage wrapper with graceful error handling.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { TypedStorage, StorageOptions } from './types.js';\n\n/**\n * Create a typed sessionStorage wrapper for the given key.\n *\n * ```ts\n * const store = createSessionStorage<{ token: string }>('auth');\n * store.set({ token: 'abc123' });\n * store.get(); // { token: 'abc123' }\n * store.remove();\n * ```\n */\nexport function createSessionStorage<T>(\n key: string,\n options?: StorageOptions<T>,\n): TypedStorage<T> {\n const serialize = options?.serialize ?? JSON.stringify;\n const deserialize = options?.deserialize ?? JSON.parse;\n const validate = options?.validate;\n\n return {\n key,\n\n get(): T | null {\n try {\n const raw = sessionStorage.getItem(key);\n if (raw === null) return null;\n const value = deserialize(raw);\n if (validate && !validate(value)) return null;\n return value as T;\n } catch {\n return null;\n }\n },\n\n set(value: T): void {\n try {\n const serialized = serialize(value);\n sessionStorage.setItem(key, serialized);\n } catch { /* QuotaExceededError or storage disabled — silently ignore */ }\n },\n\n remove(): void {\n try {\n sessionStorage.removeItem(key);\n } catch { /* storage disabled — silently ignore, matching get/set */ }\n },\n };\n}\n","/**\n * Forma Storage - IndexedDB\n *\n * Simplified IndexedDB wrapper with lazy connection and promise-based API.\n * Zero dependencies — native browser APIs only.\n */\n\nexport interface IDBStore<T> {\n get(key: string): Promise<T | undefined>;\n set(key: string, value: T): Promise<void>;\n delete(key: string): Promise<void>;\n getAll(): Promise<T[]>;\n keys(): Promise<string[]>;\n clear(): Promise<void>;\n}\n\n/** Cached database connections keyed by \"dbName::storeName\". */\nconst dbCache = new Map<string, Promise<IDBDatabase>>();\n\n/**\n * Per-database open serialization. Concurrent createIndexedDB(db, storeA/B/...)\n * calls each bump db.version+1; without serialization they compute the version\n * from a stale probe and race, so some stores are never created and their\n * connections are permanently wedged. Chaining opens for the same db name makes\n * each see the previous open's committed version.\n */\nconst openLocks = new Map<string, Promise<unknown>>();\n\n/**\n * Open (or retrieve a cached) IndexedDB database, ensuring the\n * requested object store exists.\n */\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n const cacheKey = `${dbName}::${storeName}`;\n const cached = dbCache.get(cacheKey);\n if (cached) return cached;\n\n // Serialize this open behind any in-flight open for the same db name.\n const prevLock = openLocks.get(dbName) ?? Promise.resolve();\n const promise = prevLock.then(() => doOpen(dbName, storeName, cacheKey), () => doOpen(dbName, storeName, cacheKey));\n\n dbCache.set(cacheKey, promise);\n // Advance the lock (never rejects, so the chain keeps flowing).\n openLocks.set(dbName, promise.then(() => undefined, () => undefined));\n\n // If the open fails, remove from cache so the next call retries.\n promise.catch(() => {\n dbCache.delete(cacheKey);\n });\n\n return promise;\n}\n\nfunction doOpen(dbName: string, storeName: string, cacheKey: string): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n // Wire a resolved connection so it closes + evicts on a version change\n // (another tab/store bumping the version) or a forced close. Without this a\n // later bump blocks forever and a stale connection is never dropped.\n const wire = (db: IDBDatabase) => {\n db.onversionchange = () => { db.close(); dbCache.delete(cacheKey); };\n db.onclose = () => { dbCache.delete(cacheKey); };\n resolve(db);\n };\n\n // First, try opening at the current (or default) version.\n const probe = indexedDB.open(dbName);\n\n probe.onerror = () => reject(probe.error);\n\n probe.onsuccess = () => {\n const db = probe.result;\n if (db.objectStoreNames.contains(storeName)) {\n wire(db);\n return;\n }\n // Store doesn't exist yet — close and reopen with a version bump. Opens\n // are serialized per db, so this version is current, not stale.\n const nextVersion = db.version + 1;\n db.close();\n\n const upgrade = indexedDB.open(dbName, nextVersion);\n upgrade.onerror = () => reject(upgrade.error);\n // Backstop: if another connection blocks the bump, settle (reject) so the\n // promise does not leak; the next call retries.\n upgrade.onblocked = () =>\n reject(upgrade.error ?? new DOMException('Upgrade blocked: another connection is open', 'AbortError'));\n upgrade.onupgradeneeded = () => {\n const upgradedDB = upgrade.result;\n if (!upgradedDB.objectStoreNames.contains(storeName)) {\n upgradedDB.createObjectStore(storeName);\n }\n };\n upgrade.onsuccess = () => {\n const upgradedDB = upgrade.result;\n if (!upgradedDB.objectStoreNames.contains(storeName)) {\n // Should not happen with serialized opens; reject (do not cache a\n // store-less connection) so the next call retries cleanly.\n upgradedDB.close();\n reject(new DOMException(`Object store \"${storeName}\" was not created`, 'NotFoundError'));\n return;\n }\n wire(upgradedDB);\n };\n };\n\n probe.onupgradeneeded = () => {\n const db = probe.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n };\n });\n}\n\n/**\n * Create a simplified IndexedDB store.\n *\n * ```ts\n * const store = createIndexedDB<User>('myApp', 'users');\n * await store.set('u1', { name: 'Alice' });\n * const user = await store.get('u1'); // { name: 'Alice' }\n * ```\n */\nexport function createIndexedDB<T>(\n dbName: string,\n storeName: string = 'default',\n): IDBStore<T> {\n const cacheKey = `${dbName}::${storeName}`;\n\n /** Helper: run a single read/write transaction and return the request result. */\n function withStore<R>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<R>,\n ): Promise<R> {\n return openDB(dbName, storeName).then(\n (db) =>\n new Promise<R>((resolve, reject) => {\n let request: IDBRequest<R>;\n let tx: IDBTransaction;\n try {\n tx = db.transaction(storeName, mode);\n request = fn(tx.objectStore(storeName));\n } catch (err) {\n // A transaction on a closing connection throws synchronously.\n reject(err);\n return;\n }\n let result: R;\n request.onsuccess = () => {\n result = request.result;\n // Reads are durable at request success; writes only at commit.\n if (mode === 'readonly') resolve(result);\n };\n // A readwrite value is only durable once the transaction COMMITS —\n // resolving on request.onsuccess would falsely report success for a\n // write that later aborts (e.g. QuotaExceededError at commit).\n tx.oncomplete = () => resolve(result);\n tx.onerror = () => reject(tx.error ?? request.error);\n tx.onabort = () => reject(tx.error ?? request.error ?? new DOMException('Transaction aborted', 'AbortError'));\n }),\n ).catch((err: unknown) => {\n // Evict a broken/closing/store-less connection so the next call reopens\n // cleanly (NotFoundError = the cached connection lacks the store).\n const name = (err as { name?: string } | null)?.name;\n if (name === 'InvalidStateError' || name === 'AbortError' || name === 'NotFoundError') {\n dbCache.delete(cacheKey);\n }\n throw err;\n });\n }\n\n return {\n get(key: string): Promise<T | undefined> {\n return withStore('readonly', (store) => store.get(key)) as Promise<T | undefined>;\n },\n\n set(key: string, value: T): Promise<void> {\n return withStore('readwrite', (store) => store.put(value, key)).then(\n () => undefined,\n );\n },\n\n delete(key: string): Promise<void> {\n return withStore('readwrite', (store) => store.delete(key)).then(\n () => undefined,\n );\n },\n\n getAll(): Promise<T[]> {\n return withStore('readonly', (store) => store.getAll()) as Promise<T[]>;\n },\n\n keys(): Promise<string[]> {\n return withStore('readonly', (store) => store.getAllKeys()).then(\n (keys) => keys.map(String),\n );\n },\n\n clear(): Promise<void> {\n return withStore('readwrite', (store) => store.clear()).then(\n () => undefined,\n );\n },\n };\n}\n"]}