@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +22 -0
- package/dist/src/commands/export-schema-helper.js +1 -2
- package/dist/src/commands/export-schema-helper.js.map +1 -1
- 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.cache.specs.d.ts +2 -0
- package/dist/src/getNarratives.cache.specs.d.ts.map +1 -0
- package/dist/src/{getFlows.cache.specs.js → getNarratives.cache.specs.js} +1 -1
- package/dist/src/getNarratives.cache.specs.js.map +1 -0
- package/dist/src/getNarratives.specs.js +233 -19
- package/dist/src/getNarratives.specs.js.map +1 -1
- package/dist/src/model-to-narrative.specs.d.ts +2 -0
- package/dist/src/model-to-narrative.specs.d.ts.map +1 -0
- package/dist/src/{model-to-flow.specs.js → model-to-narrative.specs.js} +594 -2
- package/dist/src/model-to-narrative.specs.js.map +1 -0
- package/dist/src/narrative-context.d.ts.map +1 -1
- package/dist/src/narrative-context.js +0 -1
- package/dist/src/narrative-context.js.map +1 -1
- package/dist/src/narrative.d.ts +1 -0
- package/dist/src/narrative.d.ts.map +1 -1
- package/dist/src/narrative.js +11 -0
- package/dist/src/narrative.js.map +1 -1
- package/dist/src/samples/mixed-given-types.narrative.js +0 -1
- package/dist/src/samples/mixed-given-types.narrative.js.map +1 -1
- package/dist/src/samples/questionnaires.narrative.js +0 -2
- package/dist/src/samples/questionnaires.narrative.js.map +1 -1
- package/dist/src/schema.d.ts +2253 -2054
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +9 -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/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.js +32 -8
- package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/debug.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/debug.js +1 -1
- package/dist/src/transformers/narrative-to-model/debug.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/commands/export-schema-helper.ts +1 -2
- package/src/data-narrative-builders.ts +41 -9
- package/src/getNarratives.specs.ts +266 -20
- package/src/{model-to-flow.specs.ts → model-to-narrative.specs.ts} +609 -1
- package/src/narrative-context.ts +0 -1
- package/src/narrative.ts +16 -1
- package/src/samples/mixed-given-types.narrative.ts +0 -1
- package/src/samples/questionnaires.narrative.ts +0 -2
- package/src/schema.ts +13 -1
- package/src/transformers/model-to-narrative/generators/flow.ts +85 -26
- package/src/transformers/model-to-narrative/generators/gwt.ts +44 -9
- package/src/transformers/narrative-to-model/debug.ts +1 -1
- package/src/types.ts +7 -9
- package/dist/src/getFlows.cache.specs.d.ts +0 -2
- package/dist/src/getFlows.cache.specs.d.ts.map +0 -1
- package/dist/src/getFlows.cache.specs.js.map +0 -1
- package/dist/src/model-to-flow.specs.d.ts +0 -2
- package/dist/src/model-to-flow.specs.d.ts.map +0 -1
- package/dist/src/model-to-flow.specs.js.map +0 -1
- /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
|
});
|
package/src/narrative-context.ts
CHANGED
|
@@ -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
|
|
|
@@ -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',
|