@auto-engineer/narrative 1.3.1 → 1.3.3

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/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/file-store": "1.3.1",
27
- "@auto-engineer/message-bus": "1.3.1",
28
- "@auto-engineer/id": "1.3.1"
26
+ "@auto-engineer/file-store": "1.3.3",
27
+ "@auto-engineer/id": "1.3.3",
28
+ "@auto-engineer/message-bus": "1.3.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.0.0",
@@ -35,7 +35,7 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "version": "1.3.1",
38
+ "version": "1.3.3",
39
39
  "scripts": {
40
40
  "build": "tsx scripts/build.ts",
41
41
  "test": "vitest run --reporter=dot",
@@ -3148,5 +3148,226 @@ narrative('All Projection Types', 'ALL-PROJ', () => {
3148
3148
  const code = getCode(await modelToNarrative(modelWithSourceInstructions));
3149
3149
  expect(code).toContain(".additionalInstructions('Filter by active orders only')");
3150
3150
  });
3151
+
3152
+ it('should generate 3 narrative files for gym membership model', async () => {
3153
+ const gymModel: Model = {
3154
+ variant: 'specs',
3155
+ narratives: [
3156
+ {
3157
+ id: 'mrwDfHhDi',
3158
+ name: 'Gym Membership Registration',
3159
+ slices: [
3160
+ {
3161
+ type: 'command',
3162
+ name: 'Register New Member',
3163
+ id: 'YcOe0aHz3',
3164
+ client: { specs: [] },
3165
+ server: {
3166
+ description: '',
3167
+ specs: [
3168
+ {
3169
+ type: 'gherkin',
3170
+ feature: '',
3171
+ rules: [
3172
+ {
3173
+ id: 'ODJGjsU2m',
3174
+ name: 'Member account creation process',
3175
+ examples: [
3176
+ {
3177
+ id: 'Jdrjvn3HV',
3178
+ name: 'Create member account',
3179
+ steps: [
3180
+ {
3181
+ id: 'D1v4V3TEF',
3182
+ keyword: 'When',
3183
+ text: 'SubmitMembershipRegistration',
3184
+ docString: { name: 'John Doe', email: 'john@example.com', phone: '123-456-7890' },
3185
+ },
3186
+ {
3187
+ id: 'IeXOn8W9v',
3188
+ keyword: 'Then',
3189
+ text: 'MemberAccountCreated',
3190
+ docString: {
3191
+ memberId: 'mem_123',
3192
+ name: 'John Doe',
3193
+ email: 'john@example.com',
3194
+ phone: '123-456-7890',
3195
+ },
3196
+ },
3197
+ ],
3198
+ },
3199
+ ],
3200
+ },
3201
+ ],
3202
+ },
3203
+ ],
3204
+ data: {
3205
+ items: [
3206
+ {
3207
+ target: { type: 'Event', name: 'MemberAccountCreated' },
3208
+ destination: { type: 'stream', pattern: 'members' },
3209
+ },
3210
+ ],
3211
+ },
3212
+ },
3213
+ },
3214
+ ],
3215
+ sourceFile: '/narratives/gym.narrative.ts',
3216
+ },
3217
+ {
3218
+ id: 'exByqGILR',
3219
+ name: 'Gym Class Booking',
3220
+ slices: [
3221
+ {
3222
+ type: 'command',
3223
+ name: 'Book Gym Class',
3224
+ id: 'iEg3Qjbbp',
3225
+ client: { specs: [] },
3226
+ server: {
3227
+ description: '',
3228
+ specs: [
3229
+ {
3230
+ type: 'gherkin',
3231
+ feature: '',
3232
+ rules: [
3233
+ {
3234
+ id: 'c7TfHiadX',
3235
+ name: 'Class booking process',
3236
+ examples: [
3237
+ {
3238
+ id: '3MoVhLhAU',
3239
+ name: 'Book a class',
3240
+ steps: [
3241
+ {
3242
+ id: 'HVYnJHNCl',
3243
+ keyword: 'When',
3244
+ text: 'BookGymClass',
3245
+ docString: { memberId: 'mem_123', classId: 'cls_456', date: '2023-10-15' },
3246
+ },
3247
+ {
3248
+ id: 'FuS1S7AMA',
3249
+ keyword: 'Then',
3250
+ text: 'ClassReservationConfirmed',
3251
+ docString: {
3252
+ reservationId: 'res_789',
3253
+ memberId: 'mem_123',
3254
+ classId: 'cls_456',
3255
+ date: '2023-10-15',
3256
+ },
3257
+ },
3258
+ ],
3259
+ },
3260
+ ],
3261
+ },
3262
+ ],
3263
+ },
3264
+ ],
3265
+ data: {
3266
+ items: [
3267
+ {
3268
+ target: { type: 'Event', name: 'ClassReservationConfirmed' },
3269
+ destination: { type: 'stream', pattern: 'reservations' },
3270
+ },
3271
+ ],
3272
+ },
3273
+ },
3274
+ },
3275
+ ],
3276
+ sourceFile: '/narratives/untitled-1.narrative.ts',
3277
+ },
3278
+ {
3279
+ id: 'o0odruqZA',
3280
+ name: 'Gym Check-In',
3281
+ slices: [
3282
+ {
3283
+ type: 'command',
3284
+ name: 'Perform Check-In',
3285
+ id: 'Cxl4UHfbX',
3286
+ client: { specs: [] },
3287
+ server: {
3288
+ description: '',
3289
+ specs: [
3290
+ {
3291
+ type: 'gherkin',
3292
+ feature: '',
3293
+ rules: [
3294
+ {
3295
+ id: 'n81cBIt30',
3296
+ name: 'Check-in process',
3297
+ examples: [
3298
+ {
3299
+ id: 'QiMaBqctq',
3300
+ name: 'Record check-in',
3301
+ steps: [
3302
+ {
3303
+ id: 'Pkcushx04',
3304
+ keyword: 'When',
3305
+ text: 'PerformCheckIn',
3306
+ docString: { memberId: 'mem_123', dateTime: '2023-10-15T08:00:00Z' },
3307
+ },
3308
+ {
3309
+ id: 'Z8Ef9Yo2R',
3310
+ keyword: 'Then',
3311
+ text: 'CheckInRecorded',
3312
+ docString: {
3313
+ checkInId: 'chk_123',
3314
+ memberId: 'mem_123',
3315
+ dateTime: '2023-10-15T08:00:00Z',
3316
+ },
3317
+ },
3318
+ ],
3319
+ },
3320
+ ],
3321
+ },
3322
+ ],
3323
+ },
3324
+ ],
3325
+ data: {
3326
+ items: [
3327
+ {
3328
+ target: { type: 'Event', name: 'CheckInRecorded' },
3329
+ destination: { type: 'stream', pattern: 'checkins' },
3330
+ },
3331
+ ],
3332
+ },
3333
+ },
3334
+ },
3335
+ ],
3336
+ sourceFile: '/narratives/untitled-1-2.narrative.ts',
3337
+ },
3338
+ ],
3339
+ messages: [
3340
+ { type: 'command', name: 'SubmitMembershipRegistration', fields: [] },
3341
+ { type: 'command', name: 'BookGymClass', fields: [] },
3342
+ { type: 'command', name: 'PerformCheckIn', fields: [] },
3343
+ { type: 'event', name: 'MemberAccountCreated', fields: [], source: 'internal' },
3344
+ { type: 'event', name: 'ClassReservationConfirmed', fields: [], source: 'internal' },
3345
+ { type: 'event', name: 'CheckInRecorded', fields: [], source: 'internal' },
3346
+ ],
3347
+ modules: [],
3348
+ };
3349
+
3350
+ const result = await modelToNarrative(gymModel);
3351
+
3352
+ // Should generate 3 files, one for each narrative
3353
+ expect(result.files.length).toBe(3);
3354
+
3355
+ // Check file paths match the sourceFile from each narrative
3356
+ const filePaths = result.files.map((f) => f.path);
3357
+ expect(filePaths).toContain('/narratives/gym.narrative.ts');
3358
+ expect(filePaths).toContain('/narratives/untitled-1.narrative.ts');
3359
+ expect(filePaths).toContain('/narratives/untitled-1-2.narrative.ts');
3360
+
3361
+ // Verify each narrative has proper content
3362
+ const code = getCode(result);
3363
+ expect(code).toContain("narrative('Gym Membership Registration'");
3364
+ expect(code).toContain("narrative('Gym Class Booking'");
3365
+ expect(code).toContain("narrative('Gym Check-In'");
3366
+
3367
+ // Check slices are generated for each narrative
3368
+ expect(code).toContain("command('Register New Member'");
3369
+ expect(code).toContain("command('Book Gym Class'");
3370
+ expect(code).toContain("command('Perform Check-In'");
3371
+ });
3151
3372
  });
