@bluelibs/runner 3.2.0 → 3.3.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.
Files changed (62) hide show
  1. package/README.md +482 -34
  2. package/dist/cli/extract-docs.d.ts +2 -0
  3. package/dist/cli/extract-docs.js +88 -0
  4. package/dist/cli/extract-docs.js.map +1 -0
  5. package/dist/define.d.ts +21 -1
  6. package/dist/define.js +71 -0
  7. package/dist/define.js.map +1 -1
  8. package/dist/defs.d.ts +163 -4
  9. package/dist/defs.js +30 -0
  10. package/dist/defs.js.map +1 -1
  11. package/dist/docs/introspect.d.ts +7 -0
  12. package/dist/docs/introspect.js +199 -0
  13. package/dist/docs/introspect.js.map +1 -0
  14. package/dist/docs/markdown.d.ts +2 -0
  15. package/dist/docs/markdown.js +148 -0
  16. package/dist/docs/markdown.js.map +1 -0
  17. package/dist/docs/model.d.ts +62 -0
  18. package/dist/docs/model.js +33 -0
  19. package/dist/docs/model.js.map +1 -0
  20. package/dist/express/docsRouter.d.ts +12 -0
  21. package/dist/express/docsRouter.js +54 -0
  22. package/dist/express/docsRouter.js.map +1 -0
  23. package/dist/globals/globalMiddleware.d.ts +1 -0
  24. package/dist/globals/globalMiddleware.js +2 -0
  25. package/dist/globals/globalMiddleware.js.map +1 -1
  26. package/dist/globals/middleware/timeout.middleware.d.ts +8 -0
  27. package/dist/globals/middleware/timeout.middleware.js +35 -0
  28. package/dist/globals/middleware/timeout.middleware.js.map +1 -0
  29. package/dist/index.d.ts +4 -2
  30. package/dist/index.js +5 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/models/DependencyProcessor.js +2 -2
  33. package/dist/models/DependencyProcessor.js.map +1 -1
  34. package/dist/models/Store.d.ts +1 -1
  35. package/dist/models/StoreConstants.d.ts +1 -1
  36. package/dist/models/StoreConstants.js +2 -1
  37. package/dist/models/StoreConstants.js.map +1 -1
  38. package/dist/models/TaskRunner.d.ts +2 -3
  39. package/dist/models/TaskRunner.js +1 -2
  40. package/dist/models/TaskRunner.js.map +1 -1
  41. package/dist/testing.d.ts +24 -0
  42. package/dist/testing.js +41 -0
  43. package/dist/testing.js.map +1 -0
  44. package/package.json +4 -4
  45. package/src/__tests__/benchmark/task-benchmark.test.ts +132 -0
  46. package/src/__tests__/createTestResource.test.ts +139 -0
  47. package/src/__tests__/globals/timeout.middleware.test.ts +88 -0
  48. package/src/__tests__/models/Semaphore.test.ts +1 -1
  49. package/src/__tests__/override.test.ts +104 -0
  50. package/src/__tests__/run.overrides.test.ts +50 -21
  51. package/src/__tests__/run.test.ts +19 -0
  52. package/src/__tests__/tags.test.ts +396 -0
  53. package/src/__tests__/typesafety.test.ts +109 -1
  54. package/src/define.ts +97 -0
  55. package/src/defs.ts +168 -8
  56. package/src/globals/globalMiddleware.ts +2 -0
  57. package/src/globals/middleware/timeout.middleware.ts +46 -0
  58. package/src/index.ts +6 -0
  59. package/src/models/DependencyProcessor.ts +2 -10
  60. package/src/models/StoreConstants.ts +2 -1
  61. package/src/models/TaskRunner.ts +1 -3
  62. package/src/testing.ts +66 -0
package/README.md CHANGED
@@ -999,40 +999,418 @@ await paymentLogger.info("Processing payment", { data: paymentData });
999
999
  await authLogger.warn("Failed login attempt", { data: { email, ip } });
