@assistant-ui/tap 0.4.2 → 0.4.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,SAEU,CAAC"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,SAGa,CAAC"}
package/dist/core/env.js CHANGED
@@ -1,3 +1,4 @@
1
- export const isDevelopment = process.env["NODE_ENV"] === "development" ||
2
- process.env["NODE_ENV"] === "test";
1
+ export const isDevelopment = typeof process !== "undefined" &&
2
+ (process.env["NODE_ENV"] === "development" ||
3
+ process.env["NODE_ENV"] === "test");
3
4
  //# sourceMappingURL=env.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GACxB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,aAAa;IACzC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,CAAC"}
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/core/env.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GACxB,OAAO,OAAO,KAAK,WAAW;IAC9B,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,aAAa;QACxC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { tapEffectEvent } from "./hooks/tap-effect-event.js";
10
10
  export { tapResource } from "./hooks/tap-resource.js";
11
11
  export { tapInlineResource } from "./hooks/tap-inline-resource.js";
12
12
  export { tapResources } from "./hooks/tap-resources.js";
13
+ export { tapSubscribableResource } from "./tapSubscribableResource.js";
13
14
  export { createResource } from "./core/createResource.js";
14
15
  export { flushResourcesSync } from "./core/scheduler.js";
15
16
  export { createResourceContext, tap, withContextProvider, } from "./core/context.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAGzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAG/C,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAG1D,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAGrD,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAGtD,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB;AAGxB,YAAY,EACV,QAAQ,EACR,qBAAqB,EACrB,eAAe,GAChB,wBAAqB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAGzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAG/C,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAG1D,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAGrD,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AAGpE,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAGtD,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB;AAGxB,YAAY,EACV,QAAQ,EACR,qBAAqB,EACrB,eAAe,GAChB,wBAAqB"}
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@ export { tapEffectEvent } from "./hooks/tap-effect-event.js";
13
13
  export { tapResource } from "./hooks/tap-resource.js";
14
14
  export { tapInlineResource } from "./hooks/tap-inline-resource.js";
15
15
  export { tapResources } from "./hooks/tap-resources.js";
16
+ // subscribable
17
+ export { tapSubscribableResource } from "./tapSubscribableResource.js";
16
18
  // imperative
17
19
  export { createResource } from "./core/createResource.js";
18
20
  export { flushResourcesSync } from "./core/scheduler.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAEzC,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAE/C,gBAAgB;AAChB,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAE1D,YAAY;AACZ,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAErD,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAEtD,UAAU;AACV,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAEzC,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAE/C,gBAAgB;AAChB,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAE1D,YAAY;AACZ,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAErD,eAAe;AACf,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AAEpE,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAEtD,UAAU;AACV,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB"}
