@opensaas/stack-core 0.21.0 → 0.23.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 (68) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +268 -0
  3. package/CLAUDE.md +18 -15
  4. package/dist/access/field-visibility.d.ts.map +1 -1
  5. package/dist/access/field-visibility.js +29 -6
  6. package/dist/access/field-visibility.js.map +1 -1
  7. package/dist/access/multi-column-read-write.test.d.ts +2 -0
  8. package/dist/access/multi-column-read-write.test.d.ts.map +1 -0
  9. package/dist/access/multi-column-read-write.test.js +149 -0
  10. package/dist/access/multi-column-read-write.test.js.map +1 -0
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/types.d.ts +289 -1
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/context/index.d.ts.map +1 -1
  16. package/dist/context/index.js +31 -0
  17. package/dist/context/index.js.map +1 -1
  18. package/dist/extend.d.ts +1 -1
  19. package/dist/extend.d.ts.map +1 -1
  20. package/dist/fields/format-prisma-default.d.ts +35 -0
  21. package/dist/fields/format-prisma-default.d.ts.map +1 -0
  22. package/dist/fields/format-prisma-default.js +52 -0
  23. package/dist/fields/format-prisma-default.js.map +1 -0
  24. package/dist/fields/format-prisma-default.test.d.ts +2 -0
  25. package/dist/fields/format-prisma-default.test.d.ts.map +1 -0
  26. package/dist/fields/format-prisma-default.test.js +54 -0
  27. package/dist/fields/format-prisma-default.test.js.map +1 -0
  28. package/dist/fields/index.d.ts +1 -1
  29. package/dist/fields/index.d.ts.map +1 -1
  30. package/dist/fields/index.js +54 -16
  31. package/dist/fields/index.js.map +1 -1
  32. package/dist/fields/select.test.js +85 -0
  33. package/dist/fields/select.test.js.map +1 -1
  34. package/dist/fields/text-keystone-compat.test.d.ts +2 -0
  35. package/dist/fields/text-keystone-compat.test.d.ts.map +1 -0
  36. package/dist/fields/text-keystone-compat.test.js +93 -0
  37. package/dist/fields/text-keystone-compat.test.js.map +1 -0
  38. package/dist/hooks/index.d.ts.map +1 -1
  39. package/dist/hooks/index.js +60 -16
  40. package/dist/hooks/index.js.map +1 -1
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +7 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/index.test.d.ts +2 -0
  46. package/dist/index.test.d.ts.map +1 -0
  47. package/dist/index.test.js +33 -0
  48. package/dist/index.test.js.map +1 -0
  49. package/dist/mcp/handler.js +0 -1
  50. package/dist/mcp/handler.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/access/field-visibility.ts +28 -6
  53. package/src/access/multi-column-read-write.test.ts +255 -0
  54. package/src/config/index.ts +2 -0
  55. package/src/config/types.ts +291 -0
  56. package/src/context/index.ts +45 -0
  57. package/src/extend.ts +6 -1
  58. package/src/fields/format-prisma-default.test.ts +64 -0
  59. package/src/fields/format-prisma-default.ts +67 -0
  60. package/src/fields/index.ts +65 -18
  61. package/src/fields/select.test.ts +99 -0
  62. package/src/fields/text-keystone-compat.test.ts +126 -0
  63. package/src/hooks/index.ts +60 -17
  64. package/src/index.test.ts +50 -0
  65. package/src/index.ts +17 -1
  66. package/src/mcp/handler.ts +0 -2
  67. package/tests/context.test.ts +80 -1
  68. package/tsconfig.tsbuildinfo +1 -1
package/src/index.ts CHANGED
@@ -14,7 +14,14 @@ export { config, list } from './config/index.js'
14
14
  // Config types a consumer annotates with.
15
15
  // Concrete field-config types (TextField, …) live on '@opensaas/stack-core/fields'
16
16
  // alongside their builders.
17
- export type { OpenSaasConfig, ListConfig, FieldConfig, OperationAccess } from './config/index.js'
17
+ export type {
18
+ OpenSaasConfig,
19
+ OutputConfig,
20
+ ListConfig,
21
+ DatabaseConfig,
22
+ FieldConfig,
23
+ OperationAccess,
24
+ } from './config/index.js'
18
25
 
19
26
  // Access control — the types a consumer writes against
