@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
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deessejs/collections",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "High-level abstraction layer built on top of Drizzle ORM",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./fields": {
|
|
15
|
+
"types": "./dist/fields/index.d.ts",
|
|
16
|
+
"import": "./dist/fields/index.mjs",
|
|
17
|
+
"require": "./dist/fields/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./plugins": {
|
|
20
|
+
"types": "./dist/plugins/index.d.ts",
|
|
21
|
+
"import": "./dist/plugins/index.mjs",
|
|
22
|
+
"require": "./dist/plugins/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"lint": "eslint src/",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"drizzle-kit": "^0.22.0",
|
|
37
|
+
"drizzle-orm": "^0.31.2",
|
|
38
|
+
"pg": "^8.11.0",
|
|
39
|
+
"zod": "^3.23.8"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@deessejs/type-testing": "^0.1.5",
|
|
43
|
+
"@neondatabase/serverless": "^1.0.2",
|
|
44
|
+
"@types/node": "^20.14.12",
|
|
45
|
+
"@types/pg": "^8.11.0",
|
|
46
|
+
"eslint": "^8.57.0",
|
|
47
|
+
"neon-serverless": "^0.5.3",
|
|
48
|
+
"tsup": "^8.2.2",
|
|
49
|
+
"tsx": "^4.21.0",
|
|
50
|
+
"typescript": "^5.5.4",
|
|
51
|
+
"vitest": "^2.0.5"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"typescript": ">=5"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"registry": "https://registry.npmjs.org/"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL adapter configuration
|
|
3
|
+
*/
|
|
4
|
+
export type PgAdapterConfig = {
|
|
5
|
+
url: string
|
|
6
|
+
migrationsPath?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL adapter
|
|
11
|
+
*/
|
|
12
|
+
export interface PgAdapter {
|
|
13
|
+
type: 'postgres'
|
|
14
|
+
config: PgAdapterConfig
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Database adapter type
|
|
19
|
+
*/
|
|
20
|
+
export type DatabaseAdapter = PgAdapter
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a PostgreSQL adapter
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const adapter = pgAdapter({
|
|
27
|
+
* url: 'postgres://user:pass@localhost:5432/db'
|
|
28
|
+
* })
|
|
29
|
+
*/
|
|
30
|
+
export const pgAdapter = (config: PgAdapterConfig): PgAdapter => {
|
|
31
|
+
return {
|
|
32
|
+
type: 'postgres',
|
|
33
|
+
config: {
|
|
34
|
+
url: config.url,
|
|
35
|
+
migrationsPath: config.migrationsPath ?? './migrations'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { FieldDefinition } from './field'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collection configuration
|
|
5
|
+
*/
|
|
6
|
+
export type CollectionConfig<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
7
|
+
slug: string
|
|
8
|
+
name?: string
|
|
9
|
+
fields: Record<string, FieldDefinition>
|
|
10
|
+
hooks?: CollectionHooks
|
|
11
|
+
dataType?: T
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Operation types
|
|
16
|
+
*/
|
|
17
|
+
export type OperationType = 'create' | 'update' | 'delete' | 'read'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook context base
|
|
21
|
+
*/
|
|
22
|
+
export type HookContextBase = {
|
|
23
|
+
collection: string
|
|
24
|
+
operation: OperationType
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Before/After Operation context
|
|
29
|
+
*/
|
|
30
|
+
export type OperationHookContext = HookContextBase & {
|
|
31
|
+
data?: Record<string, unknown>
|
|
32
|
+
where?: Record<string, unknown>
|
|
33
|
+
result?: unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Before/After Create context
|
|
38
|
+
*/
|
|
39
|
+
export type CreateHookContext = HookContextBase & {
|
|
40
|
+
operation: 'create'
|
|
41
|
+
data: Record<string, unknown>
|
|
42
|
+
result?: unknown
|
|
43
|
+
db?: unknown
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Before/After Update context
|
|
48
|
+
*/
|
|
49
|
+
export type UpdateHookContext = HookContextBase & {
|
|
50
|
+
operation: 'update'
|
|
51
|
+
data: Record<string, unknown>
|
|
52
|
+
where: Record<string, unknown>
|
|
53
|
+
previousData?: Record<string, unknown>
|
|
54
|
+
result?: unknown
|
|
55
|
+
db?: unknown
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Before/After Delete context
|
|
60
|
+
*/
|
|
61
|
+
export type DeleteHookContext = HookContextBase & {
|
|
62
|
+
operation: 'delete'
|
|
63
|
+
where: Record<string, unknown>
|
|
64
|
+
previousData?: Record<string, unknown>
|
|
65
|
+
result?: unknown
|
|
66
|
+
db?: unknown
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Before/After Read context
|
|
71
|
+
*/
|
|
72
|
+
export type ReadHookContext = HookContextBase & {
|
|
73
|
+
operation: 'read'
|
|
74
|
+
query?: Record<string, unknown>
|
|
75
|
+
result?: unknown[]
|
|
76
|
+
db?: unknown
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generic hook function type
|
|
81
|
+
*/
|
|
82
|
+
export type GenericHookFunction = (context: OperationHookContext) => Promise<void> | void
|
|
83
|
+
export type CreateHookFunction = (context: CreateHookContext) => Promise<void> | void
|
|
84
|
+
export type UpdateHookFunction = (context: UpdateHookContext) => Promise<void> | void
|
|
85
|
+
export type DeleteHookFunction = (context: DeleteHookContext) => Promise<void> | void
|
|
86
|
+
export type ReadHookFunction = (context: ReadHookContext) => Promise<void> | void
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Collection hooks
|
|
90
|
+
*/
|
|
91
|
+
export type CollectionHooks = {
|
|
92
|
+
beforeOperation?: GenericHookFunction[]
|
|
93
|
+
afterOperation?: GenericHookFunction[]
|
|
94
|
+
beforeCreate?: CreateHookFunction[]
|
|
95
|
+
afterCreate?: CreateHookFunction[]
|
|
96
|
+
beforeUpdate?: UpdateHookFunction[]
|
|
97
|
+
afterUpdate?: UpdateHookFunction[]
|
|
98
|
+
beforeDelete?: DeleteHookFunction[]
|
|
99
|
+
afterDelete?: DeleteHookFunction[]
|
|
100
|
+
beforeRead?: ReadHookFunction[]
|
|
101
|
+
afterRead?: ReadHookFunction[]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A collection definition
|
|
106
|
+
*/
|
|
107
|
+
export type Collection<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
108
|
+
slug: string
|
|
109
|
+
name?: string
|
|
110
|
+
fields: Record<string, FieldDefinition>
|
|
111
|
+
hooks?: CollectionHooks
|
|
112
|
+
dataType?: T
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a new collection
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* export const users = collection({
|
|
120
|
+
* slug: 'users',
|
|
121
|
+
* name: 'Users',
|
|
122
|
+
* fields: {
|
|
123
|
+
* name: field({ fieldType: text }),
|
|
124
|
+
* email: field({ fieldType: email, unique: true })
|
|
125
|
+
* }
|
|
126
|
+
* })
|
|
127
|
+
*/
|
|
128
|
+
export const collection = <T extends Record<string, unknown> = Record<string, unknown>>(
|
|
129
|
+
config: CollectionConfig<T>
|
|
130
|
+
): Collection<T> => {
|
|
131
|
+
return {
|
|
132
|
+
slug: config.slug,
|
|
133
|
+
name: config.name,
|
|
134
|
+
fields: config.fields,
|
|
135
|
+
hooks: config.hooks,
|
|
136
|
+
dataType: config.dataType
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Pool, type Pool as PoolType } from 'pg'
|
|
2
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
3
|
+
|
|
4
|
+
import type { Collection } from './collection'
|
|
5
|
+
import type { DatabaseAdapter } from './adapter'
|
|
6
|
+
import { buildSchema } from './schema'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Plugin interface
|
|
10
|
+
*/
|
|
11
|
+
export type Plugin = {
|
|
12
|
+
name: string
|
|
13
|
+
collections?: Record<string, Collection>
|
|
14
|
+
hooks?: Record<string, unknown[]>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration options
|
|
19
|
+
*/
|
|
20
|
+
export type ConfigOptions<T extends Collection[] = []> = {
|
|
21
|
+
database: DatabaseAdapter
|
|
22
|
+
collections: T
|
|
23
|
+
plugins?: Plugin[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Define config return type with inferred collection keys
|
|
28
|
+
*
|
|
29
|
+
* - collections: metadata only (slug, name, fields, hooks, dataType)
|
|
30
|
+
* - db: Drizzle instance with operations (via schema tables)
|
|
31
|
+
* - $meta: array of collection slugs and plugin names
|
|
32
|
+
*/
|
|
33
|
+
export type DefineConfigReturn<T extends Collection[] = []> = {
|
|
34
|
+
collections: {
|
|
35
|
+
[K in T[number] as K['slug']]: Collection
|
|
36
|
+
}
|
|
37
|
+
db: ReturnType<typeof drizzle<Record<string, unknown>>>
|
|
38
|
+
$meta: {
|
|
39
|
+
collections: T[number]['slug'][]
|
|
40
|
+
plugins: string[]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates the configuration for the data layer
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const adapter = pgAdapter({
|
|
49
|
+
* url: process.env.DATABASE_URL!
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* export const { collections, db } = defineConfig({
|
|
53
|
+
* database: adapter,
|
|
54
|
+
* collections: [users, posts],
|
|
55
|
+
* plugins: [timestampsPlugin()]
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* // collections: metadata only
|
|
59
|
+
* collections.users.slug // 'users'
|
|
60
|
+
* collections.users.fields // { name, email, ... }
|
|
61
|
+
*
|
|
62
|
+
* // db: Drizzle instance with operations
|
|
63
|
+
* await db.users.findMany()
|
|
64
|
+
* await db.users.insert(values)
|
|
65
|
+
*/
|
|
66
|
+
export const defineConfig = <T extends Collection[]>(
|
|
67
|
+
options: ConfigOptions<T>
|
|
68
|
+
): DefineConfigReturn<T> => {
|
|
69
|
+
// Initialize the database connection based on adapter type
|
|
70
|
+
let pool: PoolType | null = null
|
|
71
|
+
let dbInstance: ReturnType<typeof drizzle<Record<string, unknown>>> | null = null
|
|
72
|
+
|
|
73
|
+
let schema: Record<string, unknown> = {}
|
|
74
|
+
|
|
75
|
+
if (options.database.type === 'postgres') {
|
|
76
|
+
// Create pool from adapter config
|
|
77
|
+
pool = new Pool({
|
|
78
|
+
connectionString: options.database.config.url
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Build schema from collections
|
|
82
|
+
schema = buildSchema(options.collections as Collection[])
|
|
83
|
+
|
|
84
|
+
// Create Drizzle instance with schema
|
|
85
|
+
dbInstance = drizzle(pool, { schema })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build collections map (metadata only)
|
|
89
|
+
const collectionsMap: Record<string, Collection> = {}
|
|
90
|
+
const collectionNames: string[] = []
|
|
91
|
+
|
|
92
|
+
for (const coll of options.collections) {
|
|
93
|
+
// Store only metadata (not operations)
|
|
94
|
+
collectionsMap[coll.slug] = {
|
|
95
|
+
slug: coll.slug,
|
|
96
|
+
name: coll.name,
|
|
97
|
+
fields: coll.fields,
|
|
98
|
+
hooks: coll.hooks,
|
|
99
|
+
dataType: coll.dataType
|
|
100
|
+
}
|
|
101
|
+
collectionNames.push(coll.slug)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Build plugins map
|
|
105
|
+
const pluginNames: string[] = []
|
|
106
|
+
if (options.plugins) {
|
|
107
|
+
for (const plugin of options.plugins) {
|
|
108
|
+
pluginNames.push(plugin.name)
|
|
109
|
+
|
|
110
|
+
// Register plugin collections (metadata only)
|
|
111
|
+
if (plugin.collections) {
|
|
112
|
+
for (const [name, coll] of Object.entries(plugin.collections)) {
|
|
113
|
+
collectionsMap[name] = {
|
|
114
|
+
slug: coll.slug,
|
|
115
|
+
name: coll.name,
|
|
116
|
+
fields: coll.fields,
|
|
117
|
+
hooks: coll.hooks,
|
|
118
|
+
dataType: coll.dataType
|
|
119
|
+
}
|
|
120
|
+
collectionNames.push(name)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
collections: collectionsMap as DefineConfigReturn<T>['collections'],
|
|
128
|
+
db: dbInstance as DefineConfigReturn<T>['db'],
|
|
129
|
+
$meta: {
|
|
130
|
+
collections: collectionNames as DefineConfigReturn<T>['$meta']['collections'],
|
|
131
|
+
plugins: pluginNames
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A field type instance (already configured)
|
|
5
|
+
*/
|
|
6
|
+
export type FieldTypeInstance = {
|
|
7
|
+
schema: z.ZodType
|
|
8
|
+
database: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A field type creator (needs to be called to get instance)
|
|
13
|
+
*/
|
|
14
|
+
export type FieldTypeCreator = () => FieldTypeInstance
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Field type configuration
|
|
18
|
+
*/
|
|
19
|
+
export type FieldTypeConfig = {
|
|
20
|
+
schema: z.ZodType
|
|
21
|
+
database?: unknown
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new field type
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const text = fieldType({
|
|
29
|
+
* schema: z.string(),
|
|
30
|
+
* database: { type: 'text' }
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* const textField = text() // Get instance
|
|
34
|
+
*/
|
|
35
|
+
export const fieldType = (config: FieldTypeConfig): (() => FieldTypeInstance) => {
|
|
36
|
+
return () => ({
|
|
37
|
+
schema: config.schema,
|
|
38
|
+
database: config.database ?? {}
|
|
39
|
+
})
|
|
40
|
+
}
|
package/src/field.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FieldTypeInstance } from './field-type'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Field configuration options
|
|
5
|
+
*/
|
|
6
|
+
export type FieldOptions = {
|
|
7
|
+
fieldType: FieldTypeInstance
|
|
8
|
+
required?: boolean
|
|
9
|
+
unique?: boolean
|
|
10
|
+
indexed?: boolean
|
|
11
|
+
default?: unknown
|
|
12
|
+
label?: string
|
|
13
|
+
description?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a field definition
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* name: field({ fieldType: text() })
|
|
21
|
+
* email: field({ fieldType: email(), unique: true })
|
|
22
|
+
*/
|
|
23
|
+
export const field = (config: FieldOptions): FieldDefinition => {
|
|
24
|
+
return {
|
|
25
|
+
fieldType: config.fieldType,
|
|
26
|
+
required: config.required ?? false,
|
|
27
|
+
unique: config.unique ?? false,
|
|
28
|
+
indexed: config.indexed ?? false,
|
|
29
|
+
default: config.default,
|
|
30
|
+
label: config.label,
|
|
31
|
+
description: config.description
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A field definition
|
|
37
|
+
*/
|
|
38
|
+
export type FieldDefinition = {
|
|
39
|
+
fieldType: FieldTypeInstance
|
|
40
|
+
required: boolean
|
|
41
|
+
unique: boolean
|
|
42
|
+
indexed: boolean
|
|
43
|
+
default?: unknown
|
|
44
|
+
label?: string
|
|
45
|
+
description?: string
|
|
46
|
+
}
|
package/src/fields/f.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { fieldType, type FieldTypeInstance } from '../field-type'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Derive the database type string from a Zod schema
|
|
6
|
+
*/
|
|
7
|
+
const getItemType = (itemSchema: z.ZodType): string => {
|
|
8
|
+
if (itemSchema instanceof z.ZodString) return 'text'
|
|
9
|
+
if (itemSchema instanceof z.ZodNumber) return 'integer'
|
|
10
|
+
if (itemSchema instanceof z.ZodBoolean) return 'boolean'
|
|
11
|
+
if (itemSchema instanceof z.ZodDate) return 'timestamp'
|
|
12
|
+
if (itemSchema instanceof z.ZodEnum) return 'text'
|
|
13
|
+
if (itemSchema instanceof z.ZodArray) return 'array'
|
|
14
|
+
if (itemSchema instanceof z.ZodObject) return 'jsonb'
|
|
15
|
+
return 'text'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Field types namespace (like zod's z)
|
|
20
|
+
*/
|
|
21
|
+
export const f = {
|
|
22
|
+
/**
|
|
23
|
+
* Text field type
|
|
24
|
+
*/
|
|
25
|
+
text: (): FieldTypeInstance => fieldType({
|
|
26
|
+
schema: z.string(),
|
|
27
|
+
database: { type: 'text' }
|
|
28
|
+
})(),
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Email field type with built-in validation
|
|
32
|
+
*/
|
|
33
|
+
email: (): FieldTypeInstance => fieldType({
|
|
34
|
+
schema: z.string().email(),
|
|
35
|
+
database: { type: 'text' }
|
|
36
|
+
})(),
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* URL field type with built-in validation
|
|
40
|
+
*/
|
|
41
|
+
url: (): FieldTypeInstance => fieldType({
|
|
42
|
+
schema: z.string().url(),
|
|
43
|
+
database: { type: 'text' }
|
|
44
|
+
})(),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Number field type
|
|
48
|
+
*/
|
|
49
|
+
number: (): FieldTypeInstance => fieldType({
|
|
50
|
+
schema: z.number(),
|
|
51
|
+
database: { type: 'integer' }
|
|
52
|
+
})(),
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Boolean field type
|
|
56
|
+
*/
|
|
57
|
+
boolean: (): FieldTypeInstance => fieldType({
|
|
58
|
+
schema: z.boolean(),
|
|
59
|
+
database: { type: 'boolean' }
|
|
60
|
+
})(),
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Date field type (date only, no time)
|
|
64
|
+
*/
|
|
65
|
+
date: (): FieldTypeInstance => fieldType({
|
|
66
|
+
schema: z.date(),
|
|
67
|
+
database: { type: 'date' }
|
|
68
|
+
})(),
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Timestamp field type (date with time)
|
|
72
|
+
*/
|
|
73
|
+
timestamp: (): FieldTypeInstance => fieldType({
|
|
74
|
+
schema: z.date(),
|
|
75
|
+
database: { type: 'timestamp' }
|
|
76
|
+
})(),
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a select field type
|
|
80
|
+
*/
|
|
81
|
+
select: <T extends readonly [string, ...string[]]>(
|
|
82
|
+
options: T
|
|
83
|
+
): FieldTypeInstance => fieldType({
|
|
84
|
+
schema: z.enum(options),
|
|
85
|
+
database: { type: 'text' }
|
|
86
|
+
})(),
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* JSON field type for storing JSON data
|
|
90
|
+
*/
|
|
91
|
+
json: (schema?: z.ZodType): FieldTypeInstance => fieldType({
|
|
92
|
+
schema: schema ?? z.any(),
|
|
93
|
+
database: { type: 'jsonb' }
|
|
94
|
+
})(),
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Array field type for storing lists
|
|
98
|
+
*/
|
|
99
|
+
array: (itemSchema: z.ZodType): FieldTypeInstance => fieldType({
|
|
100
|
+
schema: z.array(itemSchema),
|
|
101
|
+
database: { type: 'array', itemType: getItemType(itemSchema) }
|
|
102
|
+
})(),
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a relation field type for foreign key relationships
|
|
106
|
+
*/
|
|
107
|
+
relation: (options: {
|
|
108
|
+
collection: string
|
|
109
|
+
singular?: boolean
|
|
110
|
+
many?: boolean
|
|
111
|
+
through?: string
|
|
112
|
+
}): FieldTypeInstance => {
|
|
113
|
+
const isMany = options.many ?? false
|
|
114
|
+
const isSingular = options.singular ?? false
|
|
115
|
+
|
|
116
|
+
return fieldType({
|
|
117
|
+
schema: isMany ? z.array(z.string()) : z.string(),
|
|
118
|
+
database: {
|
|
119
|
+
type: 'integer',
|
|
120
|
+
references: options.collection,
|
|
121
|
+
through: options.through,
|
|
122
|
+
many: isMany,
|
|
123
|
+
singular: isSingular
|
|
124
|
+
}
|
|
125
|
+
})()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @deprecated Use f.text() instead
|
|
131
|
+
*/
|
|
132
|
+
export const text = f.text
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @deprecated Use f.email() instead
|
|
136
|
+
*/
|
|
137
|
+
export const email = f.email
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @deprecated Use f.url() instead
|
|
141
|
+
*/
|
|
142
|
+
export const url = f.url
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @deprecated Use f.number() instead
|
|
146
|
+
*/
|
|
147
|
+
export const number = f.number
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @deprecated Use f.boolean() instead
|
|
151
|
+
*/
|
|
152
|
+
export const boolean = f.boolean
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @deprecated Use f.date() instead
|
|
156
|
+
*/
|
|
157
|
+
export const date = f.date
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @deprecated Use f.timestamp() instead
|
|
161
|
+
*/
|
|
162
|
+
export const timestamp = f.timestamp
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @deprecated Use f.select() instead
|
|
166
|
+
*/
|
|
167
|
+
export const select = f.select
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @deprecated Use f.json() instead
|
|
171
|
+
*/
|
|
172
|
+
export const json = f.json
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @deprecated Use f.array() instead
|
|
176
|
+
*/
|
|
177
|
+
export const array = f.array
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @deprecated Use f.relation() instead
|
|
181
|
+
*/
|
|
182
|
+
export const relation = f.relation
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @deprecated Use f.relation instead
|
|
186
|
+
*/
|
|
187
|
+
export type RelationOptions = {
|
|
188
|
+
collection: string
|
|
189
|
+
singular?: boolean
|
|
190
|
+
many?: boolean
|
|
191
|
+
through?: string
|
|
192
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './f'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Field type system
|
|
2
|
+
export { fieldType, type FieldTypeInstance, type FieldTypeCreator, type FieldTypeConfig } from './field-type'
|
|
3
|
+
|
|
4
|
+
// Field
|
|
5
|
+
export { field, type FieldDefinition, type FieldOptions } from './field'
|
|
6
|
+
|
|
7
|
+
// Built-in field types
|
|
8
|
+
export { f } from './fields'
|
|
9
|
+
export * from './fields'
|
|
10
|
+
|
|
11
|
+
// Collection
|
|
12
|
+
export { collection, type CollectionConfig, type Collection, type CollectionHooks, type OperationType, type CreateHookContext, type UpdateHookContext, type DeleteHookContext, type ReadHookContext, type OperationHookContext, type CreateHookFunction, type UpdateHookFunction, type DeleteHookFunction, type ReadHookFunction, type GenericHookFunction } from './collection'
|
|
13
|
+
|
|
14
|
+
// Operations
|
|
15
|
+
export * from './operations'
|
|
16
|
+
|
|
17
|
+
// Adapter
|
|
18
|
+
export { pgAdapter, type PgAdapter, type PgAdapterConfig, type DatabaseAdapter } from './adapter'
|
|
19
|
+
|
|
20
|
+
// Schema
|
|
21
|
+
export { buildSchema, buildTable } from './schema'
|
|
22
|
+
|
|
23
|
+
// Migrations
|
|
24
|
+
export { push, generate, migrate } from './migrations'
|
|
25
|
+
|
|
26
|
+
// Config
|
|
27
|
+
export { defineConfig, type Plugin, type ConfigOptions, type DefineConfigReturn } from './config'
|