@nadrif/thread 0.1.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/LICENSE +21 -0
- package/README.md +1156 -0
- package/package.json +133 -0
- package/src/adapters.js +404 -0
- package/src/config/adapters.js +188 -0
- package/src/config/define.js +171 -0
- package/src/config/env.js +218 -0
- package/src/config/frameworks.js +268 -0
- package/src/config/index.js +256 -0
- package/src/config/schema.js +375 -0
- package/src/config.js +52 -0
- package/src/deno.js +29 -0
- package/src/edge.js +38 -0
- package/src/env.js +361 -0
- package/src/error.js +340 -0
- package/src/factory.js +591 -0
- package/src/gpu/adapters.js +227 -0
- package/src/gpu/chains.js +261 -0
- package/src/gpu/env.js +372 -0
- package/src/gpu/gpu.js +1199 -0
- package/src/gpu/helpers.js +240 -0
- package/src/gpu/hooks.js +284 -0
- package/src/gpu/index.js +16 -0
- package/src/gpu/shaders.js +834 -0
- package/src/gpu/special.js +493 -0
- package/src/hooks.js +448 -0
- package/src/index.js +557 -0
- package/src/metrix.js +222 -0
- package/src/node.js +36 -0
- package/src/pool.js +740 -0
- package/src/serializer.js +139 -0
- package/src/thread.js +1246 -0
- package/src/types.js +1354 -0
- package/src/worker-factory.js +347 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Framework resolver for the thread config system.
|
|
3
|
+
*
|
|
4
|
+
* Maps a framework name (e.g. `'preact'`, `'react'`) to a dynamic
|
|
5
|
+
* `import()` that provides the five hooks the library needs:
|
|
6
|
+
*
|
|
7
|
+
* - **`useState`** — reactive state
|
|
8
|
+
* - **`useEffect`** — side effects and cleanup
|
|
9
|
+
* - **`useRef`** — mutable references
|
|
10
|
+
* - **`useCallback`** — memoized callbacks
|
|
11
|
+
* - **`useMemo`** — memoized values
|
|
12
|
+
*
|
|
13
|
+
* ## How it works
|
|
14
|
+
*
|
|
15
|
+
* Each framework has a **path** (the module to import) and an **extract**
|
|
16
|
+
* function (which normalizes the module's exports into the expected shape).
|
|
17
|
+
*
|
|
18
|
+
* For example:
|
|
19
|
+
* - **Preact** exports hooks directly from `preact/hooks`
|
|
20
|
+
* - **React** exports hooks from `react`
|
|
21
|
+
* - **Svelte 5** uses `svelte/reactivity` with different function names
|
|
22
|
+
* - **Vue 3** uses the composition API with `ref()`, `watchEffect()`, etc.
|
|
23
|
+
*
|
|
24
|
+
* The `extract` function handles these differences, returning a consistent
|
|
25
|
+
* `{ useState, useEffect, useRef, useCallback, useMemo }` shape.
|
|
26
|
+
*
|
|
27
|
+
* ## Adding a new framework
|
|
28
|
+
*
|
|
29
|
+
* To add a new framework:
|
|
30
|
+
* 1. Add an entry to `FRAMEWORK_MAP` with the import path and extract function
|
|
31
|
+
* 2. If the framework doesn't export React-style hooks, write an adapter
|
|
32
|
+
* in the `extract` function that wraps the framework's API
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```js
|
|
36
|
+
* // Add Solid.js support (already included)
|
|
37
|
+
* const FRAMEWORK_MAP = {
|
|
38
|
+
* solid: {
|
|
39
|
+
* path: 'solid-js',
|
|
40
|
+
* extract: (m) => m, // Solid exports hooks directly
|
|
41
|
+
* },
|
|
42
|
+
* };
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @module config/frameworks
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Framework map
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Mapping of framework names to their import configuration.
|
|
54
|
+
*
|
|
55
|
+
* Each entry has:
|
|
56
|
+
* - `path`: the npm package/module to `import()` (e.g. `'preact/hooks'`)
|
|
57
|
+
* - `extract`: function that receives the imported module and returns
|
|
58
|
+
* a normalized hooks object `{ useState, useEffect, useRef, useCallback, useMemo }`
|
|
59
|
+
*
|
|
60
|
+
* The `extract` function is needed because different frameworks export
|
|
61
|
+
* their hooks differently:
|
|
62
|
+
* - Preact: `import { useState } from 'preact/hooks'`
|
|
63
|
+
* - React: `import { useState } from 'react'`
|
|
64
|
+
* - Svelte: `import { state } from 'svelte/reactivity'`
|
|
65
|
+
* - Vue: `import { ref, watchEffect } from 'vue'`
|
|
66
|
+
*
|
|
67
|
+
* @type {Record<string, { path: string|null, extract: Function|null }>}
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
const FRAMEWORK_MAP = {
|
|
71
|
+
preact: {
|
|
72
|
+
path: 'preact/hooks',
|
|
73
|
+
extract: (m) => m,
|
|
74
|
+
},
|
|
75
|
+
react: {
|
|
76
|
+
path: 'react',
|
|
77
|
+
extract: (m) => m,
|
|
78
|
+
},
|
|
79
|
+
svelte: {
|
|
80
|
+
// Svelte 5 reactivity primitives — different API shape
|
|
81
|
+
path: 'svelte/reactivity',
|
|
82
|
+
extract: (m) => ({
|
|
83
|
+
// Svelte uses `state()` instead of `useState()`
|
|
84
|
+
useState: m.state ?? m.writable ?? m.ref,
|
|
85
|
+
// Svelte uses `effect()` instead of `useEffect()`
|
|
86
|
+
useEffect: m.effect ?? (() => {}),
|
|
87
|
+
// Svelte's `ref()` returns { value } not { current }
|
|
88
|
+
useRef: m.ref ?? m.state ?? ((v) => ({ current: v })),
|
|
89
|
+
// Svelte doesn't have useCallback — identity function
|
|
90
|
+
useCallback: (fn) => fn,
|
|
91
|
+
// Svelte doesn't have useMemo — call immediately
|
|
92
|
+
useMemo: (fn) => fn(),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
vue: {
|
|
96
|
+
// Vue 3 Composition API — uses ref() and watchEffect()
|
|
97
|
+
path: 'vue',
|
|
98
|
+
extract: (m) => ({
|
|
99
|
+
// Vue uses `ref()` for reactive state
|
|
100
|
+
useState: m.ref ?? ((v) => ({ value: v, current: v })),
|
|
101
|
+
// Vue uses `watchEffect()` or `watch()`
|
|
102
|
+
useEffect: m.watchEffect ?? m.watch ?? (() => {}),
|
|
103
|
+
// Vue's `ref()` returns { value } not { current }
|
|
104
|
+
useRef: m.ref ?? ((v) => ({ current: v })),
|
|
105
|
+
// Vue doesn't have useCallback — identity function
|
|
106
|
+
useCallback: (fn) => fn,
|
|
107
|
+
// Vue uses `computed()` for memoization
|
|
108
|
+
useMemo: m.computed ?? ((fn) => ({ value: fn() })),
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
solid: {
|
|
112
|
+
// Solid JS exports hooks directly (same API as React)
|
|
113
|
+
path: 'solid-js',
|
|
114
|
+
extract: (m) => m,
|
|
115
|
+
},
|
|
116
|
+
angular: {
|
|
117
|
+
// Angular 16+ uses signals, not React-style hooks.
|
|
118
|
+
// Not yet supported — users should use `custom` with a hook shim.
|
|
119
|
+
path: null,
|
|
120
|
+
extract: null,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Required hooks
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The five hooks that thread requires from any framework.
|
|
130
|
+
*
|
|
131
|
+
* If a framework's `extract` function returns an object missing any
|
|
132
|
+
* of these, `resolveHooks()` throws a descriptive error.
|
|
133
|
+
*
|
|
134
|
+
* @type {string[]}
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
const REQUIRED_HOOKS = ['useState', 'useEffect', 'useRef', 'useCallback', 'useMemo'];
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Public API
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve framework hooks by name or custom source.
|
|
145
|
+
*
|
|
146
|
+
* This is the core of the framework switching system. Given a framework
|
|
147
|
+
* name (from the config), it dynamically imports the right module and
|
|
148
|
+
* returns normalized hooks.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} framework
|
|
151
|
+
* Framework name. Must be one of the values in {@link FRAMEWORKS}.
|
|
152
|
+
* @param {Function|null} [customHookSource=null]
|
|
153
|
+
* Optional custom function that returns the hooks object. If provided,
|
|
154
|
+
* it overrides the framework name entirely — no import is performed.
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise<FrameworkHooks>}
|
|
157
|
+
* A normalized hooks object with `useState`, `useEffect`, `useRef`,
|
|
158
|
+
* `useCallback`, and `useMemo`.
|
|
159
|
+
*
|
|
160
|
+
* @throws {Error} If the framework is unknown, not yet supported, or
|
|
161
|
+
* missing required hooks.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```js
|
|
165
|
+
* // Resolve React hooks
|
|
166
|
+
* const hooks = await resolveHooks('react');
|
|
167
|
+
* const { useState, useEffect } = hooks;
|
|
168
|
+
*
|
|
169
|
+
* // Use in a component
|
|
170
|
+
* function Counter() {
|
|
171
|
+
* const [count, setCount] = useState(0);
|
|
172
|
+
* useEffect(() => console.log(count), [count]);
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```js
|
|
178
|
+
* // Use a custom hook source
|
|
179
|
+
* const hooks = await resolveHooks('custom', async () => {
|
|
180
|
+
* const mod = await import('my-framework/hooks');
|
|
181
|
+
* return {
|
|
182
|
+
* useState: mod.useState,
|
|
183
|
+
* useEffect: mod.useEffect,
|
|
184
|
+
* useRef: mod.useRef,
|
|
185
|
+
* useCallback: mod.useCallback,
|
|
186
|
+
* useMemo: mod.useMemo,
|
|
187
|
+
* };
|
|
188
|
+
* });
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```js
|
|
193
|
+
* // Error handling
|
|
194
|
+
* try {
|
|
195
|
+
* await resolveHooks('angular');
|
|
196
|
+
* } catch (err) {
|
|
197
|
+
* console.error(err.message);
|
|
198
|
+
* // '[thread] Framework "angular" is not supported yet. Use customHookSource to provide your own hooks.'
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export async function resolveHooks(framework, customHookSource = null) {
|
|
203
|
+
// Custom hook source overrides framework lookup
|
|
204
|
+
if (typeof customHookSource === 'function') {
|
|
205
|
+
const hooks = await customHookSource();
|
|
206
|
+
validateHooks(hooks, 'custom');
|
|
207
|
+
return hooks;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const entry = FRAMEWORK_MAP[framework];
|
|
211
|
+
|
|
212
|
+
if (!entry) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`[thread] Unknown framework: "${framework}". ` +
|
|
215
|
+
`Valid options: ${Object.keys(FRAMEWORK_MAP).join(', ')}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!entry.path) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`[thread] Framework "${framework}" is not supported yet. ` +
|
|
222
|
+
`Use customHookSource to provide your own hooks.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const mod = await import(entry.path);
|
|
227
|
+
const hooks = entry.extract(mod);
|
|
228
|
+
validateHooks(hooks, framework);
|
|
229
|
+
return hooks;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Internal
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Validate that a hooks object has all required hooks.
|
|
238
|
+
*
|
|
239
|
+
* @param {Object} hooks - The hooks object to validate.
|
|
240
|
+
* @param {string} source - Framework name for error messages.
|
|
241
|
+
* @throws {Error} If any required hook is missing.
|
|
242
|
+
* @private
|
|
243
|
+
*/
|
|
244
|
+
function validateHooks(hooks, source) {
|
|
245
|
+
if (!hooks || typeof hooks !== 'object') {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`[thread] Framework "${source}" did not return a valid hooks object.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const name of REQUIRED_HOOKS) {
|
|
252
|
+
if (typeof hooks[name] !== 'function') {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`[thread] Framework "${source}" is missing required hook: ${name}. ` +
|
|
255
|
+
`Use customHookSource to provide your own implementation.`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @typedef {Object} FrameworkHooks
|
|
263
|
+
* @property {Function} useState - Create reactive state.
|
|
264
|
+
* @property {Function} useEffect - Run side effects with cleanup.
|
|
265
|
+
* @property {Function} useRef - Create a mutable reference.
|
|
266
|
+
* @property {Function} useCallback - Memoize a callback function.
|
|
267
|
+
* @property {Function} useMemo - Memoize a computed value.
|
|
268
|
+
*/
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Core config loader for the thread library.
|
|
3
|
+
*
|
|
4
|
+
* This is the central hub of the configuration system. It:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Loads** `thread.config.js` from the user's project root
|
|
7
|
+
* 2. **Merges** it with built-in defaults
|
|
8
|
+
* 3. **Caches** the resolved config for fast access
|
|
9
|
+
* 4. **Resolves** framework hooks and state manager adapters on demand
|
|
10
|
+
*
|
|
11
|
+
* ## How config loading works
|
|
12
|
+
*
|
|
13
|
+
* The config file is loaded **synchronously** using `fs.readFileSync`.
|
|
14
|
+
* This is intentional — the config must be available before any hooks
|
|
15
|
+
* are called (hooks use top-level `await` to resolve the framework).
|
|
16
|
+
*
|
|
17
|
+
* If `thread.config.js` doesn't exist, the library uses built-in defaults
|
|
18
|
+
* (Preact + Zustand). This means **thread works out of the box** with
|
|
19
|
+
* zero configuration.
|
|
20
|
+
*
|
|
21
|
+
* ## Caching
|
|
22
|
+
*
|
|
23
|
+
* - `getConfig()` — caches the resolved config object
|
|
24
|
+
* - `getHooks()` — caches the resolved framework hooks (async import)
|
|
25
|
+
* - `getResolvedAdapter()` — caches the resolved adapter constructors
|
|
26
|
+
*
|
|
27
|
+
* All caches are cleared by `setConfig()`, which should be called during
|
|
28
|
+
* HMR (Hot Module Reload) to pick up config changes.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```js
|
|
32
|
+
* // Get the config
|
|
33
|
+
* import { getConfig } from 'thread/config';
|
|
34
|
+
*
|
|
35
|
+
* const config = getConfig();
|
|
36
|
+
* console.log(config.framework); // 'react'
|
|
37
|
+
* console.log(config.gpu.workgroupSize); // 256
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```js
|
|
42
|
+
* // Force re-resolution (e.g. during HMR)
|
|
43
|
+
* import { setConfig, getConfig } from 'thread/config';
|
|
44
|
+
*
|
|
45
|
+
* // User edits thread.config.js...
|
|
46
|
+
* setConfig(); // Clear all caches
|
|
47
|
+
* const fresh = getConfig(); // Re-reads the file
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @module config/index
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { mergeWithDefaults, validateConfig } from './schema.js';
|
|
54
|
+
import { resolveHooks } from './frameworks.js';
|
|
55
|
+
import { getAdapter } from './adapters.js';
|
|
56
|
+
|
|
57
|
+
// Re-export for consumers who need direct access
|
|
58
|
+
export { mergeWithDefaults, validateConfig } from './schema.js';
|
|
59
|
+
export { resolveHooks } from './frameworks.js';
|
|
60
|
+
export { getAdapter } from './adapters.js';
|
|
61
|
+
import { loadConfigFile, loadConfigWithOverride, setProgrammaticConfig, getProgrammaticConfig, clearProgrammaticConfig } from './env.js';
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Cache state
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/** @type {import('../types.js').threadConfig|null} Resolved config cache. */
|
|
68
|
+
let _config = null;
|
|
69
|
+
|
|
70
|
+
/** @type {Object|null} Cached resolved framework hooks. */
|
|
71
|
+
let _hooks = null;
|
|
72
|
+
|
|
73
|
+
/** @type {Object|null} Cached resolved adapter constructors. */
|
|
74
|
+
let _adapter = null;
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Internal: config file loading (delegated to config/env.js)
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Load configuration from the environment.
|
|
82
|
+
*
|
|
83
|
+
* Delegates to the cross-environment config loader which handles
|
|
84
|
+
* Node.js, Bun, Deno, and browser/edge environments. In browser/edge,
|
|
85
|
+
* returns the programmatic config if set, otherwise `null`.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Record<string, any>|null}
|
|
88
|
+
* The parsed config object, or `null` if not found.
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
function _loadConfig() {
|
|
92
|
+
return loadConfigWithOverride();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the full configuration.
|
|
97
|
+
*
|
|
98
|
+
* Loads `thread.config.js` from the project root, validates it, and
|
|
99
|
+
* merges it with defaults. If the file doesn't exist, returns
|
|
100
|
+
* the default config.
|
|
101
|
+
*
|
|
102
|
+
* @returns {import('../types.js').threadConfig}
|
|
103
|
+
* Fully resolved configuration.
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
export function resolveConfig() {
|
|
107
|
+
const file = _loadConfig();
|
|
108
|
+
const userConfig = file || {};
|
|
109
|
+
|
|
110
|
+
validateConfig(userConfig);
|
|
111
|
+
return mergeWithDefaults(userConfig);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Public API
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get the resolved thread configuration.
|
|
120
|
+
*
|
|
121
|
+
* On first call, loads and resolves `thread.config.js` from the project root.
|
|
122
|
+
* Subsequent calls return the cached config (fast, no file I/O).
|
|
123
|
+
*
|
|
124
|
+
* If `thread.config.js` doesn't exist, returns the default config
|
|
125
|
+
* (Preact + Zustand + default GPU/thread/pool settings).
|
|
126
|
+
*
|
|
127
|
+
* @returns {import('../types.js').threadConfig}
|
|
128
|
+
* The resolved configuration object.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```js
|
|
132
|
+
* import { getConfig } from 'thread/config';
|
|
133
|
+
*
|
|
134
|
+
* const config = getConfig();
|
|
135
|
+
*
|
|
136
|
+
* // Use config to decide behavior
|
|
137
|
+
* if (config.framework === 'react') {
|
|
138
|
+
* // React-specific code
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* // Access GPU defaults
|
|
142
|
+
* console.log(config.gpu.workgroupSize); // 256
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```js
|
|
147
|
+
* // Config is frozen — mutation attempts silently fail
|
|
148
|
+
* const config = getConfig();
|
|
149
|
+
* config.framework = 'vue'; // Error in strict mode, silent in sloppy
|
|
150
|
+
* console.log(config.framework); // Still 'preact'
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function getConfig() {
|
|
154
|
+
if (!_config) {
|
|
155
|
+
_config = resolveConfig();
|
|
156
|
+
}
|
|
157
|
+
return _config;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Force re-resolution of the configuration.
|
|
162
|
+
*
|
|
163
|
+
* Clears all cached state (config, hooks, adapters). The next call to
|
|
164
|
+
* `getConfig()` will re-read `thread.config.js` from disk.
|
|
165
|
+
*
|
|
166
|
+
* **When to call this:**
|
|
167
|
+
* - During HMR (Hot Module Reload) when the user edits `thread.config.js`
|
|
168
|
+
* - In test teardown to reset state between tests
|
|
169
|
+
* - When you need to programmatically change the config
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```js
|
|
173
|
+
* import { setConfig, getConfig } from 'thread/config';
|
|
174
|
+
*
|
|
175
|
+
* // User edits thread.config.js...
|
|
176
|
+
* setConfig(); // Clear caches
|
|
177
|
+
*
|
|
178
|
+
* // Next getConfig() reads the fresh file
|
|
179
|
+
* const updated = getConfig();
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function setConfig() {
|
|
183
|
+
_config = null;
|
|
184
|
+
_hooks = null;
|
|
185
|
+
_adapter = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get resolved framework hooks.
|
|
190
|
+
*
|
|
191
|
+
* On first call, dynamically imports the configured framework's hooks
|
|
192
|
+
* module (e.g. `import('preact/hooks')`). Subsequent calls return the
|
|
193
|
+
* cached hooks (fast, no import overhead).
|
|
194
|
+
*
|
|
195
|
+
* This function is used by `src/hooks.js` and `src/gpu/hooks.js` at
|
|
196
|
+
* module load time via top-level `await`:
|
|
197
|
+
*
|
|
198
|
+
* ```js
|
|
199
|
+
* const { useState, useEffect, ... } = await getHooks();
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* @returns {Promise<import('./frameworks.js').FrameworkHooks>}
|
|
203
|
+
* The resolved hooks object with `useState`, `useEffect`, `useRef`,
|
|
204
|
+
* `useCallback`, and `useMemo`.
|
|
205
|
+
*
|
|
206
|
+
* @throws {Error} If the framework is unsupported or missing required hooks.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```js
|
|
210
|
+
* const hooks = await getHooks();
|
|
211
|
+
* const { useState } = hooks;
|
|
212
|
+
*
|
|
213
|
+
* function Counter() {
|
|
214
|
+
* const [count, setCount] = useState(0);
|
|
215
|
+
* return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export async function getHooks() {
|
|
220
|
+
if (!_hooks) {
|
|
221
|
+
const config = getConfig();
|
|
222
|
+
_hooks = await resolveHooks(config.framework, config.customHookSource);
|
|
223
|
+
}
|
|
224
|
+
return _hooks;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get resolved adapter constructors for the configured state manager.
|
|
229
|
+
*
|
|
230
|
+
* Returns the adapter constructors that match the configured state
|
|
231
|
+
* manager, ready to bind threads/GPUs to your store.
|
|
232
|
+
*
|
|
233
|
+
* @returns {{ thread: Function, gpu: Function, type: string }}
|
|
234
|
+
* Adapter constructors and type hint.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```js
|
|
238
|
+
* import { getResolvedAdapter } from 'thread/config';
|
|
239
|
+
*
|
|
240
|
+
* const { thread: createBinder, gpu: createGPUBinder, type } = getResolvedAdapter();
|
|
241
|
+
*
|
|
242
|
+
* // Bind a thread to a store
|
|
243
|
+
* const binder = createBinder(myThread, useStore, 'setData');
|
|
244
|
+
* await binder.run('some data');
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export function getResolvedAdapter() {
|
|
248
|
+
if (!_adapter) {
|
|
249
|
+
const config = getConfig();
|
|
250
|
+
_adapter = getAdapter(config.stateManager, config.customAdapter);
|
|
251
|
+
}
|
|
252
|
+
return _adapter;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Re-export programmatic config helpers for browser/edge environments
|
|
256
|
+
export { setProgrammaticConfig, getProgrammaticConfig, clearProgrammaticConfig } from './env.js';
|