@@ -0,0 +1,16 @@
1
+ import { ResourceElement } from "./core/types.js";
2
+ export declare namespace tapSubscribableResource {
3
+ type Unsubscribe = () => void;
4
+ interface SubscribableResource<TState> {
5
+ /**
6
+ * Get the current state of the store.
7
+ */
8
+ getValue(): TState;
9
+ /**
10
+ * Subscribe to the store.
11
+ */
12
+ subscribe(listener: () => void): Unsubscribe;
13
+ }
14
+ }
15
+ export declare const tapSubscribableResource: <TState>(element: ResourceElement<TState>) => tapSubscribableResource.SubscribableResource<TState>;
16
+ //# sourceMappingURL=tapSubscribableResource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tapSubscribableResource.d.ts","sourceRoot":"","sources":["../src/tapSubscribableResource.ts"],"names":[],"mappings":"AAYA,OAAO,EAAgB,eAAe,EAAE,wBAAqB;AAE7D,yBAAiB,uBAAuB,CAAC;IACvC,KAAY,WAAW,GAAG,MAAM,IAAI,CAAC;IAErC,UAAiB,oBAAoB,CAAC,MAAM;QAC1C;;WAEG;QACH,QAAQ,IAAI,MAAM,CAAC;QAEnB;;WAEG;QACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;KAC9C;CACF;AAED,eAAO,MAAM,uBAAuB,GAAI,MAAM,EAC5C,SAAS,eAAe,CAAC,MAAM,CAAC,KAC/B,uBAAuB,CAAC,oBAAoB,CAAC,MAAM,CAoErD,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { commitResourceFiber, createResourceFiber, renderResourceFiber, unmountResourceFiber, } from "./core/ResourceFiber.js";
2
+ import { flushResourcesSync, UpdateScheduler } from "./core/scheduler.js";
3
+ import { tapConst } from "./hooks/tap-const.js";
4
+ import { tapMemo } from "./hooks/tap-memo.js";
5
+ import { tapEffect } from "./hooks/tap-effect.js";
6
+ import { tapEffectEvent } from "./hooks/tap-effect-event.js";
7
+ import { tapRef } from "./hooks/tap-ref.js";
8
+ export const tapSubscribableResource = (element) => {
9
+ const scheduler = tapConst(() => new UpdateScheduler(() => {
10
+ lastRenderRef.current = null;
11
+ handleUpdate();
12
+ }), []);
13
+ const fiber = tapMemo(() => {
14
+ void element.key;
15
+ return createResourceFiber(element.type, (callback) => {
16
+ if (callback()) {
17
+ scheduler.markDirty();
18
+ }
19
+ });
20
+ }, [element.type, element.key]);
21
+ const lastRenderRef = tapRef(null);
22
+ lastRenderRef.current = renderResourceFiber(fiber, element.props);
23
+ const isMountedRef = tapRef(false);
24
+ const committedPropsRef = tapRef(element.props);
25
+ const valueRef = tapRef(lastRenderRef.current.output);
26
+ const subscribers = tapConst(() => new Set(), []);
27
+ const handleUpdate = tapEffectEvent(() => {
28
+ if (!isMountedRef.current)
29
+ return; // skip update if not mounted
30
+ if (lastRenderRef.current === null) {
31
+ lastRenderRef.current = renderResourceFiber(fiber, committedPropsRef.current);
32
+ }
33
+ if (scheduler.isDirty)
34
+ return;
35
+ committedPropsRef.current = lastRenderRef.current.props;
36
+ commitResourceFiber(fiber, lastRenderRef.current);
37
+ if (scheduler.isDirty || valueRef.current === lastRenderRef.current.output)
38
+ return;
39
+ valueRef.current = lastRenderRef.current.output;
40
+ subscribers.forEach((callback) => callback());
41
+ });
42
+ tapEffect(() => {
43
+ isMountedRef.current = true;
44
+ return () => {
45
+ isMountedRef.current = false;
46
+ unmountResourceFiber(fiber);
47
+ };
48
+ }, [fiber]);
49
+ tapEffect(() => {
50
+ flushResourcesSync(handleUpdate);
51
+ });
52
+ return tapMemo(() => ({
53
+ getValue: () => valueRef.current,
54
+ subscribe: (listener) => {
55
+ subscribers.add(listener);
56
+ return () => subscribers.delete(listener);
57
+ },
58
+ }), []);
59
+ };
60
+ //# sourceMappingURL=tapSubscribableResource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tapSubscribableResource.js","sourceRoot":"","sources":["../src/tapSubscribableResource.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/tap",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Zero-dependency reactive state management inspired by React hooks",
5
5
  "keywords": [
6
6
  "state-management",
@@ -48,13 +48,13 @@
48
48
  "devDependencies": {
49
49
  "@testing-library/dom": "^10.4.1",
50
50
  "@testing-library/react": "^16.3.2",
51
- "@types/react": "^19.2.9",
52
- "@types/node": "^25.0.10",
51
+ "@types/react": "^19.2.10",
52
+ "@types/node": "^25.2.0",
53
53
  "@types/react-dom": "^19.2.3",
54
54
  "@vitest/ui": "^4.0.18",
55
55
  "react": "19.2.4",
56
56
  "react-dom": "19.2.4",
57
- "jsdom": "^27.4.0",
57
+ "jsdom": "^28.0.0",
58
58
  "vitest": "^4.0.18",
59
59
  "@assistant-ui/x-buildutils": "0.0.1"
60
60
  },
@@ -399,7 +399,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
399
399
  });
400
400
 
