@opensaas/stack-core 0.1.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 (95) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +447 -0
  3. package/dist/access/engine.d.ts +73 -0
  4. package/dist/access/engine.d.ts.map +1 -0
  5. package/dist/access/engine.js +244 -0
  6. package/dist/access/engine.js.map +1 -0
  7. package/dist/access/field-transforms.d.ts +47 -0
  8. package/dist/access/field-transforms.d.ts.map +1 -0
  9. package/dist/access/field-transforms.js +2 -0
  10. package/dist/access/field-transforms.js.map +1 -0
  11. package/dist/access/index.d.ts +3 -0
  12. package/dist/access/index.d.ts.map +1 -0
  13. package/dist/access/index.js +2 -0
  14. package/dist/access/index.js.map +1 -0
  15. package/dist/access/types.d.ts +83 -0
  16. package/dist/access/types.d.ts.map +1 -0
  17. package/dist/access/types.js +2 -0
  18. package/dist/access/types.js.map +1 -0
  19. package/dist/config/index.d.ts +39 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +38 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/config/types.d.ts +413 -0
  24. package/dist/config/types.d.ts.map +1 -0
  25. package/dist/config/types.js +2 -0
  26. package/dist/config/types.js.map +1 -0
  27. package/dist/context/index.d.ts +31 -0
  28. package/dist/context/index.d.ts.map +1 -0
  29. package/dist/context/index.js +524 -0
  30. package/dist/context/index.js.map +1 -0
  31. package/dist/context/nested-operations.d.ts +10 -0
  32. package/dist/context/nested-operations.d.ts.map +1 -0
  33. package/dist/context/nested-operations.js +261 -0
  34. package/dist/context/nested-operations.js.map +1 -0
  35. package/dist/fields/index.d.ts +78 -0
  36. package/dist/fields/index.d.ts.map +1 -0
  37. package/dist/fields/index.js +381 -0
  38. package/dist/fields/index.js.map +1 -0
  39. package/dist/hooks/index.d.ts +58 -0
  40. package/dist/hooks/index.d.ts.map +1 -0
  41. package/dist/hooks/index.js +79 -0
  42. package/dist/hooks/index.js.map +1 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +12 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/lib/case-utils.d.ts +49 -0
  48. package/dist/lib/case-utils.d.ts.map +1 -0
  49. package/dist/lib/case-utils.js +68 -0
  50. package/dist/lib/case-utils.js.map +1 -0
  51. package/dist/lib/case-utils.test.d.ts +2 -0
  52. package/dist/lib/case-utils.test.d.ts.map +1 -0
  53. package/dist/lib/case-utils.test.js +101 -0
  54. package/dist/lib/case-utils.test.js.map +1 -0
  55. package/dist/utils/password.d.ts +81 -0
  56. package/dist/utils/password.d.ts.map +1 -0
  57. package/dist/utils/password.js +132 -0
  58. package/dist/utils/password.js.map +1 -0
  59. package/dist/validation/schema.d.ts +17 -0
  60. package/dist/validation/schema.d.ts.map +1 -0
  61. package/dist/validation/schema.js +42 -0
  62. package/dist/validation/schema.js.map +1 -0
  63. package/dist/validation/schema.test.d.ts +2 -0
  64. package/dist/validation/schema.test.d.ts.map +1 -0
  65. package/dist/validation/schema.test.js +143 -0
  66. package/dist/validation/schema.test.js.map +1 -0
  67. package/docs/type-distribution-fix.md +136 -0
  68. package/package.json +48 -0
  69. package/src/access/engine.ts +360 -0
  70. package/src/access/field-transforms.ts +99 -0
  71. package/src/access/index.ts +20 -0
  72. package/src/access/types.ts +103 -0
  73. package/src/config/index.ts +71 -0
  74. package/src/config/types.ts +478 -0
  75. package/src/context/index.ts +814 -0
  76. package/src/context/nested-operations.ts +412 -0
  77. package/src/fields/index.ts +438 -0
  78. package/src/hooks/index.ts +132 -0
  79. package/src/index.ts +62 -0
  80. package/src/lib/case-utils.test.ts +127 -0
  81. package/src/lib/case-utils.ts +74 -0
  82. package/src/utils/password.ts +147 -0
  83. package/src/validation/schema.test.ts +171 -0
  84. package/src/validation/schema.ts +59 -0
  85. package/tests/access-relationships.test.ts +613 -0
  86. package/tests/access.test.ts +499 -0
  87. package/tests/config.test.ts +195 -0
  88. package/tests/context.test.ts +248 -0
  89. package/tests/hooks.test.ts +417 -0
  90. package/tests/password-type-distribution.test.ts +155 -0
  91. package/tests/password-types.test.ts +147 -0
  92. package/tests/password.test.ts +249 -0
  93. package/tsconfig.json +12 -0
  94. package/tsconfig.tsbuildinfo +1 -0
  95. package/vitest.config.ts +27 -0
