@auto-engineer/narrative 0.11.12 → 0.11.14

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 (67) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/dist/src/commands/export-schema-helper.js +1 -2
  4. package/dist/src/commands/export-schema-helper.js.map +1 -1
  5. package/dist/src/data-narrative-builders.d.ts +7 -5
  6. package/dist/src/data-narrative-builders.d.ts.map +1 -1
  7. package/dist/src/data-narrative-builders.js +19 -1
  8. package/dist/src/data-narrative-builders.js.map +1 -1
  9. package/dist/src/getNarratives.cache.specs.d.ts +2 -0
  10. package/dist/src/getNarratives.cache.specs.d.ts.map +1 -0
  11. package/dist/src/{getFlows.cache.specs.js → getNarratives.cache.specs.js} +1 -1
  12. package/dist/src/getNarratives.cache.specs.js.map +1 -0
  13. package/dist/src/getNarratives.specs.js +233 -19
  14. package/dist/src/getNarratives.specs.js.map +1 -1
  15. package/dist/src/model-to-narrative.specs.d.ts +2 -0
  16. package/dist/src/model-to-narrative.specs.d.ts.map +1 -0
  17. package/dist/src/{model-to-flow.specs.js → model-to-narrative.specs.js} +594 -2
  18. package/dist/src/model-to-narrative.specs.js.map +1 -0
  19. package/dist/src/narrative-context.d.ts.map +1 -1
  20. package/dist/src/narrative-context.js +0 -1
  21. package/dist/src/narrative-context.js.map +1 -1
  22. package/dist/src/narrative.d.ts +1 -0
  23. package/dist/src/narrative.d.ts.map +1 -1
  24. package/dist/src/narrative.js +11 -0
  25. package/dist/src/narrative.js.map +1 -1
  26. package/dist/src/samples/mixed-given-types.narrative.js +0 -1
  27. package/dist/src/samples/mixed-given-types.narrative.js.map +1 -1
  28. package/dist/src/samples/questionnaires.narrative.js +0 -2
  29. package/dist/src/samples/questionnaires.narrative.js.map +1 -1
  30. package/dist/src/schema.d.ts +2253 -2054
  31. package/dist/src/schema.d.ts.map +1 -1
  32. package/dist/src/schema.js +9 -1
  33. package/dist/src/schema.js.map +1 -1
  34. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  35. package/dist/src/transformers/model-to-narrative/generators/flow.js +49 -12
  36. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  37. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
  38. package/dist/src/transformers/model-to-narrative/generators/gwt.js +32 -8
  39. package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
  40. package/dist/src/transformers/narrative-to-model/debug.d.ts.map +1 -1
  41. package/dist/src/transformers/narrative-to-model/debug.js +1 -1
  42. package/dist/src/transformers/narrative-to-model/debug.js.map +1 -1
  43. package/dist/src/types.d.ts +6 -8
  44. package/dist/src/types.d.ts.map +1 -1
  45. package/dist/src/types.js.map +1 -1
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +5 -5
  48. package/src/commands/export-schema-helper.ts +1 -2
  49. package/src/data-narrative-builders.ts +41 -9
  50. package/src/getNarratives.specs.ts +266 -20
  51. package/src/{model-to-flow.specs.ts → model-to-narrative.specs.ts} +609 -1
  52. package/src/narrative-context.ts +0 -1
  53. package/src/narrative.ts +16 -1
  54. package/src/samples/mixed-given-types.narrative.ts +0 -1
  55. package/src/samples/questionnaires.narrative.ts +0 -2
  56. package/src/schema.ts +13 -1
  57. package/src/transformers/model-to-narrative/generators/flow.ts +85 -26
  58. package/src/transformers/model-to-narrative/generators/gwt.ts +44 -9
  59. package/src/transformers/narrative-to-model/debug.ts +1 -1
  60. package/src/types.ts +7 -9
  61. package/dist/src/getFlows.cache.specs.d.ts +0 -2
  62. package/dist/src/getFlows.cache.specs.d.ts.map +0 -1
  63. package/dist/src/getFlows.cache.specs.js.map +0 -1
  64. package/dist/src/model-to-flow.specs.d.ts +0 -2
  65. package/dist/src/model-to-flow.specs.d.ts.map +0 -1
  66. package/dist/src/model-to-flow.specs.js.map +0 -1
  67. /package/src/{getFlows.cache.specs.ts → getNarratives.cache.specs.ts} +0 -0
