@auto-engineer/narrative 0.15.0 → 0.17.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +28 -0
- package/dist/src/getNarratives.js +1 -1
- package/dist/src/getNarratives.js.map +1 -1
- package/dist/src/id/addAutoIds.d.ts.map +1 -1
- package/dist/src/id/addAutoIds.js +15 -0
- package/dist/src/id/addAutoIds.js.map +1 -1
- package/dist/src/id/hasAllIds.d.ts.map +1 -1
- package/dist/src/id/hasAllIds.js +6 -1
- package/dist/src/id/hasAllIds.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader/runtime-cjs.d.ts.map +1 -1
- package/dist/src/loader/runtime-cjs.js +3 -2
- package/dist/src/loader/runtime-cjs.js.map +1 -1
- package/dist/src/schema.d.ts +301 -143
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +26 -0
- package/dist/src/schema.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/cross-module-imports.d.ts +6 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.js +63 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts +1 -4
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/module-code.d.ts +9 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.js +102 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/index.d.ts +6 -4
- package/dist/src/transformers/model-to-narrative/index.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/index.js +10 -6
- package/dist/src/transformers/model-to-narrative/index.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/ordering.d.ts +10 -0
- package/dist/src/transformers/model-to-narrative/ordering.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/ordering.js +37 -0
- package/dist/src/transformers/model-to-narrative/ordering.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.d.ts +3 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.js +54 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/types.d.ts +12 -0
- package/dist/src/transformers/model-to-narrative/types.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/types.js +2 -0
- package/dist/src/transformers/model-to-narrative/types.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.d.ts +8 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.js +121 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.js.map +1 -0
- package/dist/src/transformers/narrative-to-model/assemble.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/assemble.js +5 -1
- package/dist/src/transformers/narrative-to-model/assemble.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/derive-modules.d.ts +3 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.d.ts.map +1 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.js +29 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/getNarratives.specs.ts +214 -1
- package/src/getNarratives.ts +1 -1
- package/src/id/addAutoIds.specs.ts +180 -0
- package/src/id/addAutoIds.ts +16 -1
- package/src/id/hasAllIds.specs.ts +87 -0
- package/src/id/hasAllIds.ts +10 -2
- package/src/index.ts +7 -0
- package/src/loader/runtime-cjs.ts +3 -2
- package/src/model-to-narrative.specs.ts +467 -17
- package/src/schema.ts +28 -0
- package/src/transformers/model-to-narrative/cross-module-imports.specs.ts +450 -0
- package/src/transformers/model-to-narrative/cross-module-imports.ts +83 -0
- package/src/transformers/model-to-narrative/generators/flow.ts +11 -10
- package/src/transformers/model-to-narrative/generators/module-code.ts +186 -0
- package/src/transformers/model-to-narrative/index.ts +19 -7
- package/src/transformers/model-to-narrative/modules.specs.ts +625 -0
- package/src/transformers/model-to-narrative/ordering.specs.ts +104 -0
- package/src/transformers/model-to-narrative/ordering.ts +46 -0
- package/src/transformers/model-to-narrative/spec-traversal.specs.ts +418 -0
- package/src/transformers/model-to-narrative/spec-traversal.ts +63 -0
- package/src/transformers/model-to-narrative/types.ts +13 -0
- package/src/transformers/model-to-narrative/validate-modules.ts +159 -0
- package/src/transformers/narrative-to-model/assemble.ts +7 -2
- package/src/transformers/narrative-to-model/derive-modules.specs.ts +121 -0
- package/src/transformers/narrative-to-model/derive-modules.ts +36 -0
- package/tsconfig.json +1 -1
- package/tsconfig.test.json +2 -1
- package/dist/src/fluent-builder.specs.d.ts +0 -2
- package/dist/src/fluent-builder.specs.d.ts.map +0 -1
- package/dist/src/fluent-builder.specs.js +0 -28
- package/dist/src/fluent-builder.specs.js.map +0 -1
- package/dist/src/getNarratives.cache.specs.d.ts +0 -2
- package/dist/src/getNarratives.cache.specs.d.ts.map +0 -1
- package/dist/src/getNarratives.cache.specs.js +0 -234
- package/dist/src/getNarratives.cache.specs.js.map +0 -1
- package/dist/src/getNarratives.specs.d.ts +0 -2
- package/dist/src/getNarratives.specs.d.ts.map +0 -1
- package/dist/src/getNarratives.specs.js +0 -1307
- package/dist/src/getNarratives.specs.js.map +0 -1
- package/dist/src/id/addAutoIds.specs.d.ts +0 -2
- package/dist/src/id/addAutoIds.specs.d.ts.map +0 -1
- package/dist/src/id/addAutoIds.specs.js +0 -602
- package/dist/src/id/addAutoIds.specs.js.map +0 -1
- package/dist/src/id/hasAllIds.specs.d.ts +0 -2
- package/dist/src/id/hasAllIds.specs.d.ts.map +0 -1
- package/dist/src/id/hasAllIds.specs.js +0 -424
- package/dist/src/id/hasAllIds.specs.js.map +0 -1
- package/dist/src/model-to-narrative.specs.d.ts +0 -2
- package/dist/src/model-to-narrative.specs.d.ts.map +0 -1
- package/dist/src/model-to-narrative.specs.js +0 -2437
- package/dist/src/model-to-narrative.specs.js.map +0 -1
- package/dist/src/narrative-context.specs.d.ts +0 -2
- package/dist/src/narrative-context.specs.d.ts.map +0 -1
- package/dist/src/narrative-context.specs.js +0 -260
- package/dist/src/narrative-context.specs.js.map +0 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.d.ts +0 -2
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.d.ts.map +0 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.js +0 -142
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.js.map +0 -1
- package/dist/src/transformers/narrative-to-model/type-inference.specs.d.ts +0 -2
- package/dist/src/transformers/narrative-to-model/type-inference.specs.d.ts.map +0 -1
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js +0 -177
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +0 -1
package/package.json
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"typescript": "^5.9.2",
|
|
24
24
|
"zod": "^3.22.4",
|
|
25
25
|
"zod-to-json-schema": "^3.22.3",
|
|
26
|
-
"@auto-engineer/
|
|
27
|
-
"@auto-engineer/
|
|
28
|
-
"@auto-engineer/
|
|
26
|
+
"@auto-engineer/id": "0.17.0",
|
|
27
|
+
"@auto-engineer/file-store": "0.17.0",
|
|
28
|
+
"@auto-engineer/message-bus": "0.17.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.0.0",
|
|
@@ -35,10 +35,11 @@
|
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
|
-
"version": "0.
|
|
38
|
+
"version": "0.17.0",
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsx scripts/build.ts",
|
|
41
41
|
"test": "vitest run --reporter=dot",
|
|
42
|
+
"test:coverage": "vitest run --reporter=dot --coverage --coverage.reporter=text",
|
|
42
43
|
"type-check": "tsc --noEmit",
|
|
43
44
|
"convert-narrative": "tsx scripts/convert-narrative.ts",
|
|
44
45
|
"link:dev": "pnpm build && pnpm link --global",
|
|
@@ -660,7 +660,8 @@ flow('questionnaires-test', () => {
|
|
|
660
660
|
});
|
|
661
661
|
|
|
662
662
|
const model = flows.toModel();
|
|
663
|
-
const
|
|
663
|
+
const result = await modelToNarrative(model);
|
|
664
|
+
const code = result.files.map((f) => f.code).join('\n');
|
|
664
665
|
|
|
665
666
|
expect(code).not.toMatch(/\.when<>\(\{\}\)/);
|
|
666
667
|
expect(code).not.toMatch(/\.when<\s*\{\s*}\s*>\(\{}\)/);
|
|
@@ -1241,6 +1242,218 @@ function validateThenEvents(example: unknown): void {
|
|
|
1241
1242
|
}
|
|
1242
1243
|
}
|
|
1243
1244
|
|
|
1245
|
+
describe('modules in toModel()', () => {
|
|
1246
|
+
it('should derive modules from narratives with different sourceFiles', async () => {
|
|
1247
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1248
|
+
|
|
1249
|
+
const ordersContent = `
|
|
1250
|
+
import { flow, command, specs, rule, example, type Command, type Event } from '@auto-engineer/narrative';
|
|
1251
|
+
|
|
1252
|
+
type CreateOrder = Command<'CreateOrder', { orderId: string }>;
|
|
1253
|
+
type OrderCreated = Event<'OrderCreated', { orderId: string; createdAt: Date }>;
|
|
1254
|
+
|
|
1255
|
+
flow('Orders', () => {
|
|
1256
|
+
command('create order')
|
|
1257
|
+
.server(() => {
|
|
1258
|
+
specs(() => {
|
|
1259
|
+
rule('creates an order', () => {
|
|
1260
|
+
example('order created')
|
|
1261
|
+
.when<CreateOrder>({ orderId: 'order-001' })
|
|
1262
|
+
.then<OrderCreated>({ orderId: 'order-001', createdAt: new Date('2030-01-01T09:00:00Z') });
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
`;
|
|
1268
|
+
|
|
1269
|
+
const usersContent = `
|
|
1270
|
+
import { flow, command, specs, rule, example, type Command, type Event } from '@auto-engineer/narrative';
|
|
1271
|
+
|
|
1272
|
+
type CreateUser = Command<'CreateUser', { userId: string; name: string }>;
|
|
1273
|
+
type UserCreated = Event<'UserCreated', { userId: string; name: string; createdAt: Date }>;
|
|
1274
|
+
|
|
1275
|
+
flow('Users', () => {
|
|
1276
|
+
command('create user')
|
|
1277
|
+
.server(() => {
|
|
1278
|
+
specs(() => {
|
|
1279
|
+
rule('creates a user', () => {
|
|
1280
|
+
example('user created')
|
|
1281
|
+
.when<CreateUser>({ userId: 'user-001', name: 'Alice' })
|
|
1282
|
+
.then<UserCreated>({ userId: 'user-001', name: 'Alice', createdAt: new Date('2030-01-01T09:00:00Z') });
|
|
1283
|
+
});
|
|
1284
|
+
});
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
`;
|
|
1288
|
+
|
|
1289
|
+
await memoryVfs.write('/test/orders.narrative.ts', new TextEncoder().encode(ordersContent));
|
|
1290
|
+
await memoryVfs.write('/test/users.narrative.ts', new TextEncoder().encode(usersContent));
|
|
1291
|
+
|
|
1292
|
+
const flows = await getNarratives({
|
|
1293
|
+
vfs: memoryVfs,
|
|
1294
|
+
root: '/test',
|
|
1295
|
+
pattern: /\.narrative\.ts$/,
|
|
1296
|
+
fastFsScan: true,
|
|
1297
|
+
});
|
|
1298
|
+
const model = flows.toModel();
|
|
1299
|
+
|
|
1300
|
+
expect(model.modules).toBeDefined();
|
|
1301
|
+
expect(model.modules.length).toBe(2);
|
|
1302
|
+
|
|
1303
|
+
const sourceFiles = model.modules.map((m) => m.sourceFile);
|
|
1304
|
+
expect(sourceFiles.some((sf) => sf.includes('orders.narrative.ts'))).toBe(true);
|
|
1305
|
+
expect(sourceFiles.some((sf) => sf.includes('users.narrative.ts'))).toBe(true);
|
|
1306
|
+
|
|
1307
|
+
const ordersModule = model.modules.find((m) => m.sourceFile.includes('orders.narrative.ts'));
|
|
1308
|
+
const usersModule = model.modules.find((m) => m.sourceFile.includes('users.narrative.ts'));
|
|
1309
|
+
|
|
1310
|
+
expect(ordersModule).toBeDefined();
|
|
1311
|
+
expect(ordersModule?.isDerived).toBe(true);
|
|
1312
|
+
|
|
1313
|
+
expect(usersModule).toBeDefined();
|
|
1314
|
+
expect(usersModule?.isDerived).toBe(true);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should include all messages in derived module declarations', async () => {
|
|
1318
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1319
|
+
|
|
1320
|
+
const content = `
|
|
1321
|
+
import { flow, command, specs, rule, example, type Command, type Event } from '@auto-engineer/narrative';
|
|
1322
|
+
|
|
1323
|
+
type CreateOrder = Command<'CreateOrder', { orderId: string }>;
|
|
1324
|
+
type OrderCreated = Event<'OrderCreated', { orderId: string }>;
|
|
1325
|
+
|
|
1326
|
+
flow('Orders', () => {
|
|
1327
|
+
command('create order')
|
|
1328
|
+
.server(() => {
|
|
1329
|
+
specs(() => {
|
|
1330
|
+
rule('creates order', () => {
|
|
1331
|
+
example('order created')
|
|
1332
|
+
.when<CreateOrder>({ orderId: 'order-001' })
|
|
1333
|
+
.then<OrderCreated>({ orderId: 'order-001' });
|
|
1334
|
+
});
|
|
1335
|
+
});
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
`;
|
|
1339
|
+
|
|
1340
|
+
await memoryVfs.write('/test/orders.narrative.ts', new TextEncoder().encode(content));
|
|
1341
|
+
|
|
1342
|
+
const flows = await getNarratives({
|
|
1343
|
+
vfs: memoryVfs,
|
|
1344
|
+
root: '/test',
|
|
1345
|
+
pattern: /\.narrative\.ts$/,
|
|
1346
|
+
fastFsScan: true,
|
|
1347
|
+
});
|
|
1348
|
+
const model = flows.toModel();
|
|
1349
|
+
|
|
1350
|
+
expect(model.modules).toHaveLength(1);
|
|
1351
|
+
const mod = model.modules[0];
|
|
1352
|
+
|
|
1353
|
+
const declaredNames = mod.declares.messages.map((m) => m.name);
|
|
1354
|
+
expect(declaredNames).toContain('CreateOrder');
|
|
1355
|
+
expect(declaredNames).toContain('OrderCreated');
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
it('should group narratives from same sourceFile into one module', async () => {
|
|
1359
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1360
|
+
|
|
1361
|
+
const content = `
|
|
1362
|
+
import { flow, command, query, specs, rule, example, type Command, type Event, type State } from '@auto-engineer/narrative';
|
|
1363
|
+
|
|
1364
|
+
type CreateTodo = Command<'CreateTodo', { todoId: string }>;
|
|
1365
|
+
type TodoCreated = Event<'TodoCreated', { todoId: string }>;
|
|
1366
|
+
type TodoList = State<'TodoList', { todos: string[] }>;
|
|
1367
|
+
|
|
1368
|
+
flow('Create Todos', () => {
|
|
1369
|
+
command('add todo')
|
|
1370
|
+
.server(() => {
|
|
1371
|
+
specs(() => {
|
|
1372
|
+
rule('adds todo', () => {
|
|
1373
|
+
example('todo added')
|
|
1374
|
+
.when<CreateTodo>({ todoId: 'todo-001' })
|
|
1375
|
+
.then<TodoCreated>({ todoId: 'todo-001' });
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
flow('View Todos', () => {
|
|
1382
|
+
query('list todos')
|
|
1383
|
+
.server(() => {
|
|
1384
|
+
specs(() => {
|
|
1385
|
+
rule('shows todos', () => {
|
|
1386
|
+
example('todos listed')
|
|
1387
|
+
.given<TodoCreated>({ todoId: 'todo-001' })
|
|
1388
|
+
.when({})
|
|
1389
|
+
.then<TodoList>({ todos: ['todo-001'] });
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
});
|
|
1393
|
+
});
|
|
1394
|
+
`;
|
|
1395
|
+
|
|
1396
|
+
await memoryVfs.write('/test/todos.narrative.ts', new TextEncoder().encode(content));
|
|
1397
|
+
|
|
1398
|
+
const flows = await getNarratives({
|
|
1399
|
+
vfs: memoryVfs,
|
|
1400
|
+
root: '/test',
|
|
1401
|
+
pattern: /\.narrative\.ts$/,
|
|
1402
|
+
fastFsScan: true,
|
|
1403
|
+
});
|
|
1404
|
+
const model = flows.toModel();
|
|
1405
|
+
|
|
1406
|
+
expect(model.modules).toHaveLength(1);
|
|
1407
|
+
expect(model.modules[0].contains.narrativeIds).toHaveLength(2);
|
|
1408
|
+
|
|
1409
|
+
const narrativeNames = model.narratives.map((n) => n.name);
|
|
1410
|
+
expect(narrativeNames).toContain('Create Todos');
|
|
1411
|
+
expect(narrativeNames).toContain('View Todos');
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
it('should validate model with modules passes schema', async () => {
|
|
1415
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1416
|
+
|
|
1417
|
+
const content = `
|
|
1418
|
+
import { flow, command, specs, rule, example, type Command, type Event } from '@auto-engineer/narrative';
|
|
1419
|
+
|
|
1420
|
+
type DoSomething = Command<'DoSomething', { id: string }>;
|
|
1421
|
+
type SomethingDone = Event<'SomethingDone', { id: string }>;
|
|
1422
|
+
|
|
1423
|
+
flow('Test', () => {
|
|
1424
|
+
command('do something')
|
|
1425
|
+
.server(() => {
|
|
1426
|
+
specs(() => {
|
|
1427
|
+
rule('does something', () => {
|
|
1428
|
+
example('something done')
|
|
1429
|
+
.when<DoSomething>({ id: '001' })
|
|
1430
|
+
.then<SomethingDone>({ id: '001' });
|
|
1431
|
+
});
|
|
1432
|
+
});
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
`;
|
|
1436
|
+
|
|
1437
|
+
await memoryVfs.write('/test/test.narrative.ts', new TextEncoder().encode(content));
|
|
1438
|
+
|
|
1439
|
+
const flows = await getNarratives({
|
|
1440
|
+
vfs: memoryVfs,
|
|
1441
|
+
root: '/test',
|
|
1442
|
+
pattern: /\.narrative\.ts$/,
|
|
1443
|
+
fastFsScan: true,
|
|
1444
|
+
});
|
|
1445
|
+
const model = flows.toModel();
|
|
1446
|
+
|
|
1447
|
+
const parseResult = modelSchema.safeParse(model);
|
|
1448
|
+
if (!parseResult.success) {
|
|
1449
|
+
console.error('Schema validation errors:', parseResult.error.format());
|
|
1450
|
+
}
|
|
1451
|
+
expect(parseResult.success).toBe(true);
|
|
1452
|
+
expect(parseResult.data?.modules).toBeDefined();
|
|
1453
|
+
expect(parseResult.data?.modules.length).toBeGreaterThan(0);
|
|
1454
|
+
});
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1244
1457
|
describe('projection DSL methods', () => {
|
|
1245
1458
|
it('should generate correct origin for singleton projection', async () => {
|
|
1246
1459
|
const memoryVfs = new InMemoryFileStore();
|
package/src/getNarratives.ts
CHANGED
|
@@ -151,7 +151,7 @@ export const getNarratives = async (
|
|
|
151
151
|
typeMap: new Map(),
|
|
152
152
|
typesByFile: new Map(),
|
|
153
153
|
givenTypesByFile: new Map(),
|
|
154
|
-
toModel: () => ({ variant: 'specs' as const, narratives: [], messages: [] }),
|
|
154
|
+
toModel: () => ({ variant: 'specs' as const, narratives: [], messages: [], modules: [] }),
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -79,6 +79,7 @@ describe('addAutoIds', () => {
|
|
|
79
79
|
],
|
|
80
80
|
messages: [],
|
|
81
81
|
integrations: [],
|
|
82
|
+
modules: [],
|
|
82
83
|
};
|
|
83
84
|
|
|
84
85
|
const AUTO_ID_REGEX = /^[A-Za-z0-9_]{9}$/;
|
|
@@ -161,6 +162,7 @@ describe('addAutoIds', () => {
|
|
|
161
162
|
],
|
|
162
163
|
messages: [],
|
|
163
164
|
integrations: [],
|
|
165
|
+
modules: [],
|
|
164
166
|
};
|
|
165
167
|
|
|
166
168
|
const result = addAutoIds(modelWithoutServer);
|
|
@@ -207,6 +209,7 @@ describe('addAutoIds', () => {
|
|
|
207
209
|
],
|
|
208
210
|
messages: [],
|
|
209
211
|
integrations: [],
|
|
212
|
+
modules: [],
|
|
210
213
|
};
|
|
211
214
|
|
|
212
215
|
const result = addAutoIds(modelWithExperienceSlice);
|
|
@@ -262,6 +265,7 @@ describe('addAutoIds', () => {
|
|
|
262
265
|
],
|
|
263
266
|
messages: [],
|
|
264
267
|
integrations: [],
|
|
268
|
+
modules: [],
|
|
265
269
|
};
|
|
266
270
|
|
|
267
271
|
const result = addAutoIds(modelWithMultipleFlowsSameSource);
|
|
@@ -320,6 +324,7 @@ describe('addAutoIds', () => {
|
|
|
320
324
|
],
|
|
321
325
|
messages: [],
|
|
322
326
|
integrations: [],
|
|
327
|
+
modules: [],
|
|
323
328
|
};
|
|
324
329
|
|
|
325
330
|
const result = addAutoIds(modelWithSpecs);
|
|
@@ -372,6 +377,7 @@ describe('addAutoIds', () => {
|
|
|
372
377
|
],
|
|
373
378
|
messages: [],
|
|
374
379
|
integrations: [],
|
|
380
|
+
modules: [],
|
|
375
381
|
};
|
|
376
382
|
|
|
377
383
|
const result = addAutoIds(modelWithSteps);
|
|
@@ -427,6 +433,7 @@ describe('addAutoIds', () => {
|
|
|
427
433
|
],
|
|
428
434
|
messages: [],
|
|
429
435
|
integrations: [],
|
|
436
|
+
modules: [],
|
|
430
437
|
};
|
|
431
438
|
|
|
432
439
|
const result = addAutoIds(modelWithExistingExampleId);
|
|
@@ -480,6 +487,7 @@ describe('addAutoIds', () => {
|
|
|
480
487
|
],
|
|
481
488
|
messages: [],
|
|
482
489
|
integrations: [],
|
|
490
|
+
modules: [],
|
|
483
491
|
};
|
|
484
492
|
|
|
485
493
|
const result = addAutoIds(modelWithErrorSteps);
|
|
@@ -515,6 +523,7 @@ describe('addAutoIds', () => {
|
|
|
515
523
|
],
|
|
516
524
|
messages: [],
|
|
517
525
|
integrations: [],
|
|
526
|
+
modules: [],
|
|
518
527
|
};
|
|
519
528
|
|
|
520
529
|
const result = addAutoIds(modelWithClientSpecs);
|
|
@@ -557,6 +566,7 @@ describe('addAutoIds', () => {
|
|
|
557
566
|
],
|
|
558
567
|
messages: [],
|
|
559
568
|
integrations: [],
|
|
569
|
+
modules: [],
|
|
560
570
|
};
|
|
561
571
|
|
|
562
572
|
const result = addAutoIds(modelWithDescribe);
|
|
@@ -603,6 +613,7 @@ describe('addAutoIds', () => {
|
|
|
603
613
|
],
|
|
604
614
|
messages: [],
|
|
605
615
|
integrations: [],
|
|
616
|
+
modules: [],
|
|
606
617
|
};
|
|
607
618
|
|
|
608
619
|
const result = addAutoIds(modelWithNestedSpecs);
|
|
@@ -647,6 +658,7 @@ describe('addAutoIds', () => {
|
|
|
647
658
|
],
|
|
648
659
|
messages: [],
|
|
649
660
|
integrations: [],
|
|
661
|
+
modules: [],
|
|
650
662
|
};
|
|
651
663
|
|
|
652
664
|
const originalSpec = modelWithClientSpecs.narratives[0].slices[0];
|
|
@@ -656,4 +668,172 @@ describe('addAutoIds', () => {
|
|
|
656
668
|
expect(originalSpec.client.specs[0].id).toBeUndefined();
|
|
657
669
|
}
|
|
658
670
|
});
|
|
671
|
+
|
|
672
|
+
describe('module ID generation', () => {
|
|
673
|
+
const AUTO_ID_REGEX = /^[A-Za-z0-9_]{9}$/;
|
|
674
|
+
|
|
675
|
+
it('should assign ID to derived module equal to sourceFile', () => {
|
|
676
|
+
const model: Model = {
|
|
677
|
+
variant: 'specs',
|
|
678
|
+
narratives: [],
|
|
679
|
+
messages: [],
|
|
680
|
+
integrations: [],
|
|
681
|
+
modules: [
|
|
682
|
+
{
|
|
683
|
+
id: '',
|
|
684
|
+
sourceFile: 'orders.narrative.ts',
|
|
685
|
+
isDerived: true,
|
|
686
|
+
contains: { narrativeIds: [] },
|
|
687
|
+
declares: { messages: [] },
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const result = addAutoIds(model);
|
|
693
|
+
|
|
694
|
+
expect(result.modules[0].id).toBe('orders.narrative.ts');
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('should generate auto ID for authored module without ID', () => {
|
|
698
|
+
const model: Model = {
|
|
699
|
+
variant: 'specs',
|
|
700
|
+
narratives: [],
|
|
701
|
+
messages: [],
|
|
702
|
+
integrations: [],
|
|
703
|
+
modules: [
|
|
704
|
+
{
|
|
705
|
+
id: '',
|
|
706
|
+
sourceFile: 'features/orders.ts',
|
|
707
|
+
isDerived: false,
|
|
708
|
+
contains: { narrativeIds: [] },
|
|
709
|
+
declares: { messages: [] },
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
const result = addAutoIds(model);
|
|
715
|
+
|
|
716
|
+
expect(result.modules[0].id).toMatch(AUTO_ID_REGEX);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it('should preserve existing ID for authored module', () => {
|
|
720
|
+
const model: Model = {
|
|
721
|
+
variant: 'specs',
|
|
722
|
+
narratives: [],
|
|
723
|
+
messages: [],
|
|
724
|
+
integrations: [],
|
|
725
|
+
modules: [
|
|
726
|
+
{
|
|
727
|
+
id: 'EXISTING-MODULE-001',
|
|
728
|
+
sourceFile: 'features/orders.ts',
|
|
729
|
+
isDerived: false,
|
|
730
|
+
contains: { narrativeIds: [] },
|
|
731
|
+
declares: { messages: [] },
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const result = addAutoIds(model);
|
|
737
|
+
|
|
738
|
+
expect(result.modules[0].id).toBe('EXISTING-MODULE-001');
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('should not mutate original modules', () => {
|
|
742
|
+
const model: Model = {
|
|
743
|
+
variant: 'specs',
|
|
744
|
+
narratives: [],
|
|
745
|
+
messages: [],
|
|
746
|
+
integrations: [],
|
|
747
|
+
modules: [
|
|
748
|
+
{
|
|
749
|
+
id: '',
|
|
750
|
+
sourceFile: 'test.ts',
|
|
751
|
+
isDerived: false,
|
|
752
|
+
contains: { narrativeIds: [] },
|
|
753
|
+
declares: { messages: [] },
|
|
754
|
+
},
|
|
755
|
+
],
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const originalId = model.modules[0].id;
|
|
759
|
+
addAutoIds(model);
|
|
760
|
+
|
|
761
|
+
expect(model.modules[0].id).toBe(originalId);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('should generate unique IDs for multiple authored modules', () => {
|
|
765
|
+
const model: Model = {
|
|
766
|
+
variant: 'specs',
|
|
767
|
+
narratives: [],
|
|
768
|
+
messages: [],
|
|
769
|
+
integrations: [],
|
|
770
|
+
modules: [
|
|
771
|
+
{
|
|
772
|
+
id: '',
|
|
773
|
+
sourceFile: 'orders.ts',
|
|
774
|
+
isDerived: false,
|
|
775
|
+
contains: { narrativeIds: [] },
|
|
776
|
+
declares: { messages: [] },
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
id: '',
|
|
780
|
+
sourceFile: 'users.ts',
|
|
781
|
+
isDerived: false,
|
|
782
|
+
contains: { narrativeIds: [] },
|
|
783
|
+
declares: { messages: [] },
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const result = addAutoIds(model);
|
|
789
|
+
|
|
790
|
+
expect(result.modules[0].id).toMatch(AUTO_ID_REGEX);
|
|
791
|
+
expect(result.modules[1].id).toMatch(AUTO_ID_REGEX);
|
|
792
|
+
expect(result.modules[0].id).not.toBe(result.modules[1].id);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should handle mixed derived and authored modules', () => {
|
|
796
|
+
const model: Model = {
|
|
797
|
+
variant: 'specs',
|
|
798
|
+
narratives: [],
|
|
799
|
+
messages: [],
|
|
800
|
+
integrations: [],
|
|
801
|
+
modules: [
|
|
802
|
+
{
|
|
803
|
+
id: '',
|
|
804
|
+
sourceFile: 'derived.narrative.ts',
|
|
805
|
+
isDerived: true,
|
|
806
|
+
contains: { narrativeIds: [] },
|
|
807
|
+
declares: { messages: [] },
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
id: '',
|
|
811
|
+
sourceFile: 'authored.ts',
|
|
812
|
+
isDerived: false,
|
|
813
|
+
contains: { narrativeIds: [] },
|
|
814
|
+
declares: { messages: [] },
|
|
815
|
+
},
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const result = addAutoIds(model);
|
|
820
|
+
|
|
821
|
+
expect(result.modules[0].id).toBe('derived.narrative.ts');
|
|
822
|
+
expect(result.modules[1].id).toMatch(AUTO_ID_REGEX);
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it('should handle empty modules array', () => {
|
|
826
|
+
const model: Model = {
|
|
827
|
+
variant: 'specs',
|
|
828
|
+
narratives: [],
|
|
829
|
+
messages: [],
|
|
830
|
+
integrations: [],
|
|
831
|
+
modules: [],
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
const result = addAutoIds(model);
|
|
835
|
+
|
|
836
|
+
expect(result.modules).toEqual([]);
|
|
837
|
+
});
|
|
838
|
+
});
|
|
659
839
|
});
|
package/src/id/addAutoIds.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientSpecNode, Example, Model, Rule, Slice, Spec, Step } from '../index';
|
|
1
|
+
import type { ClientSpecNode, Example, Model, Module, Rule, Slice, Spec, Step } from '../index';
|
|
2
2
|
import { generateAutoId } from './generators';
|
|
3
3
|
|
|
4
4
|
function ensureId(item: { id?: string }): void {
|
|
@@ -85,6 +85,18 @@ function processSlice(slice: Slice): Slice {
|
|
|
85
85
|
return sliceCopy;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function processModules(modules: Module[]): Module[] {
|
|
89
|
+
return modules.map((module) => {
|
|
90
|
+
const moduleCopy = { ...module };
|
|
91
|
+
if (module.isDerived) {
|
|
92
|
+
moduleCopy.id = module.sourceFile;
|
|
93
|
+
} else {
|
|
94
|
+
ensureId(moduleCopy);
|
|
95
|
+
}
|
|
96
|
+
return moduleCopy;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
export function addAutoIds(specs: Model): Model {
|
|
89
101
|
const result = structuredClone(specs);
|
|
90
102
|
result.narratives = result.narratives.map((narrative) => {
|
|
@@ -93,5 +105,8 @@ export function addAutoIds(specs: Model): Model {
|
|
|
93
105
|
narrativeCopy.slices = narrative.slices.map(processSlice);
|
|
94
106
|
return narrativeCopy;
|
|
95
107
|
});
|
|
108
|
+
if (result.modules) {
|
|
109
|
+
result.modules = processModules(result.modules);
|
|
110
|
+
}
|
|
96
111
|
return result;
|
|
97
112
|
}
|
|
@@ -34,6 +34,7 @@ describe('hasAllIds', () => {
|
|
|
34
34
|
],
|
|
35
35
|
messages: [],
|
|
36
36
|
integrations: [],
|
|
37
|
+
modules: [],
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
const createModelWithIds = (): Model => ({
|
|
@@ -71,6 +72,7 @@ describe('hasAllIds', () => {
|
|
|
71
72
|
],
|
|
72
73
|
messages: [],
|
|
73
74
|
integrations: [],
|
|
75
|
+
modules: [],
|
|
74
76
|
});
|
|
75
77
|
|
|
76
78
|
const createModelWithFullIds = (): Model => ({
|
|
@@ -131,6 +133,7 @@ describe('hasAllIds', () => {
|
|
|
131
133
|
],
|
|
132
134
|
messages: [],
|
|
133
135
|
integrations: [],
|
|
136
|
+
modules: [],
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
const createMultipleFlowsModel = (includeAllIds: boolean, includeAllSliceIds: boolean): Model => ({
|
|
@@ -178,6 +181,7 @@ describe('hasAllIds', () => {
|
|
|
178
181
|
],
|
|
179
182
|
messages: [],
|
|
180
183
|
integrations: [],
|
|
184
|
+
modules: [],
|
|
181
185
|
});
|
|
182
186
|
|
|
183
187
|
it('should return false for models without IDs', () => {
|
|
@@ -310,6 +314,7 @@ describe('hasAllIds', () => {
|
|
|
310
314
|
],
|
|
311
315
|
messages: [],
|
|
312
316
|
integrations: [],
|
|
317
|
+
modules: [],
|
|
313
318
|
};
|
|
314
319
|
expect(hasAllIds(model)).toBe(false);
|
|
315
320
|
});
|
|
@@ -335,6 +340,7 @@ describe('hasAllIds', () => {
|
|
|
335
340
|
],
|
|
336
341
|
messages: [],
|
|
337
342
|
integrations: [],
|
|
343
|
+
modules: [],
|
|
338
344
|
};
|
|
339
345
|
expect(hasAllIds(model)).toBe(false);
|
|
340
346
|
});
|
|
@@ -366,6 +372,7 @@ describe('hasAllIds', () => {
|
|
|
366
372
|
],
|
|
367
373
|
messages: [],
|
|
368
374
|
integrations: [],
|
|
375
|
+
modules: [],
|
|
369
376
|
};
|
|
370
377
|
expect(hasAllIds(model)).toBe(false);
|
|
371
378
|
});
|
|
@@ -398,6 +405,7 @@ describe('hasAllIds', () => {
|
|
|
398
405
|
],
|
|
399
406
|
messages: [],
|
|
400
407
|
integrations: [],
|
|
408
|
+
modules: [],
|
|
401
409
|
};
|
|
402
410
|
expect(hasAllIds(model)).toBe(false);
|
|
403
411
|
});
|
|
@@ -438,7 +446,86 @@ describe('hasAllIds', () => {
|
|
|
438
446
|
],
|
|
439
447
|
messages: [],
|
|
440
448
|
integrations: [],
|
|
449
|
+
modules: [],
|
|
441
450
|
};
|
|
442
451
|
expect(hasAllIds(model)).toBe(true);
|
|
443
452
|
});
|
|
453
|
+
|
|
454
|
+
describe('module ID validation', () => {
|
|
455
|
+
it('should return true when all modules have IDs', () => {
|
|
456
|
+
const model: Model = {
|
|
457
|
+
variant: 'specs',
|
|
458
|
+
narratives: [],
|
|
459
|
+
messages: [],
|
|
460
|
+
integrations: [],
|
|
461
|
+
modules: [
|
|
462
|
+
{
|
|
463
|
+
id: 'module-1',
|
|
464
|
+
sourceFile: 'test.ts',
|
|
465
|
+
isDerived: false,
|
|
466
|
+
contains: { narrativeIds: [] },
|
|
467
|
+
declares: { messages: [] },
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
};
|
|
471
|
+
expect(hasAllIds(model)).toBe(true);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should return false when module has empty ID', () => {
|
|
475
|
+
const model: Model = {
|
|
476
|
+
variant: 'specs',
|
|
477
|
+
narratives: [],
|
|
478
|
+
messages: [],
|
|
479
|
+
integrations: [],
|
|
480
|
+
modules: [
|
|
481
|
+
{
|
|
482
|
+
id: '',
|
|
483
|
+
sourceFile: 'test.ts',
|
|
484
|
+
isDerived: false,
|
|
485
|
+
contains: { narrativeIds: [] },
|
|
486
|
+
declares: { messages: [] },
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
};
|
|
490
|
+
expect(hasAllIds(model)).toBe(false);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should return false when any module is missing ID among valid narratives', () => {
|
|
494
|
+
const model: Model = {
|
|
495
|
+
variant: 'specs',
|
|
496
|
+
narratives: [{ name: 'Test', id: 'FLOW-001', slices: [] }],
|
|
497
|
+
messages: [],
|
|
498
|
+
integrations: [],
|
|
499
|
+
modules: [
|
|
500
|
+
{
|
|
501
|
+
id: '',
|
|
502
|
+
sourceFile: 'test.ts',
|
|
503
|
+
isDerived: true,
|
|
504
|
+
contains: { narrativeIds: ['FLOW-001'] },
|
|
505
|
+
declares: { messages: [] },
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
};
|
|
509
|
+
expect(hasAllIds(model)).toBe(false);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should return true for derived module with valid ID', () => {
|
|
513
|
+
const model: Model = {
|
|
514
|
+
variant: 'specs',
|
|
515
|
+
narratives: [],
|
|
516
|
+
messages: [],
|
|
517
|
+
integrations: [],
|
|
518
|
+
modules: [
|
|
519
|
+
{
|
|
520
|
+
id: 'derived.ts',
|
|
521
|
+
sourceFile: 'derived.ts',
|
|
522
|
+
isDerived: true,
|
|
523
|
+
contains: { narrativeIds: [] },
|
|
524
|
+
declares: { messages: [] },
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
expect(hasAllIds(model)).toBe(true);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
444
531
|
});
|