@cajoa/core 0.12.0 → 0.13.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/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  createContext: () => createContext,
30
30
  createEffect: () => createEffect,
31
31
  createMemo: () => createMemo,
32
+ createResource: () => createResource,
32
33
  createRoot: () => createRoot,
33
34
  createSignal: () => createSignal,
34
35
  getRenderer: () => getRenderer,
@@ -259,6 +260,84 @@ function createMemo(fn) {
259
260
  return value;
260
261
  }
261
262
 
263
+ // src/reactivity/resource.ts
264
+ function isPromiseLike(value) {
265
+ return typeof value?.then === "function";
266
+ }
267
+ function createResource(a, b) {
268
+ const hasSource = typeof b === "function";
269
+ const source = hasSource ? a : () => true;
270
+ const fetcher = hasSource ? b : a;
271
+ const [data, setData] = createSignal(void 0);
272
+ const [failure, setFailure] = createSignal(null);
273
+ const [loading, setLoading] = createSignal(false);
274
+ const [version, setVersion] = createSignal(0);
275
+ let run = 0;
276
+ const finish = (id, outcome) => {
277
+ if (id !== run) return;
278
+ batch(() => {
279
+ setLoading(false);
280
+ if ("error" in outcome) {
281
+ setFailure({ error: outcome.error });
282
+ } else {
283
+ setFailure(null);
284
+ setData(() => outcome.value);
285
+ }
286
+ });
287
+ };
288
+ createEffect(() => {
289
+ const s = source();
290
+ version();
291
+ run++;
292
+ const id = run;
293
+ onCleanup(() => {
294
+ run++;
295
+ });
296
+ if (s === false || s === null || s === void 0) {
297
+ batch(() => {
298
+ setLoading(false);
299
+ setFailure(null);
300
+ setData(() => void 0);
301
+ });
302
+ return;
303
+ }
304
+ setLoading(true);
305
+ setFailure(null);
306
+ let result;
307
+ try {
308
+ result = untrack(() => fetcher(s));
309
+ } catch (error) {
310
+ finish(id, { error });
311
+ return;
312
+ }
313
+ if (isPromiseLike(result)) {
314
+ result.then(
315
+ (value) => finish(id, { value }),
316
+ (error) => finish(id, { error })
317
+ );
318
+ } else {
319
+ finish(id, { value: result });
320
+ }
321
+ });
322
+ const resource = (() => {
323
+ const failed = failure();
324
+ if (failed) throw failed.error;
325
+ return data();
326
+ });
327
+ resource.loading = loading;
328
+ resource.error = () => failure()?.error;
329
+ return [
330
+ resource,
331
+ {
332
+ refetch: () => setVersion((v) => v + 1),
333
+ mutate: (value) => batch(() => {
334
+ setFailure(null);
335
+ setData(() => value);
336
+ })
337
+ }
338
+ ];
339
+ }
340
+
262
341
  // src/renderer/index.ts
263
342
  var current = null;