@@ -501,7 +501,9 @@ narrative('Test Flow with IDs', 'FLOW-123', () => {
501
501
  should('allow filtering');
502
502
  });
503
503
  })
504
- .server(() => {});
504
+ .server(() => {
505
+ specs('Product data specs', () => {});
506
+ });
505
507
  });
506
508
  `);
507
509
  });
@@ -1819,4 +1821,610 @@ narrative('Response Analytics', () => {
1819
1821
  });
1820
1822
  `);
1821
1823
  });
1824
+
1825
+ it('should omit .when({}) when given has multiple items and when is empty', async () => {
1826
+ const modelWithEmptyWhen: Model = {
1827
+ variant: 'specs',
1828
+ narratives: [
1829
+ {
1830
+ name: 'Todo List Summary',
1831
+ id: 'TODO-001',
1832
+ slices: [
1833
+ {
1834
+ name: 'views completion summary',
1835
+ id: 'SUMMARY-001',
1836
+ type: 'query',
1837
+ client: {
1838
+ description: 'Summary view client',
1839
+ },
1840
+ server: {
1841
+ description: 'Summary calculation server',
1842
+ specs: {
1843
+ name: 'Summary Statistics',
1844
+ rules: [
1845
+ {
1846
+ id: 'RULE-SUMMARY',
1847
+ description: 'summary shows overall todo list statistics',
1848
+ examples: [
1849
+ {
1850
+ description: 'calculates summary from multiple todos',
1851
+ given: [
1852
+ {
1853
+ eventRef: 'TodoAdded',
1854
+ exampleData: {
1855
+ todoId: 'todo-001',
1856
+ description: 'Buy groceries',
1857
+ status: 'pending',
1858
+ addedAt: new Date('2030-01-01T09:00:00.000Z'),
1859
+ },
1860
+ },
1861
+ {
1862
+ eventRef: 'TodoAdded',
1863
+ exampleData: {
1864
+ todoId: 'todo-002',
1865
+ description: 'Write report',
1866
+ status: 'pending',
1867
+ addedAt: new Date('2030-01-01T09:10:00.000Z'),
1868
+ },
1869
+ },
1870
+ {
1871
+ eventRef: 'TodoMarkedInProgress',
1872
+ exampleData: {
1873
+ todoId: 'todo-001',
1874
+ markedAt: new Date('2030-01-01T10:00:00.000Z'),
1875
+ },
1876
+ },
1877
+ {
1878
+ eventRef: 'TodoMarkedComplete',
1879
+ exampleData: {
1880
+ todoId: 'todo-002',
1881
+ completedAt: new Date('2030-01-01T11:00:00.000Z'),
1882
+ },
1883
+ },
1884
+ ],
1885
+ when: {
1886
+ eventRef: '',
1887
+ exampleData: {},
1888
+ },
1889
+ then: [
1890
+ {
1891
+ stateRef: 'TodoListSummary',
1892
+ exampleData: {
1893
+ summaryId: 'main-summary',
1894
+ totalTodos: 2,
1895
+ pendingCount: 0,
1896
+ inProgressCount: 1,
1897
+ completedCount: 1,
1898
+ completionPercentage: 50,
1899
+ },
1900
+ },
1901
+ ],
1902
+ },
1903
+ ],
1904
+ },
1905
+ ],
1906
+ },
1907
+ },
1908
+ },
1909
+ ],
1910
+ },
1911
+ ],
1912
+ messages: [
1913
+ {
1914
+ type: 'event',
1915
+ name: 'TodoAdded',
1916
+ fields: [
1917
+ { name: 'todoId', type: 'string', required: true },
1918
+ { name: 'description', type: 'string', required: true },
1919
+ { name: 'status', type: 'string', required: true },
1920
+ { name: 'addedAt', type: 'Date', required: true },
1921
+ ],
1922
+ source: 'internal',
1923
+ metadata: { version: 1 },
1924
+ },
1925
+ {
1926
+ type: 'event',
1927
+ name: 'TodoMarkedInProgress',
1928
+ fields: [
1929
+ { name: 'todoId', type: 'string', required: true },
1930
+ { name: 'markedAt', type: 'Date', required: true },
1931
+ ],
1932
+ source: 'internal',
1933
+ metadata: { version: 1 },
1934
+ },
1935
+ {
1936
+ type: 'event',
1937
+ name: 'TodoMarkedComplete',
1938
+ fields: [
1939
+ { name: 'todoId', type: 'string', required: true },
1940
+ { name: 'completedAt', type: 'Date', required: true },
1941
+ ],
1942
+ source: 'internal',
1943
+ metadata: { version: 1 },
1944
+ },
1945
+ {
1946
+ type: 'state',
1947
+ name: 'TodoListSummary',
1948
+ fields: [
1949
+ { name: 'summaryId', type: 'string', required: true },
1950
+ { name: 'totalTodos', type: 'number', required: true },
1951
+ { name: 'pendingCount', type: 'number', required: true },
1952
+ { name: 'inProgressCount', type: 'number', required: true },
1953
+ { name: 'completedCount', type: 'number', required: true },
1954
+ { name: 'completionPercentage', type: 'number', required: true },
1955
+ ],
1956
+ metadata: { version: 1 },
1957
+ },
1958
+ ],
1959
+ integrations: [],
1960
+ };
1961
+
1962
+ const code = await modelToNarrative(modelWithEmptyWhen);
1963
+
1964
+ expect(code).toEqual(`import { example, narrative, query, rule, specs } from '@auto-engineer/narrative';
1965
+ import type { Event, State } from '@auto-engineer/narrative';
1966
+ type TodoAdded = Event<
1967
+ 'TodoAdded',
1968
+ {
1969
+ todoId: string;
1970
+ description: string;
1971
+ status: string;
1972
+ addedAt: Date;
1973
+ }
1974
+ >;
1975
+ type TodoMarkedInProgress = Event<
1976
+ 'TodoMarkedInProgress',
1977
+ {
1978
+ todoId: string;
1979
+ markedAt: Date;
1980
+ }
1981
+ >;
1982
+ type TodoMarkedComplete = Event<
1983
+ 'TodoMarkedComplete',
1984
+ {
1985
+ todoId: string;
1986
+ completedAt: Date;
1987
+ }
1988
+ >;
1989
+ type TodoListSummary = State<
1990
+ 'TodoListSummary',
1991
+ {
1992
+ summaryId: string;
1993
+ totalTodos: number;
1994
+ pendingCount: number;
1995
+ inProgressCount: number;
1996
+ completedCount: number;
1997
+ completionPercentage: number;
1998
+ }
1999
+ >;
2000
+ narrative('Todo List Summary', 'TODO-001', () => {
2001
+ query('views completion summary', 'SUMMARY-001').server(() => {
2002
+ specs('Summary Statistics', () => {
2003
+ rule('summary shows overall todo list statistics', 'RULE-SUMMARY', () => {
2004
+ example('calculates summary from multiple todos')
2005
+ .given<TodoAdded>({
2006
+ todoId: 'todo-001',
2007
+ description: 'Buy groceries',
2008
+ status: 'pending',
2009
+ addedAt: new Date('2030-01-01T09:00:00.000Z'),
2010
+ })
2011
+ .and<TodoAdded>({
2012
+ todoId: 'todo-002',
2013
+ description: 'Write report',
2014
+ status: 'pending',
2015
+ addedAt: new Date('2030-01-01T09:10:00.000Z'),
2016
+ })
2017
+ .and<TodoMarkedInProgress>({ todoId: 'todo-001', markedAt: new Date('2030-01-01T10:00:00.000Z') })
2018
+ .and<TodoMarkedComplete>({ todoId: 'todo-002', completedAt: new Date('2030-01-01T11:00:00.000Z') })
2019
+ .then<TodoListSummary>({
2020
+ summaryId: 'main-summary',
2021
+ totalTodos: 2,
2022
+ pendingCount: 0,
2023
+ inProgressCount: 1,
2024
+ completedCount: 1,
2025
+ completionPercentage: 50,
2026
+ });
2027
+ });
2028
+ });
2029
+ });
2030
+ });
2031
+ `);
2032
+
2033
+ expect(code).not.toContain('.when({})');
2034
+ expect(code).not.toContain('.when<');
2035
+ });
2036
+
2037
+ describe('projection DSL generation', () => {
2038
+ it('should generate fromSingletonProjection for singleton projections', async () => {
2039
+ const modelWithSingletonProjection: Model = {
2040
+ variant: 'specs',
2041
+ narratives: [
2042
+ {
2043
+ name: 'Todo Summary Flow',
2044
+ id: 'TODO-SUMMARY',
2045
+ slices: [
2046
+ {
2047
+ name: 'views todo summary',
2048
+ id: 'SUMMARY-SLICE',
2049
+ type: 'query',
2050
+ client: {
2051
+ description: 'Summary client',
2052
+ },
2053
+ server: {
2054
+ description: 'Summary server',
2055
+ data: [
2056
+ {
2057
+ target: {
2058
+ type: 'State',
2059
+ name: 'TodoListSummary',
2060
+ },
2061
+ origin: {
2062
+ type: 'projection',
2063
+ name: 'TodoSummary',
2064
+ singleton: true,
2065
+ },
2066
+ },
2067
+ ],
2068
+ specs: {
2069
+ name: 'Summary Rules',
2070
+ rules: [],
2071
+ },
2072
+ },
2073
+ },
2074
+ ],
2075
+ },
2076
+ ],
2077
+ messages: [
2078
+ {
2079
+ type: 'state',
2080
+ name: 'TodoListSummary',
2081
+ fields: [
2082
+ { name: 'summaryId', type: 'string', required: true },
2083
+ { name: 'totalTodos', type: 'number', required: true },
2084
+ ],
2085
+ metadata: { version: 1 },
2086
+ },
2087
+ ],
2088
+ integrations: [],
2089
+ };
2090
+
2091
+ const code = await modelToNarrative(modelWithSingletonProjection);
2092
+
2093
+ expect(code).toEqual(`import { data, narrative, query, source, specs } from '@auto-engineer/narrative';
2094
+ import type { State } from '@auto-engineer/narrative';
2095
+ type TodoListSummary = State<
2096
+ 'TodoListSummary',
2097
+ {
2098
+ summaryId: string;
2099
+ totalTodos: number;
2100
+ }
2101
+ >;
2102
+ narrative('Todo Summary Flow', 'TODO-SUMMARY', () => {
2103
+ query('views todo summary', 'SUMMARY-SLICE').server(() => {
2104
+ data([source().state('TodoListSummary').fromSingletonProjection('TodoSummary')]);
2105
+ specs('Summary Rules', () => {});
2106
+ });
2107
+ });
2108
+ `);
2109
+ });
2110
+
2111
+ it('should generate fromProjection with single idField for regular projections', async () => {
2112
+ const modelWithRegularProjection: Model = {
2113
+ variant: 'specs',
2114
+ narratives: [
2115
+ {
2116
+ name: 'Todo Flow',
2117
+ id: 'TODO-FLOW',
2118
+ slices: [
2119
+ {
2120
+ name: 'views todo',
2121
+ id: 'TODO-SLICE',
2122
+ type: 'query',
2123
+ client: {
2124
+ description: 'Todo client',
2125
+ },
2126
+ server: {
2127
+ description: 'Todo server',
2128
+ data: [
2129
+ {
2130
+ target: {
2131
+ type: 'State',
2132
+ name: 'TodoState',
2133
+ },
2134
+ origin: {
2135
+ type: 'projection',
2136
+ name: 'Todos',
2137
+ idField: 'todoId',
2138
+ },
2139
+ },
2140
+ ],
2141
+ specs: {
2142
+ name: 'Todo Rules',
2143
+ rules: [],
2144
+ },
2145
+ },
2146
+ },
2147
+ ],
2148
+ },
2149
+ ],
2150
+ messages: [
2151
+ {
2152
+ type: 'state',
2153
+ name: 'TodoState',
2154
+ fields: [
2155
+ { name: 'todoId', type: 'string', required: true },
2156
+ { name: 'description', type: 'string', required: true },
2157
+ ],
2158
+ metadata: { version: 1 },
2159
+ },
2160
+ ],
2161
+ integrations: [],
2162
+ };
2163
+
2164
+ const code = await modelToNarrative(modelWithRegularProjection);
2165
+
2166
+ expect(code).toEqual(`import { data, narrative, query, source, specs } from '@auto-engineer/narrative';
2167
+ import type { State } from '@auto-engineer/narrative';
2168
+ type TodoState = State<
2169
+ 'TodoState',
2170
+ {
2171
+ todoId: string;
2172
+ description: string;
2173
+ }
2174
+ >;
2175
+ narrative('Todo Flow', 'TODO-FLOW', () => {
2176
+ query('views todo', 'TODO-SLICE').server(() => {
2177
+ data([source().state('TodoState').fromProjection('Todos', 'todoId')]);
2178
+ specs('Todo Rules', () => {});
2179
+ });
2180
+ });
2181
+ `);
2182
+ });
2183
+
2184
+ it('should generate fromCompositeProjection with array idField for composite key projections', async () => {
2185
+ const modelWithCompositeProjection: Model = {
2186
+ variant: 'specs',
2187
+ narratives: [
2188
+ {
2189
+ name: 'User Project Flow',
2190
+ id: 'USER-PROJECT-FLOW',
2191
+ slices: [
2192
+ {
2193
+ name: 'views user project',
2194
+ id: 'USER-PROJECT-SLICE',
2195
+ type: 'query',
2196
+ client: {
2197
+ description: 'User project client',
2198
+ },
2199
+ server: {
2200
+ description: 'User project server',
2201
+ data: [
2202
+ {
2203
+ target: {
2204
+ type: 'State',
2205
+ name: 'UserProjectState',
2206
+ },
2207
+ origin: {
2208
+ type: 'projection',
2209
+ name: 'UserProjects',
2210
+ idField: ['userId', 'projectId'],
2211
+ },
2212
+ },
2213
+ ],
2214
+ specs: {
2215
+ name: 'User Project Rules',
2216
+ rules: [],
2217
+ },
2218
+ },
2219
+ },
2220
+ ],
2221
+ },
2222
+ ],
2223
+ messages: [
2224
+ {
2225
+ type: 'state',
2226
+ name: 'UserProjectState',
2227
+ fields: [
2228
+ { name: 'userId', type: 'string', required: true },
2229
+ { name: 'projectId', type: 'string', required: true },
2230
+ { name: 'role', type: 'string', required: true },
2231
+ ],
2232
+ metadata: { version: 1 },
2233
+ },
2234
+ ],
2235
+ integrations: [],
2236
+ };
2237
+
2238
+ const code = await modelToNarrative(modelWithCompositeProjection);
2239
+
2240
+ expect(code).toEqual(`import { data, narrative, query, source, specs } from '@auto-engineer/narrative';
2241
+ import type { State } from '@auto-engineer/narrative';
2242
+ type UserProjectState = State<
2243
+ 'UserProjectState',
2244
+ {
2245
+ userId: string;
2246
+ projectId: string;
2247
+ role: string;
2248
+ }
2249
+ >;
2250
+ narrative('User Project Flow', 'USER-PROJECT-FLOW', () => {
2251
+ query('views user project', 'USER-PROJECT-SLICE').server(() => {
2252
+ data([source().state('UserProjectState').fromCompositeProjection('UserProjects', ['userId', 'projectId'])]);
2253
+ specs('User Project Rules', () => {});
2254
+ });
2255
+ });
2256
+ `);
2257
+ });
2258
+
2259
+ it('should generate all three projection types in a single narrative', async () => {
2260
+ const modelWithAllProjectionTypes: Model = {
2261
+ variant: 'specs',
2262
+ narratives: [
2263
+ {
2264
+ name: 'All Projection Types',
2265
+ id: 'ALL-PROJ',
2266
+ slices: [
2267
+ {
2268
+ name: 'views summary',
2269
+ id: 'SUMMARY-SLICE',
2270
+ type: 'query',
2271
+ client: {
2272
+ description: 'Summary client',
2273
+ },
2274
+ server: {
2275
+ description: 'Summary server',
2276
+ data: [
2277
+ {
2278
+ target: {
2279
+ type: 'State',
2280
+ name: 'TodoListSummary',
2281
+ },
2282
+ origin: {
2283
+ type: 'projection',
2284
+ name: 'TodoSummary',
2285
+ singleton: true,
2286
+ },
2287
+ },
2288
+ ],
2289
+ specs: {
2290
+ name: 'Summary Rules',
2291
+ rules: [],
2292
+ },
2293
+ },
2294
+ },
2295
+ {
2296
+ name: 'views todo',
2297
+ id: 'TODO-SLICE',
2298
+ type: 'query',
2299
+ client: {
2300
+ description: 'Todo client',
2301
+ },
2302
+ server: {
2303
+ description: 'Todo server',
2304
+ data: [
2305
+ {
2306
+ target: {
2307
+ type: 'State',
2308
+ name: 'TodoState',
2309
+ },
2310
+ origin: {
2311
+ type: 'projection',
2312
+ name: 'Todos',
2313
+ idField: 'todoId',
2314
+ },
2315
+ },
2316
+ ],
2317
+ specs: {
2318
+ name: 'Todo Rules',
2319
+ rules: [],
2320
+ },
2321
+ },
2322
+ },
2323
+ {
2324
+ name: 'views user project todos',
2325
+ id: 'USER-PROJECT-SLICE',
2326
+ type: 'query',
2327
+ client: {
2328
+ description: 'User project client',
2329
+ },
2330
+ server: {
2331
+ description: 'User project server',
2332
+ data: [
2333
+ {
2334
+ target: {
2335
+ type: 'State',
2336
+ name: 'UserProjectTodos',
2337
+ },
2338
+ origin: {
2339
+ type: 'projection',
2340
+ name: 'UserProjectTodos',
2341
+ idField: ['userId', 'projectId'],
2342
+ },
2343
+ },
2344
+ ],
2345
+ specs: {
2346
+ name: 'User Project Rules',
2347
+ rules: [],
2348
+ },
2349
+ },
2350
+ },
2351
+ ],
2352
+ },
2353
+ ],
2354
+ messages: [
2355
+ {
2356
+ type: 'state',
2357
+ name: 'TodoListSummary',
2358
+ fields: [
2359
+ { name: 'summaryId', type: 'string', required: true },
2360
+ { name: 'totalTodos', type: 'number', required: true },
2361
+ ],
2362
+ metadata: { version: 1 },
2363
+ },
2364
+ {
2365
+ type: 'state',
2366
+ name: 'TodoState',
2367
+ fields: [
2368
+ { name: 'todoId', type: 'string', required: true },
2369
+ { name: 'description', type: 'string', required: true },
2370
+ ],
2371
+ metadata: { version: 1 },
2372
+ },
2373
+ {
2374
+ type: 'state',
2375
+ name: 'UserProjectTodos',
2376
+ fields: [
2377
+ { name: 'userId', type: 'string', required: true },
2378
+ { name: 'projectId', type: 'string', required: true },
2379
+ { name: 'todos', type: 'Array<string>', required: true },
2380
+ ],
2381
+ metadata: { version: 1 },
2382
+ },
2383
+ ],
2384
+ integrations: [],
2385
+ };
2386
+
2387
+ const code = await modelToNarrative(modelWithAllProjectionTypes);
2388
+
2389
+ expect(code).toEqual(`import { data, narrative, query, source, specs } from '@auto-engineer/narrative';
2390
+ import type { State } from '@auto-engineer/narrative';
2391
+ type TodoListSummary = State<
2392
+ 'TodoListSummary',
2393
+ {
2394
+ summaryId: string;
2395
+ totalTodos: number;
2396
+ }
2397
+ >;
2398
+ type TodoState = State<
2399
+ 'TodoState',
2400
+ {
2401
+ todoId: string;
2402
+ description: string;
2403
+ }
2404
+ >;
2405
+ type UserProjectTodos = State<
2406
+ 'UserProjectTodos',
2407
+ {
2408
+ userId: string;
2409
+ projectId: string;
2410
+ todos: string[];
2411
+ }
2412
+ >;
2413
+ narrative('All Projection Types', 'ALL-PROJ', () => {
2414
+ query('views summary', 'SUMMARY-SLICE').server(() => {
2415
+ data([source().state('TodoListSummary').fromSingletonProjection('TodoSummary')]);
2416
+ specs('Summary Rules', () => {});
2417
+ });
2418
+ query('views todo', 'TODO-SLICE').server(() => {
2419
+ data([source().state('TodoState').fromProjection('Todos', 'todoId')]);
2420
+ specs('Todo Rules', () => {});
2421
+ });
2422
+ query('views user project todos', 'USER-PROJECT-SLICE').server(() => {
2423
+ data([source().state('UserProjectTodos').fromCompositeProjection('UserProjectTodos', ['userId', 'projectId'])]);
2424
+ specs('User Project Rules', () => {});
2425
+ });
2426
+ });
2427
+ `);
2428
+ });
2429
+ });
1822
2430
  });