20
27
  export type {
@@ -40,3 +47,12 @@ export { ValidationError } from './hooks/index.js'
40
47
  // with a clear per-field message instead of deep inside generation.
41
48
  export { validateFieldConfig, validateConfigFields } from './validation/field-config.js'
42
49
  export type { FieldConfigValidationError } from './validation/field-config.js'
50
+
51
+ // Fragment-based query API — composable, type-safe reads that mirror
52
+ // Keystone's GraphQL fragments without a GraphQL runtime. The migration
53
+ // guide, CHANGELOG, and migrate-context-calls skill all advertise importing
54
+ // these from the root entry point. The internal runtime helpers (isFragment,
55
+ // buildInclude, pickFields) and the Fragment/FieldSelection types stay off the
56
+ // root surface — those live on '@opensaas/stack-core/internal'.
57
+ export { defineFragment, runQuery, runQueryOne } from './query/index.js'
58
+ export type { ResultOf, RelationSelector, QueryArgs } from './query/index.js'
@@ -410,8 +410,6 @@ async function handleToolsCall(
410
410
  const toolName = params?.name
411
411
  const toolArgs = params?.arguments || {}
412
412
 
413
- console.log('Handling tool call:', toolName, toolArgs)
414
-
415
413
  if (!toolName) {
416
414
  return new Response(
417
415
  JSON.stringify({
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
2
  import { getContext } from '../src/context/index.js'
3
3
  import type { OpenSaasConfig } from '../src/config/types.js'
4
4
 
@@ -425,4 +425,83 @@ describe('getContext', () => {
425
425
  expect(configWithHook.lists.User.hooks?.resolveInput).toHaveBeenCalledTimes(2)
426
426
  })
427
427
  })
428
+
429
+ // `select` is not honoured by context.db reads — it is a visible no-op: the
430
+ // op warns (once per list+operation) and still returns the full, access-
431
+ // filtered result. Each test re-imports getContext via vi.resetModules() so
432
+ // the module-level warn-once cache starts empty.
433
+ describe('select no-op warning', () => {
434
+ let warnSpy: ReturnType<typeof vi.spyOn>
435
+ let freshGetContext: typeof getContext
436
+
437
+ beforeEach(async () => {
438
+ vi.resetModules()
439
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
440
+ const mod = await import('../src/context/index.js')
441
+ freshGetContext = mod.getContext
442
+ })
443
+
444
+ afterEach(() => {
445
+ warnSpy.mockRestore()
446
+ })
447
+
448
+ it('warns AND still returns the row when findUnique is passed a select', async () => {
449
+ const mockUser = { id: '1', name: 'John', email: 'john@example.com' }
450
+ mockPrisma.user.findFirst.mockResolvedValue(mockUser)
451
+
452
+ const context = await freshGetContext(config, mockPrisma, null)
453
+ const result = await context.db.user.findUnique({
454
+ where: { id: '1' },
455
+ select: { name: true },
456
+ })
457
+
458
+ expect(warnSpy).toHaveBeenCalledTimes(1)
459
+ expect(warnSpy.mock.calls[0][0]).toContain('`select` is ignored')
460
+ expect(warnSpy.mock.calls[0][0]).toContain('findUnique')
461
+ // Behaviour unchanged: the op still runs and returns the full row.
462
+ expect(mockPrisma.user.findFirst).toHaveBeenCalled()
463
+ expect(result).toEqual(mockUser)
464
+ })
465
+
466
+ it('warns AND still returns the rows when findMany is passed a select', async () => {
467
+ const mockUsers = [
468
+ { id: '1', name: 'John' },
469
+ { id: '2', name: 'Jane' },
470
+ ]
471
+ mockPrisma.user.findMany.mockResolvedValue(mockUsers)
472
+
473
+ const context = await freshGetContext(config, mockPrisma, null)
474
+ const result = await context.db.user.findMany({ select: { name: true } })
475
+
476
+ expect(warnSpy).toHaveBeenCalledTimes(1)
477
+ expect(warnSpy.mock.calls[0][0]).toContain('`select` is ignored')
478
+ expect(warnSpy.mock.calls[0][0]).toContain('findMany')
479
+ expect(mockPrisma.user.findMany).toHaveBeenCalled()
480
+ expect(result).toEqual(mockUsers)
481
+ })
482
+
483
+ it('warns only once per list+operation across repeated calls', async () => {
484
+ mockPrisma.user.findMany.mockResolvedValue([])
485
+
486
+ const context = await freshGetContext(config, mockPrisma, null)
487
+ await context.db.user.findMany({ select: { name: true } })
488
+ await context.db.user.findMany({ select: { name: true } })
489
+ await context.db.user.findMany({ select: { email: true } })
490
+
491
+ expect(warnSpy).toHaveBeenCalledTimes(1)
492
+ })
493
+
494
+ it('does NOT warn for findUnique/findMany using only include or query', async () => {
495
+ const mockUser = { id: '1', name: 'John' }
496
+ mockPrisma.user.findFirst.mockResolvedValue(mockUser)
497
+ mockPrisma.user.findMany.mockResolvedValue([mockUser])
498
+
499
+ const context = await freshGetContext(config, mockPrisma, null)
500
+ await context.db.user.findUnique({ where: { id: '1' }, include: { posts: true } })
501
+ await context.db.user.findMany({ include: { posts: true } })
502
+ await context.db.user.findMany()
503
+
504
+ expect(warnSpy).not.toHaveBeenCalled()
505
+ })
506
+ })
428
507
  })