@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +268 -0
- package/CLAUDE.md +18 -15
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +29 -6
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/multi-column-read-write.test.d.ts +2 -0
- package/dist/access/multi-column-read-write.test.d.ts.map +1 -0
- package/dist/access/multi-column-read-write.test.js +149 -0
- package/dist/access/multi-column-read-write.test.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/types.d.ts +289 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +31 -0
- package/dist/context/index.js.map +1 -1
- package/dist/extend.d.ts +1 -1
- package/dist/extend.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.d.ts +35 -0
- package/dist/fields/format-prisma-default.d.ts.map +1 -0
- package/dist/fields/format-prisma-default.js +52 -0
- package/dist/fields/format-prisma-default.js.map +1 -0
- package/dist/fields/format-prisma-default.test.d.ts +2 -0
- package/dist/fields/format-prisma-default.test.d.ts.map +1 -0
- package/dist/fields/format-prisma-default.test.js +54 -0
- package/dist/fields/format-prisma-default.test.js.map +1 -0
- package/dist/fields/index.d.ts +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +54 -16
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/select.test.js +85 -0
- package/dist/fields/select.test.js.map +1 -1
- package/dist/fields/text-keystone-compat.test.d.ts +2 -0
- package/dist/fields/text-keystone-compat.test.d.ts.map +1 -0
- package/dist/fields/text-keystone-compat.test.js +93 -0
- package/dist/fields/text-keystone-compat.test.js.map +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +60 -16
- 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 +7 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +33 -0
- package/dist/index.test.js.map +1 -0
- package/dist/mcp/handler.js +0 -1
- package/dist/mcp/handler.js.map +1 -1
- package/package.json +1 -1
- package/src/access/field-visibility.ts +28 -6
- package/src/access/multi-column-read-write.test.ts +255 -0
- package/src/config/index.ts +2 -0
- package/src/config/types.ts +291 -0
- package/src/context/index.ts +45 -0
- package/src/extend.ts +6 -1
- package/src/fields/format-prisma-default.test.ts +64 -0
- package/src/fields/format-prisma-default.ts +67 -0
- package/src/fields/index.ts +65 -18
- package/src/fields/select.test.ts +99 -0
- package/src/fields/text-keystone-compat.test.ts +126 -0
- package/src/hooks/index.ts +60 -17
- package/src/index.test.ts +50 -0
- package/src/index.ts +17 -1
- package/src/mcp/handler.ts +0 -2
- package/tests/context.test.ts +80 -1
- 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 {
|
|
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'
|
package/src/mcp/handler.ts
CHANGED
package/tests/context.test.ts
CHANGED
|
@@ -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
|
})
|