@opensaas/stack-core 0.37.0 → 0.39.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 +191 -0
- package/CLAUDE.md +42 -3
- package/dist/access/access-filter.d.ts +70 -113
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +183 -210
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +148 -188
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts +66 -26
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +62 -17
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/declared-dependencies.test.d.ts +2 -0
- package/dist/access/declared-dependencies.test.d.ts.map +1 -0
- package/dist/access/declared-dependencies.test.js +226 -0
- package/dist/access/declared-dependencies.test.js.map +1 -0
- package/dist/access/depth-limits.d.ts +8 -7
- package/dist/access/depth-limits.d.ts.map +1 -1
- package/dist/access/depth-limits.js +8 -7
- package/dist/access/depth-limits.js.map +1 -1
- package/dist/access/engine.d.ts +0 -20
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +0 -25
- package/dist/access/engine.js.map +1 -1
- package/dist/access/errors.d.ts +61 -8
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +91 -12
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +39 -1
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +108 -42
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +229 -2
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-transforms.d.ts +0 -27
- package/dist/access/field-transforms.d.ts.map +1 -1
- package/dist/access/field-visibility.d.ts +2 -5
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +89 -41
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -3
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +12 -2
- package/dist/access/index.js.map +1 -1
- package/dist/access/query-validation.d.ts +121 -0
- package/dist/access/query-validation.d.ts.map +1 -0
- package/dist/access/query-validation.js +292 -0
- package/dist/access/query-validation.js.map +1 -0
- package/dist/access/relationship-count.d.ts +1 -1
- package/dist/access/relationship-count.d.ts.map +1 -1
- package/dist/access/relationship-count.js +2 -10
- package/dist/access/relationship-count.js.map +1 -1
- package/dist/access/relationship-label-filter.d.ts +23 -23
- package/dist/access/relationship-label-filter.d.ts.map +1 -1
- package/dist/access/relationship-label-filter.js +5 -76
- package/dist/access/relationship-label-filter.js.map +1 -1
- package/dist/access/relationship-label-filter.test.js +19 -56
- package/dist/access/relationship-label-filter.test.js.map +1 -1
- package/dist/access/transaction-registry.d.ts +45 -0
- package/dist/access/transaction-registry.d.ts.map +1 -0
- package/dist/access/transaction-registry.js +37 -0
- package/dist/access/transaction-registry.js.map +1 -0
- package/dist/access/types.d.ts +55 -59
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +9 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/label.d.ts +6 -13
- package/dist/config/label.d.ts.map +1 -1
- package/dist/config/label.js +6 -13
- package/dist/config/label.js.map +1 -1
- package/dist/config/nav-count.d.ts +11 -19
- package/dist/config/nav-count.d.ts.map +1 -1
- package/dist/config/nav-count.js +11 -19
- package/dist/config/nav-count.js.map +1 -1
- package/dist/config/plugin-engine.d.ts +0 -10
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +1 -37
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +94 -109
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/apply-defaults.d.ts +8 -31
- package/dist/context/apply-defaults.d.ts.map +1 -1
- package/dist/context/apply-defaults.js +12 -41
- package/dist/context/apply-defaults.js.map +1 -1
- package/dist/context/hook-pipeline.d.ts +4 -29
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +8 -41
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +4 -15
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +223 -215
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts +7 -26
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +73 -171
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/transaction-boundary.d.ts +36 -47
- package/dist/context/transaction-boundary.d.ts.map +1 -1
- package/dist/context/transaction-boundary.js +92 -79
- package/dist/context/transaction-boundary.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +25 -64
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +81 -111
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/format-prisma-default.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.js +4 -7
- package/dist/fields/format-prisma-default.js.map +1 -1
- package/dist/fields/index.d.ts +17 -2
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +136 -147
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +26 -3
- package/dist/filter/collect.d.ts.map +1 -1
- package/dist/filter/collect.js +30 -12
- package/dist/filter/collect.js.map +1 -1
- package/dist/filter/filter.test.js +98 -15
- package/dist/filter/filter.test.js.map +1 -1
- package/dist/filter/index.d.ts +1 -0
- package/dist/filter/index.d.ts.map +1 -1
- package/dist/filter/index.js.map +1 -1
- package/dist/filter/map.d.ts.map +1 -1
- package/dist/filter/map.js +0 -5
- package/dist/filter/map.js.map +1 -1
- package/dist/filter/parse.d.ts.map +1 -1
- package/dist/filter/parse.js +2 -9
- package/dist/filter/parse.js.map +1 -1
- package/dist/filter/serialize.d.ts.map +1 -1
- package/dist/filter/serialize.js +2 -4
- package/dist/filter/serialize.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -42
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/case-utils.d.ts +0 -32
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +0 -32
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/mcp/handler.d.ts +0 -4
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +12 -69
- package/dist/mcp/handler.js.map +1 -1
- package/dist/query/index.d.ts +55 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +41 -117
- package/dist/query/index.js.map +1 -1
- package/dist/query/relationship-options.d.ts +1 -1
- package/dist/query/relationship-options.js +1 -1
- package/dist/utils/password.d.ts +0 -68
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +7 -76
- package/dist/utils/password.js.map +1 -1
- package/dist/validation/field-config.d.ts.map +1 -1
- package/dist/validation/field-config.js +0 -6
- package/dist/validation/field-config.js.map +1 -1
- package/dist/validation/schema.d.ts +0 -7
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +0 -10
- package/dist/validation/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.test.ts +205 -275
- package/src/access/access-filter.ts +236 -272
- package/src/access/declared-dependencies.test.ts +277 -0
- package/src/access/declared-dependencies.ts +117 -37
- package/src/access/depth-limits.ts +8 -7
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +97 -12
- package/src/access/field-access.test.ts +312 -2
- package/src/access/field-access.ts +118 -42
- package/src/access/field-transforms.ts +8 -39
- package/src/access/field-visibility.ts +99 -45
- package/src/access/index.ts +17 -5
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +3 -11
- package/src/access/relationship-label-filter.test.ts +19 -68
- package/src/access/relationship-label-filter.ts +27 -104
- package/src/access/transaction-registry.ts +58 -0
- package/src/access/types.ts +83 -86
- package/src/config/index.ts +10 -22
- package/src/config/label.ts +6 -13
- package/src/config/nav-count.ts +11 -19
- package/src/config/plugin-engine.ts +1 -37
- package/src/config/types.ts +95 -110
- package/src/context/apply-defaults.ts +12 -44
- package/src/context/hook-pipeline.ts +12 -67
- package/src/context/index.ts +311 -276
- package/src/context/nested-operations.ts +94 -226
- package/src/context/transaction-boundary.ts +113 -109
- package/src/context/write-pipeline.ts +99 -155
- package/src/fields/format-prisma-default.ts +4 -7
- package/src/fields/index.ts +155 -147
- package/src/filter/collect.ts +48 -12
- package/src/filter/filter.test.ts +153 -15
- package/src/filter/index.ts +1 -0
- package/src/filter/map.ts +0 -5
- package/src/filter/parse.ts +2 -9
- package/src/filter/serialize.ts +2 -4
- package/src/hooks/index.ts +5 -59
- package/src/index.ts +23 -4
- package/src/internal.ts +5 -0
- package/src/lib/case-utils.ts +0 -32
- package/src/mcp/handler.ts +13 -71
- package/src/query/index.ts +82 -235
- package/src/query/relationship-options.ts +1 -1
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/access-relationships.test.ts +18 -16
- package/tests/computed-field-selective-evaluation.test.ts +418 -0
- package/tests/context.test.ts +727 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/needs-declared-dependencies.test.ts +7 -4
- package/tests/resolve-chain.test.ts +11 -11
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -2,6 +2,7 @@ import { describe, test, expect } from 'vitest'
|
|
|
2
2
|
import {
|
|
3
3
|
text,
|
|
4
4
|
integer,
|
|
5
|
+
bigInt,
|
|
5
6
|
checkbox,
|
|
6
7
|
timestamp,
|
|
7
8
|
password,
|
|
@@ -327,6 +328,227 @@ describe('Field Types', () => {
|
|
|
327
328
|
})
|
|
328
329
|
})
|
|
329
330
|
|
|
331
|
+
describe('bigInt field', () => {
|
|
332
|
+
describe('getZodSchema', () => {
|
|
333
|
+
test('returns optional bigint schema for non-required field', () => {
|
|
334
|
+
const field = bigInt()
|
|
335
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
336
|
+
|
|
337
|
+
expect(schema.safeParse(25n).success).toBe(true)
|
|
338
|
+
expect(schema.safeParse(undefined).success).toBe(true)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
test('returns required schema for required field in create mode', () => {
|
|
342
|
+
const field = bigInt({ validation: { isRequired: true } })
|
|
343
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
344
|
+
|
|
345
|
+
expect(schema.safeParse(25n).success).toBe(true)
|
|
346
|
+
expect(schema.safeParse(undefined).success).toBe(false)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
test('returns optional schema for required field in update mode', () => {
|
|
350
|
+
const field = bigInt({ validation: { isRequired: true } })
|
|
351
|
+
const schema = field.getZodSchema('epoch', 'update')
|
|
352
|
+
|
|
353
|
+
expect(schema.safeParse(25n).success).toBe(true)
|
|
354
|
+
expect(schema.safeParse(undefined).success).toBe(true)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('coerces a bigint input to a bigint output unchanged', () => {
|
|
358
|
+
const field = bigInt()
|
|
359
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
360
|
+
|
|
361
|
+
const result = schema.safeParse(9007199254740993n)
|
|
362
|
+
expect(result.success).toBe(true)
|
|
363
|
+
if (result.success) {
|
|
364
|
+
expect(result.data).toBe(9007199254740993n)
|
|
365
|
+
expect(typeof result.data).toBe('bigint')
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
test('coerces a safe-integer number input to a bigint output', () => {
|
|
370
|
+
const field = bigInt()
|
|
371
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
372
|
+
|
|
373
|
+
const result = schema.safeParse(1700000000000)
|
|
374
|
+
expect(result.success).toBe(true)
|
|
375
|
+
if (result.success) {
|
|
376
|
+
expect(result.data).toBe(1700000000000n)
|
|
377
|
+
expect(typeof result.data).toBe('bigint')
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
test('coerces a numeric string input to a bigint output, round-tripping beyond Number.MAX_SAFE_INTEGER', () => {
|
|
382
|
+
const field = bigInt()
|
|
383
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
384
|
+
|
|
385
|
+
const result = schema.safeParse('9223372036854775807')
|
|
386
|
+
expect(result.success).toBe(true)
|
|
387
|
+
if (result.success) {
|
|
388
|
+
expect(result.data).toBe(9223372036854775807n)
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
test('accepts a negative numeric string', () => {
|
|
393
|
+
const field = bigInt()
|
|
394
|
+
const schema = field.getZodSchema('delta', 'create')
|
|
395
|
+
|
|
396
|
+
const result = schema.safeParse('-42')
|
|
397
|
+
expect(result.success).toBe(true)
|
|
398
|
+
if (result.success) {
|
|
399
|
+
expect(result.data).toBe(-42n)
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test('rejects a non-integer number', () => {
|
|
404
|
+
const field = bigInt()
|
|
405
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
406
|
+
|
|
407
|
+
expect(schema.safeParse(1.5).success).toBe(false)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
test('rejects a number above Number.MAX_SAFE_INTEGER instead of silently losing precision', () => {
|
|
411
|
+
const field = bigInt()
|
|
412
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
413
|
+
|
|
414
|
+
expect(schema.safeParse(Number.MAX_SAFE_INTEGER + 10).success).toBe(false)
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
test('rejects a non-numeric string', () => {
|
|
418
|
+
const field = bigInt()
|
|
419
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
420
|
+
|
|
421
|
+
expect(schema.safeParse('not-a-number').success).toBe(false)
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
test('rejects a decimal string', () => {
|
|
425
|
+
const field = bigInt()
|
|
426
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
427
|
+
|
|
428
|
+
expect(schema.safeParse('1.5').success).toBe(false)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
test('validates min value', () => {
|
|
432
|
+
const field = bigInt({ validation: { min: 0n } })
|
|
433
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
434
|
+
|
|
435
|
+
expect(schema.safeParse(-1n).success).toBe(false)
|
|
436
|
+
expect(schema.safeParse(0n).success).toBe(true)
|
|
437
|
+
expect(schema.safeParse(1n).success).toBe(true)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
test('validates max value', () => {
|
|
441
|
+
const field = bigInt({ validation: { max: 100n } })
|
|
442
|
+
const schema = field.getZodSchema('epoch', 'create')
|
|
443
|
+
|
|
444
|
+
expect(schema.safeParse(99n).success).toBe(true)
|
|
445
|
+
expect(schema.safeParse(100n).success).toBe(true)
|
|
446
|
+
expect(schema.safeParse(101n).success).toBe(false)
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
test('validates min and max together', () => {
|
|
450
|
+
const field = bigInt({ validation: { min: 18n, max: 65n } })
|
|
451
|
+
const schema = field.getZodSchema('age', 'create')
|
|
452
|
+
|
|
453
|
+
expect(schema.safeParse(17n).success).toBe(false)
|
|
454
|
+
expect(schema.safeParse(18n).success).toBe(true)
|
|
455
|
+
expect(schema.safeParse(65n).success).toBe(true)
|
|
456
|
+
expect(schema.safeParse(66n).success).toBe(false)
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
describe('getPrismaType', () => {
|
|
461
|
+
test('returns BigInt type for basic field', () => {
|
|
462
|
+
const field = bigInt()
|
|
463
|
+
const prismaType = field.getPrismaType('epoch')
|
|
464
|
+
|
|
465
|
+
expect(prismaType.type).toBe('BigInt')
|
|
466
|
+
expect(prismaType.modifiers).toBe('?')
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
test('returns required BigInt for required field', () => {
|
|
470
|
+
const field = bigInt({ validation: { isRequired: true } })
|
|
471
|
+
const prismaType = field.getPrismaType('epoch')
|
|
472
|
+
|
|
473
|
+
expect(prismaType.type).toBe('BigInt')
|
|
474
|
+
expect(prismaType.modifiers).toBeUndefined()
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
test('db.isNullable: false makes field non-nullable regardless of validation', () => {
|
|
478
|
+
const field = bigInt({ db: { isNullable: false } })
|
|
479
|
+
const prismaType = field.getPrismaType('epoch')
|
|
480
|
+
|
|
481
|
+
expect(prismaType.modifiers).toBeUndefined()
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
test('db.map generates @map attribute', () => {
|
|
485
|
+
const field = bigInt({ db: { map: 'occurred_at_ms' } })
|
|
486
|
+
const prismaType = field.getPrismaType('epoch')
|
|
487
|
+
|
|
488
|
+
expect(prismaType.modifiers).toContain('@map("occurred_at_ms")')
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
test('defaultValue generates a bare @default literal', () => {
|
|
492
|
+
const field = bigInt({ defaultValue: 0n })
|
|
493
|
+
const prismaType = field.getPrismaType('epoch')
|
|
494
|
+
|
|
495
|
+
expect(prismaType.modifiers).toContain('@default(0)')
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
test('isIndexed: true requests a block-level index', () => {
|
|
499
|
+
const field = bigInt({ isIndexed: true })
|
|
500
|
+
const prismaType = field.getPrismaType('epoch')
|
|
501
|
+
|
|
502
|
+
expect(prismaType.index).toBe(true)
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
test("isIndexed: 'unique' generates @unique modifier", () => {
|
|
506
|
+
const field = bigInt({ isIndexed: 'unique' })
|
|
507
|
+
const prismaType = field.getPrismaType('epoch')
|
|
508
|
+
|
|
509
|
+
expect(prismaType.modifiers).toContain('@unique')
|
|
510
|
+
})
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
describe('getTypeScriptType', () => {
|
|
514
|
+
test('returns optional bigint type for non-required field', () => {
|
|
515
|
+
const field = bigInt()
|
|
516
|
+
const tsType = field.getTypeScriptType()
|
|
517
|
+
|
|
518
|
+
expect(tsType.type).toBe('bigint')
|
|
519
|
+
expect(tsType.optional).toBe(true)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
test('returns required bigint type for required field', () => {
|
|
523
|
+
const field = bigInt({ validation: { isRequired: true } })
|
|
524
|
+
const tsType = field.getTypeScriptType()
|
|
525
|
+
|
|
526
|
+
expect(tsType.type).toBe('bigint')
|
|
527
|
+
expect(tsType.optional).toBe(false)
|
|
528
|
+
})
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
describe('getFilterSpec', () => {
|
|
532
|
+
test('parses a valid integer token into a BigInt condition', () => {
|
|
533
|
+
const field = bigInt()
|
|
534
|
+
const spec = field.getFilterSpec?.('epoch', 'Event', {} as never)
|
|
535
|
+
|
|
536
|
+
expect(spec?.toCondition('eq', '9007199254740993')).toEqual({
|
|
537
|
+
epoch: { equals: 9007199254740993n },
|
|
538
|
+
})
|
|
539
|
+
expect(spec?.toCondition('gt', '5')).toEqual({ epoch: { gt: 5n } })
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
test('degrades to free text (null) for a non-integer token', () => {
|
|
543
|
+
const field = bigInt()
|
|
544
|
+
const spec = field.getFilterSpec?.('epoch', 'Event', {} as never)
|
|
545
|
+
|
|
546
|
+
expect(spec?.toCondition('eq', '1.5')).toBeNull()
|
|
547
|
+
expect(spec?.toCondition('eq', 'not-a-number')).toBeNull()
|
|
548
|
+
})
|
|
549
|
+
})
|
|
550
|
+
})
|
|
551
|
+
|
|
330
552
|
describe('checkbox field', () => {
|
|
331
553
|
describe('getZodSchema', () => {
|
|
332
554
|
test('returns optional boolean schema', () => {
|
|
@@ -326,6 +326,40 @@ describe('MCP Handler', () => {
|
|
|
326
326
|
})
|
|
327
327
|
})
|
|
328
328
|
|
|
329
|
+
// Regression test (issue #907 / ADR-0029): a bare `JSON.stringify` throws
|
|
330
|
+
// `TypeError: Do not know how to serialize a BigInt` on a bigInt() field's
|
|
331
|
+
// value. This must respond successfully, rendering the value as a decimal
|
|
332
|
+
// string — the field's MCP wire representation.
|
|
333
|
+
it('should serialize a bigint field value as a decimal string instead of throwing', async () => {
|
|
334
|
+
const mockResults = [{ id: '1', title: 'Post 1', occurredAtMs: 9007199254740993n }]
|
|
335
|
+
mockPrisma.post.findMany.mockResolvedValue(mockResults)
|
|
336
|
+
|
|
337
|
+
const handlers = createMcpHandlers({ config, getSession: mockGetSession, getContext })
|
|
338
|
+
|
|
339
|
+
const request = new Request('http://localhost/api/mcp', {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { 'Content-Type': 'application/json' },
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
jsonrpc: '2.0',
|
|
344
|
+
id: 1,
|
|
345
|
+
method: 'tools/call',
|
|
346
|
+
params: {
|
|
347
|
+
name: 'list_post_query',
|
|
348
|
+
arguments: { where: {}, take: 10 },
|
|
349
|
+
},
|
|
350
|
+
}),
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const response = await handlers.POST(request)
|
|
354
|
+
expect(response.status).toBe(200)
|
|
355
|
+
|
|
356
|
+
const data = await response.json()
|
|
357
|
+
expect(data.result.content[0].text).toContain('"occurredAtMs": "9007199254740993"')
|
|
358
|
+
|
|
359
|
+
const result = JSON.parse(data.result.content[0].text)
|
|
360
|
+
expect(result.items[0].occurredAtMs).toBe('9007199254740993')
|
|
361
|
+
})
|
|
362
|
+
|
|
329
363
|
it('should execute create operation', async () => {
|
|
330
364
|
const mockResult = { id: '1', title: 'New Post', content: 'New Content' }
|
|
331
365
|
mockPrisma.post.create.mockResolvedValue(mockResult)
|
|
@@ -294,11 +294,14 @@ describe('a computed field declares the relations it needs (#850, ADR-0025)', ()
|
|
|
294
294
|
expect(result).toHaveProperty('title')
|
|
295
295
|
})
|
|
296
296
|
|
|
297
|
-
it(
|
|
297
|
+
it("a declaration cycle across two lists terminates via the declaration fold's own cycle guard (ADR-0026 note)", async () => {
|
|
298
298
|
// Order.total needs lineItems; LineItem.orderTitle needs order — a
|
|
299
|
-
// two-list declaration cycle. This must not hang or crash: it rides
|
|
300
|
-
//
|
|
301
|
-
//
|
|
299
|
+
// two-list declaration cycle. This must not hang or crash: it rides
|
|
300
|
+
// `foldDeclaredDependencies`'s own `visitedLists` cycle guard — the same
|
|
301
|
+
// list-name-path mechanism the pre-ADR-0026 relationship-graph auto-walk
|
|
302
|
+
// used, re-pointed at the declaration fold now that nothing walks the
|
|
303
|
+
// relationship graph unprompted (defense in depth; the primary backstop
|
|
304
|
+
// is `validateNeedsClosureDepth` at generate time, see needs-closure.ts).
|
|
302
305
|
const testConfig = await buildTestConfig({ withDeclarationCycle: true })
|
|
303
306
|
const mockPrisma = createMockPrisma()
|
|
304
307
|
mockPrisma.order.findMany.mockResolvedValue([
|
|
@@ -331,7 +331,7 @@ describe('resolve chain — concurrent hook invocations are isolated (#844)', ()
|
|
|
331
331
|
expect(observedLengths.sort()).toEqual([1, 1, 1])
|
|
332
332
|
})
|
|
333
333
|
|
|
334
|
-
it('an unrelated top-level read in flight alongside a hook still gets its
|
|
334
|
+
it('an unrelated top-level read in flight alongside a hook still gets its own explicit include scoped', async () => {
|
|
335
335
|
let releaseSlowHook: () => void = () => {}
|
|
336
336
|
|
|
337
337
|
const config: OpenSaasConfig = {
|
|
@@ -389,21 +389,21 @@ describe('resolve chain — concurrent hook invocations are isolated (#844)', ()
|
|
|
389
389
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
390
390
|
|
|
391
391
|
// While the slow hook is still in flight, issue a plain top-level read
|
|
392
|
-
// that has nothing to do with it
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
392
|
+
// that has nothing to do with it, explicitly naming a nested path two
|
|
393
|
+
// levels deep (child → grandchild) — the "One hop" rule (ADR-0026) means
|
|
394
|
+
// reaching grandchild requires naming it, which this request does.
|
|
395
|
+
const fastPromise = context.db.fast.findMany({
|
|
396
|
+
include: { child: { include: { grandchild: true } } },
|
|
397
|
+
})
|
|
397
398
|
|
|
398
399
|
await fastPromise
|
|
399
400
|
releaseSlowHook()
|
|
400
401
|
await slowPromise
|
|
401
402
|
|
|
402
|
-
// The unrelated read's access-controlled scoping must
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
// derived context, never leak into a concurrently-running, unrelated one.
|
|
403
|
+
// The unrelated read's access-controlled scoping must descend exactly as
|
|
404
|
+
// far as ITS OWN request named, unaffected by a totally different read's
|
|
405
|
+
// hook being in flight concurrently — no shared, leaking context state
|
|
406
|
+
// between the two.
|
|
407
407
|
expect(prisma.fast.findMany.mock.calls[0][0].include).toEqual({
|
|
408
408
|
child: { include: { grandchild: true } },
|
|
409
409
|
})
|