mycelia-kernel-plugin 1.3.0 → 1.4.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.
@@ -9,20 +9,35 @@ import {
9
9
  executeHooksAndCreateFacets,
10
10
  validateHookDependencies,
11
11
  } from './hook-processor.js';
12
+ import { instrumentBuildPhase } from '../utils/instrumentation.js';
12
13
 
13
14
  // Re-export deepMerge for backward compatibility
14
15
  export { deepMerge } from './context-resolver.js';
15
16
 
16
17
  /**
17
- * VERIFY (pure):
18
- * - resolve ctx (pure)
19
- * - collect hooks (defaults + user)
20
- * - instantiate facets
21
- * - validate + topo-sort (with caching)
18
+ * VERIFY (pure, side-effect-free):
19
+ *
20
+ * This is the first phase of the two-phase build process. It:
21
+ * 1. Resolves context (pure operation)
22
+ * 2. Collects hooks (defaults + user)
23
+ * 3. Orders hooks by dependencies (topological sort)
24
+ * 4. Executes hooks to create facets (temporary, for dependency lookups)
25
+ * 5. Validates facets against contracts
26
+ * 6. Builds dependency graph and sorts facets (with caching)
27
+ *
28
+ * Key property: This phase is pure - it doesn't mutate the subsystem state.
29
+ * Facets are temporarily added to api.__facets for dependency lookups only.
30
+ *
31
+ * @param {BaseSubsystem} subsystem - Subsystem to verify
32
+ * @param {Object} ctx - Context to merge
33
+ * @param {DependencyGraphCache} graphCache - Optional cache for dependency graphs
34
+ * @returns {Object} Build plan with { resolvedCtx, orderedKinds, facetsByKind, graphCache }
22
35
  */