264
343
  function setRenderer(renderer) {
@@ -294,11 +373,21 @@ function h(tag, props, ...children) {
294
373
  if (onRef) untrack(() => onRef(el));
295
374
  return el;
296
375
  }
376
+ function materialize(value) {
377
+ if (typeof value !== "function") return value;
378
+ const holder = getRenderer().createFragment();
379
+ appendChildren(holder, [value]);
380
+ return holder;
381
+ }
297
382
  function toNodes(value) {
298
383
  const r = getRenderer();
299
384
  const out = [];
300
385
  for (const v of [value].flat(Infinity)) {
301
386
  if (v == null || typeof v === "boolean") continue;
387
+ if (typeof v === "function") {
388
+ out.push(materialize(v));
389
+ continue;
390
+ }
302
391
  out.push(r.isNode(v) ? v : r.createText(String(v)));
303
392
  }
304
393
  return out;
@@ -400,11 +489,7 @@ function For(props) {
400
489
 
401
490
  // src/components/error-boundary.ts
402
491
  function resolve2(value) {
403
- let current2 = value;
404
- while (typeof current2 === "function") {
405
- current2 = current2();
406
- }
407
- return current2;
492
+ return typeof value === "function" ? value() : value;
408
493
  }
409
494
  function ErrorBoundary(props) {
410
495
  const [version, setVersion] = createSignal(0);
@@ -434,7 +519,7 @@ function ErrorBoundary(props) {
434
519
  let early = null;
435
520
  const content = untrack(
436
521
  () => catchError(
437
- () => resolve2(props.children ?? null),
522
+ () => materialize(resolve2(props.children ?? null)),
438
523
  (error) => {
439
524
  if (creating) early = { error };
440
525
  else fail(error);
@@ -477,6 +562,7 @@ function Fragment(props) {
477
562
  createContext,
478
563
  createEffect,
479
564
  createMemo,
565
+ createResource,
480
566
  createRoot,
481
567
  createSignal,
482
568
  getRenderer,
package/dist/index.d.cts CHANGED
@@ -17,6 +17,19 @@ declare function useContext<T>(ctx: Context<T>): T;
17
17
 
18
18
  declare function createMemo<T>(fn: () => T): () => T;
19
19
 
20
+ interface Resource<T> {
21
+ (): T | undefined;
22
+ loading: () => boolean;
23
+ error: () => unknown;
24
+ }
25
+ interface ResourceActions<T> {
26
+ refetch: () => void;
27
+ mutate(value: T | undefined): void;
28
+ }
29
+ type ResourceTuple<T> = [Resource<T>, ResourceActions<T>];
30
+ declare function createResource<T>(fetcher: () => T | Promise<T>): ResourceTuple<T>;
31
+ declare function createResource<T, S>(source: () => S | false | null | undefined, fetcher: (source: S) => T | Promise<T>): ResourceTuple<T>;
32
+
20
33
  type HostNode = object;
21
34
  interface Renderer<N = any> {
22
35
  createElement(tag: string): N;
@@ -55,7 +68,7 @@ declare function ErrorBoundary(props: {
55
68
  fallback: Child | ((error: unknown, reset: () => void) => Child);
56
69
  children?: Lazy<Child>;
57
70
  onError?: (error: unknown) => void;
58
- }): () => Child;
71
+ }): () => unknown;
59
72
 
60
73
  declare function jsx(type: any, props: Record<string, any> | null): any;
61
74
  declare const jsxs: typeof jsx;
@@ -76,5 +89,5 @@ declare function Fragment(props: {
76
89
  declare function setRenderer(renderer: Renderer): void;
77
90
  declare function getRenderer(): Renderer;
78
91
 
79
- export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
80
- export type { Child, Context, HostNode, Renderer };
92
+ export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createResource, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
93
+ export type { Child, Context, HostNode, Renderer, Resource, ResourceActions };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,19 @@ declare function useContext<T>(ctx: Context<T>): T;
17
17
 
18
18
  declare function createMemo<T>(fn: () => T): () => T;
19
19
 
20
+ interface Resource<T> {
21
+ (): T | undefined;
22
+ loading: () => boolean;
23
+ error: () => unknown;
24
+ }
25
+ interface ResourceActions<T> {
26
+ refetch: () => void;
27
+ mutate(value: T | undefined): void;
28
+ }
29
+ type ResourceTuple<T> = [Resource<T>, ResourceActions<T>];
30
+ declare function createResource<T>(fetcher: () => T | Promise<T>): ResourceTuple<T>;
31
+ declare function createResource<T, S>(source: () => S | false | null | undefined, fetcher: (source: S) => T | Promise<T>): ResourceTuple<T>;
32
+
20
33
  type HostNode = object;
21
34
  interface Renderer<N = any> {
22
35
  createElement(tag: string): N;
@@ -55,7 +68,7 @@ declare function ErrorBoundary(props: {
55
68
  fallback: Child | ((error: unknown, reset: () => void) => Child);
56
69
  children?: Lazy<Child>;
57
70
  onError?: (error: unknown) => void;
58
- }): () => Child;
71
+ }): () => unknown;
59
72
 
60
73
  declare function jsx(type: any, props: Record<string, any> | null): any;
61
74
  declare const jsxs: typeof jsx;
@@ -76,5 +89,5 @@ declare function Fragment(props: {
76
89
  declare function setRenderer(renderer: Renderer): void;
77
90
  declare function getRenderer(): Renderer;
78
91
 
79
- export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
80
- export type { Child, Context, HostNode, Renderer };
92
+ export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createResource, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
93
+ export type { Child, Context, HostNode, Renderer, Resource, ResourceActions };
package/dist/index.js CHANGED
@@ -211,6 +211,84 @@ function createMemo(fn) {
211
211
  return value;
212
212
  }
213
213
 
214
+ // src/reactivity/resource.ts
215
+ function isPromiseLike(value) {
216
+ return typeof value?.then === "function";
217
+ }
218
+ function createResource(a, b) {
219
+ const hasSource = typeof b === "function";
220
+ const source = hasSource ? a : () => true;
221
+ const fetcher = hasSource ? b : a;
222
+ const [data, setData] = createSignal(void 0);
223
+ const [failure, setFailure] = createSignal(null);
224
+ const [loading, setLoading] = createSignal(false);
225
+ const [version, setVersion] = createSignal(0);
226
+ let run = 0;
227
+ const finish = (id, outcome) => {
228
+ if (id !== run) return;
229
+ batch(() => {
230
+ setLoading(false);
231
+ if ("error" in outcome) {
232
+ setFailure({ error: outcome.error });
233
+ } else {
234
+ setFailure(null);
235
+ setData(() => outcome.value);
236
+ }
237
+ });
238
+ };
239
+ createEffect(() => {
240
+ const s = source();
241
+ version();
242
+ run++;
243
+ const id = run;
244
+ onCleanup(() => {
245
+ run++;
246
+ });
247
+ if (s === false || s === null || s === void 0) {
248
+ batch(() => {
249
+ setLoading(false);
250
+ setFailure(null);
251
+ setData(() => void 0);
252
+ });
253
+ return;
254
+ }
255
+ setLoading(true);
256
+ setFailure(null);
257
+ let result;
258
+ try {
259
+ result = untrack(() => fetcher(s));
260
+ } catch (error) {
261
+ finish(id, { error });
262
+ return;
263
+ }
264
+ if (isPromiseLike(result)) {
265
+ result.then(
266
+ (value) => finish(id, { value }),
267
+ (error) => finish(id, { error })
268
+ );
269
+ } else {
270
+ finish(id, { value: result });
271
+ }
272
+ });
273
+ const resource = (() => {
274
+ const failed = failure();
275
+ if (failed) throw failed.error;
276
+ return data();
277
+ });
278
+ resource.loading = loading;
279
+ resource.error = () => failure()?.error;
280
+ return [
281
+ resource,
282
+ {
283
+ refetch: () => setVersion((v) => v + 1),
284
+ mutate: (value) => batch(() => {
285
+ setFailure(null);
286
+ setData(() => value);
287
+ })
288
+ }
289
+ ];
290
+ }
291
+
214
292
  // src/renderer/index.ts
215
293
  var current = null;
216
294
  function setRenderer(renderer) {
@@ -246,11 +324,21 @@ function h(tag, props, ...children) {
246
324
  if (onRef) untrack(() => onRef(el));
247
325
  return el;
248
326
  }
327
+ function materialize(value) {
328
+ if (typeof value !== "function") return value;
329
+ const holder = getRenderer().createFragment();
330
+ appendChildren(holder, [value]);
331
+ return holder;
332
+ }
249
333
  function toNodes(value) {
250
334
  const r = getRenderer();
251
335
  const out = [];
252
336
  for (const v of [value].flat(Infinity)) {
253
337
  if (v == null || typeof v === "boolean") continue;
338
+ if (typeof v === "function") {
339
+ out.push(materialize(v));
340
+ continue;
341
+ }
254
342
  out.push(r.isNode(v) ? v : r.createText(String(v)));
255
343
  }
256
344
  return out;
@@ -352,11 +440,7 @@ function For(props) {
352
440
 
353
441
  // src/components/error-boundary.ts
354
442
  function resolve2(value) {
355
- let current2 = value;
356
- while (typeof current2 === "function") {
357
- current2 = current2();
358
- }
359
- return current2;
443
+ return typeof value === "function" ? value() : value;
360
444
  }
361
445
  function ErrorBoundary(props) {
362
446
  const [version, setVersion] = createSignal(0);
@@ -386,7 +470,7 @@ function ErrorBoundary(props) {
386
470
  let early = null;
387
471
  const content = untrack(
388
472
  () => catchError(
389
- () => resolve2(props.children ?? null),
473
+ () => materialize(resolve2(props.children ?? null)),
390
474
  (error) => {
391
475
  if (creating) early = { error };
392
476
  else fail(error);
@@ -428,6 +512,7 @@ export {
428
512
  createContext,
429
513
  createEffect,
430
514
  createMemo,
515
+ createResource,
431
516
  createRoot,
432
517
  createSignal,
433
518
  getRenderer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cajoa/core",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Cajoa core: signals, JSX runtime and the renderer interface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",