@deessejs/collections 0.0.1
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/package.json +60 -0
- package/src/adapter.ts +38 -0
- package/src/collection.ts +138 -0
- package/src/config.ts +134 -0
- package/src/field-type.ts +40 -0
- package/src/field.ts +46 -0
- package/src/fields/f.ts +192 -0
- package/src/fields/index.ts +1 -0
- package/src/index.ts +27 -0
- package/src/migrations.ts +49 -0
- package/src/operations/collection-operations.ts +808 -0
- package/src/operations/index.ts +2 -0
- package/src/operations/types.ts +141 -0
- package/src/schema.ts +62 -0
- package/tests/adapter.test.ts +35 -0
- package/tests/collection.test.ts +205 -0
- package/tests/config.test.ts +58 -0
- package/tests/field-type.test.ts +181 -0
- package/tests/field.test.ts +201 -0
- package/tests/fixtures.ts +44 -0
- package/tests/hooks.test.ts +1076 -0
- package/tests/integration/hooks.test.ts +329 -0
- package/tests/metadata.test.ts +200 -0
- package/tests/schema.test.ts +58 -0
- package/tests/type-inference.test.ts +108 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +11 -0
- package/vitest.config.ts +29 -0
- package/vitest.integration.config.ts +9 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
2
|
+
import { Pool } from 'pg'
|
|
3
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
4
|
+
import { collection } from '../../src/collection'
|
|
5
|
+
import { field } from '../../src/field'
|
|
6
|
+
import { f } from '../../src'
|
|
7
|
+
import { buildTable } from '../../src/schema'
|
|
8
|
+
import { createCollectionOperations } from '../../src/operations/collection-operations'
|
|
9
|
+
|
|
10
|
+
// Skip all tests if no DATABASE_URL is provided
|
|
11
|
+
const dbUrl = process.env.DATABASE_URL
|
|
12
|
+
const itIfDb = dbUrl ? it : it.skip
|
|
13
|
+
|
|
14
|
+
describe('hooks integration with real database', () => {
|
|
15
|
+
let pool: any
|
|
16
|
+
let db: any
|
|
17
|
+
|
|
18
|
+
const testTableName = `test_users_${Date.now()}`
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
if (!dbUrl) {
|
|
22
|
+
console.log('DATABASE_URL not set, skipping integration tests')
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Use pg Pool for PostgreSQL
|
|
28
|
+
pool = new Pool({ connectionString: dbUrl })
|
|
29
|
+
db = drizzle(pool)
|
|
30
|
+
|
|
31
|
+
// Create test table
|
|
32
|
+
await pool.query(`
|
|
33
|
+
CREATE TABLE ${testTableName} (
|
|
34
|
+
id SERIAL PRIMARY KEY,
|
|
35
|
+
name TEXT NOT NULL,
|
|
36
|
+
email TEXT,
|
|
37
|
+
active BOOLEAN DEFAULT true,
|
|
38
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
39
|
+
)
|
|
40
|
+
`)
|
|
41
|
+
|
|
42
|
+
console.log(`Table ${testTableName} created`)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to setup test table:', error)
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
47
|
+
}, 60000)
|
|
48
|
+
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
if (pool) {
|
|
51
|
+
try {
|
|
52
|
+
await pool.query(`DROP TABLE IF EXISTS ${testTableName}`)
|
|
53
|
+
await pool.end()
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Failed to cleanup test table:', error)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, 60000)
|
|
59
|
+
|
|
60
|
+
itIfDb('executes hooks in correct order for create', async () => {
|
|
61
|
+
const executionOrder: string[] = []
|
|
62
|
+
|
|
63
|
+
const users = collection({
|
|
64
|
+
slug: testTableName,
|
|
65
|
+
fields: {
|
|
66
|
+
name: field({ fieldType: f.text() }),
|
|
67
|
+
email: field({ fieldType: f.text() })
|
|
68
|
+
},
|
|
69
|
+
hooks: {
|
|
70
|
+
beforeOperation: [async () => { executionOrder.push('beforeOperation') }],
|
|
71
|
+
beforeCreate: [async () => { executionOrder.push('beforeCreate') }],
|
|
72
|
+
afterCreate: [async () => { executionOrder.push('afterCreate') }],
|
|
73
|
+
afterOperation: [async () => { executionOrder.push('afterOperation') }]
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const table = buildTable(users)
|
|
78
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
79
|
+
|
|
80
|
+
await operations.create({
|
|
81
|
+
data: { name: 'John', email: 'john@example.com' },
|
|
82
|
+
returning: true
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(executionOrder).toEqual([
|
|
86
|
+
'beforeOperation',
|
|
87
|
+
'beforeCreate',
|
|
88
|
+
'afterCreate',
|
|
89
|
+
'afterOperation'
|
|
90
|
+
])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
itIfDb('passes correct context to create hooks', async () => {
|
|
94
|
+
let receivedContext: any = null
|
|
95
|
+
|
|
96
|
+
const users = collection({
|
|
97
|
+
slug: testTableName,
|
|
98
|
+
fields: {
|
|
99
|
+
name: field({ fieldType: f.text() })
|
|
100
|
+
},
|
|
101
|
+
hooks: {
|
|
102
|
+
beforeCreate: [async (context) => { receivedContext = context }]
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const table = buildTable(users)
|
|
107
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
108
|
+
|
|
109
|
+
await operations.create({
|
|
110
|
+
data: { name: 'Jane' }
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
expect(receivedContext.collection).toBe(testTableName)
|
|
114
|
+
expect(receivedContext.operation).toBe('create')
|
|
115
|
+
expect(receivedContext.data.name).toBe('Jane')
|
|
116
|
+
expect(receivedContext.db).toBeDefined()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
itIfDb('hooks can modify data before create', async () => {
|
|
120
|
+
const users = collection({
|
|
121
|
+
slug: testTableName,
|
|
122
|
+
fields: {
|
|
123
|
+
name: field({ fieldType: f.text() }),
|
|
124
|
+
email: field({ fieldType: f.text() })
|
|
125
|
+
},
|
|
126
|
+
hooks: {
|
|
127
|
+
beforeCreate: [async (context) => {
|
|
128
|
+
context.data.email = 'modified@example.com'
|
|
129
|
+
}]
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const table = buildTable(users)
|
|
134
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
135
|
+
|
|
136
|
+
const result = await operations.create({
|
|
137
|
+
data: { name: 'Test', email: 'original@example.com' },
|
|
138
|
+
returning: true
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(result?.email).toBe('modified@example.com')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
itIfDb('executes hooks in correct order for update', async () => {
|
|
145
|
+
const executionOrder: string[] = []
|
|
146
|
+
|
|
147
|
+
const users = collection({
|
|
148
|
+
slug: testTableName,
|
|
149
|
+
fields: {
|
|
150
|
+
name: field({ fieldType: f.text() })
|
|
151
|
+
},
|
|
152
|
+
hooks: {
|
|
153
|
+
beforeOperation: [async () => { executionOrder.push('beforeOperation') }],
|
|
154
|
+
beforeUpdate: [async () => { executionOrder.push('beforeUpdate') }],
|
|
155
|
+
afterUpdate: [async () => { executionOrder.push('afterUpdate') }],
|
|
156
|
+
afterOperation: [async () => { executionOrder.push('afterOperation') }]
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const table = buildTable(users)
|
|
161
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
162
|
+
|
|
163
|
+
const created = await operations.create({
|
|
164
|
+
data: { name: 'Original' },
|
|
165
|
+
returning: true
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
executionOrder.length = 0
|
|
169
|
+
|
|
170
|
+
await operations.update({
|
|
171
|
+
where: { id: created?.id },
|
|
172
|
+
data: { name: 'Updated' }
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
expect(executionOrder).toEqual([
|
|
176
|
+
'beforeOperation',
|
|
177
|
+
'beforeUpdate',
|
|
178
|
+
'afterUpdate',
|
|
179
|
+
'afterOperation'
|
|
180
|
+
])
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
itIfDb('passes previousData to update hooks', async () => {
|
|
184
|
+
let previousData: any = null
|
|
185
|
+
|
|
186
|
+
const users = collection({
|
|
187
|
+
slug: testTableName,
|
|
188
|
+
fields: {
|
|
189
|
+
name: field({ fieldType: f.text() })
|
|
190
|
+
},
|
|
191
|
+
hooks: {
|
|
192
|
+
beforeUpdate: [async (context) => { previousData = context.previousData }]
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const table = buildTable(users)
|
|
197
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
198
|
+
|
|
199
|
+
const created = await operations.create({
|
|
200
|
+
data: { name: 'Original' },
|
|
201
|
+
returning: true
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
await operations.update({
|
|
205
|
+
where: { id: created?.id },
|
|
206
|
+
data: { name: 'Updated' }
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
expect(previousData?.name).toBe('Original')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
itIfDb('executes hooks in correct order for delete', async () => {
|
|
213
|
+
const executionOrder: string[] = []
|
|
214
|
+
|
|
215
|
+
const users = collection({
|
|
216
|
+
slug: testTableName,
|
|
217
|
+
fields: {
|
|
218
|
+
name: field({ fieldType: f.text() })
|
|
219
|
+
},
|
|
220
|
+
hooks: {
|
|
221
|
+
beforeOperation: [async () => { executionOrder.push('beforeOperation') }],
|
|
222
|
+
beforeDelete: [async () => { executionOrder.push('beforeDelete') }],
|
|
223
|
+
afterDelete: [async () => { executionOrder.push('afterDelete') }],
|
|
224
|
+
afterOperation: [async () => { executionOrder.push('afterOperation') }]
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const table = buildTable(users)
|
|
229
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
230
|
+
|
|
231
|
+
const created = await operations.create({
|
|
232
|
+
data: { name: 'ToDelete' },
|
|
233
|
+
returning: true
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
executionOrder.length = 0
|
|
237
|
+
|
|
238
|
+
await operations.delete({
|
|
239
|
+
where: { id: created?.id }
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
expect(executionOrder).toEqual([
|
|
243
|
+
'beforeOperation',
|
|
244
|
+
'beforeDelete',
|
|
245
|
+
'afterDelete',
|
|
246
|
+
'afterOperation'
|
|
247
|
+
])
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
itIfDb('executes hooks for read operations', async () => {
|
|
251
|
+
const executionOrder: string[] = []
|
|
252
|
+
|
|
253
|
+
const users = collection({
|
|
254
|
+
slug: testTableName,
|
|
255
|
+
fields: {
|
|
256
|
+
name: field({ fieldType: f.text() })
|
|
257
|
+
},
|
|
258
|
+
hooks: {
|
|
259
|
+
beforeOperation: [async () => { executionOrder.push('beforeOperation') }],
|
|
260
|
+
beforeRead: [async () => { executionOrder.push('beforeRead') }],
|
|
261
|
+
afterRead: [async () => { executionOrder.push('afterRead') }],
|
|
262
|
+
afterOperation: [async () => { executionOrder.push('afterOperation') }]
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const table = buildTable(users)
|
|
267
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
268
|
+
|
|
269
|
+
await operations.findMany()
|
|
270
|
+
|
|
271
|
+
expect(executionOrder).toEqual([
|
|
272
|
+
'beforeOperation',
|
|
273
|
+
'beforeRead',
|
|
274
|
+
'afterRead',
|
|
275
|
+
'afterOperation'
|
|
276
|
+
])
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
itIfDb('throws error when beforeOperation hook throws', async () => {
|
|
280
|
+
const users = collection({
|
|
281
|
+
slug: testTableName,
|
|
282
|
+
fields: {
|
|
283
|
+
name: field({ fieldType: f.text() })
|
|
284
|
+
},
|
|
285
|
+
hooks: {
|
|
286
|
+
beforeOperation: [async () => { throw new Error('Hook error') }]
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
const table = buildTable(users)
|
|
291
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
292
|
+
|
|
293
|
+
await expect(operations.findMany()).rejects.toThrow('Hook error')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
itIfDb('createMany executes hooks for each item', async () => {
|
|
297
|
+
const executionOrder: string[] = []
|
|
298
|
+
|
|
299
|
+
const users = collection({
|
|
300
|
+
slug: testTableName,
|
|
301
|
+
fields: {
|
|
302
|
+
name: field({ fieldType: f.text() })
|
|
303
|
+
},
|
|
304
|
+
hooks: {
|
|
305
|
+
beforeCreate: [async (context) => {
|
|
306
|
+
executionOrder.push(`before-${context.data.name}`)
|
|
307
|
+
}],
|
|
308
|
+
afterCreate: [async (context) => {
|
|
309
|
+
executionOrder.push(`after-${context.data.name}`)
|
|
310
|
+
}]
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const table = buildTable(users)
|
|
315
|
+
const operations = createCollectionOperations(users, testTableName, db, table, users.hooks)
|
|
316
|
+
|
|
317
|
+
await operations.createMany({
|
|
318
|
+
data: [
|
|
319
|
+
{ name: 'Alice' },
|
|
320
|
+
{ name: 'Bob' }
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
expect(executionOrder).toContain('before-Alice')
|
|
325
|
+
expect(executionOrder).toContain('before-Bob')
|
|
326
|
+
expect(executionOrder).toContain('after-Alice')
|
|
327
|
+
expect(executionOrder).toContain('after-Bob')
|
|
328
|
+
})
|
|
329
|
+
})
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { defineConfig, collection, field, f } from '../src'
|
|
3
|
+
import { testAdapter, testCollections } from './fixtures'
|
|
4
|
+
|
|
5
|
+
describe('Collection Metadata', () => {
|
|
6
|
+
describe('collections return metadata (slug, name, fields)', () => {
|
|
7
|
+
it('collections.users has slug property', () => {
|
|
8
|
+
const users = collection({
|
|
9
|
+
slug: 'users',
|
|
10
|
+
name: 'Users',
|
|
11
|
+
fields: {
|
|
12
|
+
name: field({ fieldType: f.text() }),
|
|
13
|
+
email: field({ fieldType: f.email() })
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const config = defineConfig({
|
|
18
|
+
database: testAdapter,
|
|
19
|
+
collections: [users]
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(config.collections.users.slug).toBe('users')
|
|
23
|
+
expect(config.collections.users.name).toBe('Users')
|
|
24
|
+
expect(config.collections.users.fields).toBeDefined()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('collections.users has optional name property', () => {
|
|
28
|
+
const users = collection({
|
|
29
|
+
slug: 'users',
|
|
30
|
+
fields: { name: field({ fieldType: f.text() }) }
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const config = defineConfig({
|
|
34
|
+
database: testAdapter,
|
|
35
|
+
collections: [users]
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
expect(config.collections.users.name).toBeUndefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('collections.users has fields property', () => {
|
|
42
|
+
const users = collection({
|
|
43
|
+
slug: 'users',
|
|
44
|
+
fields: {
|
|
45
|
+
name: field({ fieldType: f.text() }),
|
|
46
|
+
email: field({ fieldType: f.email() })
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const config = defineConfig({
|
|
51
|
+
database: testAdapter,
|
|
52
|
+
collections: [users]
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expect(config.collections.users.fields.name).toBeDefined()
|
|
56
|
+
expect(config.collections.users.fields.email).toBeDefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('preserves collection slug and name', () => {
|
|
60
|
+
const users = collection({
|
|
61
|
+
slug: 'users',
|
|
62
|
+
name: 'Users',
|
|
63
|
+
fields: { name: field({ fieldType: f.text() }) }
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const config = defineConfig({
|
|
67
|
+
database: testAdapter,
|
|
68
|
+
collections: [users]
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
expect(config.collections.users.slug).toBe('users')
|
|
72
|
+
expect(config.collections.users.name).toBe('Users')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('collections do NOT have operations', () => {
|
|
77
|
+
it('collections does NOT have findMany, create, update, delete', () => {
|
|
78
|
+
const config = defineConfig({
|
|
79
|
+
database: testAdapter,
|
|
80
|
+
collections: [testCollections.users]
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(config.collections.users.findMany).toBeUndefined()
|
|
84
|
+
expect(config.collections.users.create).toBeUndefined()
|
|
85
|
+
expect(config.collections.users.update).toBeUndefined()
|
|
86
|
+
expect(config.collections.users.delete).toBeUndefined()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('multiple collections metadata', () => {
|
|
91
|
+
it('config.collections has correct keys for multiple collections', () => {
|
|
92
|
+
const config = defineConfig({
|
|
93
|
+
database: testAdapter,
|
|
94
|
+
collections: [testCollections.users, testCollections.posts, testCollections.comments]
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
expect(config.collections.users).toBeDefined()
|
|
98
|
+
expect(config.collections.users.slug).toBe('users')
|
|
99
|
+
expect(config.collections.posts).toBeDefined()
|
|
100
|
+
expect(config.collections.posts.slug).toBe('posts')
|
|
101
|
+
expect(config.collections.comments).toBeDefined()
|
|
102
|
+
expect(config.collections.comments.slug).toBe('comments')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('can access different collections metadata', () => {
|
|
106
|
+
const config = defineConfig({
|
|
107
|
+
database: testAdapter,
|
|
108
|
+
collections: [testCollections.users, testCollections.posts]
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(config.collections.users.slug).toBe('users')
|
|
112
|
+
expect(config.collections.posts.slug).toBe('posts')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('$meta collections tracking', () => {
|
|
117
|
+
it('$meta.collections is array of slugs', () => {
|
|
118
|
+
const config = defineConfig({
|
|
119
|
+
database: testAdapter,
|
|
120
|
+
collections: [testCollections.users, testCollections.posts]
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(config.$meta.collections).toContain('users')
|
|
124
|
+
expect(config.$meta.collections).toContain('posts')
|
|
125
|
+
expect(config.$meta.collections).toHaveLength(2)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('$meta.plugins is array of plugin names', () => {
|
|
129
|
+
const mockPlugin = {
|
|
130
|
+
name: 'test-plugin',
|
|
131
|
+
collections: {}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const config = defineConfig({
|
|
135
|
+
database: testAdapter,
|
|
136
|
+
collections: [testCollections.users],
|
|
137
|
+
plugins: [mockPlugin]
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
expect(config.$meta.plugins).toContain('test-plugin')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('tracks plugin collections correctly', () => {
|
|
144
|
+
const mockPlugin1 = {
|
|
145
|
+
name: 'plugin-1',
|
|
146
|
+
collections: {
|
|
147
|
+
plugin1Table: collection({
|
|
148
|
+
slug: 'plugin1Table',
|
|
149
|
+
fields: { data: field({ fieldType: f.text() }) }
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const mockPlugin2 = {
|
|
155
|
+
name: 'plugin-2',
|
|
156
|
+
collections: {
|
|
157
|
+
plugin2Table: collection({
|
|
158
|
+
slug: 'plugin2Table',
|
|
159
|
+
fields: { value: field({ fieldType: f.text() }) }
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const config = defineConfig({
|
|
165
|
+
database: testAdapter,
|
|
166
|
+
collections: [testCollections.users],
|
|
167
|
+
plugins: [mockPlugin1, mockPlugin2]
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
expect(config.$meta.collections).toHaveLength(3)
|
|
171
|
+
expect(config.$meta.plugins).toHaveLength(2)
|
|
172
|
+
expect(config.$meta.plugins).toContain('plugin-1')
|
|
173
|
+
expect(config.$meta.plugins).toContain('plugin-2')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('works with empty collections array', () => {
|
|
177
|
+
const config = defineConfig({
|
|
178
|
+
database: testAdapter,
|
|
179
|
+
collections: []
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
expect(Object.keys(config.collections)).toHaveLength(0)
|
|
183
|
+
expect(config.$meta.collections).toHaveLength(0)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('DB Instance', () => {
|
|
189
|
+
describe('db is Drizzle instance', () => {
|
|
190
|
+
it('db is defined', () => {
|
|
191
|
+
const config = defineConfig({
|
|
192
|
+
database: testAdapter,
|
|
193
|
+
collections: [testCollections.users]
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
expect(config.db).toBeDefined()
|
|
197
|
+
expect(config.db).not.toBeNull()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { buildSchema, buildTable } from '../src'
|
|
3
|
+
import { collection } from '../src/collection'
|
|
4
|
+
import { field } from '../src/field'
|
|
5
|
+
import { f } from '../src'
|
|
6
|
+
|
|
7
|
+
describe('buildSchema', () => {
|
|
8
|
+
it('builds a table from a collection', () => {
|
|
9
|
+
const users = collection({
|
|
10
|
+
slug: 'users',
|
|
11
|
+
fields: {
|
|
12
|
+
name: field({ fieldType: f.text() }),
|
|
13
|
+
email: field({ fieldType: f.text() })
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const table = buildTable(users)
|
|
18
|
+
|
|
19
|
+
expect(table).toBeDefined()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('builds schema from multiple collections', () => {
|
|
23
|
+
const users = collection({
|
|
24
|
+
slug: 'users',
|
|
25
|
+
fields: {
|
|
26
|
+
name: field({ fieldType: f.text() })
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const posts = collection({
|
|
31
|
+
slug: 'posts',
|
|
32
|
+
fields: {
|
|
33
|
+
title: field({ fieldType: f.text() })
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const schema = buildSchema([users, posts])
|
|
38
|
+
|
|
39
|
+
expect(schema.users).toBeDefined()
|
|
40
|
+
expect(schema.posts).toBeDefined()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('handles different field types', () => {
|
|
44
|
+
const items = collection({
|
|
45
|
+
slug: 'items',
|
|
46
|
+
fields: {
|
|
47
|
+
name: field({ fieldType: f.text() }),
|
|
48
|
+
count: field({ fieldType: f.number() }),
|
|
49
|
+
active: field({ fieldType: f.boolean() }),
|
|
50
|
+
createdAt: field({ fieldType: f.timestamp() })
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const schema = buildSchema([items])
|
|
55
|
+
|
|
56
|
+
expect(schema.items).toBeDefined()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { defineConfig, collection, field, f } from '../src'
|
|
3
|
+
import { testAdapter, testCollections } from './fixtures'
|
|
4
|
+
|
|
5
|
+
describe('Type Inference', () => {
|
|
6
|
+
describe('collection slug inference', () => {
|
|
7
|
+
it('infers users and posts as collection keys', () => {
|
|
8
|
+
const config = defineConfig({
|
|
9
|
+
database: testAdapter,
|
|
10
|
+
collections: [testCollections.users, testCollections.posts]
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// Type test: config.collections should have users and posts keys
|
|
14
|
+
const _users = config.collections.users
|
|
15
|
+
const _posts = config.collections.posts
|
|
16
|
+
|
|
17
|
+
expect(_users).toBeDefined()
|
|
18
|
+
expect(_posts).toBeDefined()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('infers single collection slug', () => {
|
|
22
|
+
const config = defineConfig({
|
|
23
|
+
database: testAdapter,
|
|
24
|
+
collections: [testCollections.users]
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const _users = config.collections.users
|
|
28
|
+
expect(_users).toBeDefined()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('infers $meta.collections as array of slugs', () => {
|
|
32
|
+
const config = defineConfig({
|
|
33
|
+
database: testAdapter,
|
|
34
|
+
collections: [testCollections.users, testCollections.posts]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Type test: $meta.collections should be string[]
|
|
38
|
+
const _meta: string[] = config.$meta.collections
|
|
39
|
+
expect(_meta).toContain('users')
|
|
40
|
+
expect(_meta).toContain('posts')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('collections have metadata only', () => {
|
|
45
|
+
it('collections have slug property', () => {
|
|
46
|
+
const config = defineConfig({
|
|
47
|
+
database: testAdapter,
|
|
48
|
+
collections: [testCollections.users]
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Collections have metadata
|
|
52
|
+
expect(config.collections.users.slug).toBe('users')
|
|
53
|
+
expect(config.collections.users.fields).toBeDefined()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('collections do NOT have operations', () => {
|
|
57
|
+
const config = defineConfig({
|
|
58
|
+
database: testAdapter,
|
|
59
|
+
collections: [testCollections.users]
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Operations should not exist on collections
|
|
63
|
+
expect(config.collections.users.findMany).toBeUndefined()
|
|
64
|
+
expect(config.collections.users.create).toBeUndefined()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('db is Drizzle instance', () => {
|
|
69
|
+
it('db is defined', () => {
|
|
70
|
+
const config = defineConfig({
|
|
71
|
+
database: testAdapter,
|
|
72
|
+
collections: [testCollections.users]
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(config.db).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('empty collections', () => {
|
|
80
|
+
it('works with empty collections array', () => {
|
|
81
|
+
const config = defineConfig({
|
|
82
|
+
database: testAdapter,
|
|
83
|
+
collections: []
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Type test: collections should be empty object
|
|
87
|
+
expect(Object.keys(config.collections)).toHaveLength(0)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('collection metadata type', () => {
|
|
92
|
+
it('preserves collection slug and name', () => {
|
|
93
|
+
const users = collection({
|
|
94
|
+
slug: 'users',
|
|
95
|
+
name: 'Users',
|
|
96
|
+
fields: { name: field({ fieldType: f.text() }) }
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const config = defineConfig({
|
|
100
|
+
database: testAdapter,
|
|
101
|
+
collections: [users]
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(config.collections.users.slug).toBe('users')
|
|
105
|
+
expect(config.collections.users.name).toBe('Users')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|