23
36
  export function verifySubsystemBuild(subsystem, ctx = {}, graphCache = null) {
37
+ // Step 1: Resolve context (deep merge with subsystem.ctx)
24
38
  const resolvedCtx = resolveCtx(subsystem, ctx);
25
39
 
40
+ // Step 2: Collect all hooks (defaults + user)
26
41
  // Check whether the defaults are defined as a DefaultHooks instance or an array of hooks.
27
42
  // If it's a DefaultHooks instance, use the list() method to get the array of hooks.
28
43
  const defaults = Array.isArray(subsystem.defaultHooks)
@@ -32,22 +47,28 @@ export function verifySubsystemBuild(subsystem, ctx = {}, graphCache = null) {
32
47
  const user = Array.isArray(subsystem.hooks) ? subsystem.hooks : [];
33
48
  const hooks = [...defaults, ...user];
34
49
 
35
- // Extract hook metadata
50
+ // Step 3: Extract hook metadata (kind, required, overwrite, etc.)
36
51
  const hooksByKind = extractHookMetadata(hooks);
37
52
 
38
- // Order hooks based on dependencies
53
+ // Step 4: Order hooks based on their dependencies (topological sort)
54
+ // This ensures hooks are executed in the correct order
39
55
  const orderedHooks = orderHooksByDependencies(hooks);
40
56
 
41
- // Execute hooks and create facets
57
+ // Step 5: Execute hooks and create facets
58
+ // Facets are created here temporarily so later hooks can access them via subsystem.find()
59
+ // They will be properly initialized/attached in the execute phase
42
60
  const { facetsByKind } = executeHooksAndCreateFacets(orderedHooks, resolvedCtx, subsystem, hooksByKind);
43
61
 
44
- // Validate facets against their contracts (before dependency graph building)
62
+ // Step 6: Validate facets against their contracts (before dependency graph building)
63
+ // This ensures all facets satisfy their declared contracts
45
64
  validateFacets(facetsByKind, resolvedCtx, subsystem, defaultContractRegistry);
46
65
 
47
- // Validate hook.required dependencies exist
66
+ // Step 7: Validate hook.required dependencies exist
67
+ // Double-check that all declared dependencies are satisfied
48
68
  validateHookDependencies(hooksByKind, facetsByKind, subsystem);
49
69
 
50
- // Create cache key from sorted facet kinds
70
+ // Step 8: Create cache key from sorted facet kinds
71
+ // This key is used to cache the dependency graph computation
51
72
  const kinds = Object.keys(facetsByKind);
52
73
  const cacheKey = graphCache ? createCacheKey(kinds) : null;
53
74
 
@@ -56,21 +77,25 @@ export function verifySubsystemBuild(subsystem, ctx = {}, graphCache = null) {
56
77
  resolvedCtx.graphCache = graphCache;
57
78
  }
58
79
 
59
- // Check cache before building graph
80
+ // Step 9: Check cache before building graph
81
+ // If we've computed this dependency graph before, reuse the result
60
82
  if (graphCache && cacheKey) {
61
83
  const cached = graphCache.get(cacheKey);
62
84
  if (cached) {
63
85
  if (cached.valid) {
64
86
  // Return cached result (skip graph building and sorting)
87
+ // This significantly speeds up repeated builds with the same dependency structure
65
88
  return { resolvedCtx, orderedKinds: cached.orderedKinds, facetsByKind, graphCache };
66
89
  } else {
67
- // Throw cached error
90
+ // Throw cached error (don't recompute known-invalid graph)
68
91
  throw new Error(cached.error || 'Cached dependency graph error');
69
92
  }
70
93
  }
71
94
  }
72
95
 
73
- // Build graph and sort (will cache result in topoSort)
96
+ // Step 10: Build dependency graph and sort facets
97
+ // This computes the initialization order based on facet dependencies
98
+ // The result will be cached in topoSort for future use
74
99
  const graph = buildDepGraph(hooksByKind, facetsByKind, subsystem);
75
100
  const orderedKinds = topoSort(graph, graphCache, cacheKey);
76
101
 
@@ -79,17 +104,35 @@ export function verifySubsystemBuild(subsystem, ctx = {}, graphCache = null) {
79
104
 
80
105
  /**
81
106
  * EXECUTE (transactional):
82
- * - assign resolved ctx
83
- * - add/init/attach facets via FacetManager.addMany
84
- * - build children
107
+ *
108
+ * This is the second phase of the two-phase build process. It:
109
+ * 1. Assigns resolved context to subsystem
110
+ * 2. Separates facets into new vs overwrite
111
+ * 3. Removes overwritten facets
112
+ * 4. Adds/initializes/attaches facets via FacetManager.addMany (transactional)
113
+ * 5. Builds child subsystems recursively
114
+ *
115
+ * Key property: This phase is transactional - if any step fails, all changes are rolled back.
116
+ * Facets are properly initialized (onInit callbacks) and attached to the subsystem.
117
+ *
118
+ * @param {BaseSubsystem} subsystem - Subsystem to build
119
+ * @param {Object} plan - Build plan from verifySubsystemBuild
85
120
  */
86
121
  export async function buildSubsystem(subsystem, plan) {
122
+ // Instrument the build phase if enabled
123
+ return instrumentBuildPhase(subsystem, async () => {
124
+ return buildSubsystemInternal(subsystem, plan);
125
+ });
126
+ }
127
+
128
+ async function buildSubsystemInternal(subsystem, plan) {
87
129
  if (!plan) throw new Error('buildSubsystem: invalid plan');
88
130
  const { resolvedCtx, orderedKinds, facetsByKind } = plan;
89
131
  if (!Array.isArray(orderedKinds)) throw new Error('buildSubsystem: invalid plan');
90
132
  if (!facetsByKind || typeof facetsByKind !== 'object' || Array.isArray(facetsByKind)) throw new Error('buildSubsystem: invalid plan');
91
133
 
92
134
  // Validate consistency: if one is non-empty, the other must match
135
+ // This ensures the plan is internally consistent
93
136
  const hasOrderedKinds = orderedKinds.length > 0;
94
137
  const hasFacetsByKind = Object.keys(facetsByKind).length > 0;
95
138
 
@@ -99,14 +142,17 @@ export async function buildSubsystem(subsystem, plan) {
99
142
  if (hasOrderedKinds && !hasFacetsByKind) throw new Error('buildSubsystem: invalid plan');
100
143
  // Both empty is valid (no facets to add)
101
144
 
145
+ // Step 1: Assign resolved context to subsystem
102
146
  subsystem.ctx = resolvedCtx;
103
147
 
104
- // Separate facets into new and overwrite
148
+ // Step 2: Separate facets into new and overwrite categories
149
+ // This allows us to handle overwrites correctly (remove old, add new)
105
150
  const facetsToAdd = {};
106
151
  const kindsToAdd = [];
107
152
  const facetsToOverwrite = {};
108
153
  const kindsToOverwrite = [];
109
154
 
155
+ // Process facets in dependency order (from topological sort)
110
156
  for (const kind of orderedKinds) {
111
157
  const facet = facetsByKind[kind];
112
158
  const existingFacet = subsystem.api.__facets.find(kind);
@@ -118,13 +164,14 @@ export async function buildSubsystem(subsystem, plan) {
118
164
  } else if (existingFacet === facet) {
119
165
  // Same facet instance - this was added during verify phase for dependency lookups
120
166
  // It needs to be properly initialized/attached, so add it
167
+ // This handles the case where facets are temporarily added in verify phase
121
168
  facetsToAdd[kind] = facet;
122
169
  kindsToAdd.push(kind);
123
170
  } else {
124
171
  // Different facet instance - check if we can overwrite
125
172
  const canOverwrite = facet.shouldOverwrite?.() === true;
126
173
  if (canOverwrite) {
127
- // Remove old facet first, then add new one
174
+ // Mark for overwrite: remove old facet first, then add new one
128
175
  facetsToOverwrite[kind] = facet;
129
176
  kindsToOverwrite.push(kind);
130
177
  } else {
@@ -134,7 +181,8 @@ export async function buildSubsystem(subsystem, plan) {
134
181
  }
135
182
  }
136
183
 
137
- // First, remove overwritten facets
184
+ // Step 3: Remove overwritten facets first (before adding new ones)
185
+ // This ensures clean state before adding replacements
138
186
  for (const kind of kindsToOverwrite) {
139
187
  subsystem.api.__facets.remove(kind);
140
188
  // Also remove from subsystem property if it exists
@@ -142,16 +190,22 @@ export async function buildSubsystem(subsystem, plan) {
142
190
  try {
143
191
  delete subsystem[kind];
144
192
  } catch {
145
- // Best-effort cleanup
193
+ // Best-effort cleanup (property might be non-configurable)
146
194
  }
147
195
  }
148
196
  }
149
197
 
150
- // Then add all facets (new + overwritten)
198
+ // Step 4: Add all facets (new + overwritten) in a single transactional operation
199
+ // This ensures atomicity - if any facet fails to initialize, all are rolled back
151
200
  const allFacets = { ...facetsToAdd, ...facetsToOverwrite };
152
201
  const allKinds = [...kindsToAdd, ...kindsToOverwrite];
153
202
 
154
203
  if (allKinds.length > 0) {
204
+ // addMany is transactional - it will:
205
+ // 1. Register all facets
206
+ // 2. Initialize them (call onInit callbacks) in parallel within dependency levels
207
+ // 3. Attach them to the subsystem
208
+ // 4. Roll back everything if any initialization fails
155
209
  await subsystem.api.__facets.addMany(allKinds, allFacets, {
156
210
  init: true,
157
211
  attach: true,
@@ -160,6 +214,8 @@ export async function buildSubsystem(subsystem, plan) {
160
214
  });
161
215
  }
162
216
 
217
+ // Step 5: Build child subsystems recursively
218
+ // This allows nested plugin systems with their own dependency graphs
163
219
  await buildChildren(subsystem);
164
220
  }
165
221
 
@@ -19,6 +19,7 @@ import { createFacetContract } from '../facet-contract.js';
19
19
  * Required methods:
20
20
  * - on: Register a listener for a specific message path
21
21
  * - off: Unregister a listener for a specific message path
22
+ * - emit: Emit an event to listeners for a specific path
22
23
  * - hasListeners: Check if listeners are enabled
23
24
  * - enableListeners: Enable listeners and initialize ListenerManager
24
25
  * - disableListeners: Disable listeners (but keep manager instance)
@@ -37,6 +38,7 @@ export const listenersContract = createFacetContract({
37
38
  requiredMethods: [
38
39
  'on',
39
40
  'off',
41
+ 'emit',
40
42
  'hasListeners',
41
43
  'enableListeners',
42
44
  'disableListeners'
package/src/core/facet.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getDefaultVersion, isValidSemver } from '../utils/semver.js';
2
+ import { instrumentFacetInit, instrumentDisposeCallback } from '../utils/instrumentation.js';
2
3
 
3
4
  export class Facet {
4
5
  #kind;
@@ -121,15 +122,27 @@ export class Facet {
121
122
 
122
123
  async init(ctx, api, subsystem) {
123
124
  if (this.#isInit) return;
124
- if (this.#initCallback) {
125
+
126
+ // Use instrumentation if available and enabled, otherwise call callback directly
127
+ if (subsystem && typeof instrumentFacetInit === 'function') {
128
+ // Instrumentation will handle timing and call the callback
129
+ await instrumentFacetInit(this, ctx, api, subsystem, this.#initCallback);
130
+ } else if (this.#initCallback) {
131
+ // No instrumentation - call callback directly
125
132
  await this.#initCallback({ ctx, api, subsystem, facet: this });
126
133
  }
134
+
127
135
  this.#isInit = true;
128
136
  Object.freeze(this);
129
137
  }
130
138
 
131
- async dispose() {
132
- if (this.#disposeCallback) {
139
+ async dispose(subsystem) {
140
+ // Use instrumentation if available and enabled, otherwise call callback directly
141
+ if (subsystem && this.#disposeCallback && typeof instrumentDisposeCallback === 'function') {
142
+ // Instrumentation will handle timing and call the callback
143
+ await instrumentDisposeCallback(this, subsystem, this.#disposeCallback);
144
+ } else if (this.#disposeCallback) {
145
+ // No instrumentation - call callback directly
133
146
  await this.#disposeCallback(this);
134
147
  }
135
148
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * useSimpleListeners Hook
3
+ *
4
+ * Provides simple listener management functionality to subsystems using mitt.
5
+ * A lightweight alternative to useListeners that wraps the mitt event emitter.
6
+ *
7
+ * @param {Object} ctx - Context object containing config.listeners for listener configuration
8
+ * @param {Object} api - Subsystem API being built
9
+ * @param {BaseSubsystem} subsystem - Subsystem instance
10
+ * @returns {Facet} Facet object with listener methods
11
+ */
12
+ import mitt from 'mitt';
13
+ import { Facet } from '../../core/facet.js';
14
+ import { createHook } from '../../core/create-hook.js';
15
+ import { getDebugFlag } from '../../utils/debug-flag.js';
16
+ import { createLogger } from '../../utils/logger.js';
17
+
18
+ export const useSimpleListeners = createHook({
19
+ kind: 'listeners',
20
+ version: '1.0.0',
21
+ overwrite: false,
22
+ required: [],
23
+ attach: true,
24
+ source: import.meta.url,
25
+ contract: 'listeners',
26
+ // eslint-disable-next-line no-unused-vars
27
+ fn: (ctx, api, _subsystem) => {
28
+ const { name } = api;
29
+ const config = ctx.config?.listeners || {};
30
+ const debug = getDebugFlag(config, ctx);
31
+
32
+ // Listeners are optional - can be enabled/disabled
33
+ let emitter = null;
34
+ let listenersEnabled = false;
35
+
36
+ return new Facet('listeners', { attach: true, source: import.meta.url, contract: 'listeners' })
37
+ .add({
38
+
39
+ /**
40
+ * Check if listeners are enabled
41
+ * @returns {boolean} True if listeners are enabled
42
+ */
43
+ hasListeners() {
44
+ return listenersEnabled && emitter !== null;
45
+ },
46
+
47
+ /**
48
+ * Enable listeners
49
+ * @param {Object} [listenerOptions={}] - Options (currently unused, for API compatibility)
50
+ */
51
+ enableListeners(listenerOptions = {}) {
52
+ if (emitter === null) {
53
+ emitter = mitt();
54
+ }
55
+ listenersEnabled = true;
56
+ },
57
+
58
+ /**
59
+ * Disable listeners
60
+ */
61
+ disableListeners() {
62
+ listenersEnabled = false;
63
+ },
64
+
65
+ /**
66
+ * Register a listener for a specific path
67
+ * @param {string} path - Message path to listen for
68
+ * @param {Function} handler - Handler function
69
+ * @param {Object} [options={}] - Registration options (for API compatibility)
70
+ * @returns {boolean} Success status
71
+ */
72
+ on(path, handler, options = {}) {
73
+ // Check if listeners are enabled
74
+ if (!listenersEnabled || emitter === null) {
75
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
76
+ if (runtimeDebug) {
77
+ const runtimeLogger = createLogger(runtimeDebug, `useSimpleListeners ${name}`);
78
+ runtimeLogger.warn('Cannot register listener - listeners not enabled');
79
+ }
80
+ return false;
81
+ }
82
+
83
+ // Validate handler is a function
84
+ if (typeof handler !== 'function') {
85
+ if (debug) {
86
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
87
+ runtimeLogger.warn('Handler must be a function');
88
+ }
89
+ return false;
90
+ }
91
+
92
+ // Register with mitt
93
+ emitter.on(path, handler);
94
+ return true;
95
+ },
96
+
97
+ /**
98
+ * Unregister a listener for a specific path
99
+ * @param {string} path - Message path
100
+ * @param {Function} handler - Handler function to remove
101
+ * @param {Object} [options={}] - Unregistration options (for API compatibility)
102
+ * @returns {boolean} Success status
103
+ */
104
+ off(path, handler, options = {}) {
105
+ // Check if listeners are enabled
106
+ if (!listenersEnabled || emitter === null) {
107
+ const runtimeDebug = options.debug !== undefined ? options.debug : debug;
108
+ if (runtimeDebug) {
109
+ const runtimeLogger = createLogger(runtimeDebug, `useSimpleListeners ${name}`);
110
+ runtimeLogger.warn('Cannot unregister listener - listeners not enabled');
111
+ }
112
+ return false;
113
+ }
114
+
115
+ // Validate handler is a function
116
+ if (typeof handler !== 'function') {
117
+ if (debug) {
118
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
119
+ runtimeLogger.warn('Handler must be a function');
120
+ }
121
+ return false;
122
+ }
123
+
124
+ // Unregister from mitt
125
+ emitter.off(path, handler);
126
+ return true;
127
+ },
128
+
129
+ /**
130
+ * Emit an event to listeners for a specific path
131
+ * @param {string} path - Message path to emit to
132
+ * @param {any} message - Message/data to send to listeners
133
+ * @returns {number} Number of listeners notified, or 0 if listeners not enabled
134
+ *
135
+ * @example
136
+ * // Emit event to listeners
137
+ * const notified = subsystem.listeners.emit('layers/create', message);
138
+ */
139
+ emit(path, message) {
140
+ // Check if listeners are enabled
141
+ if (!listenersEnabled || emitter === null) {
142
+ if (debug) {
143
+ const runtimeLogger = createLogger(debug, `useSimpleListeners ${name}`);
144
+ runtimeLogger.warn('Cannot emit event - listeners not enabled');
145
+ }
146
+ return 0;
147
+ }
148
+
149
+ // Get listeners count before emitting (mitt doesn't return count)
150
+ const listeners = emitter.all.get(path);
151
+ const count = listeners ? listeners.length : 0;
152
+
153
+ // Emit to mitt
154
+ emitter.emit(path, message);
155
+
156
+ return count;
157
+ },
158
+
159
+ /**
160
+ * Expose emitter property for direct access
161
+ * Returns null if listeners are not enabled
162
+ */
163
+ get listeners() {
164
+ return emitter;
165
+ },
166
+
167
+ // Expose emitter for internal use (compatible with listeners contract)
168
+ _listenerManager: () => emitter
169
+ });
170
+ }
171
+ });
172
+
package/src/index.js CHANGED
@@ -31,14 +31,33 @@ export * from './contract/contracts/index.js';
31
31
 
32
32
  // Hook exports
33
33
  export { useListeners } from './hooks/listeners/use-listeners.js';
34
+ export { useSimpleListeners } from './hooks/listeners/use-simple-listeners.js';
34
35
  export { useQueue } from './hooks/queue/use-queue.js';
35
36
  export { useSpeak } from './hooks/speak/use-speak.js';
36
37
 
38
+ // Framework bindings are available via subpath exports:
39
+ // - 'mycelia-kernel-plugin/react' for React bindings
40
+ // - 'mycelia-kernel-plugin/vue' for Vue bindings
41
+ // - 'mycelia-kernel-plugin/svelte' for Svelte bindings
42
+ // - 'mycelia-kernel-plugin/angular' for Angular bindings
43
+ // - 'mycelia-kernel-plugin/qwik' for Qwik bindings
44
+ // - 'mycelia-kernel-plugin/solid' for Solid.js bindings
45
+ //
46
+ // They are not re-exported from the main entry point to avoid
47
+ // requiring framework dependencies when using the core system.
48
+
37
49
  // Utility exports
38
50
  export { createLogger, createSubsystemLogger } from './utils/logger.js';
39
51
  export { getDebugFlag } from './utils/debug-flag.js';
40
52
  export { findFacet } from './utils/find-facet.js';
41
53
  export { useBase } from './utils/use-base.js';
54
+ export {
55
+ isInstrumentationEnabled,
56
+ instrumentHookExecution,
57
+ instrumentFacetInit,
58
+ instrumentDisposeCallback,
59
+ instrumentBuildPhase
60
+ } from './utils/instrumentation.js';
42
61
  export {
43
62
  parseVersion,
44
63
  isValidSemver,
@@ -1,5 +1,6 @@
1
1
  import { FacetManagerTransaction } from './facet-manager-transaction.js';
2
2
  import { createSubsystemLogger } from '../utils/logger.js';
3
+ import { instrumentFacetInit, instrumentDisposeCallback } from '../utils/instrumentation.js';
3
4
 
4
5
  export class FacetManager {
5
6
  #facets = new Map(); // Map<kind, Array<facet>> - stores arrays of facets per kind, sorted by orderIndex
@@ -80,6 +81,7 @@ export class FacetManager {
80
81
  // 2) Init now
81
82
  try {
82
83
  if (opts.init && typeof facet.init === 'function') {
84
+ // facet.init() will handle instrumentation internally
83
85
  await facet.init(opts.ctx, opts.api, this.#subsystem);
84
86
  }
85
87
  } catch (err) {
@@ -546,14 +548,20 @@ export class FacetManager {
546
548
  if (Array.isArray(facets)) {
547
549
  for (const facet of facets) {
548
550
  if (typeof facet.dispose === 'function') {
549
- try { await facet.dispose(subsystem); }
551
+ try {
552
+ // facet.dispose() will handle instrumentation internally
553
+ await facet.dispose(subsystem);
554
+ }
550
555
  catch (e) { errors.push({ kind, error: e }); }
551
556
  }
552
557
  }
553
558
  } else {
554
559
  // Legacy: single facet
555
560
  if (typeof facets.dispose === 'function') {
556
- try { await facets.dispose(subsystem); }
561
+ try {
562
+ // facet.dispose() will handle instrumentation internally
563
+ await facets.dispose(subsystem);
564
+ }
557
565
  catch (e) { errors.push({ kind, error: e }); }
558
566
  }
559
567
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Qwik Builder Helpers
3
+ */
4
+
5
+ /**
6
+ * createQwikSystemBuilder - Create a reusable system builder function for Qwik
7
+ *
8
+ * @param {string} name - System name
9
+ * @param {Function} configure - Configuration function: (builder) => builder
10
+ * @returns {Function} Build function: () => Promise<System>
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { useBase } from 'mycelia-kernel-plugin';
15
+ *
16
+ * const buildTodoSystem = createQwikSystemBuilder('todo-app', (b) =>
17
+ * b
18
+ * .config('database', { host: 'localhost' })
19
+ * .use(useDatabase)
20
+ * .use(useListeners)
21
+ * );
22
+ *
23
+ * // Then use in Provider
24
+ * <MyceliaProvider build={buildTodoSystem}>
25
+ * <App />
26
+ * </MyceliaProvider>
27
+ * ```
28
+ */
29
+ export function createQwikSystemBuilder(name, configure) {
30
+ return async function build() {
31
+ // Import useBase - users should have it available
32
+ const { useBase } = await import('../utils/use-base.js');
33
+ let builder = useBase(name);
34
+ builder = configure(builder);
35
+ return builder.build();
36
+ };
37
+ }
38
+
39
+