@auto-engineer/narrative 0.11.13 → 0.11.15
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 +20 -0
- package/dist/src/data-narrative-builders.d.ts +7 -5
- package/dist/src/data-narrative-builders.d.ts.map +1 -1
- package/dist/src/data-narrative-builders.js +19 -1
- package/dist/src/data-narrative-builders.js.map +1 -1
- package/dist/src/getNarratives.specs.js +227 -0
- package/dist/src/getNarratives.specs.js.map +1 -1
- package/dist/src/model-to-narrative.specs.js +385 -1
- package/dist/src/model-to-narrative.specs.js.map +1 -1
- package/dist/src/schema.d.ts +398 -199
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +8 -1
- package/dist/src/schema.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.js +49 -12
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/src/types.d.ts +6 -8
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/data-narrative-builders.ts +41 -9
- package/src/getNarratives.specs.ts +260 -0
- package/src/model-to-narrative.specs.ts +397 -1
- package/src/schema.ts +12 -1
- package/src/transformers/model-to-narrative/generators/flow.ts +85 -26
- package/src/types.ts +7 -9
package/package.json
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"typescript": "^5.9.2",
|
|
20
20
|
"zod": "^3.22.4",
|
|
21
21
|
"zod-to-json-schema": "^3.22.3",
|
|
22
|
-
"@auto-engineer/file-store": "0.11.
|
|
23
|
-
"@auto-engineer/id": "0.11.
|
|
24
|
-
"@auto-engineer/message-bus": "0.11.
|
|
22
|
+
"@auto-engineer/file-store": "0.11.15",
|
|
23
|
+
"@auto-engineer/id": "0.11.15",
|
|
24
|
+
"@auto-engineer/message-bus": "0.11.15"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^20.0.0",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
|
30
30
|
"fake-indexeddb": "^6.0.0",
|
|
31
31
|
"tsx": "^4.20.3",
|
|
32
|
-
"@auto-engineer/cli": "0.11.
|
|
32
|
+
"@auto-engineer/cli": "0.11.15"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"version": "0.11.
|
|
37
|
+
"version": "0.11.15",
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsx scripts/build.ts",
|
|
40
40
|
"test": "vitest run --reporter=dot",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataSinkItem, DataSourceItem, MessageTarget, Integration } from './types';
|
|
1
|
+
import type { DataSinkItem, DataSourceItem, MessageTarget, Integration, DefaultRecord } from './types';
|
|
2
2
|
import { createIntegrationOrigin } from './types';
|
|
3
3
|
import { integrationExportRegistry } from './integration-export-registry';
|
|
4
4
|
|
|
@@ -240,16 +240,44 @@ export class StateSinkBuilder extends MessageTargetBuilder<DataSinkItem> {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
// State source builder
|
|
243
|
-
export class StateSourceBuilder extends MessageTargetBuilder<DataSourceItem> {
|
|
243
|
+
export class StateSourceBuilder<S = unknown> extends MessageTargetBuilder<DataSourceItem> {
|
|
244
244
|
constructor(name: string) {
|
|
245
245
|
super();
|
|
246
246
|
this.target = { type: 'State', name };
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
fromSingletonProjection(name: string): ChainableSource {
|
|
250
250
|
const sourceItem: DataSourceItem = {
|
|
251
251
|
target: this.target as MessageTarget,
|
|
252
|
-
origin: { type: 'projection', name,
|
|
252
|
+
origin: { type: 'projection', name, singleton: true },
|
|
253
|
+
__type: 'source' as const,
|
|
254
|
+
...(this.instructions != null && this.instructions !== '' && { _additionalInstructions: this.instructions }),
|
|
255
|
+
};
|
|
256
|
+
return createChainableSource(sourceItem);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fromProjection<
|
|
260
|
+
K extends S extends import('./types').State<string, infer D extends DefaultRecord, DefaultRecord | undefined>
|
|
261
|
+
? keyof D
|
|
262
|
+
: string,
|
|
263
|
+
>(name: string, idField: K): ChainableSource {
|
|
264
|
+
const sourceItem: DataSourceItem = {
|
|
265
|
+
target: this.target as MessageTarget,
|
|
266
|
+
origin: { type: 'projection', name, idField: idField as string },
|
|
267
|
+
__type: 'source' as const,
|
|
268
|
+
...(this.instructions != null && this.instructions !== '' && { _additionalInstructions: this.instructions }),
|
|
269
|
+
};
|
|
270
|
+
return createChainableSource(sourceItem);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fromCompositeProjection<
|
|
274
|
+
K extends S extends import('./types').State<string, infer D extends DefaultRecord, DefaultRecord | undefined>
|
|
275
|
+
? keyof D
|
|
276
|
+
: string,
|
|
277
|
+
>(name: string, idFields: K[]): ChainableSource {
|
|
278
|
+
const sourceItem: DataSourceItem = {
|
|
279
|
+
target: this.target as MessageTarget,
|
|
280
|
+
origin: { type: 'projection', name, idField: idFields as string[] },
|
|
253
281
|
__type: 'source' as const,
|
|
254
282
|
...(this.instructions != null && this.instructions !== '' && { _additionalInstructions: this.instructions }),
|
|
255
283
|
};
|
|
@@ -364,14 +392,16 @@ export class DataSinkBuilder {
|
|
|
364
392
|
}
|
|
365
393
|
|
|
366
394
|
export class DataSourceBuilder {
|
|
367
|
-
state(
|
|
395
|
+
state<S extends import('./types').State<string, DefaultRecord> = import('./types').State<string, DefaultRecord>>(
|
|
396
|
+
nameOrBuilder: string | BuilderResult,
|
|
397
|
+
): StateSourceBuilder<S> {
|
|
368
398
|
if (typeof nameOrBuilder === 'string') {
|
|
369
|
-
return new StateSourceBuilder(nameOrBuilder);
|
|
399
|
+
return new StateSourceBuilder<S>(nameOrBuilder);
|
|
370
400
|
}
|
|
371
401
|
|
|
372
402
|
// Handle state builder function
|
|
373
403
|
if (isValidBuilderResult(nameOrBuilder) && nameOrBuilder.__messageCategory === 'state') {
|
|
374
|
-
return new StateSourceBuilder(nameOrBuilder.type);
|
|
404
|
+
return new StateSourceBuilder<S>(nameOrBuilder.type);
|
|
375
405
|
}
|
|
376
406
|
|
|
377
407
|
throw new Error('Invalid state parameter - must be a string or state builder function');
|
|
@@ -405,7 +435,9 @@ export function typedSink(builderResult: BuilderResult): EventSinkBuilder | Comm
|
|
|
405
435
|
}
|
|
406
436
|
|
|
407
437
|
// Type-safe source function that accepts builder results
|
|
408
|
-
export function typedSource
|
|
438
|
+
export function typedSource<
|
|
439
|
+
S extends import('./types').State<string, DefaultRecord> = import('./types').State<string, DefaultRecord>,
|
|
440
|
+
>(builderResult: BuilderResult): StateSourceBuilder<S> {
|
|
409
441
|
if (!isValidBuilderResult(builderResult)) {
|
|
410
442
|
throw new Error('Invalid builder result - must be from State builders');
|
|
411
443
|
}
|
|
@@ -414,5 +446,5 @@ export function typedSource(builderResult: BuilderResult): StateSourceBuilder {
|
|
|
414
446
|
throw new Error('Source can only be created from State builders');
|
|
415
447
|
}
|
|
416
448
|
|
|
417
|
-
return new StateSourceBuilder(builderResult.type);
|
|
449
|
+
return new StateSourceBuilder<S>(builderResult.type);
|
|
418
450
|
}
|
|
@@ -1232,3 +1232,263 @@ function validateThenEvents(example: unknown): void {
|
|
|
1232
1232
|
});
|
|
1233
1233
|
}
|
|
1234
1234
|
}
|
|
1235
|
+
|
|
1236
|
+
describe('projection DSL methods', () => {
|
|
1237
|
+
it('should generate correct origin for singleton projection', async () => {
|
|
1238
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1239
|
+
const flowContent = `
|
|
1240
|
+
import { flow, query, specs, rule, example, data, source, type Event, type State } from '@auto-engineer/narrative';
|
|
1241
|
+
|
|
1242
|
+
type TodoAdded = Event<'TodoAdded', { todoId: string; description: string; addedAt: Date }>;
|
|
1243
|
+
type TodoListSummary = State<'TodoListSummary', { summaryId: string; totalTodos: number }>;
|
|
1244
|
+
|
|
1245
|
+
flow('Projection Test', () => {
|
|
1246
|
+
query('views summary')
|
|
1247
|
+
.server(() => {
|
|
1248
|
+
specs(() => {
|
|
1249
|
+
rule('shows summary', () => {
|
|
1250
|
+
example('summary')
|
|
1251
|
+
.given<TodoAdded>({ todoId: 'todo-001', description: 'Test', addedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1252
|
+
.when({})
|
|
1253
|
+
.then<TodoListSummary>({ summaryId: 'main', totalTodos: 1 });
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
data([source().state<TodoListSummary>('TodoListSummary').fromSingletonProjection('TodoSummary')]);
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
`;
|
|
1260
|
+
|
|
1261
|
+
await memoryVfs.write('/test/projection.narrative.ts', new TextEncoder().encode(flowContent));
|
|
1262
|
+
|
|
1263
|
+
const flows = await getNarratives({ vfs: memoryVfs, root: '/test', pattern, fastFsScan: true });
|
|
1264
|
+
const model = flows.toModel();
|
|
1265
|
+
|
|
1266
|
+
const projectionFlow = model.narratives.find((f) => f.name === 'Projection Test');
|
|
1267
|
+
expect(projectionFlow).toBeDefined();
|
|
1268
|
+
|
|
1269
|
+
if (!projectionFlow) return;
|
|
1270
|
+
|
|
1271
|
+
const summarySlice = projectionFlow.slices.find((s) => s.name === 'views summary');
|
|
1272
|
+
expect(summarySlice?.type).toBe('query');
|
|
1273
|
+
|
|
1274
|
+
if (summarySlice?.type !== 'query') return;
|
|
1275
|
+
|
|
1276
|
+
const data = summarySlice.server.data as DataSource[] | undefined;
|
|
1277
|
+
expect(data).toBeDefined();
|
|
1278
|
+
expect(data).toHaveLength(1);
|
|
1279
|
+
|
|
1280
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1281
|
+
type: 'projection',
|
|
1282
|
+
name: 'TodoSummary',
|
|
1283
|
+
singleton: true,
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
expect(data?.[0].origin).not.toHaveProperty('idField');
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it('should generate correct origin for regular projection with single idField', async () => {
|
|
1290
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1291
|
+
const flowContent = `
|
|
1292
|
+
import { flow, query, specs, rule, example, data, source, type Event, type State } from '@auto-engineer/narrative';
|
|
1293
|
+
|
|
1294
|
+
type TodoAdded = Event<'TodoAdded', { todoId: string; description: string; addedAt: Date }>;
|
|
1295
|
+
type TodoState = State<'TodoState', { todoId: string; description: string; status: string }>;
|
|
1296
|
+
|
|
1297
|
+
flow('Projection Test', () => {
|
|
1298
|
+
query('views todo')
|
|
1299
|
+
.server(() => {
|
|
1300
|
+
specs(() => {
|
|
1301
|
+
rule('shows todo', () => {
|
|
1302
|
+
example('todo')
|
|
1303
|
+
.given<TodoAdded>({ todoId: 'todo-001', description: 'Test', addedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1304
|
+
.when({})
|
|
1305
|
+
.then<TodoState>({ todoId: 'todo-001', description: 'Test', status: 'pending' });
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
data([source().state<TodoState>('TodoState').fromProjection('Todos', 'todoId')]);
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
`;
|
|
1312
|
+
|
|
1313
|
+
await memoryVfs.write('/test/projection.narrative.ts', new TextEncoder().encode(flowContent));
|
|
1314
|
+
|
|
1315
|
+
const flows = await getNarratives({ vfs: memoryVfs, root: '/test', pattern, fastFsScan: true });
|
|
1316
|
+
const model = flows.toModel();
|
|
1317
|
+
|
|
1318
|
+
const projectionFlow = model.narratives.find((f) => f.name === 'Projection Test');
|
|
1319
|
+
expect(projectionFlow).toBeDefined();
|
|
1320
|
+
|
|
1321
|
+
if (!projectionFlow) return;
|
|
1322
|
+
|
|
1323
|
+
const todoSlice = projectionFlow.slices.find((s) => s.name === 'views todo');
|
|
1324
|
+
expect(todoSlice?.type).toBe('query');
|
|
1325
|
+
|
|
1326
|
+
if (todoSlice?.type !== 'query') return;
|
|
1327
|
+
|
|
1328
|
+
const data = todoSlice.server.data as DataSource[] | undefined;
|
|
1329
|
+
expect(data).toBeDefined();
|
|
1330
|
+
expect(data).toHaveLength(1);
|
|
1331
|
+
|
|
1332
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1333
|
+
type: 'projection',
|
|
1334
|
+
name: 'Todos',
|
|
1335
|
+
idField: 'todoId',
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
expect(data?.[0].origin).not.toHaveProperty('singleton');
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it('should generate correct origin for composite projection with multiple idFields', async () => {
|
|
1342
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1343
|
+
const flowContent = `
|
|
1344
|
+
import { flow, query, specs, rule, example, data, source, type Event, type State } from '@auto-engineer/narrative';
|
|
1345
|
+
|
|
1346
|
+
type UserProjectAssigned = Event<'UserProjectAssigned', { userId: string; projectId: string; assignedAt: Date }>;
|
|
1347
|
+
type UserProjectState = State<'UserProjectState', { userId: string; projectId: string; role: string }>;
|
|
1348
|
+
|
|
1349
|
+
flow('Projection Test', () => {
|
|
1350
|
+
query('views user project')
|
|
1351
|
+
.server(() => {
|
|
1352
|
+
specs(() => {
|
|
1353
|
+
rule('shows user project', () => {
|
|
1354
|
+
example('user project')
|
|
1355
|
+
.given<UserProjectAssigned>({ userId: 'user-001', projectId: 'proj-001', assignedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1356
|
+
.when({})
|
|
1357
|
+
.then<UserProjectState>({ userId: 'user-001', projectId: 'proj-001', role: 'admin' });
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1360
|
+
data([source().state<UserProjectState>('UserProjectState').fromCompositeProjection('UserProjects', ['userId', 'projectId'])]);
|
|
1361
|
+
});
|
|
1362
|
+
});
|
|
1363
|
+
`;
|
|
1364
|
+
|
|
1365
|
+
await memoryVfs.write('/test/projection.narrative.ts', new TextEncoder().encode(flowContent));
|
|
1366
|
+
|
|
1367
|
+
const flows = await getNarratives({ vfs: memoryVfs, root: '/test', pattern, fastFsScan: true });
|
|
1368
|
+
const model = flows.toModel();
|
|
1369
|
+
|
|
1370
|
+
const projectionFlow = model.narratives.find((f) => f.name === 'Projection Test');
|
|
1371
|
+
expect(projectionFlow).toBeDefined();
|
|
1372
|
+
|
|
1373
|
+
if (!projectionFlow) return;
|
|
1374
|
+
|
|
1375
|
+
const userProjectSlice = projectionFlow.slices.find((s) => s.name === 'views user project');
|
|
1376
|
+
expect(userProjectSlice?.type).toBe('query');
|
|
1377
|
+
|
|
1378
|
+
if (userProjectSlice?.type !== 'query') return;
|
|
1379
|
+
|
|
1380
|
+
const data = userProjectSlice.server.data as DataSource[] | undefined;
|
|
1381
|
+
expect(data).toBeDefined();
|
|
1382
|
+
expect(data).toHaveLength(1);
|
|
1383
|
+
|
|
1384
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1385
|
+
type: 'projection',
|
|
1386
|
+
name: 'UserProjects',
|
|
1387
|
+
idField: ['userId', 'projectId'],
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
expect(data?.[0].origin).not.toHaveProperty('singleton');
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
it('should validate all three projection patterns together', async () => {
|
|
1394
|
+
const memoryVfs = new InMemoryFileStore();
|
|
1395
|
+
const flowContent = `
|
|
1396
|
+
import { flow, query, specs, rule, example, data, source, type Event, type State } from '@auto-engineer/narrative';
|
|
1397
|
+
|
|
1398
|
+
type TodoAdded = Event<'TodoAdded', { todoId: string; userId: string; projectId: string; description: string; addedAt: Date }>;
|
|
1399
|
+
|
|
1400
|
+
type TodoListSummary = State<'TodoListSummary', { summaryId: string; totalTodos: number }>;
|
|
1401
|
+
type TodoState = State<'TodoState', { todoId: string; description: string; status: string }>;
|
|
1402
|
+
type UserProjectTodos = State<'UserProjectTodos', { userId: string; projectId: string; todos: string[] }>;
|
|
1403
|
+
|
|
1404
|
+
flow('All Projection Patterns', () => {
|
|
1405
|
+
query('views summary')
|
|
1406
|
+
.server(() => {
|
|
1407
|
+
specs(() => {
|
|
1408
|
+
rule('shows summary', () => {
|
|
1409
|
+
example('summary')
|
|
1410
|
+
.given<TodoAdded>({ todoId: 'todo-001', userId: 'u1', projectId: 'p1', description: 'Test', addedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1411
|
+
.when({})
|
|
1412
|
+
.then<TodoListSummary>({ summaryId: 'main', totalTodos: 1 });
|
|
1413
|
+
});
|
|
1414
|
+
});
|
|
1415
|
+
data([source().state<TodoListSummary>('TodoListSummary').fromSingletonProjection('TodoSummary')]);
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
query('views todo')
|
|
1419
|
+
.server(() => {
|
|
1420
|
+
specs(() => {
|
|
1421
|
+
rule('shows todo', () => {
|
|
1422
|
+
example('todo')
|
|
1423
|
+
.given<TodoAdded>({ todoId: 'todo-001', userId: 'u1', projectId: 'p1', description: 'Test', addedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1424
|
+
.when({})
|
|
1425
|
+
.then<TodoState>({ todoId: 'todo-001', description: 'Test', status: 'pending' });
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
data([source().state<TodoState>('TodoState').fromProjection('Todos', 'todoId')]);
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
query('views user project todos')
|
|
1432
|
+
.server(() => {
|
|
1433
|
+
specs(() => {
|
|
1434
|
+
rule('shows user project todos', () => {
|
|
1435
|
+
example('user project todos')
|
|
1436
|
+
.given<TodoAdded>({ todoId: 'todo-001', userId: 'u1', projectId: 'p1', description: 'Test', addedAt: new Date('2030-01-01T09:00:00Z') })
|
|
1437
|
+
.when({})
|
|
1438
|
+
.then<UserProjectTodos>({ userId: 'u1', projectId: 'p1', todos: ['todo-001'] });
|
|
1439
|
+
});
|
|
1440
|
+
});
|
|
1441
|
+
data([source().state<UserProjectTodos>('UserProjectTodos').fromCompositeProjection('UserProjectTodos', ['userId', 'projectId'])]);
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
`;
|
|
1445
|
+
|
|
1446
|
+
await memoryVfs.write('/test/projection.narrative.ts', new TextEncoder().encode(flowContent));
|
|
1447
|
+
|
|
1448
|
+
const flows = await getNarratives({ vfs: memoryVfs, root: '/test', pattern, fastFsScan: true });
|
|
1449
|
+
const model = flows.toModel();
|
|
1450
|
+
|
|
1451
|
+
const parseResult = modelSchema.safeParse(model);
|
|
1452
|
+
if (!parseResult.success) {
|
|
1453
|
+
console.error('Schema validation errors:', parseResult.error.format());
|
|
1454
|
+
}
|
|
1455
|
+
expect(parseResult.success).toBe(true);
|
|
1456
|
+
|
|
1457
|
+
const projectionFlow = model.narratives.find((f) => f.name === 'All Projection Patterns');
|
|
1458
|
+
expect(projectionFlow).toBeDefined();
|
|
1459
|
+
|
|
1460
|
+
if (!projectionFlow) return;
|
|
1461
|
+
|
|
1462
|
+
expect(projectionFlow.slices).toHaveLength(3);
|
|
1463
|
+
|
|
1464
|
+
const summarySlice = projectionFlow.slices.find((s) => s.name === 'views summary');
|
|
1465
|
+
if (summarySlice?.type === 'query') {
|
|
1466
|
+
const data = summarySlice.server.data as DataSource[] | undefined;
|
|
1467
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1468
|
+
type: 'projection',
|
|
1469
|
+
name: 'TodoSummary',
|
|
1470
|
+
singleton: true,
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
const todoSlice = projectionFlow.slices.find((s) => s.name === 'views todo');
|
|
1475
|
+
if (todoSlice?.type === 'query') {
|
|
1476
|
+
const data = todoSlice.server.data as DataSource[] | undefined;
|
|
1477
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1478
|
+
type: 'projection',
|
|
1479
|
+
name: 'Todos',
|
|
1480
|
+
idField: 'todoId',
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const userProjectSlice = projectionFlow.slices.find((s) => s.name === 'views user project todos');
|
|
1485
|
+
if (userProjectSlice?.type === 'query') {
|
|
1486
|
+
const data = userProjectSlice.server.data as DataSource[] | undefined;
|
|
1487
|
+
expect(data?.[0].origin).toMatchObject({
|
|
1488
|
+
type: 'projection',
|
|
1489
|
+
name: 'UserProjectTodos',
|
|
1490
|
+
idField: ['userId', 'projectId'],
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
});
|