401
401
  describe("Source 8: setState with function updater", () => {
402
- it.skip("should double-render with function updater in flushResourcesSync", () => {
402
+ it("should double-render with function updater in flushResourcesSync", () => {
403
403
  const events: string[] = [];
404
404
 
405
405
  const TestResource = resource(() => {
package/src/core/env.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export const isDevelopment =
2
- process.env["NODE_ENV"] === "development" ||
3
- process.env["NODE_ENV"] === "test";
2
+ typeof process !== "undefined" &&
3
+ (process.env["NODE_ENV"] === "development" ||
4
+ process.env["NODE_ENV"] === "test");
package/src/index.ts CHANGED
@@ -17,6 +17,9 @@ export { tapResource } from "./hooks/tap-resource";
17
17
  export { tapInlineResource } from "./hooks/tap-inline-resource";
18
18
  export { tapResources } from "./hooks/tap-resources";
19
19
 
20
+ // subscribable
21
+ export { tapSubscribableResource } from "./tapSubscribableResource";
22
+
20
23
  // imperative
21
24
  export { createResource } from "./core/createResource";
22
25
  export { flushResourcesSync } from "./core/scheduler";
@@ -0,0 +1,101 @@
1
+ import {
2
+ commitResourceFiber,
3
+ createResourceFiber,
4
+ renderResourceFiber,
5
+ unmountResourceFiber,
6
+ } from "./core/ResourceFiber";
7
+ import { flushResourcesSync, UpdateScheduler } from "./core/scheduler";
8
+ import { tapConst } from "./hooks/tap-const";
9
+ import { tapMemo } from "./hooks/tap-memo";
10
+ import { tapEffect } from "./hooks/tap-effect";
11
+ import { tapEffectEvent } from "./hooks/tap-effect-event";
12
+ import { tapRef } from "./hooks/tap-ref";
13
+ import { RenderResult, ResourceElement } from "./core/types";
14
+
15
+ export namespace tapSubscribableResource {
16
+ export type Unsubscribe = () => void;
17
+
18
+ export interface SubscribableResource<TState> {
19
+ /**
20
+ * Get the current state of the store.
21
+ */
22
+ getValue(): TState;
23
+
24
+ /**
25
+ * Subscribe to the store.
26
+ */
27
+ subscribe(listener: () => void): Unsubscribe;
28
+ }
29
+ }
30
+
31
+ export const tapSubscribableResource = <TState>(
32
+ element: ResourceElement<TState>,
33
+ ): tapSubscribableResource.SubscribableResource<TState> => {
34
+ const scheduler = tapConst(
35
+ () =>
36
+ new UpdateScheduler(() => {
37
+ lastRenderRef.current = null;
38
+ handleUpdate();
39
+ }),
40
+ [],
41
+ );
42
+ const fiber = tapMemo(() => {
43
+ void element.key;
44
+
45
+ return createResourceFiber(element.type, (callback) => {
46
+ if (callback()) {
47
+ scheduler.markDirty();
48
+ }
49
+ });
50
+ }, [element.type, element.key]);
51
+
52
+ const lastRenderRef = tapRef<RenderResult | null>(null);
53
+ lastRenderRef.current = renderResourceFiber(fiber, element.props);
54
+
55
+ const isMountedRef = tapRef(false);
56
+ const committedPropsRef = tapRef(element.props);
57
+ const valueRef = tapRef<TState>(lastRenderRef.current.output);
58
+ const subscribers = tapConst(() => new Set<() => void>(), []);
59
+ const handleUpdate = tapEffectEvent(() => {
60
+ if (!isMountedRef.current) return; // skip update if not mounted
61
+
62
+ if (lastRenderRef.current === null) {
63
+ lastRenderRef.current = renderResourceFiber(
64
+ fiber,
65
+ committedPropsRef.current,
66
+ );
67
+ }
68
+
69
+ if (scheduler.isDirty) return;
70
+ committedPropsRef.current = lastRenderRef.current.props;
71
+ commitResourceFiber(fiber, lastRenderRef.current);
72
+
73
+ if (scheduler.isDirty || valueRef.current === lastRenderRef.current.output)
74
+ return;
75
+ valueRef.current = lastRenderRef.current.output;
76
+ subscribers.forEach((callback) => callback());
77
+ });
78
+
79
+ tapEffect(() => {
80
+ isMountedRef.current = true;
81
+ return () => {
82
+ isMountedRef.current = false;
83
+ unmountResourceFiber(fiber);
84
+ };
85
+ }, [fiber]);
86
+
87
+ tapEffect(() => {
88
+ flushResourcesSync(handleUpdate);
89
+ });
90
+
91
+ return tapMemo(
92
+ () => ({
93
+ getValue: () => valueRef.current,
94
+ subscribe: (listener: () => void) => {
95
+ subscribers.add(listener);
96
+ return () => subscribers.delete(listener);
97
+ },
98
+ }),
99
+ [],
100
+ );
101
+ };