1000
1000
  ```
1001
1001
 
1002
- ## Meta: Tagging Your Components
1002
+ ## Meta: Documenting and Organizing Your Components
1003
1003
 
1004
- Sometimes you want to attach metadata to your tasks and resources for documentation, filtering, or middleware logic:
1004
+ _The structured way to describe what your components do and control their behavior_
1005
+
1006
+ Metadata in BlueLibs Runner provides a systematic way to document, categorize, and control the behavior of your tasks, resources, events, and middleware. Think of it as your component's passport - it tells you and your tools everything they need to know about what this component does and how it should be treated.
1007
+
1008
+ ### Basic Metadata Properties
1009
+
1010
+ Every component can have these basic metadata properties:
1011
+
1012
+ ```typescript
1013
+ interface IMeta {
1014
+ title?: string; // Human-readable name
1015
+ description?: string; // What this component does
1016
+ tags?: TagType[]; // Categories and behavioral flags
1017
+ }
1018
+ ```
1019
+
1020
+ ### Simple Documentation Example
1005
1021
 
1006
1022
  ```typescript
1007
- const apiTask = task({
1008
- id: "app.tasks.api.createUser",
1023
+ const userService = resource({
1024
+ id: "app.services.user",
1009
1025
  meta: {
1010
- title: "Create User API",
1011
- description: "Creates a new user account",
1012
- tags: ["api", "user", "public"],
1026
+ title: "User Management Service",
1027
+ description:
1028
+ "Handles user creation, authentication, and profile management",
1029
+ tags: ["service", "user", "core"],
1013
1030
  },
1014
- run: async (userData) => {
1015
- // Business logic
1031
+ dependencies: { database },
1032
+ init: async (_, { database }) => ({
1033
+ createUser: async (userData) => {
1034
+ /* ... */
1035
+ },
1036
+ authenticateUser: async (credentials) => {
1037
+ /* ... */
1038
+ },
1039
+ }),
1040
+ });
1041
+
1042
+ const sendWelcomeEmail = task({
1043
+ id: "app.tasks.sendWelcomeEmail",
1044
+ meta: {
1045
+ title: "Send Welcome Email",
1046
+ description: "Sends a welcome email to newly registered users",
1047
+ tags: ["email", "automation", "user-onboarding"],
1048
+ },
1049
+ dependencies: { emailService },
1050
+ run: async (userData, { emailService }) => {
1051
+ // Email sending logic
1016
1052
  },
1017
1053
  });
1054
+ ```
1055
+
1056
+ ### Tags: The Powerful Classification System
1057
+
1058
+ Tags are the most powerful part of the metadata system. They can be simple strings or sophisticated configuration objects that control component behavior.
1018
1059
 
1019
- // Middleware that only applies to API tasks
1020
- const apiMiddleware = middleware({
1021
- id: "app.middleware.api",
1060
+ #### String Tags for Simple Classification
1061
+
1062
+ ```typescript
1063
+ const adminTask = task({
1064
+ id: "app.tasks.admin.deleteUser",
1065
+ meta: {
1066
+ title: "Delete User Account",
1067
+ description: "Permanently removes a user account and all associated data",
1068
+ tags: [
1069
+ "admin", // Access level
1070
+ "destructive", // Behavioral flag
1071
+ "user", // Domain
1072
+ "gdpr-compliant", // Compliance flag
1073
+ ],
1074
+ },
1075
+ run: async (userId) => {
1076
+ // Deletion logic
1077
+ },
1078
+ });
1079
+
1080
+ // Middleware that adds extra logging for destructive operations
1081
+ const auditMiddleware = middleware({
1082
+ id: "app.middleware.audit",
1022
1083
  run: async ({ task, next }) => {
1023
- if (task.meta?.tags?.includes("api")) {
1024
- // Apply API-specific logic
1084
+ const isDestructive = task.definition.meta?.tags?.includes("destructive");
1085
+
1086
+ if (isDestructive) {
1087
+ console.log(`🔥 DESTRUCTIVE OPERATION: ${task.definition.id}`);
1088
+ await auditLogger.log({
1089
+ operation: task.definition.id,
1090
+ user: getCurrentUser(),
1091
+ timestamp: new Date(),
1092
+ });
1025
1093
  }
1094
+
1026
1095
  return next(task.input);
1027
1096
  },
1028
1097
  });
1029
1098
  ```
1030
1099
 
1100
+ #### Advanced Tags with Configuration
1101
+
1102
+ For more sophisticated control, you can create structured tags that carry configuration:
1103
+
1104
+ ```typescript
1105
+ import { tag } from "@bluelibs/runner";
1106
+
1107
+ // Define a reusable tag with configuration
1108
+ const performanceTag = tag<{ alertAboveMs: number; criticalAboveMs: number }>({
1109
+ id: "performance.monitoring",
1110
+ });
1111
+
1112
+ const rateLimitTag = tag<{ maxRequestsPerMinute: number; burstLimit?: number }>(
1113
+ {
1114
+ id: "rate.limit",
1115
+ }
1116
+ );
1117
+
1118
+ const cacheTag = tag<{ ttl: number; keyPattern?: string }>({
1119
+ id: "cache.strategy",
1120
+ });
1121
+
1122
+ // Use structured tags in your components
1123
+ const expensiveTask = task({
1124
+ id: "app.tasks.expensiveCalculation",
1125
+ meta: {
1126
+ title: "Complex Data Processing",
1127
+ description: "Performs heavy computational analysis on large datasets",
1128
+ tags: [
1129
+ "computation",
1130
+ "background",
1131
+ performanceTag.with({
1132
+ alertAboveMs: 5000,
1133
+ criticalAboveMs: 15000,
1134
+ }),
1135
+ cacheTag.with({
1136
+ ttl: 300000, // 5 minutes
1137
+ keyPattern: "calc-{userId}-{datasetId}",
1138
+ }),
1139
+ ],
1140
+ },
1141
+ run: async (input) => {
1142
+ // Heavy computation here
1143
+ },
1144
+ });
1145
+
1146
+ const apiEndpoint = task({
1147
+ id: "app.tasks.api.getUserProfile",
1148
+ meta: {
1149
+ title: "Get User Profile",
1150
+ description: "Returns user profile information with privacy filtering",
1151
+ tags: [
1152
+ "api",
1153
+ "public",
1154
+ rateLimitTag.with({
1155
+ maxRequestsPerMinute: 100,
1156
+ burstLimit: 20,
1157
+ }),
1158
+ cacheTag.with({ ttl: 60000 }), // 1 minute cache
1159
+ ],
1160
+ },
1161
+ run: async (userId) => {
1162
+ // API logic
1163
+ },
1164
+ });
1165
+ ```
1166
+
1167
+ #### Smart Middleware Using Structured Tags
1168
+
1169
+ ```typescript
1170
+ const performanceMiddleware = middleware({
1171
+ id: "app.middleware.performance",
1172
+ run: async ({ task, next }) => {
1173
+ const tags = task.definition.meta?.tags || [];
1174
+ const perfConfig = performanceTag.extract(tags);
1175
+
1176
+ if (perfConfig) {
1177
+ const startTime = Date.now();
1178
+
1179
+ try {
1180
+ const result = await next(task.input);
1181
+ const duration = Date.now() - startTime;
1182
+
1183
+ if (duration > perfConfig.config.criticalAboveMs) {
1184
+ await alerting.critical(
1185
+ `Task ${task.definition.id} took ${duration}ms`
1186
+ );
1187
+ } else if (duration > perfConfig.config.alertAboveMs) {
1188
+ await alerting.warn(`Task ${task.definition.id} took ${duration}ms`);
1189
+ }
1190
+
1191
+ return result;
1192
+ } catch (error) {
1193
+ const duration = Date.now() - startTime;
1194
+ await alerting.error(
1195
+ `Task ${task.definition.id} failed after ${duration}ms`,
1196
+ error
1197
+ );
1198
+ throw error;
1199
+ }
1200
+ }
1201
+
1202
+ return next(task.input);
1203
+ },
1204
+ });
1205
+
1206
+ const rateLimitMiddleware = middleware({
1207
+ id: "app.middleware.rateLimit",
1208
+ dependencies: { redis },
1209
+ run: async ({ task, next }, { redis }) => {
1210
+ // Extraction can be done at task.definition level or at task.definition.meta.tags
1211
+ const rateLimitCurrentTag = rateLimitTag.extract(task.definition);
1212
+
1213
+ // Alternative way
1214
+ const tags = task.definition.meta?.tags;
1215
+ const rateLimitCurrentTag = rateLimitTag.extract(tags);
1216
+
1217
+ if (rateLimitCurrentTag) {
1218
+ const key = `rateLimit:${task.definition.id}`;
1219
+ const current = await redis.incr(key);
1220
+
1221
+ if (current === 1) {
1222
+ await redis.expire(key, 60); // 1 minute window
1223
+ }
1224
+
1225
+ if (current > rateLimitCurrentTag.config.maxRequestsPerMinute) {
1226
+ throw new Error("Rate limit exceeded");
1227
+ }
1228
+ }
1229
+
1230
+ return next(task.input);
1231
+ },
1232
+ });
1233
+ ```
1234
+
1235
+ ### When to Use Metadata
1236
+
1237
+ #### ✅ Great Use Cases
1238
+
1239
+ **Documentation & Discovery**
1240
+
1241
+ ```typescript
1242
+ const paymentProcessor = resource({
1243
+ meta: {
1244
+ title: "Payment Processing Service",
1245
+ description:
1246
+ "Handles credit card payments via Stripe API with fraud detection",
1247
+ tags: ["payment", "stripe", "pci-compliant", "critical"],
1248
+ },
1249
+ // ... implementation
1250
+ });
1251
+ ```
1252
+
1253
+ **Conditional Behavior**
1254
+
1255
+ ```typescript
1256
+ const backgroundTask = task({
1257
+ meta: {
1258
+ tags: ["background", "low-priority", retryTag.with({ maxAttempts: 5 })],
1259
+ },
1260
+ // ... implementation
1261
+ });
1262
+ ```
1263
+
1264
+ **Cross-Cutting Concerns**
1265
+
1266
+ ```typescript
1267
+ // All tasks tagged with "audit" get automatic logging
1268
+ const sensitiveOperation = task({
1269
+ meta: {
1270
+ tags: ["audit", "sensitive", "admin-only"],
1271
+ },
1272
+ // ... implementation
1273
+ });
1274
+ ```
1275
+
1276
+ **Environment-Specific Behavior**
1277
+
1278
+ ```typescript
1279
+ const developmentTask = task({
1280
+ meta: {
1281
+ tags: ["development-only", debugTag.with({ verbose: true })],
1282
+ },
1283
+ // ... implementation
1284
+ });
1285
+ ```
1286
+
1287
+ #### ❌ When NOT to Use Metadata
1288
+
1289
+ **Simple Internal Logic** - Don't overcomplicate straightforward code:
1290
+
1291
+ ```typescript
1292
+ // ❌ Overkill
1293
+ const simple = task({
1294
+ meta: { tags: ["internal", "utility"] },
1295
+ run: () => Math.random(),
1296
+ });
1297
+
1298
+ // ✅ Better
1299
+ const generateId = () => Math.random().toString(36);
1300
+ ```
1301
+
1302
+ **One-Off Tasks** - If it's used once, metadata won't help:
1303
+
1304
+ ```typescript
1305
+ // ❌ Unnecessary
1306
+ const oneTimeScript = task({
1307
+ meta: { title: "Migration Script", tags: ["migration"] },
1308
+ run: () => {
1309
+ /* run once and forget */
1310
+ },
1311
+ });
1312
+ ```
1313
+
1314
+ ### Extending Metadata: Custom Properties
1315
+
1316
+ For advanced use cases, you can extend the metadata interfaces to add your own properties:
1317
+
1318
+ ```typescript
1319
+ // In your types file
1320
+ declare module "@bluelibs/runner" {
1321
+ interface ITaskMeta {
1322
+ author?: string;
1323
+ version?: string;
1324
+ deprecated?: boolean;
1325
+ apiVersion?: "v1" | "v2" | "v3";
1326
+ costLevel?: "low" | "medium" | "high";
1327
+ }
1328
+
1329
+ interface IResourceMeta {
1330
+ healthCheck?: string; // URL for health checking
1331
+ dependencies?: string[]; // External service dependencies
1332
+ scalingPolicy?: "auto" | "manual";
1333
+ }
1334
+ }
1335
+
1336
+ // Now use your custom properties
1337
+ const expensiveApiTask = task({
1338
+ id: "app.tasks.ai.generateImage",
1339
+ meta: {
1340
+ title: "AI Image Generation",
1341
+ description: "Uses OpenAI DALL-E to generate images from text prompts",
1342
+ tags: ["ai", "expensive", "external-api"],
1343
+ author: "AI Team",
1344
+ version: "2.1.0",
1345
+ apiVersion: "v2",
1346
+ costLevel: "high", // Custom property!
1347
+ },
1348
+ run: async (prompt) => {
1349
+ // AI generation logic
1350
+ },
1351
+ });
1352
+
1353
+ const database = resource({
1354
+ id: "app.database.primary",
1355
+ meta: {
1356
+ title: "Primary PostgreSQL Database",
1357
+ tags: ["database", "critical", "persistent"],
1358
+ healthCheck: "/health/db", // Custom property!
1359
+ dependencies: ["postgresql", "connection-pool"],
1360
+ scalingPolicy: "auto",
1361
+ },
1362
+ // ... implementation
1363
+ });
1364
+ ```
1365
+
1366
+ ### Advanced Patterns
1367
+
1368
+ #### Tag-Based Component Selection
1369
+
1370
+ ```typescript
1371
+ // Find all API endpoints
1372
+ function getApiTasks(store: Store) {
1373
+ return store.getAllTasks().filter((task) => task.meta?.tags?.includes("api"));
1374
+ }
1375
+
1376
+ // Find all tasks with specific performance requirements
1377
+ function getPerformanceCriticalTasks(store: Store) {
1378
+ return store.getAllTasks().filter((task) => {
1379
+ const tags = task.meta?.tags || [];
1380
+ return performanceTag.extract(tags) !== null;
1381
+ });
1382
+ }
1383
+ ```
1384
+
1385
+ #### Dynamic Middleware Application
1386
+
1387
+ ```typescript
1388
+ const app = resource({
1389
+ id: "app",
1390
+ register: [
1391
+ // Apply performance middleware globally but only to tagged tasks
1392
+ performanceMiddleware.everywhere({
1393
+ tasks: true,
1394
+ resources: false,
1395
+ }),
1396
+ // Apply rate limiting only to API tasks
1397
+ rateLimitMiddleware.everywhere({
1398
+ tasks: true,
1399
+ resources: false,
1400
+ }),
1401
+ ],
1402
+ });
1403
+ ```
1404
+
1405
+ Metadata transforms your components from anonymous functions into self-documenting, discoverable, and controllable building blocks. Use it wisely, and your future self (and your team) will thank you.
1406
+
1031
1407
  ## Advanced Usage: When You Need More Power
1032
1408
 
1033
1409
  ### Overrides: Swapping Components at Runtime
1034
1410
 
1035
- Sometimes you need to replace a component entirely. Maybe you're testing, maybe you're A/B testing, maybe you just changed your mind:
1411
+ Sometimes you need to replace a component entirely. Maybe you're doing integration testing or you want to override a library from an external package.
1412
+
1413
+ You can now use a dedicated helper `override()` to safely override any property on tasks, resources, or middleware — except `id`. This ensures the identity is preserved, while allowing behavior changes.
1036
1414
 
1037
1415
  ```typescript
1038
1416
  const productionEmailer = resource({
@@ -1040,9 +1418,15 @@ const productionEmailer = resource({
1040
1418
  init: async () => new SMTPEmailer(),
1041
1419
  });
1042
1420
 
1421
+ // Option 1: Using override() to change behavior while preserving id (Recommended)
1422
+ const testEmailer = override(productionEmailer, {
1423
+ init: async () => new MockEmailer(),
1424
+ });
1425
+
1426
+ // Option 2: Using spread operator, does not provide type-safety
1043
1427
  const testEmailer = resource({
1044
- ...productionEmailer, // Copy everything else
1045
- init: async () => new MockEmailer(), // But use a different implementation
1428
+ ...productionEmailer,
1429
+ init: async () => {},
1046
1430
  });
1047
1431
 
1048
1432
  const app = resource({
@@ -1050,8 +1434,36 @@ const app = resource({
1050
1434
  register: [productionEmailer],
1051
1435
  overrides: [testEmailer], // This replaces the production version
1052
1436
  });
1437
+
1438
+ import { override } from "@bluelibs/runner";
1439
+
1440
+ // Tasks
1441
+ const originalTask = task({ id: "app.tasks.compute", run: async () => 1 });
1442
+ const overriddenTask = override(originalTask, {
1443
+ run: async () => 2,
1444
+ });
1445
+
1446
+ // Resources
1447
+ const originalResource = resource({ id: "app.db", init: async () => "conn" });
1448
+ const overriddenResource = override(originalResource, {
1449
+ init: async () => "mock-conn",
1450
+ });
1451
+
1452
+ // Middleware
1453
+ const originalMiddleware = middleware({
1454
+ id: "app.middleware.log",
1455
+ run: async ({ next }) => next(),
1456
+ });
1457
+ const overriddenMiddleware = override(originalMiddleware, {
1458
+ run: async ({ task, next }) => {
1459
+ const result = await next(task?.input as any);
1460
+ return { wrapped: result } as any;
1461
+ },
1462
+ });
1053
1463
  ```
1054
1464
 
1465
+ Overrides are applied after everything is registered. If multiple overrides target the same id, the one defined higher in the resource tree (closer to the root) wins, because it’s applied last. Conflicting overrides are allowed; overriding something that wasn’t registered throws. Use override() to change behavior safely while preserving the original id.
1466
+
1055
1467
  ### Namespacing: Keeping Things Organized
1056
1468
 
1057
1469
  As your app grows, you'll want consistent naming. Here's the convention that won't drive you crazy:
@@ -1444,34 +1856,70 @@ describe("registerUser task", () => {
1444
1856
  });
1445
1857
  ```
1446
1858
 
1447
- ### Integration Testing: The Real Deal
1859
+ ### Integration Testing: The Real Deal (But Actually Fun)
1448
1860
 
1449
- Integration testing with overrides lets you test the whole system with controlled components:
1861
+ Spin up your whole app, keep all the middleware/events, and still test like a human. The trick: a tiny test harness.
1450
1862
 
1451
1863
  ```typescript
1452
- const testDatabase = resource({
1453
- id: "app.database",
1454
- init: async () => new MemoryDatabase(), // In-memory test database
1864
+ import {
1865
+ run,
1866
+ createTestResource,
1867
+ resource,
1868
+ task,
1869
+ override,
1870
+ } from "@bluelibs/runner";
1871
+
1872
+ // Your real app
1873
+ const app = resource({
1874
+ id: "app",
1875
+ register: [
1876
+ /* tasks, resources, middleware */
1877
+ ],
1455
1878
  });
1456
1879
 
1457
- // Just like a shaworma wrap!
1458
- const testApp = resource({
1459
- id: "test.app",
1460
- register: [productionApp],
1461
- overrides: [testDatabase], // Replace real database with test one
1880
+ // Optional: overrides for infra (hello, fast tests!)
1881
+ const testDb = resource({
1882
+ id: "app.database",
1883
+ init: async () => new InMemoryDb(),
1462
1884
  });
1885
+ const mockMailer = override(realMailer, { init: async () => fakeMailer });
1463
1886
 
1464
- describe("Full application", () => {
1465
- it("should handle user registration flow", async () => {
1466
- const { dispose } = await run(testApp);
1887
+ // Create the test harness
1888
+ const harness = createTestResource(app, { overrides: [testDb, mockMailer] });
1467
1889
 
1468
- // Test your application end-to-end
1890
+ // A task you want to drive in your tests
1891
+ const registerUser = task({ id: "app.tasks.registerUser" /* ... */ });
1469
1892
 
1470
- await dispose(); // Clean up
1471
- });
1472
- });
1893
+ // Boom: full ecosystem run (middleware, events, overrides) with a tiny driver
1894
+ const { value: t, dispose } = await run(harness);
1895
+ const result = await t.runTask(registerUser, { email: "x@y.z" });
1896
+ expect(result).toMatchObject({ success: true });
1897
+ await dispose();
1473
1898
  ```
1474
1899
 
1900
+ Prefer scenario tests? Return whatever you want from the harness and assert outside:
1901
+
1902
+ ```typescript
1903
+ const flowHarness = createTestResource(
1904
+ resource({
1905
+ id: "app",
1906
+ register: [db, createUser, issueToken],
1907
+ })
1908
+ );
1909
+
1910
+ const { value: t, dispose } = await run(flowHarness);
1911
+ const user = await t.runTask(createUser, { email: "a@b.c" });
1912
+ const token = await t.runTask(issueToken, { userId: user.id });
1913
+ expect(token).toBeTruthy();
1914
+ await dispose();
1915
+ ```
1916
+
1917
+ Why this rocks:
1918
+
1919
+ - Minimal ceremony, no API pollution
1920
+ - Real wiring (middleware/events/overrides) – what runs in prod runs in tests
1921
+ - You choose: drive tasks directly or build domain-y flows
1922
+
1475
1923
  ## Semaphore
1476
1924
 
1477
1925
  Ever had too many database connections competing for resources? Your connection pool under pressure? The `Semaphore` is here to manage concurrent operations like a professional traffic controller.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ // Note: avoid file URLs to keep CommonJS require working well
7
+ const introspect_1 = require("../docs/introspect");
8
+ function parseArgs(argv) {
9
+ const args = { entry: "", outDir: "./documentation" };
10
+ const offset = argv[2] === "extract-docs" ? 3 : 2;
11
+ const rest = argv.slice(offset);
12
+ if (!rest[0]) {
13
+ console.error("Usage: runner extract-docs <entry> [--export <name>] [--out-dir <dir>] [--include-globals] [--config <path>] [--format json|md|all]");
14
+ process.exit(1);
15
+ }
16
+ args.entry = rest[0];
17
+ for (let i = 1; i < rest.length; i++) {
18
+ const flag = rest[i];
19
+ if (flag === "--export") {
20
+ args.exportName = rest[++i];
21
+ }
22
+ else if (flag === "--out-dir") {
23
+ args.outDir = rest[++i];
24
+ }
25
+ else if (flag === "--include-globals") {
26
+ args.includeGlobals = true;
27
+ }
28
+ else if (flag === "--config") {
29
+ args.configPath = rest[++i];
30
+ }
31
+ else if (flag === "--format") {
32
+ const val = rest[++i];
33
+ if (val === "json" || val === "md" || val === "all") {
34
+ args.format = val;
35
+ }
36
+ }
37
+ }
38
+ return args;
39
+ }
40
+ async function loadModule(entryPath) {
41
+ const resolved = path.isAbsolute(entryPath)
42
+ ? entryPath
43
+ : path.resolve(process.cwd(), entryPath);
44
+ // Prefer dynamic import; in CommonJS this becomes require(resolved)
45
+ return await Promise.resolve(`${resolved}`).then(s => require(s));
46
+ }
47
+ async function main() {
48
+ const args = parseArgs(process.argv);
49
+ const mod = await loadModule(args.entry);
50
+ const exportName = args.exportName || "default";
51
+ let resource;
52
+ if (exportName === "default") {
53
+ resource = (mod && mod.default) || mod;
54
+ }
55
+ else {
56
+ resource = mod[exportName];
57
+ }
58
+ if (!resource || typeof resource !== "object") {
59
+ console.error(`Could not find exported resource '${exportName}' from ${args.entry}`);
60
+ process.exit(2);
61
+ }
62
+ const config = args.configPath
63
+ ? JSON.parse(fs.readFileSync(args.configPath, "utf-8"))
64
+ : {};
65
+ const graph = await (0, introspect_1.introspectResource)(resource, config, {
66
+ includeGlobals: args.includeGlobals,
67
+ });
68
+ const outDir = path.isAbsolute(args.outDir)
69
+ ? args.outDir
70
+ : path.resolve(process.cwd(), args.outDir);
71
+ fs.mkdirSync(outDir, { recursive: true });
72
+ const format = args.format || "json";
73
+ if (format === "json" || format === "all") {
74
+ const outFile = path.join(outDir, "graph.json");
75
+ fs.writeFileSync(outFile, JSON.stringify(graph, null, 2), "utf-8");
76
+ console.log(`Docs graph written to ${outFile}`);
77
+ }
78
+ if (format === "md" || format === "all") {
79
+ const { renderMarkdown } = await Promise.resolve().then(() => require("../docs/markdown"));
80
+ renderMarkdown(graph, outDir);
81
+ console.log(`Markdown docs written under ${outDir}`);
82
+ }
83
+ }
84
+ main().catch((err) => {
85
+ console.error(err);
86
+ process.exit(99);
87
+ });
88
+ //# sourceMappingURL=extract-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-docs.js","sourceRoot":"","sources":["../../src/cli/extract-docs.ts"],"names":[],"mappings":"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"}