@bluelibs/runner 3.4.1 → 3.4.2

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/src/define.ts CHANGED
@@ -41,6 +41,23 @@ import { generateCallerIdFromFile, getCallerFile } from "./tools/getCallerFile";
41
41
 
42
42
  // Helper function to get the caller file
43
43
 
44
+ /**
45
+ * Define a task.
46
+ * Generates a strongly-typed task object with id, lifecycle events, dependencies,
47
+ * middleware, and metadata.
48
+ *
49
+ * - If `id` is omitted, an anonymous, file-based id is generated.
50
+ * - Wires lifecycle events: `beforeRun`, `afterRun`, `onError`.
51
+ * - Carries through dependencies, middleware, input schema, and metadata.
52
+ *
53
+ * @typeParam Input - Input type accepted by the task's `run` function.
54
+ * @typeParam Output - Promise type returned by the `run` function.
55
+ * @typeParam Deps - Dependency map type this task requires.
56
+ * @typeParam TOn - Event type or "*" this task listens to.
57
+ * @typeParam TMeta - Arbitrary metadata type carried by the task.
58
+ * @param taskConfig - The task definition config.
59
+ * @returns A branded task definition usable by the runner.
60
+ */
44
61
  export function defineTask<
45
62
  Input = undefined,
46
63
  Output extends Promise<any> = any,
@@ -50,12 +67,6 @@ export function defineTask<
50
67
  >(
51
68
  taskConfig: ITaskDefinition<Input, Output, Deps, TOn, TMeta>
52
69
  ): ITask<Input, Output, Deps, TOn, TMeta> {
53
- /**
54
- * Creates a task definition.
55
- * - Generates an anonymous id based on file path when `id` is omitted
56
- * - Wires lifecycle events: beforeRun, afterRun, onError
57
- * - Carries through dependencies and middleware as declared
58
- */
59
70
  const filePath = getCallerFile();
60
71
  const isAnonymous = !Boolean(taskConfig.id);
61
72
  const id = taskConfig.id || generateCallerIdFromFile(filePath, "task");
@@ -118,10 +129,21 @@ export function defineResource<
118
129
  >
119
130
  ): IResource<TConfig, TValue, TDeps, TPrivate, TMeta> {
120
131
  /**
121
- * Creates a resource definition.
122
- * - Generates anonymous id when omitted (resource or index flavor)
123
- * - Wires lifecycle events: beforeInit, afterInit, onError
124
- * - Exposes `.with(config)` for config‑bound registration
132
+ * Define a resource.
133
+ * Produces a strongly-typed resource with id, lifecycle events, registration hooks,
134
+ * and optional config schema.
135
+ *
136
+ * - If `id` is omitted, an anonymous, file-based id is generated (resource or index flavored).
137
+ * - Wires lifecycle events: `beforeInit`, `afterInit`, `onError`.
138
+ * - Provides `.with(config)` for config-bound registration with optional runtime validation.
139
+ *
140
+ * @typeParam TConfig - Configuration type accepted by the resource.
141
+ * @typeParam TValue - Promise type resolved by the resource `init`.
142
+ * @typeParam TDeps - Dependency map type this resource requires.
143
+ * @typeParam TPrivate - Private context type exposed to middleware during init.
144
+ * @typeParam TMeta - Arbitrary metadata type carried by the resource.
145
+ * @param constConfig - The resource definition config.
146
+ * @returns A branded resource definition usable by the runner.
125
147
  */
126
148
  // The symbolFilePath might already come from defineIndex() for example
127
149
  const filePath: string = constConfig[symbolFilePath] || getCallerFile();
@@ -149,10 +171,14 @@ export function defineResource<
149
171
  try {
150
172
  config = this.configSchema.parse(config);
151
173
  } catch (error) {
152
- throw new ValidationError("Resource config", this.id, error instanceof Error ? error : new Error(String(error)));
174
+ throw new ValidationError(
175
+ "Resource config",
176
+ this.id,
177
+ error instanceof Error ? error : new Error(String(error))
178
+ );
153
179
  }
154
180
  }