3152
3373
  });
@@ -29,6 +29,9 @@ export function jsonToExpr(
29
29
  case 'string':
30
30
  return f.createStringLiteral(v);
31
31
  case 'number':
32
+ if (v < 0) {
33
+ return f.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, f.createNumericLiteral(String(Math.abs(v))));
34
+ }
32
35
  return f.createNumericLiteral(String(v));
33
36
  case 'boolean':
34
37
  return v ? f.createTrue() : f.createFalse();
@@ -122,6 +122,31 @@ describe('buildGwtSpecBlock', () => {
122
122
 
123
123
  expect(code).toMatch(/rule\([^)]+\)\s*=>/);
124
124
  });
125
+
126
+ it('handles negative numbers in example data', () => {
127
+ const gwtBlock: GWTBlock & { ruleDescription: string; exampleDescription: string; ruleId: string } = {
128
+ when: {
129
+ commandRef: 'AdjustBalance',
130
+ exampleData: { accountId: 'acc-001', amount: -100, adjustment: -50.5 },
131
+ },
132
+ then: [
133
+ {
134
+ eventRef: 'BalanceAdjusted',
135
+ exampleData: { accountId: 'acc-001', newBalance: -150.5, change: -100 },
136
+ },
137
+ ],
138
+ ruleDescription: 'balance can be adjusted with negative amounts',
139
+ exampleDescription: 'adjusts balance with negative values',
140
+ ruleId: 'rNegNum01',
141
+ };
142
+
143
+ const result = buildGwtSpecBlock(ts, ts.factory, gwtBlock, 'command');
144
+ const code = printNode(result);
145
+
146
+ expect(code).toContain('-100');
147
+ expect(code).toContain('-50.5');
148
+ expect(code).toContain('-150.5');
149
+ });
125
150
  });
126
151
 
127
152
  describe('buildConsolidatedGwtSpecBlock', () => {
@@ -123,6 +123,9 @@ function chainThenCall(
123
123
  g: GWTBlock,
124
124
  messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
125
125
  ): tsNS.Expression {
126
+ if (g.then.length === 0) {
127
+ return base;
128
+ }
126
129
  const firstThenItem = g.then[0];
127
130
  const thenTypeRef = getThenTypeRef(firstThenItem);
128
131
  const typeInfo = messages && thenTypeRef ? getFieldTypeInfo(messages, thenTypeRef) : undefined;