@parsrun/entity 0.1.35

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/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # @parsrun/entity
2
+
3
+ Single-source entity definitions for the Pars framework. Define once, generate ArkType validation schemas and Drizzle ORM tables.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @parsrun/entity
9
+ # or
10
+ pnpm add @parsrun/entity
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { defineEntity, ref, enumField } from '@parsrun/entity'
17
+ import { toPgTable } from '@parsrun/entity/pg'
18
+ // or for SQLite/D1:
19
+ // import { toSqliteTable } from '@parsrun/entity/sqlite'
20
+
21
+ // Define your entity once
22
+ const Product = defineEntity({
23
+ name: 'products',
24
+ tenant: true, // Adds tenantId field
25
+ timestamps: true, // Adds insertedAt, updatedAt
26
+ softDelete: true, // Adds deletedAt
27
+
28
+ fields: {
29
+ name: 'string >= 1',
30
+ slug: 'string',
31
+ description: { type: 'string', optional: true },
32
+ price: { type: 'number', min: 0 },
33
+ stock: { type: 'number.integer', min: 0, default: 0 },
34
+ status: enumField(['draft', 'active', 'archived'], { default: 'draft' }),
35
+ categoryId: ref('categories', { onDelete: 'set null', optional: true }),
36
+ },
37
+
38
+ indexes: [
39
+ { fields: ['tenantId', 'slug'], unique: true },
40
+ { fields: ['tenantId', 'status'] },
41
+ ],
42
+ })
43
+
44
+ // Generated schemas available:
45
+ Product.schema // Full entity schema
46
+ Product.createSchema // For create operations (no id, timestamps)
47
+ Product.updateSchema // For updates (all optional)
48
+ Product.querySchema // For filtering (includes pagination)
49
+
50
+ // Generate Drizzle table
51
+ const productsTable = toPgTable(Product)
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ### Validation
57
+
58
+ ```typescript
59
+ import { type } from 'arktype'
60
+
61
+ // Validate create input
62
+ const input = Product.createSchema(requestBody)
63
+ if (input instanceof type.errors) {
64
+ return { error: 'Validation failed', details: input }
65
+ }
66
+
67
+ // input is now typed and validated
68
+ await db.insert(productsTable).values({
69
+ tenantId: ctx.tenantId,
70
+ ...input,
71
+ })
72
+ ```
73
+
74
+ ### Database Queries
75
+
76
+ ```typescript
77
+ import { eq, and } from 'drizzle-orm'
78
+ import { toPgTable } from '@parsrun/entity/pg'
79
+
80
+ const productsTable = toPgTable(Product)
81
+
82
+ // Select
83
+ const products = await db
84
+ .select()
85
+ .from(productsTable)
86
+ .where(and(
87
+ eq(productsTable.tenantId, tenantId),
88
+ eq(productsTable.status, 'active')
89
+ ))
90
+
91
+ // Insert
92
+ const [product] = await db
93
+ .insert(productsTable)
94
+ .values({ tenantId, name: 'Widget', price: 9.99 })
95
+ .returning()
96
+
97
+ // Update
98
+ await db
99
+ .update(productsTable)
100
+ .set({ price: 14.99 })
101
+ .where(eq(productsTable.id, productId))
102
+ ```
103
+
104
+ ### Multiple Entities with References
105
+
106
+ ```typescript
107
+ import { createPgSchema } from '@parsrun/entity/pg'
108
+
109
+ const Category = defineEntity({
110
+ name: 'categories',
111
+ tenant: true,
112
+ timestamps: true,
113
+ fields: {
114
+ name: 'string >= 1',
115
+ slug: 'string',
116
+ },
117
+ })
118
+
119
+ const Product = defineEntity({
120
+ name: 'products',
121
+ tenant: true,
122
+ timestamps: true,
123
+ fields: {
124
+ name: 'string >= 1',
125
+ categoryId: ref(Category, { onDelete: 'cascade' }),
126
+ },
127
+ })
128
+
129
+ // Creates tables with proper foreign key references
130
+ const schema = createPgSchema({ Category, Product })
131
+ // schema.Category, schema.Product
132
+ ```
133
+
134
+ ## Field Types
135
+
136
+ | Type | Description | Example |
137
+ |------|-------------|---------|
138
+ | `'string'` | Text field | `name: 'string'` |
139
+ | `'string >= N'` | Min length | `name: 'string >= 1'` |
140
+ | `'string.uuid'` | UUID field | `id: 'string.uuid'` |
141
+ | `'string.email'` | Email field | `email: 'string.email'` |
142
+ | `'string.url'` | URL field | `website: 'string.url'` |
143
+ | `'number'` | Numeric field | `price: 'number'` |
144
+ | `'number >= N'` | Min value | `price: 'number >= 0'` |
145
+ | `'number.integer'` | Integer field | `stock: 'number.integer'` |
146
+ | `'boolean'` | Boolean field | `isActive: 'boolean'` |
147
+ | `'Date'` | Timestamp | `expiresAt: 'Date'` |
148
+ | `'json'` | JSON field | `metadata: 'json'` |
149
+ | `"'a' \| 'b'"` | Union/Enum | `status: "'draft' \| 'active'"` |
150
+
151
+ ## Helper Functions
152
+
153
+ ### `enumField(values, options?)`
154
+
155
+ ```typescript
156
+ status: enumField(['draft', 'active', 'archived'], {
157
+ default: 'draft',
158
+ optional: false
159
+ })
160
+ ```
161
+
162
+ ### `ref(entity, options?)`
163
+
164
+ ```typescript
165
+ categoryId: ref('categories', {
166
+ field: 'id', // default: 'id'
167
+ onDelete: 'cascade', // 'cascade' | 'set null' | 'restrict'
168
+ optional: true
169
+ })
170
+
171
+ // Or with entity reference
172
+ categoryId: ref(Category, { onDelete: 'cascade' })
173
+ ```
174
+
175
+ ### `decimal(precision, scale, options?)`
176
+
177
+ ```typescript
178
+ price: decimal(10, 2, { min: 0 }) // DECIMAL(10,2)
179
+ ```
180
+
181
+ ### `jsonField(options?)`
182
+
183
+ ```typescript
184
+ metadata: jsonField({ optional: true, default: {} })
185
+ ```
186
+
187
+ ## SQLite / Cloudflare D1
188
+
189
+ ```typescript
190
+ import { toSqliteTable, createSqliteSchema } from '@parsrun/entity/sqlite'
191
+
192
+ const productsTable = toSqliteTable(Product)
193
+
194
+ // Or multiple tables
195
+ const schema = createSqliteSchema({ Category, Product })
196
+ ```
197
+
198
+ ## TypeScript Types
199
+
200
+ ```typescript
201
+ import type { InferEntity, InferCreateInput, InferUpdateInput } from '@parsrun/entity'
202
+
203
+ type Product = InferEntity<typeof Product>
204
+ type CreateProductInput = InferCreateInput<typeof Product>
205
+ type UpdateProductInput = InferUpdateInput<typeof Product>
206
+ ```
207
+
208
+ ## License
209
+
210
+ MIT
@@ -0,0 +1,66 @@
1
+ import { F as Field, E as EntityDefinition, a as Entity, b as FieldDefinition } from './types-CmS0cBdC.js';
2
+ export { D as DrizzleOptions, d as EntitySchemas, c as FieldType, I as IndexDefinition, f as InferCreateInput, e as InferEntity, g as InferUpdateInput, S as SimpleFieldDefinition } from './types-CmS0cBdC.js';
3
+ export { type } from 'arktype';
4
+
5
+ /**
6
+ * Define an entity with single-source schema generation
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const Product = defineEntity({
11
+ * name: 'products',
12
+ * tenant: true,
13
+ * timestamps: true,
14
+ * softDelete: true,
15
+ * fields: {
16
+ * name: 'string >= 1',
17
+ * price: { type: 'number', min: 0 },
18
+ * status: "'draft' | 'active' | 'archived'",
19
+ * },
20
+ * indexes: [
21
+ * { fields: ['tenantId', 'status'] },
22
+ * ],
23
+ * })
24
+ *
25
+ * // Use schemas
26
+ * const validated = Product.createSchema(input)
27
+ * const products = await db.select().from(Product.table)
28
+ * ```
29
+ */
30
+ declare function defineEntity<TName extends string, TFields extends Record<string, Field>>(definition: EntityDefinition<TFields> & {
31
+ name: TName;
32
+ }): Entity<TName, TFields, Record<string, unknown>>;
33
+ /**
34
+ * Create a reference field to another entity
35
+ */
36
+ declare function ref(entity: string | {
37
+ name: string;
38
+ }, options?: {
39
+ field?: string;
40
+ onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
41
+ optional?: boolean;
42
+ }): FieldDefinition;
43
+ /**
44
+ * Create an enum field from a list of values
45
+ */
46
+ declare function enumField<T extends string>(values: readonly T[], options?: {
47
+ default?: T;
48
+ optional?: boolean;
49
+ }): FieldDefinition;
50
+ /**
51
+ * Create a JSON field
52
+ */
53
+ declare function jsonField<T = Record<string, unknown>>(options?: {
54
+ optional?: boolean;
55
+ default?: T;
56
+ }): FieldDefinition;
57
+ /**
58
+ * Create a decimal field with precision
59
+ */
60
+ declare function decimal(precision: number, scale: number, options?: {
61
+ min?: number;
62
+ max?: number;
63
+ optional?: boolean;
64
+ }): FieldDefinition;
65
+
66
+ export { Entity, EntityDefinition, Field, FieldDefinition, decimal, defineEntity, enumField, jsonField, ref };
package/dist/index.js ADDED
@@ -0,0 +1,200 @@
1
+ import { type } from 'arktype';
2
+ export { type } from 'arktype';
3
+
4
+ // src/define.ts
5
+ function fieldToArkType(field) {
6
+ const def = typeof field === "string" ? { type: field } : field;
7
+ let typeStr = def.type;
8
+ if (def.min !== void 0 || def.max !== void 0) {
9
+ if (typeStr === "string" || typeStr.startsWith("string")) {
10
+ if (def.min !== void 0 && def.max !== void 0) {
11
+ typeStr = `string >= ${def.min} <= ${def.max}`;
12
+ } else if (def.min !== void 0) {
13
+ typeStr = `string >= ${def.min}`;
14
+ } else if (def.max !== void 0) {
15
+ typeStr = `string <= ${def.max}`;
16
+ }
17
+ } else if (typeStr === "number" || typeStr.startsWith("number")) {
18
+ const base = typeStr.includes(".integer") ? "number.integer" : "number";
19
+ if (def.min !== void 0 && def.max !== void 0) {
20
+ typeStr = `${base} >= ${def.min} <= ${def.max}`;
21
+ } else if (def.min !== void 0) {
22
+ typeStr = `${base} >= ${def.min}`;
23
+ } else if (def.max !== void 0) {
24
+ typeStr = `${base} <= ${def.max}`;
25
+ }
26
+ }
27
+ }
28
+ if (def.optional) {
29
+ typeStr = `${typeStr} | undefined`;
30
+ }
31
+ return typeStr;
32
+ }
33
+ function buildSchemaObject(definition, mode) {
34
+ const schema = {};
35
+ if (mode === "full") {
36
+ schema["id"] = "string.uuid";
37
+ }
38
+ if (definition.tenant) {
39
+ if (mode === "full" || mode === "create") {
40
+ schema["tenantId"] = "string.uuid";
41
+ }
42
+ if (mode === "query") {
43
+ schema["tenantId?"] = "string.uuid";
44
+ }
45
+ }
46
+ for (const [name, field] of Object.entries(definition.fields)) {
47
+ const def = typeof field === "string" ? { } : field;
48
+ if (mode === "update" || mode === "query") {
49
+ schema[`${name}?`] = fieldToArkType(field);
50
+ } else if (mode === "create") {
51
+ if (def.default !== void 0 || def.optional) {
52
+ schema[`${name}?`] = fieldToArkType(field);
53
+ } else {
54
+ schema[name] = fieldToArkType(field);
55
+ }
56
+ } else {
57
+ if (def.optional) {
58
+ schema[`${name}?`] = fieldToArkType(field);
59
+ } else {
60
+ schema[name] = fieldToArkType(field);
61
+ }
62
+ }
63
+ }
64
+ if (definition.timestamps) {
65
+ if (mode === "full") {
66
+ schema["insertedAt"] = "Date";
67
+ schema["updatedAt"] = "Date";
68
+ }
69
+ if (mode === "query") {
70
+ schema["insertedAt?"] = "Date";
71
+ schema["updatedAt?"] = "Date";
72
+ schema["insertedAfter?"] = "Date";
73
+ schema["insertedBefore?"] = "Date";
74
+ }
75
+ }
76
+ if (definition.softDelete) {
77
+ if (mode === "full") {
78
+ schema["deletedAt?"] = "Date";
79
+ }
80
+ if (mode === "query") {
81
+ schema["includeDeleted?"] = "boolean";
82
+ }
83
+ }
84
+ if (mode === "query") {
85
+ schema["limit?"] = "number.integer > 0";
86
+ schema["offset?"] = "number.integer >= 0";
87
+ schema["cursor?"] = "string";
88
+ schema["orderBy?"] = "string";
89
+ schema["orderDirection?"] = "'asc' | 'desc'";
90
+ schema["search?"] = "string";
91
+ }
92
+ return schema;
93
+ }
94
+ function defineEntity(definition) {
95
+ const fullSchemaObj = buildSchemaObject(definition, "full");
96
+ const createSchemaObj = buildSchemaObject(definition, "create");
97
+ const updateSchemaObj = buildSchemaObject(definition, "update");
98
+ const querySchemaObj = buildSchemaObject(definition, "query");
99
+ const schema = type(fullSchemaObj);
100
+ const createSchema = type(createSchemaObj);
101
+ const updateSchema = type(updateSchemaObj);
102
+ const querySchema = type(querySchemaObj);
103
+ const autoFields = ["id"];
104
+ if (definition.timestamps) {
105
+ autoFields.push("insertedAt", "updatedAt");
106
+ }
107
+ if (definition.softDelete) {
108
+ autoFields.push("deletedAt");
109
+ }
110
+ const requiredFields = [];
111
+ const optionalFields = [];
112
+ if (definition.tenant) {
113
+ requiredFields.push("tenantId");
114
+ }
115
+ for (const [name, field] of Object.entries(definition.fields)) {
116
+ const def = typeof field === "string" ? { } : field;
117
+ if (def.optional || def.default !== void 0) {
118
+ optionalFields.push(name);
119
+ } else {
120
+ requiredFields.push(name);
121
+ }
122
+ }
123
+ return {
124
+ name: definition.name,
125
+ definition,
126
+ schema,
127
+ createSchema,
128
+ updateSchema,
129
+ querySchema,
130
+ infer: {},
131
+ autoFields,
132
+ requiredFields,
133
+ optionalFields
134
+ };
135
+ }
136
+ function ref(entity, options) {
137
+ const entityName = typeof entity === "string" ? entity : entity.name;
138
+ const result = {
139
+ type: "string.uuid",
140
+ db: {
141
+ references: {
142
+ entity: entityName,
143
+ field: options?.field ?? "id",
144
+ onDelete: options?.onDelete ?? "restrict"
145
+ }
146
+ }
147
+ };
148
+ if (options?.optional !== void 0) {
149
+ result.optional = options.optional;
150
+ }
151
+ return result;
152
+ }
153
+ function enumField(values, options) {
154
+ const typeStr = values.map((v) => `'${v}'`).join(" | ");
155
+ const result = {
156
+ type: typeStr
157
+ };
158
+ if (options?.default !== void 0) {
159
+ result.default = options.default;
160
+ }
161
+ if (options?.optional !== void 0) {
162
+ result.optional = options.optional;
163
+ }
164
+ return result;
165
+ }
166
+ function jsonField(options) {
167
+ const result = {
168
+ type: "json"
169
+ };
170
+ if (options?.optional !== void 0) {
171
+ result.optional = options.optional;
172
+ }
173
+ if (options?.default !== void 0) {
174
+ result.default = options.default;
175
+ }
176
+ return result;
177
+ }
178
+ function decimal(precision, scale, options) {
179
+ const result = {
180
+ type: "number",
181
+ db: {
182
+ precision,
183
+ scale
184
+ }
185
+ };
186
+ if (options?.min !== void 0) {
187
+ result.min = options.min;
188
+ }
189
+ if (options?.max !== void 0) {
190
+ result.max = options.max;
191
+ }
192
+ if (options?.optional !== void 0) {
193
+ result.optional = options.optional;
194
+ }
195
+ return result;
196
+ }
197
+
198
+ export { decimal, defineEntity, enumField, jsonField, ref };
199
+ //# sourceMappingURL=index.js.map
200
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/define.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { type, type Type } from 'arktype'\nimport type {\n EntityDefinition,\n Field,\n FieldDefinition,\n Entity,\n} from './types.js'\n\n/**\n * Convert a field definition to an ArkType type string\n */\nfunction fieldToArkType(field: Field): string {\n const def: FieldDefinition = typeof field === 'string'\n ? { type: field }\n : field\n\n let typeStr = def.type\n\n // Handle min/max constraints\n if (def.min !== undefined || def.max !== undefined) {\n if (typeStr === 'string' || typeStr.startsWith('string')) {\n if (def.min !== undefined && def.max !== undefined) {\n typeStr = `string >= ${def.min} <= ${def.max}`\n } else if (def.min !== undefined) {\n typeStr = `string >= ${def.min}`\n } else if (def.max !== undefined) {\n typeStr = `string <= ${def.max}`\n }\n } else if (typeStr === 'number' || typeStr.startsWith('number')) {\n const base = typeStr.includes('.integer') ? 'number.integer' : 'number'\n if (def.min !== undefined && def.max !== undefined) {\n typeStr = `${base} >= ${def.min} <= ${def.max}`\n } else if (def.min !== undefined) {\n typeStr = `${base} >= ${def.min}`\n } else if (def.max !== undefined) {\n typeStr = `${base} <= ${def.max}`\n }\n }\n }\n\n // Handle optional fields\n if (def.optional) {\n // For optional fields, allow undefined or the type\n typeStr = `${typeStr} | undefined`\n }\n\n return typeStr\n}\n\n/**\n * Build the full schema object for ArkType\n */\nfunction buildSchemaObject(\n definition: EntityDefinition<Record<string, Field>>,\n mode: 'full' | 'create' | 'update' | 'query'\n): Record<string, string> {\n const schema: Record<string, string> = {}\n\n // Add id field for full schema\n if (mode === 'full') {\n schema['id'] = 'string.uuid'\n }\n\n // Add tenantId if tenant-scoped\n if (definition.tenant) {\n if (mode === 'full' || mode === 'create') {\n schema['tenantId'] = 'string.uuid'\n }\n if (mode === 'query') {\n schema['tenantId?'] = 'string.uuid'\n }\n }\n\n // Add user-defined fields\n for (const [name, field] of Object.entries(definition.fields)) {\n const def: FieldDefinition = typeof field === 'string'\n ? { type: field }\n : field\n\n if (mode === 'update' || mode === 'query') {\n // All fields optional for update/query\n schema[`${name}?`] = fieldToArkType(field)\n } else if (mode === 'create') {\n // Skip fields with defaults or optional fields\n if (def.default !== undefined || def.optional) {\n schema[`${name}?`] = fieldToArkType(field)\n } else {\n schema[name] = fieldToArkType(field)\n }\n } else {\n // Full schema\n if (def.optional) {\n schema[`${name}?`] = fieldToArkType(field)\n } else {\n schema[name] = fieldToArkType(field)\n }\n }\n }\n\n // Add timestamp fields\n if (definition.timestamps) {\n if (mode === 'full') {\n schema['insertedAt'] = 'Date'\n schema['updatedAt'] = 'Date'\n }\n if (mode === 'query') {\n schema['insertedAt?'] = 'Date'\n schema['updatedAt?'] = 'Date'\n schema['insertedAfter?'] = 'Date'\n schema['insertedBefore?'] = 'Date'\n }\n }\n\n // Add soft delete field\n if (definition.softDelete) {\n if (mode === 'full') {\n schema['deletedAt?'] = 'Date'\n }\n if (mode === 'query') {\n schema['includeDeleted?'] = 'boolean'\n }\n }\n\n // Add pagination for query\n if (mode === 'query') {\n schema['limit?'] = 'number.integer > 0'\n schema['offset?'] = 'number.integer >= 0'\n schema['cursor?'] = 'string'\n schema['orderBy?'] = 'string'\n schema['orderDirection?'] = \"'asc' | 'desc'\"\n schema['search?'] = 'string'\n }\n\n return schema\n}\n\n/**\n * Define an entity with single-source schema generation\n *\n * @example\n * ```typescript\n * const Product = defineEntity({\n * name: 'products',\n * tenant: true,\n * timestamps: true,\n * softDelete: true,\n * fields: {\n * name: 'string >= 1',\n * price: { type: 'number', min: 0 },\n * status: \"'draft' | 'active' | 'archived'\",\n * },\n * indexes: [\n * { fields: ['tenantId', 'status'] },\n * ],\n * })\n *\n * // Use schemas\n * const validated = Product.createSchema(input)\n * const products = await db.select().from(Product.table)\n * ```\n */\nexport function defineEntity<\n TName extends string,\n TFields extends Record<string, Field>,\n>(\n definition: EntityDefinition<TFields> & { name: TName }\n): Entity<TName, TFields, Record<string, unknown>> {\n // Build schema objects\n const fullSchemaObj = buildSchemaObject(definition, 'full')\n const createSchemaObj = buildSchemaObject(definition, 'create')\n const updateSchemaObj = buildSchemaObject(definition, 'update')\n const querySchemaObj = buildSchemaObject(definition, 'query')\n\n // Create ArkType schemas\n const schema = type(fullSchemaObj as Record<string, string>)\n const createSchema = type(createSchemaObj as Record<string, string>)\n const updateSchema = type(updateSchemaObj as Record<string, string>)\n const querySchema = type(querySchemaObj as Record<string, string>)\n\n // Determine auto and required fields\n const autoFields: string[] = ['id']\n if (definition.timestamps) {\n autoFields.push('insertedAt', 'updatedAt')\n }\n if (definition.softDelete) {\n autoFields.push('deletedAt')\n }\n\n const requiredFields: string[] = []\n const optionalFields: string[] = []\n\n if (definition.tenant) {\n requiredFields.push('tenantId')\n }\n\n for (const [name, field] of Object.entries(definition.fields)) {\n const def: FieldDefinition = typeof field === 'string'\n ? { type: field }\n : field\n\n if (def.optional || def.default !== undefined) {\n optionalFields.push(name)\n } else {\n requiredFields.push(name)\n }\n }\n\n return {\n name: definition.name as TName,\n definition,\n schema: schema as Type<Record<string, unknown>>,\n createSchema: createSchema as Type<Record<string, unknown>>,\n updateSchema: updateSchema as Type<Record<string, unknown>>,\n querySchema: querySchema as Type<Record<string, unknown>>,\n infer: {} as Record<string, unknown>,\n autoFields,\n requiredFields,\n optionalFields,\n }\n}\n\n/**\n * Create a reference field to another entity\n */\nexport function ref(\n entity: string | { name: string },\n options?: {\n field?: string\n onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action'\n optional?: boolean\n }\n): FieldDefinition {\n const entityName = typeof entity === 'string' ? entity : entity.name\n const result: FieldDefinition = {\n type: 'string.uuid',\n db: {\n references: {\n entity: entityName,\n field: options?.field ?? 'id',\n onDelete: options?.onDelete ?? 'restrict',\n },\n },\n }\n if (options?.optional !== undefined) {\n result.optional = options.optional\n }\n return result\n}\n\n/**\n * Create an enum field from a list of values\n */\nexport function enumField<T extends string>(\n values: readonly T[],\n options?: { default?: T; optional?: boolean }\n): FieldDefinition {\n const typeStr = values.map(v => `'${v}'`).join(' | ')\n const result: FieldDefinition = {\n type: typeStr as `'${string}'`,\n }\n if (options?.default !== undefined) {\n result.default = options.default\n }\n if (options?.optional !== undefined) {\n result.optional = options.optional\n }\n return result\n}\n\n/**\n * Create a JSON field\n */\nexport function jsonField<T = Record<string, unknown>>(\n options?: { optional?: boolean; default?: T }\n): FieldDefinition {\n const result: FieldDefinition = {\n type: 'json',\n }\n if (options?.optional !== undefined) {\n result.optional = options.optional\n }\n if (options?.default !== undefined) {\n result.default = options.default\n }\n return result\n}\n\n/**\n * Create a decimal field with precision\n */\nexport function decimal(\n precision: number,\n scale: number,\n options?: { min?: number; max?: number; optional?: boolean }\n): FieldDefinition {\n const result: FieldDefinition = {\n type: 'number',\n db: {\n precision,\n scale,\n },\n }\n if (options?.min !== undefined) {\n result.min = options.min\n }\n if (options?.max !== undefined) {\n result.max = options.max\n }\n if (options?.optional !== undefined) {\n result.optional = options.optional\n }\n return result\n}\n"]}
package/dist/pg.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { a as Entity, F as Field } from './types-CmS0cBdC.js';
2
+ import 'arktype';
3
+
4
+ /**
5
+ * Generate a Drizzle pgTable from an entity definition
6
+ */
7
+ declare function toPgTable<E extends Entity<string, Record<string, Field>, unknown>>(entity: E, tableRefs?: Record<string, any>): any;
8
+ /**
9
+ * Create multiple tables with resolved references
10
+ */
11
+ declare function createPgSchema<T extends Record<string, Entity<string, Record<string, Field>, unknown>>>(entities: T): {
12
+ [K in keyof T]: any;
13
+ };
14
+
15
+ export { createPgSchema, toPgTable, toPgTable as toTable };
package/dist/pg.js ADDED
@@ -0,0 +1,137 @@
1
+ import { uuid, timestamp, pgTable, uniqueIndex, index, text, json, boolean, numeric, integer } from 'drizzle-orm/pg-core';
2
+ import { sql } from 'drizzle-orm';
3
+
4
+ // src/pg.ts
5
+ function fieldToColumn(name, field) {
6
+ const def = typeof field === "string" ? { type: field } : field;
7
+ const columnName = def.db?.column ?? toSnakeCase(name);
8
+ let column;
9
+ const fieldType = def.type.split(" ")[0] ?? "string";
10
+ switch (fieldType) {
11
+ case "string.uuid":
12
+ column = uuid(columnName);
13
+ break;
14
+ case "string.email":
15
+ case "string.url":
16
+ case "string":
17
+ column = text(columnName);
18
+ break;
19
+ case "number.integer":
20
+ column = integer(columnName);
21
+ break;
22
+ case "number":
23
+ if (def.db?.precision) {
24
+ column = numeric(columnName, {
25
+ precision: def.db.precision,
26
+ scale: def.db.scale ?? 2
27
+ });
28
+ } else {
29
+ column = numeric(columnName);
30
+ }
31
+ break;
32
+ case "boolean":
33
+ column = boolean(columnName);
34
+ break;
35
+ case "Date":
36
+ column = timestamp(columnName, { withTimezone: true });
37
+ break;
38
+ case "json":
39
+ column = json(columnName);
40
+ break;
41
+ default:
42
+ column = text(columnName);
43
+ }
44
+ if (!def.optional && def.default === void 0) {
45
+ column = column.notNull();
46
+ }
47
+ if (def.default !== void 0) {
48
+ column = column.default(def.default);
49
+ }
50
+ return column;
51
+ }
52
+ function toSnakeCase(str) {
53
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
54
+ }
55
+ function toPgTable(entity, tableRefs) {
56
+ const def = entity.definition;
57
+ const columns = {};
58
+ columns["id"] = uuid("id").primaryKey().defaultRandom();
59
+ if (def.tenant) {
60
+ const tenantsTable = tableRefs?.["tenants"];
61
+ if (tenantsTable) {
62
+ columns["tenantId"] = uuid("tenant_id").notNull().references(() => tenantsTable.id, { onDelete: "cascade" });
63
+ } else {
64
+ columns["tenantId"] = uuid("tenant_id").notNull();
65
+ }
66
+ }
67
+ for (const [name, field] of Object.entries(def.fields)) {
68
+ const fieldDef = typeof field === "string" ? { } : field;
69
+ if (fieldDef.db?.references && tableRefs) {
70
+ const refTable = tableRefs[fieldDef.db.references.entity];
71
+ if (refTable) {
72
+ const refField = fieldDef.db.references.field ?? "id";
73
+ const onDelete = fieldDef.db.references.onDelete ?? "restrict";
74
+ let col = uuid(toSnakeCase(name)).references(() => refTable[refField], { onDelete });
75
+ if (!fieldDef.optional) {
76
+ col = col.notNull();
77
+ }
78
+ columns[name] = col;
79
+ } else {
80
+ columns[name] = fieldToColumn(name, field);
81
+ }
82
+ } else {
83
+ columns[name] = fieldToColumn(name, field);
84
+ }
85
+ }
86
+ if (def.timestamps) {
87
+ columns["insertedAt"] = timestamp("inserted_at", { withTimezone: true }).notNull().defaultNow();
88
+ columns["updatedAt"] = timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date());
89
+ }
90
+ if (def.softDelete) {
91
+ columns["deletedAt"] = timestamp("deleted_at", { withTimezone: true });
92
+ }
93
+ return pgTable(def.name, columns, (t) => {
94
+ const indexes = {};
95
+ if (def.indexes) {
96
+ for (const idx of def.indexes) {
97
+ const idxName = idx.name ?? `${def.name}_${idx.fields.join("_")}_idx`;
98
+ const idxColumns = idx.fields.map((f) => t[f]).filter(Boolean);
99
+ if (idxColumns.length === 0) continue;
100
+ if (idx.unique) {
101
+ if (idx.where) {
102
+ indexes[idxName] = uniqueIndex(idxName).on(...idxColumns).where(sql.raw(idx.where));
103
+ } else {
104
+ indexes[idxName] = uniqueIndex(idxName).on(...idxColumns);
105
+ }
106
+ } else {
107
+ if (idx.where) {
108
+ indexes[idxName] = index(idxName).on(...idxColumns).where(sql.raw(idx.where));
109
+ } else {
110
+ indexes[idxName] = index(idxName).on(...idxColumns);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ if (def.tenant && t.tenantId) {
116
+ const tenantIdxName = `${def.name}_tenant_id_idx`;
117
+ if (!indexes[tenantIdxName]) {
118
+ indexes[tenantIdxName] = index(tenantIdxName).on(t.tenantId);
119
+ }
120
+ }
121
+ return indexes;
122
+ });
123
+ }
124
+ function createPgSchema(entities) {
125
+ const tables = {};
126
+ for (const [key, entity] of Object.entries(entities)) {
127
+ tables[key] = toPgTable(entity);
128
+ }
129
+ for (const [key, entity] of Object.entries(entities)) {
130
+ tables[key] = toPgTable(entity, tables);
131
+ }
132
+ return tables;
133
+ }
134
+
135
+ export { createPgSchema, toPgTable, toPgTable as toTable };
136
+ //# sourceMappingURL=pg.js.map
137
+ //# sourceMappingURL=pg.js.map