@logixjs/query 0.0.1
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/Engine.cjs +77 -0
- package/dist/Engine.cjs.map +1 -0
- package/dist/Engine.d.cts +57 -0
- package/dist/Engine.d.ts +57 -0
- package/dist/Engine.js +8 -0
- package/dist/Engine.js.map +1 -0
- package/dist/Query.cjs +457 -0
- package/dist/Query.cjs.map +1 -0
- package/dist/Query.d.cts +61 -0
- package/dist/Query.d.ts +61 -0
- package/dist/Query.js +10 -0
- package/dist/Query.js.map +1 -0
- package/dist/TanStack-C3mxN6Aj.d.cts +40 -0
- package/dist/TanStack-jQhqYG8i.d.ts +40 -0
- package/dist/TanStack.cjs +139 -0
- package/dist/TanStack.cjs.map +1 -0
- package/dist/TanStack.d.cts +5 -0
- package/dist/TanStack.d.ts +5 -0
- package/dist/TanStack.js +10 -0
- package/dist/TanStack.js.map +1 -0
- package/dist/Traits.cjs +65 -0
- package/dist/Traits.cjs.map +1 -0
- package/dist/Traits.d.cts +76 -0
- package/dist/Traits.d.ts +76 -0
- package/dist/Traits.js +10 -0
- package/dist/Traits.js.map +1 -0
- package/dist/chunk-L745BR5P.js +124 -0
- package/dist/chunk-L745BR5P.js.map +1 -0
- package/dist/chunk-NXFX6RIH.js +355 -0
- package/dist/chunk-NXFX6RIH.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-THVAADR2.js +53 -0
- package/dist/chunk-THVAADR2.js.map +1 -0
- package/dist/chunk-V3F5KWNH.js +30 -0
- package/dist/chunk-V3F5KWNH.js.map +1 -0
- package/dist/index.cjs +582 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Logix from '@logixjs/core';
|
|
2
|
+
|
|
3
|
+
type QueryTrigger = 'onMount' | 'onKeyChange' | 'manual';
|
|
4
|
+
type QueryConcurrency = 'switch' | 'exhaust';
|
|
5
|
+
type QueryResourceRef = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
};
|
|
8
|
+
type QueryResource = QueryResourceRef | Logix.Resource.ResourceSpec<any, any, any, any>;
|
|
9
|
+
type QueryResourceKey<R> = R extends Logix.Resource.ResourceSpec<infer Key, any, any, any> ? Key : unknown;
|
|
10
|
+
type QueryResourceData<R> = R extends Logix.Resource.ResourceSpec<any, infer Data, any, any> ? Data : unknown;
|
|
11
|
+
type QueryResourceError<R> = R extends Logix.Resource.ResourceSpec<any, any, infer Err, any> ? Err : unknown;
|
|
12
|
+
type QueryKeyState<TParams, TUI> = Readonly<{
|
|
13
|
+
readonly params: TParams;
|
|
14
|
+
readonly ui: TUI;
|
|
15
|
+
}>;
|
|
16
|
+
type QueryDepsPath<TParams, TUI> = Logix.StateTrait.StateFieldPath<QueryKeyState<TParams, TUI>>;
|
|
17
|
+
type DepsArgs<S extends object, Deps extends ReadonlyArray<string>> = {
|
|
18
|
+
readonly [K in keyof Deps]: Deps[K] extends Logix.StateTrait.StateFieldPath<S> ? Logix.StateTrait.StateAtPath<S, Deps[K]> : any;
|
|
19
|
+
};
|
|
20
|
+
type BivariantCallback<Args extends ReadonlyArray<any>, R> = {
|
|
21
|
+
bivarianceHack(...args: Args): R;
|
|
22
|
+
}['bivarianceHack'];
|
|
23
|
+
interface QuerySourceConfig<TParams, TUI = unknown, R extends QueryResource = QueryResource, Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>> {
|
|
24
|
+
readonly resource: R;
|
|
25
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
26
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional static tags for invalidate(byTag), used to narrow byTag from "refresh all" to a matched subset.
|
|
29
|
+
* - Must be serializable (recommend: string constants only)
|
|
30
|
+
* - If omitted, byTag conservatively falls back to refreshing everything
|
|
31
|
+
*/
|
|
32
|
+
readonly tags?: ReadonlyArray<string>;
|
|
33
|
+
readonly debounceMs?: number;
|
|
34
|
+
readonly concurrency?: QueryConcurrency;
|
|
35
|
+
readonly key: BivariantCallback<DepsArgs<QueryKeyState<TParams, TUI>, Deps>, QueryResourceKey<R> | undefined>;
|
|
36
|
+
}
|
|
37
|
+
type QueryBuilder<TParams, TUI> = {
|
|
38
|
+
readonly source: <R extends QueryResource, const Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>>(config: {
|
|
39
|
+
readonly resource: R;
|
|
40
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
41
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
42
|
+
readonly tags?: ReadonlyArray<string>;
|
|
43
|
+
readonly debounceMs?: number;
|
|
44
|
+
readonly concurrency?: QueryConcurrency;
|
|
45
|
+
readonly key: (...depsValues: DepsArgs<QueryKeyState<TParams, TUI>, Deps>) => QueryResourceKey<R> | undefined;
|
|
46
|
+
}) => QuerySourceConfig<TParams, TUI, R, Deps>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Query.source:
|
|
50
|
+
* - Strongly typed helper to preserve `deps` as a tuple and infer `key(...depsValues)` argument types from it.
|
|
51
|
+
* - Recommended usage inside `Query.make(..., { queries: { name: Query.source({ ... }) } })`.
|
|
52
|
+
*/
|
|
53
|
+
declare const source: <TParams, TUI = unknown, R extends QueryResource = QueryResource, const Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>>(config: {
|
|
54
|
+
readonly resource: R;
|
|
55
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
56
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
57
|
+
readonly tags?: ReadonlyArray<string>;
|
|
58
|
+
readonly debounceMs?: number;
|
|
59
|
+
readonly concurrency?: QueryConcurrency;
|
|
60
|
+
readonly key: (...depsValues: DepsArgs<QueryKeyState<TParams, TUI>, Deps>) => QueryResourceKey<R> | undefined;
|
|
61
|
+
}) => QuerySourceConfig<TParams, TUI, R, Deps>;
|
|
62
|
+
interface QueryTraitsInput<TParams, TUI> {
|
|
63
|
+
readonly queries: Readonly<Record<string, QuerySourceConfig<TParams, TUI, any>>>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lowering rules (Query -> StateTrait):
|
|
67
|
+
* - Each Query rule maps to a `StateTrait.source`, writing back to `queries.<name>` (isomorphic to module state).
|
|
68
|
+
* - keySelector computes key from `{ params; ui }`; keyHash/race gating is guaranteed by the kernel source runtime.
|
|
69
|
+
*
|
|
70
|
+
* Notes:
|
|
71
|
+
* - deps uses module-root paths (e.g. "params.q" / "ui.query.autoEnabled"), aligned with the constraints in 007/004.
|
|
72
|
+
* - concurrency "exhaust" is represented as "exhaust-trailing" in the kernel.
|
|
73
|
+
*/
|
|
74
|
+
declare const toStateTraitSpec: <TParams, TUI>(input: QueryTraitsInput<TParams, TUI>) => Logix.StateTrait.StateTraitSpec<any>;
|
|
75
|
+
|
|
76
|
+
export { type QueryBuilder, type QueryConcurrency, type QueryDepsPath, type QueryKeyState, type QueryResource, type QueryResourceData, type QueryResourceError, type QueryResourceKey, type QueryResourceRef, type QuerySourceConfig, type QueryTraitsInput, type QueryTrigger, source, toStateTraitSpec };
|
package/dist/Traits.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Logix from '@logixjs/core';
|
|
2
|
+
|
|
3
|
+
type QueryTrigger = 'onMount' | 'onKeyChange' | 'manual';
|
|
4
|
+
type QueryConcurrency = 'switch' | 'exhaust';
|
|
5
|
+
type QueryResourceRef = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
};
|
|
8
|
+
type QueryResource = QueryResourceRef | Logix.Resource.ResourceSpec<any, any, any, any>;
|
|
9
|
+
type QueryResourceKey<R> = R extends Logix.Resource.ResourceSpec<infer Key, any, any, any> ? Key : unknown;
|
|
10
|
+
type QueryResourceData<R> = R extends Logix.Resource.ResourceSpec<any, infer Data, any, any> ? Data : unknown;
|
|
11
|
+
type QueryResourceError<R> = R extends Logix.Resource.ResourceSpec<any, any, infer Err, any> ? Err : unknown;
|
|
12
|
+
type QueryKeyState<TParams, TUI> = Readonly<{
|
|
13
|
+
readonly params: TParams;
|
|
14
|
+
readonly ui: TUI;
|
|
15
|
+
}>;
|
|
16
|
+
type QueryDepsPath<TParams, TUI> = Logix.StateTrait.StateFieldPath<QueryKeyState<TParams, TUI>>;
|
|
17
|
+
type DepsArgs<S extends object, Deps extends ReadonlyArray<string>> = {
|
|
18
|
+
readonly [K in keyof Deps]: Deps[K] extends Logix.StateTrait.StateFieldPath<S> ? Logix.StateTrait.StateAtPath<S, Deps[K]> : any;
|
|
19
|
+
};
|
|
20
|
+
type BivariantCallback<Args extends ReadonlyArray<any>, R> = {
|
|
21
|
+
bivarianceHack(...args: Args): R;
|
|
22
|
+
}['bivarianceHack'];
|
|
23
|
+
interface QuerySourceConfig<TParams, TUI = unknown, R extends QueryResource = QueryResource, Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>> {
|
|
24
|
+
readonly resource: R;
|
|
25
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
26
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional static tags for invalidate(byTag), used to narrow byTag from "refresh all" to a matched subset.
|
|
29
|
+
* - Must be serializable (recommend: string constants only)
|
|
30
|
+
* - If omitted, byTag conservatively falls back to refreshing everything
|
|
31
|
+
*/
|
|
32
|
+
readonly tags?: ReadonlyArray<string>;
|
|
33
|
+
readonly debounceMs?: number;
|
|
34
|
+
readonly concurrency?: QueryConcurrency;
|
|
35
|
+
readonly key: BivariantCallback<DepsArgs<QueryKeyState<TParams, TUI>, Deps>, QueryResourceKey<R> | undefined>;
|
|
36
|
+
}
|
|
37
|
+
type QueryBuilder<TParams, TUI> = {
|
|
38
|
+
readonly source: <R extends QueryResource, const Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>>(config: {
|
|
39
|
+
readonly resource: R;
|
|
40
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
41
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
42
|
+
readonly tags?: ReadonlyArray<string>;
|
|
43
|
+
readonly debounceMs?: number;
|
|
44
|
+
readonly concurrency?: QueryConcurrency;
|
|
45
|
+
readonly key: (...depsValues: DepsArgs<QueryKeyState<TParams, TUI>, Deps>) => QueryResourceKey<R> | undefined;
|
|
46
|
+
}) => QuerySourceConfig<TParams, TUI, R, Deps>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Query.source:
|
|
50
|
+
* - Strongly typed helper to preserve `deps` as a tuple and infer `key(...depsValues)` argument types from it.
|
|
51
|
+
* - Recommended usage inside `Query.make(..., { queries: { name: Query.source({ ... }) } })`.
|
|
52
|
+
*/
|
|
53
|
+
declare const source: <TParams, TUI = unknown, R extends QueryResource = QueryResource, const Deps extends ReadonlyArray<string> = ReadonlyArray<QueryDepsPath<TParams, TUI>>>(config: {
|
|
54
|
+
readonly resource: R;
|
|
55
|
+
readonly deps: Deps & ReadonlyArray<QueryDepsPath<TParams, TUI>>;
|
|
56
|
+
readonly triggers?: ReadonlyArray<QueryTrigger>;
|
|
57
|
+
readonly tags?: ReadonlyArray<string>;
|
|
58
|
+
readonly debounceMs?: number;
|
|
59
|
+
readonly concurrency?: QueryConcurrency;
|
|
60
|
+
readonly key: (...depsValues: DepsArgs<QueryKeyState<TParams, TUI>, Deps>) => QueryResourceKey<R> | undefined;
|
|
61
|
+
}) => QuerySourceConfig<TParams, TUI, R, Deps>;
|
|
62
|
+
interface QueryTraitsInput<TParams, TUI> {
|
|
63
|
+
readonly queries: Readonly<Record<string, QuerySourceConfig<TParams, TUI, any>>>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lowering rules (Query -> StateTrait):
|
|
67
|
+
* - Each Query rule maps to a `StateTrait.source`, writing back to `queries.<name>` (isomorphic to module state).
|
|
68
|
+
* - keySelector computes key from `{ params; ui }`; keyHash/race gating is guaranteed by the kernel source runtime.
|
|
69
|
+
*
|
|
70
|
+
* Notes:
|
|
71
|
+
* - deps uses module-root paths (e.g. "params.q" / "ui.query.autoEnabled"), aligned with the constraints in 007/004.
|
|
72
|
+
* - concurrency "exhaust" is represented as "exhaust-trailing" in the kernel.
|
|
73
|
+
*/
|
|
74
|
+
declare const toStateTraitSpec: <TParams, TUI>(input: QueryTraitsInput<TParams, TUI>) => Logix.StateTrait.StateTraitSpec<any>;
|
|
75
|
+
|
|
76
|
+
export { type QueryBuilder, type QueryConcurrency, type QueryDepsPath, type QueryKeyState, type QueryResource, type QueryResourceData, type QueryResourceError, type QueryResourceKey, type QueryResourceRef, type QuerySourceConfig, type QueryTraitsInput, type QueryTrigger, source, toStateTraitSpec };
|
package/dist/Traits.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-PZ5AY32C.js";
|
|
4
|
+
|
|
5
|
+
// src/TanStack.ts
|
|
6
|
+
var TanStack_exports = {};
|
|
7
|
+
__export(TanStack_exports, {
|
|
8
|
+
engine: () => engine,
|
|
9
|
+
observe: () => observe
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// src/internal/engine/tanstack.ts
|
|
13
|
+
import { Effect, Option } from "effect";
|
|
14
|
+
var makeQueryKey = (resourceId, keyHash) => [resourceId, keyHash];
|
|
15
|
+
var engine = (queryClient, config) => {
|
|
16
|
+
const byResourceId = /* @__PURE__ */ new Map();
|
|
17
|
+
const maxEntriesPerResource = config?.maxEntriesPerResource ?? 2e3;
|
|
18
|
+
try {
|
|
19
|
+
const queryCache = queryClient.getQueryCache?.();
|
|
20
|
+
if (queryCache && typeof queryCache.clear === "function") {
|
|
21
|
+
const original = queryCache.clear.bind(queryCache);
|
|
22
|
+
queryCache.clear = () => {
|
|
23
|
+
byResourceId.clear();
|
|
24
|
+
return original();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
const ensureBucket = (resourceId) => {
|
|
30
|
+
let bucket = byResourceId.get(resourceId);
|
|
31
|
+
if (!bucket) {
|
|
32
|
+
bucket = /* @__PURE__ */ new Map();
|
|
33
|
+
byResourceId.set(resourceId, bucket);
|
|
34
|
+
}
|
|
35
|
+
return bucket;
|
|
36
|
+
};
|
|
37
|
+
const evictIfNeeded = (bucket) => {
|
|
38
|
+
if (maxEntriesPerResource === Infinity) return;
|
|
39
|
+
if (!Number.isFinite(maxEntriesPerResource) || maxEntriesPerResource <= 0) return;
|
|
40
|
+
while (bucket.size > maxEntriesPerResource) {
|
|
41
|
+
const firstKey = bucket.keys().next().value;
|
|
42
|
+
if (firstKey === void 0) return;
|
|
43
|
+
bucket.delete(firstKey);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const fetchFast = (resourceId, keyHash, effect) => Effect.suspend(() => {
|
|
47
|
+
const hit = byResourceId.get(resourceId)?.get(keyHash);
|
|
48
|
+
if (hit !== void 0) {
|
|
49
|
+
return Effect.succeed(hit);
|
|
50
|
+
}
|
|
51
|
+
return Effect.map(effect, (out) => {
|
|
52
|
+
if (out !== void 0) {
|
|
53
|
+
const bucket = ensureBucket(resourceId);
|
|
54
|
+
bucket.set(keyHash, out);
|
|
55
|
+
evictIfNeeded(bucket);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
fetch: ({ resourceId, keyHash, effect }) => fetchFast(resourceId, keyHash, effect),
|
|
62
|
+
fetchFast,
|
|
63
|
+
invalidate: (request) => Effect.gen(function* () {
|
|
64
|
+
yield* Effect.sync(() => {
|
|
65
|
+
if (request.kind === "byParams") {
|
|
66
|
+
const bucket = byResourceId.get(request.resourceId);
|
|
67
|
+
if (!bucket) return;
|
|
68
|
+
bucket.delete(request.keyHash);
|
|
69
|
+
if (bucket.size === 0) {
|
|
70
|
+
byResourceId.delete(request.resourceId);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (request.kind === "byResource") {
|
|
75
|
+
byResourceId.delete(request.resourceId);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
byResourceId.clear();
|
|
79
|
+
});
|
|
80
|
+
yield* Effect.tryPromise({
|
|
81
|
+
try: () => {
|
|
82
|
+
if (request.kind === "byResource") {
|
|
83
|
+
return queryClient.invalidateQueries({
|
|
84
|
+
queryKey: [request.resourceId]
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (request.kind === "byParams") {
|
|
88
|
+
return queryClient.invalidateQueries({
|
|
89
|
+
queryKey: makeQueryKey(request.resourceId, request.keyHash)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return queryClient.invalidateQueries({
|
|
93
|
+
predicate: (q) => {
|
|
94
|
+
const tags = q.meta?.tags;
|
|
95
|
+
return Array.isArray(tags) && tags.includes(request.tag);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
catch: (e) => e
|
|
100
|
+
}).pipe(Effect.asVoid);
|
|
101
|
+
}),
|
|
102
|
+
peekFresh: ({ resourceId, keyHash }) => Effect.sync(() => {
|
|
103
|
+
const hit = byResourceId.get(resourceId)?.get(keyHash);
|
|
104
|
+
return hit === void 0 ? Option.none() : Option.some(hit);
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/internal/tanstack/observer.ts
|
|
110
|
+
import { Effect as Effect2 } from "effect";
|
|
111
|
+
import { QueryObserver } from "@tanstack/query-core";
|
|
112
|
+
var observe = (params) => Effect2.gen(function* () {
|
|
113
|
+
const observer = new QueryObserver(params.queryClient, params.options);
|
|
114
|
+
const unsubscribe = observer.subscribe(params.onResult);
|
|
115
|
+
yield* Effect2.addFinalizer(() => Effect2.sync(() => unsubscribe()));
|
|
116
|
+
return observer;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
engine,
|
|
121
|
+
observe,
|
|
122
|
+
TanStack_exports
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=chunk-L745BR5P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/TanStack.ts","../src/internal/engine/tanstack.ts","../src/internal/tanstack/observer.ts"],"sourcesContent":["export * from './internal/engine/tanstack.js'\nexport * from './internal/tanstack/observer.js'\n","import { Effect, Option } from 'effect'\nimport type { QueryClient } from '@tanstack/query-core'\nimport type { Engine, InvalidateRequest } from '../../Engine.js'\n\nconst makeQueryKey = (resourceId: string, keyHash: string) => [resourceId, keyHash] as const\n\nexport interface TanStackEngineConfig {\n /**\n * The max number of entries per resourceId in the local fast cache.\n * - Goal: prevent unbounded memory growth when the key space is unbounded.\n * - Semantics: evicts oldest entries in insertion order (FIFO); cache misses fall back to re-loading.\n * - Set to Infinity to disable the cap (not recommended for long-running processes).\n */\n readonly maxEntriesPerResource?: number\n}\n\n/**\n * TanStack engine:\n * - Uses QueryClient for invalidation (easy integration with TanStack ecosystem).\n * - fetch/peekFresh uses a slim in-memory cache to avoid frequently creating Query objects on the Logix runtime hot path.\n * - Still preserves Logix Runtime's keyHash gating and ResourceSnapshot source-of-truth semantics.\n */\nexport const engine = (queryClient: QueryClient, config?: TanStackEngineConfig): Engine => {\n const byResourceId = new Map<string, Map<string, unknown>>()\n const maxEntriesPerResource = config?.maxEntriesPerResource ?? 2000\n\n // Align with external QueryClient.clear(): when QueryCache is cleared, clear the local fast cache as well.\n try {\n const queryCache: any = (queryClient as any).getQueryCache?.()\n if (queryCache && typeof queryCache.clear === 'function') {\n const original = queryCache.clear.bind(queryCache)\n queryCache.clear = () => {\n byResourceId.clear()\n return original()\n }\n }\n } catch {\n // ignore\n }\n\n const ensureBucket = (resourceId: string): Map<string, unknown> => {\n let bucket = byResourceId.get(resourceId)\n if (!bucket) {\n bucket = new Map<string, unknown>()\n byResourceId.set(resourceId, bucket)\n }\n return bucket\n }\n\n const evictIfNeeded = (bucket: Map<string, unknown>): void => {\n if (maxEntriesPerResource === Infinity) return\n if (!Number.isFinite(maxEntriesPerResource) || maxEntriesPerResource <= 0) return\n while (bucket.size > maxEntriesPerResource) {\n const firstKey = bucket.keys().next().value as string | undefined\n if (firstKey === undefined) return\n bucket.delete(firstKey)\n }\n }\n\n const fetchFast: Engine['fetchFast'] = (resourceId, keyHash, effect) =>\n Effect.suspend(() => {\n const hit = byResourceId.get(resourceId)?.get(keyHash)\n if (hit !== undefined) {\n return Effect.succeed(hit as any)\n }\n\n return Effect.map(effect, (out) => {\n if (out !== undefined) {\n const bucket = ensureBucket(resourceId)\n bucket.set(keyHash, out)\n evictIfNeeded(bucket)\n }\n return out as any\n })\n })\n\n return {\n fetch: ({ resourceId, keyHash, effect }) => fetchFast!(resourceId, keyHash, effect),\n\n fetchFast,\n\n invalidate: (request: InvalidateRequest) =>\n Effect.gen(function* () {\n yield* Effect.sync(() => {\n if (request.kind === 'byParams') {\n const bucket = byResourceId.get(request.resourceId)\n if (!bucket) return\n bucket.delete(request.keyHash)\n if (bucket.size === 0) {\n byResourceId.delete(request.resourceId)\n }\n return\n }\n\n if (request.kind === 'byResource') {\n byResourceId.delete(request.resourceId)\n return\n }\n\n // byTag: we currently lack a serializable tags mapping; conservatively clear all (explainable but coarse).\n byResourceId.clear()\n })\n\n // Sync to QueryClient (if there are external observers/ecosystem consumers).\n yield* Effect.tryPromise({\n try: () => {\n if (request.kind === 'byResource') {\n return queryClient.invalidateQueries({\n queryKey: [request.resourceId],\n })\n }\n\n if (request.kind === 'byParams') {\n return queryClient.invalidateQueries({\n queryKey: makeQueryKey(request.resourceId, request.keyHash),\n })\n }\n\n return queryClient.invalidateQueries({\n predicate: (q) => {\n const tags = (q.meta as any)?.tags\n return Array.isArray(tags) && tags.includes(request.tag)\n },\n })\n },\n catch: (e) => e,\n }).pipe(Effect.asVoid)\n }),\n\n peekFresh: <A>({ resourceId, keyHash }: { readonly resourceId: string; readonly keyHash: string }) =>\n Effect.sync(() => {\n const hit = byResourceId.get(resourceId)?.get(keyHash) as A | undefined\n return hit === undefined ? Option.none<A>() : Option.some(hit)\n }),\n }\n}\n","import { Effect } from 'effect'\nimport type { QueryClient } from '@tanstack/query-core'\nimport { QueryObserver } from '@tanstack/query-core'\n\n/**\n * observe (subscribe within scope + cleanup):\n * - For scenarios where you want \"TanStack-like automatic tracking\" of enabled/queryKey changes.\n * - Provided as infrastructure; domain logic can choose to use it (no hard dependency).\n */\nexport const observe = <TData = unknown>(params: {\n readonly queryClient: QueryClient\n readonly options: ConstructorParameters<typeof QueryObserver<TData>>[1]\n readonly onResult: (result: unknown) => void\n}): Effect.Effect<QueryObserver<TData>, never, any> =>\n Effect.gen(function* () {\n const observer = new QueryObserver<TData>(params.queryClient, params.options as any)\n const unsubscribe = observer.subscribe(params.onResult as any)\n yield* Effect.addFinalizer(() => Effect.sync(() => unsubscribe()))\n return observer\n })\n"],"mappings":";;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,SAAS,QAAQ,cAAc;AAI/B,IAAM,eAAe,CAAC,YAAoB,YAAoB,CAAC,YAAY,OAAO;AAkB3E,IAAM,SAAS,CAAC,aAA0B,WAA0C;AACzF,QAAM,eAAe,oBAAI,IAAkC;AAC3D,QAAM,wBAAwB,QAAQ,yBAAyB;AAG/D,MAAI;AACF,UAAM,aAAmB,YAAoB,gBAAgB;AAC7D,QAAI,cAAc,OAAO,WAAW,UAAU,YAAY;AACxD,YAAM,WAAW,WAAW,MAAM,KAAK,UAAU;AACjD,iBAAW,QAAQ,MAAM;AACvB,qBAAa,MAAM;AACnB,eAAO,SAAS;AAAA,MAClB;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,eAAe,CAAC,eAA6C;AACjE,QAAI,SAAS,aAAa,IAAI,UAAU;AACxC,QAAI,CAAC,QAAQ;AACX,eAAS,oBAAI,IAAqB;AAClC,mBAAa,IAAI,YAAY,MAAM;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,CAAC,WAAuC;AAC5D,QAAI,0BAA0B,SAAU;AACxC,QAAI,CAAC,OAAO,SAAS,qBAAqB,KAAK,yBAAyB,EAAG;AAC3E,WAAO,OAAO,OAAO,uBAAuB;AAC1C,YAAM,WAAW,OAAO,KAAK,EAAE,KAAK,EAAE;AACtC,UAAI,aAAa,OAAW;AAC5B,aAAO,OAAO,QAAQ;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,YAAiC,CAAC,YAAY,SAAS,WAC3D,OAAO,QAAQ,MAAM;AACnB,UAAM,MAAM,aAAa,IAAI,UAAU,GAAG,IAAI,OAAO;AACrD,QAAI,QAAQ,QAAW;AACrB,aAAO,OAAO,QAAQ,GAAU;AAAA,IAClC;AAEA,WAAO,OAAO,IAAI,QAAQ,CAAC,QAAQ;AACjC,UAAI,QAAQ,QAAW;AACrB,cAAM,SAAS,aAAa,UAAU;AACtC,eAAO,IAAI,SAAS,GAAG;AACvB,sBAAc,MAAM;AAAA,MACtB;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH,CAAC;AAEH,SAAO;AAAA,IACL,OAAO,CAAC,EAAE,YAAY,SAAS,OAAO,MAAM,UAAW,YAAY,SAAS,MAAM;AAAA,IAElF;AAAA,IAEA,YAAY,CAAC,YACX,OAAO,IAAI,aAAa;AACtB,aAAO,OAAO,KAAK,MAAM;AACvB,YAAI,QAAQ,SAAS,YAAY;AAC/B,gBAAM,SAAS,aAAa,IAAI,QAAQ,UAAU;AAClD,cAAI,CAAC,OAAQ;AACb,iBAAO,OAAO,QAAQ,OAAO;AAC7B,cAAI,OAAO,SAAS,GAAG;AACrB,yBAAa,OAAO,QAAQ,UAAU;AAAA,UACxC;AACA;AAAA,QACF;AAEA,YAAI,QAAQ,SAAS,cAAc;AACjC,uBAAa,OAAO,QAAQ,UAAU;AACtC;AAAA,QACF;AAGA,qBAAa,MAAM;AAAA,MACrB,CAAC;AAGD,aAAO,OAAO,WAAW;AAAA,QACvB,KAAK,MAAM;AACT,cAAI,QAAQ,SAAS,cAAc;AACjC,mBAAO,YAAY,kBAAkB;AAAA,cACnC,UAAU,CAAC,QAAQ,UAAU;AAAA,YAC/B,CAAC;AAAA,UACH;AAEA,cAAI,QAAQ,SAAS,YAAY;AAC/B,mBAAO,YAAY,kBAAkB;AAAA,cACnC,UAAU,aAAa,QAAQ,YAAY,QAAQ,OAAO;AAAA,YAC5D,CAAC;AAAA,UACH;AAEA,iBAAO,YAAY,kBAAkB;AAAA,YACnC,WAAW,CAAC,MAAM;AAChB,oBAAM,OAAQ,EAAE,MAAc;AAC9B,qBAAO,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,QAAQ,GAAG;AAAA,YACzD;AAAA,UACF,CAAC;AAAA,QACH;AAAA,QACA,OAAO,CAAC,MAAM;AAAA,MAChB,CAAC,EAAE,KAAK,OAAO,MAAM;AAAA,IACvB,CAAC;AAAA,IAEH,WAAW,CAAI,EAAE,YAAY,QAAQ,MACnC,OAAO,KAAK,MAAM;AAChB,YAAM,MAAM,aAAa,IAAI,UAAU,GAAG,IAAI,OAAO;AACrD,aAAO,QAAQ,SAAY,OAAO,KAAQ,IAAI,OAAO,KAAK,GAAG;AAAA,IAC/D,CAAC;AAAA,EACL;AACF;;;ACvIA,SAAS,UAAAA,eAAc;AAEvB,SAAS,qBAAqB;AAOvB,IAAM,UAAU,CAAkB,WAKvCA,QAAO,IAAI,aAAa;AACtB,QAAM,WAAW,IAAI,cAAqB,OAAO,aAAa,OAAO,OAAc;AACnF,QAAM,cAAc,SAAS,UAAU,OAAO,QAAe;AAC7D,SAAOA,QAAO,aAAa,MAAMA,QAAO,KAAK,MAAM,YAAY,CAAC,CAAC;AACjE,SAAO;AACT,CAAC;","names":["Effect"]}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Engine
|
|
3
|
+
} from "./chunk-THVAADR2.js";
|
|
4
|
+
import {
|
|
5
|
+
toStateTraitSpec
|
|
6
|
+
} from "./chunk-V3F5KWNH.js";
|
|
7
|
+
|
|
8
|
+
// src/Query.ts
|
|
9
|
+
import * as Logix3 from "@logixjs/core";
|
|
10
|
+
import { Schema } from "effect";
|
|
11
|
+
|
|
12
|
+
// src/internal/logics/auto-trigger.ts
|
|
13
|
+
import * as Logix from "@logixjs/core";
|
|
14
|
+
import { Duration, Effect, Fiber } from "effect";
|
|
15
|
+
var defaultTriggers = ["onMount", "onKeyChange"];
|
|
16
|
+
var getTriggers = (triggers) => triggers ?? defaultTriggers;
|
|
17
|
+
var isManualOnly = (triggers) => triggers.length === 1 && triggers[0] === "manual";
|
|
18
|
+
var getSnapshotKeyHash = (snapshot) => {
|
|
19
|
+
if (!snapshot || typeof snapshot !== "object") return void 0;
|
|
20
|
+
const keyHash = snapshot.keyHash;
|
|
21
|
+
return typeof keyHash === "string" ? keyHash : void 0;
|
|
22
|
+
};
|
|
23
|
+
var getSnapshotStatus = (snapshot) => {
|
|
24
|
+
if (!snapshot || typeof snapshot !== "object") return void 0;
|
|
25
|
+
const status = snapshot.status;
|
|
26
|
+
return typeof status === "string" ? status : void 0;
|
|
27
|
+
};
|
|
28
|
+
var autoTrigger = (module, config) => module.logic(($) => {
|
|
29
|
+
const pending = /* @__PURE__ */ new Map();
|
|
30
|
+
const lastKeyHash = /* @__PURE__ */ new Map();
|
|
31
|
+
const hydrateFromFreshCache = (name, q, keyHash) => Effect.gen(function* () {
|
|
32
|
+
const engineOpt = yield* Effect.serviceOption(Engine);
|
|
33
|
+
if (engineOpt._tag === "None") return false;
|
|
34
|
+
const engine = engineOpt.value;
|
|
35
|
+
if (!engine.peekFresh) return false;
|
|
36
|
+
const dataOpt = yield* engine.peekFresh({
|
|
37
|
+
resourceId: q.resource.id,
|
|
38
|
+
keyHash
|
|
39
|
+
});
|
|
40
|
+
if (dataOpt._tag === "None") return false;
|
|
41
|
+
const snapshot = Logix.Resource.Snapshot.success({
|
|
42
|
+
keyHash,
|
|
43
|
+
data: dataOpt.value
|
|
44
|
+
});
|
|
45
|
+
yield* $.state.mutate((draft) => {
|
|
46
|
+
;
|
|
47
|
+
draft.queries[name] = snapshot;
|
|
48
|
+
});
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
const cancelPending = (name) => Effect.gen(function* () {
|
|
52
|
+
const prev = pending.get(name);
|
|
53
|
+
if (!prev) return;
|
|
54
|
+
pending.delete(name);
|
|
55
|
+
yield* Fiber.interruptFork(prev);
|
|
56
|
+
});
|
|
57
|
+
const sourcePathOf = (name) => `queries.${name}`;
|
|
58
|
+
const refresh = (name, options) => $.traits.source.refresh(sourcePathOf(name), options);
|
|
59
|
+
const scheduleDebounced = (name, ms) => Effect.gen(function* () {
|
|
60
|
+
yield* cancelPending(name);
|
|
61
|
+
const fiber = yield* Effect.forkScoped(
|
|
62
|
+
Effect.sleep(Duration.millis(ms)).pipe(
|
|
63
|
+
Effect.zipRight(refresh(name)),
|
|
64
|
+
Effect.ensuring(Effect.sync(() => pending.delete(name))),
|
|
65
|
+
Effect.catchAllCause(() => Effect.void)
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
pending.set(name, fiber);
|
|
69
|
+
});
|
|
70
|
+
const computeKeyHash = (state, q) => {
|
|
71
|
+
const getAtPath = (root, path) => {
|
|
72
|
+
const parts = path.split(".");
|
|
73
|
+
let cur = root;
|
|
74
|
+
for (const p of parts) {
|
|
75
|
+
if (cur == null) return void 0;
|
|
76
|
+
cur = cur[p];
|
|
77
|
+
}
|
|
78
|
+
return cur;
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
const keyState = { params: state.params, ui: state.ui };
|
|
82
|
+
const args = q.deps.map((dep) => getAtPath(keyState, dep));
|
|
83
|
+
const key = q.key(...args);
|
|
84
|
+
if (key === void 0) return void 0;
|
|
85
|
+
return Logix.Resource.keyHash(key);
|
|
86
|
+
} catch {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const maybeAutoRefresh = (phase, stateOverride) => Effect.gen(function* () {
|
|
91
|
+
const state = stateOverride ?? (yield* $.state.read);
|
|
92
|
+
for (const [name, q] of Object.entries(config.queries)) {
|
|
93
|
+
const queryName = name;
|
|
94
|
+
const triggers = getTriggers(q.triggers);
|
|
95
|
+
if (isManualOnly(triggers)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (phase === "mount" && !triggers.includes("onMount")) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (phase === "keyChange" && !triggers.includes("onKeyChange")) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const keyHash = computeKeyHash(state, q);
|
|
105
|
+
const snapshot = state?.queries?.[queryName];
|
|
106
|
+
const snapshotKeyHash = getSnapshotKeyHash(snapshot);
|
|
107
|
+
const snapshotStatus = getSnapshotStatus(snapshot);
|
|
108
|
+
if (keyHash === void 0) {
|
|
109
|
+
lastKeyHash.set(queryName, void 0);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const last = lastKeyHash.get(queryName);
|
|
113
|
+
if (snapshotStatus && snapshotStatus !== "idle" && snapshotKeyHash === keyHash && last === keyHash) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (snapshotStatus && snapshotStatus !== "idle" && snapshotKeyHash === keyHash) {
|
|
117
|
+
lastKeyHash.set(queryName, keyHash);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
lastKeyHash.set(queryName, keyHash);
|
|
121
|
+
const hydrated = yield* hydrateFromFreshCache(queryName, q, keyHash);
|
|
122
|
+
if (hydrated) {
|
|
123
|
+
yield* cancelPending(queryName);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const debounceMs = q.debounceMs ?? 0;
|
|
127
|
+
if (phase === "keyChange" && debounceMs > 0) {
|
|
128
|
+
yield* scheduleDebounced(queryName, debounceMs);
|
|
129
|
+
} else {
|
|
130
|
+
yield* cancelPending(queryName);
|
|
131
|
+
yield* refresh(queryName);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const refreshAll = (options) => Effect.forEach(
|
|
136
|
+
Object.keys(config.queries),
|
|
137
|
+
(name) => cancelPending(name).pipe(Effect.zipRight(refresh(name, options)))
|
|
138
|
+
).pipe(Effect.asVoid);
|
|
139
|
+
const setup = $.lifecycle.onStart(maybeAutoRefresh("mount"));
|
|
140
|
+
const run = Effect.suspend(
|
|
141
|
+
() => Effect.all(
|
|
142
|
+
[
|
|
143
|
+
// params/ui changes: handled by Query's default logic, avoiding scattered UI-side useEffect triggers.
|
|
144
|
+
$.onAction("setParams").runFork(
|
|
145
|
+
(action) => Effect.gen(function* () {
|
|
146
|
+
const state = yield* $.state.read;
|
|
147
|
+
const next = { ...state, params: action.payload };
|
|
148
|
+
yield* maybeAutoRefresh("keyChange", next);
|
|
149
|
+
})
|
|
150
|
+
),
|
|
151
|
+
$.onAction("setUi").runFork(
|
|
152
|
+
(action) => Effect.gen(function* () {
|
|
153
|
+
const state = yield* $.state.read;
|
|
154
|
+
const next = { ...state, ui: action.payload };
|
|
155
|
+
yield* maybeAutoRefresh("keyChange", next);
|
|
156
|
+
})
|
|
157
|
+
),
|
|
158
|
+
// Manual refresh: not restricted by triggers (manual-only still allows explicit refresh).
|
|
159
|
+
$.onAction("refresh").runFork(
|
|
160
|
+
(action) => Effect.gen(function* () {
|
|
161
|
+
const target = action.payload;
|
|
162
|
+
if (typeof target === "string" && target.length > 0) {
|
|
163
|
+
yield* cancelPending(target);
|
|
164
|
+
yield* refresh(target, { force: true });
|
|
165
|
+
} else {
|
|
166
|
+
yield* refreshAll({ force: true });
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
],
|
|
171
|
+
{ concurrency: "unbounded" }
|
|
172
|
+
).pipe(Effect.asVoid)
|
|
173
|
+
);
|
|
174
|
+
return { setup, run };
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/internal/logics/invalidate.ts
|
|
178
|
+
import * as Logix2 from "@logixjs/core";
|
|
179
|
+
import { Effect as Effect2 } from "effect";
|
|
180
|
+
var toInvalidateTargets = (request, queries) => {
|
|
181
|
+
if (request.kind === "byResource") {
|
|
182
|
+
return Object.entries(queries).filter(([, q]) => q.resource.id === request.resourceId).map(([name, q]) => ({ name, resourceId: q.resource.id }));
|
|
183
|
+
}
|
|
184
|
+
if (request.kind === "byParams") {
|
|
185
|
+
return Object.entries(queries).filter(([, q]) => q.resource.id === request.resourceId).map(([name, q]) => ({ name, resourceId: q.resource.id }));
|
|
186
|
+
}
|
|
187
|
+
const entries = Object.entries(queries);
|
|
188
|
+
const tagged = entries.filter(([, q]) => Array.isArray(q.tags) && q.tags.includes(request.tag));
|
|
189
|
+
const selected = tagged.length > 0 ? tagged : entries;
|
|
190
|
+
return selected.map(([name, q]) => ({ name, resourceId: q.resource.id }));
|
|
191
|
+
};
|
|
192
|
+
var invalidate = (module, config) => module.logic(
|
|
193
|
+
($) => Effect2.gen(function* () {
|
|
194
|
+
yield* $.onAction("invalidate").runFork(
|
|
195
|
+
(action) => Effect2.gen(function* () {
|
|
196
|
+
const request = action.payload;
|
|
197
|
+
yield* Logix2.TraitLifecycle.scopedExecute($, {
|
|
198
|
+
kind: "query:invalidate",
|
|
199
|
+
request
|
|
200
|
+
});
|
|
201
|
+
const engineOpt = yield* Effect2.serviceOption(Engine);
|
|
202
|
+
if (engineOpt._tag !== "None") {
|
|
203
|
+
yield* engineOpt.value.invalidate(request);
|
|
204
|
+
}
|
|
205
|
+
const targets = toInvalidateTargets(request, config.queries);
|
|
206
|
+
const sourcePathOf = (name) => `queries.${name}`;
|
|
207
|
+
yield* Effect2.forEach(
|
|
208
|
+
targets,
|
|
209
|
+
(t) => $.traits.source.refresh(sourcePathOf(t.name), { force: true })
|
|
210
|
+
).pipe(Effect2.asVoid);
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// src/Query.ts
|
|
217
|
+
var InvalidateRequestSchema = Schema.Union(
|
|
218
|
+
Schema.Struct({
|
|
219
|
+
kind: Schema.Literal("byResource"),
|
|
220
|
+
resourceId: Schema.String
|
|
221
|
+
}),
|
|
222
|
+
Schema.Struct({
|
|
223
|
+
kind: Schema.Literal("byParams"),
|
|
224
|
+
resourceId: Schema.String,
|
|
225
|
+
keyHash: Schema.String
|
|
226
|
+
}),
|
|
227
|
+
Schema.Struct({
|
|
228
|
+
kind: Schema.Literal("byTag"),
|
|
229
|
+
tag: Schema.String
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
var assertQueryName = (name) => {
|
|
233
|
+
if (!name) {
|
|
234
|
+
throw new Error(`[Query.make] query name must be non-empty`);
|
|
235
|
+
}
|
|
236
|
+
if (name.includes(".")) {
|
|
237
|
+
throw new Error(`[Query.make] query name must not include "."; got "${name}"`);
|
|
238
|
+
}
|
|
239
|
+
if (name === "params" || name === "ui" || name === "queries") {
|
|
240
|
+
throw new Error(`[Query.make] query name "${name}" is reserved`);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var make = (id, config) => {
|
|
244
|
+
const queryBuilder = {
|
|
245
|
+
source: (q) => q
|
|
246
|
+
};
|
|
247
|
+
const queries = (() => {
|
|
248
|
+
const raw = config.queries;
|
|
249
|
+
if (!raw) return {};
|
|
250
|
+
return raw(queryBuilder);
|
|
251
|
+
})();
|
|
252
|
+
const queriesForTraits = queries;
|
|
253
|
+
for (const name of Object.keys(queries)) {
|
|
254
|
+
assertQueryName(name);
|
|
255
|
+
}
|
|
256
|
+
const RefreshTargetSchema = (() => {
|
|
257
|
+
const names = Object.keys(queries);
|
|
258
|
+
if (names.length === 0) return Schema.Never;
|
|
259
|
+
if (names.length === 1) {
|
|
260
|
+
return Schema.Literal(names[0]);
|
|
261
|
+
}
|
|
262
|
+
const asUnionMembers = (items) => {
|
|
263
|
+
if (items.length < 2) {
|
|
264
|
+
throw new Error(`[Query.make] internal error: expected at least 2 query names for union`);
|
|
265
|
+
}
|
|
266
|
+
return items;
|
|
267
|
+
};
|
|
268
|
+
const members = names.map((n) => Schema.Literal(n));
|
|
269
|
+
return Schema.Union(...asUnionMembers(members));
|
|
270
|
+
})();
|
|
271
|
+
const UiSchema = Schema.Unknown;
|
|
272
|
+
const QueriesSchema = Schema.Struct(
|
|
273
|
+
Object.fromEntries(Object.keys(queries).map((k) => [k, Schema.Unknown]))
|
|
274
|
+
);
|
|
275
|
+
const StateSchema = Schema.Struct({
|
|
276
|
+
params: config.params,
|
|
277
|
+
ui: UiSchema,
|
|
278
|
+
queries: QueriesSchema
|
|
279
|
+
});
|
|
280
|
+
const Actions = {
|
|
281
|
+
setParams: config.params,
|
|
282
|
+
setUi: UiSchema,
|
|
283
|
+
refresh: Schema.UndefinedOr(RefreshTargetSchema),
|
|
284
|
+
invalidate: InvalidateRequestSchema
|
|
285
|
+
};
|
|
286
|
+
const reducers = {
|
|
287
|
+
setParams: (state, action, sink) => {
|
|
288
|
+
sink?.("params");
|
|
289
|
+
return { ...state, params: action.payload ?? state.params };
|
|
290
|
+
},
|
|
291
|
+
setUi: (state, action, sink) => {
|
|
292
|
+
sink?.("ui");
|
|
293
|
+
return { ...state, ui: action.payload ?? state.ui };
|
|
294
|
+
},
|
|
295
|
+
refresh: (state) => state,
|
|
296
|
+
invalidate: (state) => state
|
|
297
|
+
};
|
|
298
|
+
const queryTraits = Object.keys(queriesForTraits).length > 0 ? toStateTraitSpec({ queries: queriesForTraits }) : void 0;
|
|
299
|
+
const traits = queryTraits || config.traits ? {
|
|
300
|
+
...queryTraits,
|
|
301
|
+
...config.traits
|
|
302
|
+
} : void 0;
|
|
303
|
+
const module = Logix3.Module.make(id, {
|
|
304
|
+
state: StateSchema,
|
|
305
|
+
actions: Actions,
|
|
306
|
+
reducers,
|
|
307
|
+
traits
|
|
308
|
+
});
|
|
309
|
+
const logics = [
|
|
310
|
+
autoTrigger(module.tag, { queries: queriesForTraits }),
|
|
311
|
+
invalidate(module.tag, { queries: queriesForTraits })
|
|
312
|
+
];
|
|
313
|
+
const initial = (params) => ({
|
|
314
|
+
params: params ?? config.initialParams,
|
|
315
|
+
ui: config.ui ?? {},
|
|
316
|
+
queries: Object.fromEntries(
|
|
317
|
+
Object.keys(queries).map((k) => [k, Logix3.Resource.Snapshot.idle()])
|
|
318
|
+
)
|
|
319
|
+
});
|
|
320
|
+
const controller = {
|
|
321
|
+
make: (runtime) => {
|
|
322
|
+
const dispatch = runtime.dispatch;
|
|
323
|
+
const actions = module.actions;
|
|
324
|
+
return {
|
|
325
|
+
runtime,
|
|
326
|
+
getState: runtime.getState,
|
|
327
|
+
dispatch,
|
|
328
|
+
controller: {
|
|
329
|
+
setParams: (params) => dispatch(actions.setParams(params)),
|
|
330
|
+
setUi: (ui) => dispatch(actions.setUi(ui)),
|
|
331
|
+
refresh: (target) => dispatch(actions.refresh(target)),
|
|
332
|
+
invalidate: (request) => dispatch(actions.invalidate(request))
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
module.controller = controller;
|
|
338
|
+
const EXTEND_HANDLE = /* @__PURE__ */ Symbol.for("logix.module.handle.extend");
|
|
339
|
+
module.tag[EXTEND_HANDLE] = (runtime, base) => {
|
|
340
|
+
const c = controller.make(runtime);
|
|
341
|
+
return {
|
|
342
|
+
...base,
|
|
343
|
+
controller: c.controller
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
return module.implement({
|
|
347
|
+
initial: initial(),
|
|
348
|
+
logics: [...logics]
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export {
|
|
353
|
+
make
|
|
354
|
+
};
|
|
355
|
+
//# sourceMappingURL=chunk-NXFX6RIH.js.map
|