155
-
181
+
156
182
  return {
157
183
  [symbolResourceWithConfig]: true,
158
184
  id: this.id,
@@ -237,8 +263,13 @@ export function defineEvent<TPayload = void>(
237
263
  config?: IEventDefinition<TPayload>
238
264
  ): IEvent<TPayload> {
239
265
  /**
240
- * Creates an event definition. Anonymous ids are generated from file path
241
- * when omitted. The returned object is branded for runtime checks.
266
+ * Define an event.
267
+ * Generates a branded event definition with a stable id (anonymous if omitted)
268
+ * and file path metadata for better debugging.
269
+ *
270
+ * @typeParam TPayload - Payload type carried by the event.
271
+ * @param config - Optional event definition (id, etc.).
272
+ * @returns A branded event definition.
242
273
  */
243
274
  const callerFilePath = getCallerFile();
244
275
  const eventConfig = config || {};
@@ -261,18 +292,25 @@ export type MiddlewareEverywhereOptions = {
261
292
  resources?: boolean;
262
293
  };
263
294
 
295
+ /**
296
+ * Define a middleware.
297
+ * Creates a middleware definition with anonymous id generation, `.with(config)`,
298
+ * and `.everywhere()` helpers.
299
+ *
300
+ * - `.with(config)` merges config (optionally validated via `configSchema`).
301
+ * - `.everywhere()` marks the middleware global (optionally scoping to tasks/resources).
302
+ *
303
+ * @typeParam TConfig - Configuration type accepted by the middleware.
304
+ * @typeParam TDependencies - Dependency map type required by the middleware.
305
+ * @param middlewareDef - The middleware definition config.
306
+ * @returns A branded middleware definition usable by the runner.
307
+ */
264
308
  export function defineMiddleware<
265
309
  TConfig extends Record<string, any>,
266
310
  TDependencies extends DependencyMapType
267
311
  >(
268
312
  middlewareDef: IMiddlewareDefinition<TConfig, TDependencies>
269
313
  ): IMiddleware<TConfig, TDependencies> {
270
- /**
271
- * Creates a middleware definition with:
272
- * - Anonymous id generation when omitted
273
- * - `.with(config)` to create configured instances
274
- * - `.everywhere()` to mark as global (optionally scoping to tasks/resources)
275
- */
276
314
  const filePath = getCallerFile();
277
315
  const object = {
278
316
  [symbolFilePath]: filePath,
@@ -291,10 +329,14 @@ export function defineMiddleware<
291
329
  try {
292
330
  config = object.configSchema.parse(config);
293
331
  } catch (error) {
294
- throw new ValidationError("Middleware config", object.id, error instanceof Error ? error : new Error(String(error)));
332
+ throw new ValidationError(
333
+ "Middleware config",
334
+ object.id,
335
+ error instanceof Error ? error : new Error(String(error))
336
+ );
295
337
  }
296
338
  }
297
-
339
+
298
340
  return {
299
341
  ...object,
300
342
  [symbolMiddlewareConfigured]: true,
@@ -319,31 +361,60 @@ export function defineMiddleware<
319
361
  };
320
362
  }
321
363
 
364
+ /**
365
+ * Type guard: checks if a definition is a Task.
366
+ * @param definition - Any value to test.
367
+ * @returns True when `definition` is a branded Task.
368
+ */
322
369
  export function isTask(definition: any): definition is ITask {
323
370
  return definition && definition[symbolTask];
324
371
  }
325
372
 
373
+ /**
374
+ * Type guard: checks if a definition is a Resource.
375
+ * @param definition - Any value to test.
376
+ * @returns True when `definition` is a branded Resource.
377
+ */
326
378
  export function isResource(definition: any): definition is IResource {
327
379
  return definition && definition[symbolResource];
328
380
  }
329
381
 
382
+ /**
383
+ * Type guard: checks if a definition is a Resource that carries config via `.with()`.
384
+ * @param definition - Any value to test.
385
+ * @returns True when `definition` is a branded ResourceWithConfig.
386
+ */
330
387
  export function isResourceWithConfig(
331
388
  definition: any
332
389
  ): definition is IResourceWithConfig {
333
390
  return definition && definition[symbolResourceWithConfig];
334
391
  }
335
392
 
393
+ /**
394
+ * Type guard: checks if a definition is an Event.
395
+ * @param definition - Any value to test.
396
+ * @returns True when `definition` is a branded Event.
397
+ */
336
398
  export function isEvent(definition: any): definition is IEvent {
337
399
  return definition && definition[symbolEvent];
338
400
  }
339
401
 
402
+ /**
403
+ * Type guard: checks if a definition is a Middleware.
404
+ * @param definition - Any value to test.
405
+ * @returns True when `definition` is a branded Middleware.
406
+ */
340
407
  export function isMiddleware(definition: any): definition is IMiddleware {
341
408
  return definition && definition[symbolMiddleware];
342
409
  }
343
410
 
344
411
  /**
345
412
  * Override helper that preserves the original `id` and returns the same type.
346
- * You can override any property except `id`.
413
+ * You can override any property except `id`. The override is shallow-merged over the base.
414
+ *
415
+ * @param base - The base definition to override.
416
+ * @param patch - Properties to override (except `id`).
417
+ * @returns A definition of the same kind with overrides applied.
347
418
  */
348
419
  export function defineOverride<T extends ITask<any, any, any, any>>(
349
420
  base: T,
@@ -371,9 +442,14 @@ export function defineOverride(
371
442
  }
372
443
 
373
444
  /**
374
- * Creates a tag definition.
445
+ * Create a tag definition.
375
446
  * - `.with(config)` to create configured instances
376
- * - `.extract(tags)` to extract this tag from a list of tags
447
+ * - `.extract(tags)` to extract this tag from a list of tags or a taggable's meta
448
+ *
449
+ * @typeParam TConfig - Configuration type carried by configured tags.
450
+ * @typeParam TEnforceContract - Optional helper type to enforce a contract when tags are used.
451
+ * @param definition - The tag definition (id).
452
+ * @returns A tag object with helpers to configure and extract.
377
453
  */
378
454
  export function defineTag<TConfig = void, TEnforceContract = void>(
379
455
  definition: ITagDefinition<TConfig, TEnforceContract>
@@ -44,18 +44,29 @@ export function generateCallerIdFromFile(
44
44
  suffix: string = "",
45
45
  fallbackParts: number = 4
46
46
  ): symbol {
47
- filePath = filePath.replace(/\\/g, "/"); // Normalize path for consistency.
48
- const parts = filePath.split("/");
49
- const srcIndex = parts.lastIndexOf("src");
50
- const nodeModulesIndex = parts.lastIndexOf("node_modules");
47
+ // Normalize paths for consistency across platforms
48
+ const normalizedPath = filePath.replace(/\\/g, "/");
49
+ const cwdNormalized = process.cwd().replace(/\\/g, "/");
51
50
 
52
- const breakIndex = Math.max(srcIndex, nodeModulesIndex);
51
+ const parts = normalizedPath.split("/");
52
+ const nodeModulesIndex = parts.lastIndexOf("node_modules");
53
53
 
54
54
  let relevantParts: string[];
55
55
 
56
- if (breakIndex !== -1) {
57
- relevantParts = parts.slice(breakIndex + 1);
56
+ if (nodeModulesIndex !== -1) {
57
+ // If inside node_modules, generate id relative to the package path
58
+ relevantParts = parts.slice(nodeModulesIndex + 1);
59
+ } else if (
60
+ normalizedPath === cwdNormalized ||
61
+ normalizedPath.startsWith(cwdNormalized + "/")
62
+ ) {
63
+ // Prefer generating id relative to the workspace root (process.cwd())
64
+ const relativeToCwd = normalizedPath
65
+ .slice(cwdNormalized.length)
66
+ .replace(/^\//, "");
67
+ relevantParts = relativeToCwd.length > 0 ? relativeToCwd.split("/") : [""];
58
68
  } else {
69
+ // Fallback: use the last N parts if path is outside cwd and not in node_modules
59
70
  relevantParts = parts.slice(-fallbackParts);
60
71
  }
61
72