@@ -321,7 +321,6 @@ export function recordExample(description: string): void {
321
321
  const rule = objectRules[context.currentRuleIndex];
322
322
  rule.examples.push({
323
323
  description,
324
- when: { commandRef: '', exampleData: {} }, // Default, will be updated
325
324
  then: [],
326
325
  });
327
326
  context.currentExampleIndex = rule.examples.length - 1;
package/src/narrative.ts CHANGED
@@ -93,7 +93,7 @@ export function rule(description: string, idOrFn: string | (() => void), fn?: ()
93
93
  callback();
94
94
  }
95
95
 
96
- export const example = (description: string) => {
96
+ export const example = (description: string): TypedExampleBuilder => {
97
97
  recordExample(description);
98
98
  return createExampleBuilder();
99
99
  };
@@ -122,6 +122,7 @@ interface TypedExampleBuilder {
122
122
  interface TypedGivenBuilder<G> {
123
123
  and<U>(data: ExtractData<U> | ExtractData<U>[], context?: ContextFor<U>): TypedGivenBuilder<G | U>;
124
124
  when<W>(data: ExtractData<W> | ExtractData<W>[], context?: ContextFor<W>): TypedGivenWhenBuilder<G, W>;
125
+ then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, never, T>;
125
126
  }
126
127
 
127
128
  interface TypedWhenBuilder<W> {
@@ -180,6 +181,20 @@ function createGivenBuilder<G>(): TypedGivenBuilder<G> {
180
181
  },
181
182
  };
182
183
  },
184
+ then<T>(data: ExtractData<T> | ExtractData<T>[], context?: ContextFor<T>): TypedGivenThenBuilder<G, never, T> {
185
+ const thenItems = Array.isArray(data) ? data : [data];
186
+ recordThenData(thenItems, normalizeContext(context as Partial<Record<string, string>>));
187
+ return {
188
+ and<A>(
189
+ data: ExtractData<A> | ExtractData<A>[],
190
+ context?: ContextFor<A>,
191
+ ): TypedGivenThenBuilder<G, never, T | A> {
192
+ const andItems = Array.isArray(data) ? data : [data];
193
+ recordAndThenData(andItems, normalizeContext(context as Partial<Record<string, string>>));
194
+ return createThenBuilder<never, T | A>() as TypedGivenThenBuilder<G, never, T | A>;
195
+ },
196
+ };
197
+ },
183
198
  };
184
199
  }
185
200
 
@@ -58,7 +58,6 @@ flow('Mixed Given Types', 'FLOW-MGT', () => {
58
58
  itemId: 'item-002',
59
59
  addedAt: new Date('2024-01-01T10:02:00Z'),
60
60
  })
61
- .when({}) // empty when clause
62
61
  .then<SystemStatus>({
63
62
  systemId: 'system-001',
64
63
  itemCount: 2,
@@ -135,7 +135,6 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
135
135
  link: 'https://app.example.com/q/q-001?participant=participant-abc',
136
136
  sentAt: new Date('2030-01-01T09:00:00Z'),
137
137
  })
138
- .when({})
139
138
  .then<QuestionnaireProgress>({
140
139
  questionnaireId: 'q-001',
141
140
  participantId: 'participant-abc',
@@ -259,7 +258,6 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
259
258
  answer: 'No',
260
259
  savedAt: new Date('2030-01-01T09:05:00Z'),
261
260
  })
262
- .when({}) // FIX ME
263
261
  .then<QuestionnaireProgress>({
264
262
  questionnaireId: 'q-001',
265
263
  participantId: 'participant-abc',