@opensaas/stack-core 0.1.7 → 0.4.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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +352 -0
  3. package/CLAUDE.md +46 -1
  4. package/dist/access/engine.d.ts +7 -6
  5. package/dist/access/engine.d.ts.map +1 -1
  6. package/dist/access/engine.js +55 -0
  7. package/dist/access/engine.js.map +1 -1
  8. package/dist/access/engine.test.d.ts +2 -0
  9. package/dist/access/engine.test.d.ts.map +1 -0
  10. package/dist/access/engine.test.js +125 -0
  11. package/dist/access/engine.test.js.map +1 -0
  12. package/dist/access/types.d.ts +39 -9
  13. package/dist/access/types.d.ts.map +1 -1
  14. package/dist/config/index.d.ts +40 -20
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +34 -15
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/plugin-engine.d.ts.map +1 -1
  19. package/dist/config/plugin-engine.js +9 -0
  20. package/dist/config/plugin-engine.js.map +1 -1
  21. package/dist/config/types.d.ts +277 -84
  22. package/dist/config/types.d.ts.map +1 -1
  23. package/dist/context/index.d.ts +5 -3
  24. package/dist/context/index.d.ts.map +1 -1
  25. package/dist/context/index.js +146 -20
  26. package/dist/context/index.js.map +1 -1
  27. package/dist/context/nested-operations.d.ts.map +1 -1
  28. package/dist/context/nested-operations.js +88 -72
  29. package/dist/context/nested-operations.js.map +1 -1
  30. package/dist/fields/index.d.ts +65 -9
  31. package/dist/fields/index.d.ts.map +1 -1
  32. package/dist/fields/index.js +98 -16
  33. package/dist/fields/index.js.map +1 -1
  34. package/dist/hooks/index.d.ts +28 -12
  35. package/dist/hooks/index.d.ts.map +1 -1
  36. package/dist/hooks/index.js +16 -0
  37. package/dist/hooks/index.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/mcp/handler.js +1 -0
  42. package/dist/mcp/handler.js.map +1 -1
  43. package/dist/validation/schema.d.ts.map +1 -1
  44. package/dist/validation/schema.js +4 -2
  45. package/dist/validation/schema.js.map +1 -1
  46. package/package.json +8 -9
  47. package/src/access/engine.test.ts +145 -0
  48. package/src/access/engine.ts +73 -9
  49. package/src/access/types.ts +38 -8
  50. package/src/config/index.ts +45 -23
  51. package/src/config/plugin-engine.ts +13 -3
  52. package/src/config/types.ts +347 -117
  53. package/src/context/index.ts +176 -23
  54. package/src/context/nested-operations.ts +83 -71
  55. package/src/fields/index.ts +132 -27
  56. package/src/hooks/index.ts +63 -20
  57. package/src/index.ts +9 -0
  58. package/src/mcp/handler.ts +2 -1
  59. package/src/validation/schema.ts +4 -2
  60. package/tests/context.test.ts +38 -6
  61. package/tests/field-types.test.ts +729 -0
  62. package/tests/password-type-distribution.test.ts +0 -1
  63. package/tests/password-types.test.ts +0 -1
  64. package/tests/plugin-engine.test.ts +1102 -0
  65. package/tests/sudo.test.ts +230 -2
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest'
2
2
  import { getContext } from '../src/context/index.js'
3
3
  import { config, list } from '../src/config/index.js'
4
- import { text, integer } from '../src/fields/index.js'
4
+ import { text, integer, relationship } from '../src/fields/index.js'
5
5
  import type { PrismaClient } from '@prisma/client'
6
6
 