@@ -0,0 +1,4 @@
1
+
2
+ > @opensaas/stack-core@0.1.0 build /home/runner/work/stack/stack/packages/core
3
+ > tsc
4
+
package/README.md ADDED
@@ -0,0 +1,447 @@
1
+ # @opensaas/stack-core
2
+
3
+ Core OpenSaas Stack - config system, field types, access control, and code generation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @opensaas/stack-core
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 📝 **Schema Definition** - Config-first approach to defining your data model
14
+ - 🔒 **Access Control** - Automatic enforcement at database layer
15
+ - 🎯 **Type Generation** - Generate TypeScript types and Prisma schema
16
+ - 🔄 **Field Types** - Extensible field type system
17
+ - 🪝 **Hooks** - Data transformation and validation lifecycle
18
+ - 🛡️ **AI-Safe** - Silent failures prevent information leakage
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Define Your Schema
23
+
24
+ Create `opensaas.config.ts`:
25
+
26
+ ```typescript
27
+ import { config, list } from '@opensaas/stack-core'
28
+ import { text, integer, select, relationship } from '@opensaas/stack-core/fields'
29
+ import type { AccessControl } from '@opensaas/stack-core'
30
+
31
+ const isSignedIn: AccessControl = ({ session }) => !!session
32
+
33
+ const isAuthor: AccessControl = ({ session }) => {
34
+ if (!session) return false
35
+ return { authorId: { equals: session.userId } }
36
+ }
37
+
38
+ export default config({
39
+ db: {
40
+ provider: 'postgresql',
41
+ url: process.env.DATABASE_URL,
42
+ },
43
+ lists: {
44
+ User: list({
45
+ fields: {
46
+ name: text({ validation: { isRequired: true } }),
47
+ email: text({ isIndexed: 'unique' }),
48
+ posts: relationship({ ref: 'Post.author', many: true }),
49
+ },
50
+ }),
51
+ Post: list({
52
+ fields: {
53
+ title: text({ validation: { isRequired: true } }),
54
+ slug: text({ isIndexed: 'unique' }),
55
+ content: text(),
56
+ status: select({
57
+ options: [
58
+ { label: 'Draft', value: 'draft' },
59
+ { label: 'Published', value: 'published' },
60
+ ],
61
+ defaultValue: 'draft',
62
+ }),
63
+ author: relationship({ ref: 'User.posts' }),
64
+ internalNotes: text({
65
+ access: {
66
+ read: isAuthor,
67
+ create: isAuthor,
68
+ update: isAuthor,
69
+ },
70
+ }),
71
+ },
72
+ access: {
73
+ operation: {
74
+ query: ({ session }) => {
75
+ if (!session) return { status: { equals: 'published' } }
76
+ return true
77
+ },
78
+ create: isSignedIn,
79
+ update: isAuthor,
80
+ delete: isAuthor,
81
+ },
82
+ },
83
+ }),
84
+ },
85
+ })
86
+ ```
87
+
88
+ ### 2. Generate Schema and Types
89
+
90
+ ```bash
91
+ opensaas generate
92
+ ```
93
+
94
+ This creates:
95
+
96
+ - `prisma/schema.prisma` - Prisma schema
97
+ - `.opensaas/types.ts` - TypeScript types
98
+
99
+ ### 3. Create Context
100
+
101
+ ```typescript
102
+ // lib/context.ts
103
+ import { getContext } from '@opensaas/stack-core'
104
+ import { PrismaClient } from '@prisma/client'
105
+ import config from '../opensaas.config'
106
+
107
+ export const prisma = new PrismaClient()
108
+
109
+ export async function getContextWithUser(userId: string) {
110
+ return getContext(config, prisma, { userId })
111
+ }
112
+
113
+ export async function getContext() {
114
+ return getContext(config, prisma, null)
115
+ }
116
+ ```
117
+
118
+ ### 4. Use in Your App
119
+
120
+ ```typescript
121
+ import { getContextWithUser } from './lib/context'
122
+
123
+ export async function createPost(userId: string, data: any) {
124
+ const context = getContextWithUser(userId)
125
+
126
+ // Access control automatically enforced
127
+ const post = await context.db.post.create({ data })
128
+
129
+ if (!post) {
130
+ return { error: 'Access denied' }
131
+ }
132
+
133
+ return { post }
134
+ }
135
+ ```
136
+
137
+ ## Field Types
138
+
139
+ ### Available Fields
140
+
141
+ - **text()** - String field
142
+ - **integer()** - Number field
143
+ - **checkbox()** - Boolean field
144
+ - **timestamp()** - Date/time field
145
+ - **password()** - Password field (excluded from reads)
146
+ - **select()** - Enum field with options
147
+ - **relationship()** - Foreign key relationship
148
+
149
+ ### Field Options
150
+
151
+ All fields support:
152
+
153
+ ```typescript
154
+ text({
155
+ validation: {
156
+ isRequired: true,
157
+ length: { min: 3, max: 100 },
158
+ },
159
+ isIndexed: 'unique', // or true for non-unique index
160
+ defaultValue: 'Hello',
161
+ access: {
162
+ read: ({ session }) => !!session,
163
+ create: ({ session }) => !!session,
164
+ update: ({ session }) => !!session,
165
+ },
166
+ hooks: {
167
+ resolveInput: async ({ resolvedData }) => resolvedData,
168
+ validateInput: async ({ resolvedData }) => {
169
+ /* validate */
170
+ },
171
+ },
172
+ ui: {
173
+ fieldType: 'custom', // Reference global component
174
+ component: CustomComponent, // Or provide directly
175
+ },
176
+ })
177
+ ```
178
+
179
+ ### Creating Custom Field Types
180
+
181
+ Field types are fully self-contained:
182
+
183
+ ```typescript
184
+ import type { BaseFieldConfig } from '@opensaas/stack-core'
185
+ import { z } from 'zod'
186
+
187
+ export type MyCustomField = BaseFieldConfig & {
188
+ type: 'myCustom'
189
+ customOption?: string
190
+ }
191
+
192
+ export function myCustom(options?: Omit<MyCustomField, 'type'>): MyCustomField {
193
+ return {
194
+ type: 'myCustom',
195
+ ...options,
196
+ getZodSchema: (fieldName, operation) => {
197
+ return z.string().optional()
198
+ },
199
+ getPrismaType: (fieldName) => {
200
+ return { type: 'String', modifiers: '?' }
201
+ },
202
+ getTypeScriptType: () => {
203
+ return { type: 'string', optional: true }
204
+ },
205
+ }
206
+ }
207
+ ```
208
+
209
+ ## Access Control
210
+
211
+ ### Operation-Level Access
212
+
213
+ Control who can query, create, update, or delete:
214
+
215
+ ```typescript
216
+ access: {
217
+ operation: {
218
+ query: true, // Everyone can read
219
+ create: isSignedIn, // Must be signed in
220
+ update: isAuthor, // Only author
221
+ delete: isAuthor, // Only author
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Filter-Based Access
227
+
228
+ Return Prisma filters to scope access:
229
+
230
+ ```typescript
231
+ const isAuthor: AccessControl = ({ session }) => {
232
+ if (!session) return false
233
+ return { authorId: { equals: session.userId } }
234
+ }
235
+
236
+ // Applied as: where: { AND: [userFilter, { authorId: { equals: userId } }] }
237
+ ```
238
+
239
+ ### Field-Level Access
240
+
241
+ Control access to individual fields:
242
+
243
+ ```typescript
244
+ internalNotes: text({
245
+ access: {
246
+ read: isAuthor, // Only author can see
247
+ create: isAuthor, // Only author can set on create
248
+ update: isAuthor, // Only author can modify
249
+ },
250
+ })
251
+ ```
252
+
253
+ ### Silent Failures
254
+
255
+ Access-denied operations return `null` or `[]` instead of throwing:
256
+
257
+ ```typescript
258
+ const post = await context.db.post.update({
259
+ where: { id: postId },
260
+ data: { title: 'New Title' },
261
+ })
262
+
263
+ if (!post) {
264
+ // Either doesn't exist OR user lacks access
265
+ // No information leaked about which
266
+ return { error: 'Not found' }
267
+ }
268
+ ```
269
+
270
+ ## Hooks
271
+
272
+ Transform and validate data during operations:
273
+
274
+ ```typescript
275
+ hooks: {
276
+ // Transform input before validation
277
+ resolveInput: async ({ resolvedData, operation, session }) => {
278
+ if (operation === 'create') {
279
+ return { ...resolvedData, createdBy: session.userId }
280
+ }
281
+ return resolvedData
282
+ },
283
+
284
+ // Custom validation
285
+ validateInput: async ({ resolvedData, fieldPath }) => {
286
+ if (resolvedData.title?.includes('spam')) {
287
+ throw new Error('Title contains prohibited content')
288
+ }
289
+ },
290
+
291
+ // Before database operation
292
+ beforeOperation: async ({ operation, resolvedData }) => {
293
+ console.log(`About to ${operation}`, resolvedData)
294
+ },
295
+
296
+ // After database operation
297
+ afterOperation: async ({ operation, item }) => {
298
+ if (operation === 'create') {
299
+ await sendNotification(item)
300
+ }
301
+ },
302
+ }
303
+ ```
304
+
305
+ ### Hook Execution Order
306
+
307
+ 1. `resolveInput` - Transform input
308
+ 2. `validateInput` - Custom validation
309
+ 3. Field validation - Built-in rules
310
+ 4. Field-level access - Filter writable fields
311
+ 5. `beforeOperation` - Pre-operation side effects
312
+ 6. **Database operation**
313
+ 7. `afterOperation` - Post-operation side effects
314
+
315
+ ## Context API
316
+
317
+ ### Creating Context
318
+
319
+ ```typescript
320
+ import { getContext } from '@opensaas/stack-core'
321
+
322
+ // With session
323
+ const context = getContext(config, prisma, { userId: '123' })
324
+
325
+ // Anonymous
326
+ const context = getContext(config, prisma, null)
327
+ ```
328
+
329
+ ### Using Context
330
+
331
+ ```typescript
332
+ // All Prisma operations supported
333
+ const post = await context.db.post.create({ data })
334
+ const posts = await context.db.post.findMany()
335
+ const post = await context.db.post.findUnique({ where: { id } })
336
+ const post = await context.db.post.update({ where: { id }, data })
337
+ const post = await context.db.post.delete({ where: { id } })
338
+
339
+ // Access control is automatic
340
+ // Returns null/[] if access denied
341
+ ```
342
+
343
+ ## Generators
344
+
345
+ ### Prisma Schema
346
+
347
+ ```typescript
348
+ import { writePrismaSchema } from '@opensaas/stack-core'
349
+
350
+ writePrismaSchema(config, './prisma/schema.prisma')
351
+ ```
352
+
353
+ ### TypeScript Types
354
+
355
+ ```typescript
356
+ import { writeTypes } from '@opensaas/stack-core'
357
+
358
+ writeTypes(config, './.opensaas/types.ts')
359
+ ```
360
+
361
+ ### Utility Functions
362
+
363
+ ```typescript
364
+ import { getDbKey, getUrlKey, getListKeyFromUrl } from '@opensaas/stack-core'
365
+
366
+ getDbKey('BlogPost') // 'blogPost' - for context.db access
367
+ getUrlKey('BlogPost') // 'blog-post' - for URLs
368
+ getListKeyFromUrl('blog-post') // 'BlogPost' - parse from URLs
369
+ ```
370
+
371
+ ## Validation
372
+
373
+ Built-in validation with Zod:
374
+
375
+ ```typescript
376
+ text({
377
+ validation: {
378
+ isRequired: true,
379
+ length: { min: 3, max: 100 },
380
+ },
381
+ })
382
+
383
+ integer({
384
+ validation: {
385
+ isRequired: true,
386
+ min: 0,
387
+ max: 1000,
388
+ },
389
+ })
390
+ ```
391
+
392
+ Custom validation in hooks:
393
+
394
+ ```typescript
395
+ hooks: {
396
+ validateInput: async ({ resolvedData }) => {
397
+ const { title } = resolvedData
398
+ if (title && !isValidSlug(slugify(title))) {
399
+ throw new ValidationError('Title contains invalid characters')
400
+ }
401
+ }
402
+ }
403
+ ```
404
+
405
+ ## Testing
406
+
407
+ ```typescript
408
+ import { describe, it, expect } from 'vitest'
409
+ import { getContext } from '@opensaas/stack-core'
410
+ import config from './opensaas.config'
411
+
412
+ describe('Post access control', () => {
413
+ it('allows author to update their post', async () => {
414
+ const context = getContext(config, prisma, { userId: authorId })
415
+ const updated = await context.db.post.update({
416
+ where: { id: postId },
417
+ data: { title: 'New Title' },
418
+ })
419
+ expect(updated).toBeTruthy()
420
+ expect(updated?.title).toBe('New Title')
421
+ })
422
+
423
+ it('denies non-author from updating post', async () => {
424
+ const context = getContext(config, prisma, { userId: otherUserId })
425
+ const updated = await context.db.post.update({
426
+ where: { id: postId },
427
+ data: { title: 'Hacked!' },
428
+ })
429
+ expect(updated).toBeNull() // Silent failure
430
+ })
431
+ })
432
+ ```
433
+
434
+ ## Examples
435
+
436
+ - [Blog Example](../../examples/blog) - Complete working example
437
+ - [Custom Field Example](../../examples/custom-field) - Extending field types
438
+
439
+ ## Learn More
440
+
441
+ - [API Reference](../../docs/API.md) - Complete API documentation
442
+ - [OpenSaas Stack](../../README.md) - Stack overview
443
+ - [CLAUDE.md](../../CLAUDE.md) - Development guide
444
+
445
+ ## License
446
+
447
+ MIT
@@ -0,0 +1,73 @@
1
+ import type { AccessControl, Session, AccessContext, PrismaFilter } from './types.js';
2
+ import type { FieldAccess } from './types.js';
3
+ import type { OpenSaasConfig, ListConfig, FieldConfig } from '../config/types.js';
4
+ /**
5
+ * Check if access control result is a boolean
6
+ */
7
+ export declare function isBoolean(value: unknown): value is boolean;
8
+ /**
9
+ * Check if access control result is a Prisma filter
10
+ */
11
+ export declare function isPrismaFilter(value: unknown): value is PrismaFilter;
12
+ /**
13
+ * Parse a relationship ref and get the related list configuration
14
+ * Relationship refs are in the format "ListName.fieldName"
15
+ *
16
+ * @param relationshipRef - The ref string (e.g., "Post.author")
17
+ * @param config - The OpenSaas configuration
18
+ * @returns The related list name and config, or null if not found
19
+ */
20
+ export declare function getRelatedListConfig(relationshipRef: string, config: OpenSaasConfig): {
21
+ listName: string;
22
+ listConfig: ListConfig;
23
+ } | null;
24
+ /**
25
+ * Execute an access control function
26
+ */
27
+ export declare function checkAccess<T = Record<string, unknown>>(accessControl: AccessControl<T> | undefined, args: {
28
+ session: Session;
29
+ item?: T;
30
+ context: AccessContext;
31
+ }): Promise<boolean | PrismaFilter<T>>;
32
+ /**
33
+ * Merge user filter with access control filter
34
+ */
35
+ export declare function mergeFilters(userFilter: PrismaFilter | undefined, accessFilter: boolean | PrismaFilter): PrismaFilter | null;
36
+ /**
37
+ * Check field-level access for a specific operation
38
+ */
39
+ export declare function checkFieldAccess(fieldAccess: FieldAccess | undefined, operation: 'read' | 'create' | 'update', args: {
40
+ session: Session;
41
+ item?: Record<string, unknown>;
42
+ context: AccessContext;
43
+ }): Promise<boolean>;
44
+ /**
45
+ * Build Prisma include object with access control filters
46
+ * This allows us to filter relationships at the database level instead of in memory
47
+ */
48
+ export declare function buildIncludeWithAccessControl(fieldConfigs: Record<string, FieldConfig>, args: {
49
+ session: Session;
50
+ context: AccessContext;
51
+ }, config: OpenSaasConfig, depth?: number): Promise<Record<string, boolean | {
52
+ where?: PrismaFilter;
53
+ include?: Record<string, boolean | /*elided*/ any>;
54
+ }> | undefined>;
55
+ /**
56
+ * Filter fields from an object based on read access
57
+ * Recursively applies access control to nested relationships
58
+ */
59
+ export declare function filterReadableFields<T extends Record<string, unknown>>(item: T, fieldConfigs: Record<string, FieldConfig>, args: {
60
+ session: Session;
61
+ context: AccessContext;
62
+ }, config?: OpenSaasConfig, depth?: number): Promise<Partial<T>>;
63
+ /**
64
+ * Filter fields from input data based on write access (create/update)
65
+ */
66
+ export declare function filterWritableFields<T extends Record<string, unknown>>(data: T, fieldConfigs: Record<string, {
67
+ access?: FieldAccess;
68
+ }>, operation: 'create' | 'update', args: {
69
+ session: Session;
70
+ item?: Record<string, unknown>;
71
+ context: AccessContext;
72
+ }): Promise<Partial<T>>;
73
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"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"}