7
7
  describe('Sudo Context', () => {
@@ -24,7 +24,6 @@ describe('Sudo Context', () => {
24
24
  const testConfig = config({
25
25
  db: {
26
26
  provider: 'sqlite',
27
- url: 'file:./test.db',
28
27
  },
29
28
  lists: {
30
29
  Post: list({
@@ -403,4 +402,233 @@ describe('Sudo Context', () => {
403
402
  expect(sudoResult).toHaveLength(1)
404
403
  })
405
404
  })
405
+
406
+ describe('Nested Operations with Sudo Mode', () => {
407
+ // Mock Prisma client with User and Post models for nested operations
408
+ const mockPrismaWithRelations = {
409
+ user: {
410
+ findUnique: vi.fn(),
411
+ findMany: vi.fn(),
412
+ create: vi.fn(),
413
+ update: vi.fn(),
414
+ },
415
+ post: {
416
+ findUnique: vi.fn(),
417
+ findMany: vi.fn(),
418
+ create: vi.fn(),
419
+ update: vi.fn(),
420
+ },
421
+ } as unknown as PrismaClient
422
+
423
+ const nestedTestConfig = config({
424
+ db: {
425
+ provider: 'sqlite',
426
+ },
427
+ lists: {
428
+ User: list({
429
+ fields: {
430
+ email: text({ validation: { isRequired: true } }),
431
+ name: text(),
432
+ posts: relationship({ ref: 'Post.author', many: true }),
433
+ },
434
+ access: {
435
+ operation: {
436
+ create: async () => false, // Block all creates
437
+ update: async () => false, // Block all updates
438
+ },
439
+ },
440
+ }),
441
+ Post: list({
442
+ fields: {
443
+ title: text({ validation: { isRequired: true } }),
444
+ content: text(),
445
+ author: relationship({ ref: 'User.posts' }),
446
+ },
447
+ access: {
448
+ operation: {
449
+ create: async () => true,
450
+ update: async () => true,
451
+ },
452
+ },
453
+ }),
454
+ },
455
+ })
456
+
457
+ beforeEach(() => {
458
+ vi.clearAllMocks()
459
+ })
460
+
461
+ it('should allow nested create in sudo mode when access control would deny', async () => {
462
+ const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
463
+ const sudoContext = context.sudo()
464
+
465
+ // Mock successful creation
466
+ mockPrismaWithRelations.post.create.mockResolvedValue({
467
+ id: '1',
468
+ title: 'Test Post',
469
+ content: 'Test Content',
470
+ authorId: 'user-1',
471
+ })
472
+
473
+ // In sudo mode, nested create should succeed despite User access control blocking creates
474
+ const result = await sudoContext.db.post.create({
475
+ data: {
476
+ title: 'Test Post',
477
+ content: 'Test Content',
478
+ author: {
479
+ create: {
480
+ email: 'test@example.com',
481
+ name: 'Test User',
482
+ },
483
+ },
484
+ },
485
+ })
486
+
487
+ expect(result).toBeDefined()
488
+ expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
489
+ })
490
+
491
+ it('should allow nested connect in sudo mode when access control would deny', async () => {
492
+ const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
493
+ const sudoContext = context.sudo()
494
+
495
+ // Mock existing user
496
+ mockPrismaWithRelations.user.findUnique.mockResolvedValue({
497
+ id: 'user-1',
498
+ email: 'existing@example.com',
499
+ name: 'Existing User',
500
+ })
501
+
502
+ // Mock successful creation
503
+ mockPrismaWithRelations.post.create.mockResolvedValue({
504
+ id: '1',
505
+ title: 'Test Post',
506
+ content: 'Test Content',
507
+ authorId: 'user-1',
508
+ })
509
+
510
+ // In sudo mode, nested connect should succeed despite User access control blocking updates
511
+ const result = await sudoContext.db.post.create({
512
+ data: {
513
+ title: 'Test Post',
514
+ content: 'Test Content',
515
+ author: {
516
+ connect: { id: 'user-1' },
517
+ },
518
+ },
519
+ })
520
+
521
+ expect(result).toBeDefined()
522
+ expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
523
+ })
524
+
525
+ it('should allow nested update in sudo mode when access control would deny', async () => {
526
+ const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
527
+ const sudoContext = context.sudo()
528
+
529
+ // Mock existing post (needed for access control check in main operation)
530
+ mockPrismaWithRelations.post.findUnique.mockResolvedValue({
531
+ id: '1',
532
+ title: 'Original Post',
533
+ content: 'Original Content',
534
+ authorId: 'user-1',
535
+ })
536
+
537
+ // Mock existing user (for nested update)
538
+ mockPrismaWithRelations.user.findUnique.mockResolvedValue({
539
+ id: 'user-1',
540
+ email: 'existing@example.com',
541
+ name: 'Existing User',
542
+ })
543
+
544
+ // Mock successful update
545
+ mockPrismaWithRelations.post.update.mockResolvedValue({
546
+ id: '1',
547
+ title: 'Updated Post',
548
+ content: 'Updated Content',
549
+ authorId: 'user-1',
550
+ })
551
+
552
+ // In sudo mode, nested update should succeed despite User access control blocking updates
553
+ const result = await sudoContext.db.post.update({
554
+ where: { id: '1' },
555
+ data: {
556
+ title: 'Updated Post',
557
+ author: {
558
+ update: {
559
+ where: { id: 'user-1' },
560
+ data: { name: 'Updated Name' },
561
+ },
562
+ },
563
+ },
564
+ })
565
+
566
+ expect(result).toBeDefined()
567
+ expect(mockPrismaWithRelations.post.update).toHaveBeenCalled()
568
+ })
569
+
570
+ it('should allow nested connectOrCreate in sudo mode when access control would deny', async () => {
571
+ const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
572
+ const sudoContext = context.sudo()
573
+
574
+ // Mock existing user check (will find existing user)
575
+ mockPrismaWithRelations.user.findUnique.mockResolvedValue({
576
+ id: 'user-1',
577
+ email: 'existing@example.com',
578
+ name: 'Existing User',
579
+ })
580
+
581
+ // Mock successful creation
582
+ mockPrismaWithRelations.post.create.mockResolvedValue({
583
+ id: '1',
584
+ title: 'Test Post',
585
+ content: 'Test Content',
586
+ authorId: 'user-1',
587
+ })
588
+
589
+ // In sudo mode, nested connectOrCreate should succeed despite access control
590
+ const result = await sudoContext.db.post.create({
591
+ data: {
592
+ title: 'Test Post',
593
+ content: 'Test Content',
594
+ author: {
595
+ connectOrCreate: {
596
+ where: { id: 'user-1' },
597
+ create: {
598
+ email: 'new@example.com',
599
+ name: 'New User',
600
+ },
601
+ },
602
+ },
603
+ },
604
+ })
605
+
606
+ expect(result).toBeDefined()
607
+ expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
608
+ })
609
+
610
+ it('should still enforce access control in nested operations without sudo mode', async () => {
611
+ const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
612
+
613
+ // Mock existing user
614
+ mockPrismaWithRelations.user.findUnique.mockResolvedValue({
615
+ id: 'user-1',
616
+ email: 'existing@example.com',
617
+ name: 'Existing User',
618
+ })
619
+
620
+ // Without sudo, nested connect should fail due to User update access control
621
+ await expect(
622
+ context.db.post.create({
623
+ data: {
624
+ title: 'Test Post',
625
+ content: 'Test Content',
626
+ author: {
627
+ connect: { id: 'user-1' },
628
+ },
629
+ },
630
+ }),
631
+ ).rejects.toThrow('Access denied')
632
+ })
633
+ })
406
634
  })