@parsrun/entity 0.1.36 → 0.1.38

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/dist/index.js CHANGED
@@ -5,6 +5,9 @@ export { type } from 'arktype';
5
5
  function fieldToArkType(field) {
6
6
  const def = typeof field === "string" ? { type: field } : field;
7
7
  let typeStr = def.type;
8
+ if (typeStr === "json") {
9
+ typeStr = "object";
10
+ }
8
11
  if (def.min !== void 0 || def.max !== void 0) {
9
12
  if (typeStr === "string" || typeStr.startsWith("string")) {
10
13
  if (def.min !== void 0 && def.max !== void 0) {
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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 json type - ArkType doesn't have 'json', use 'object' instead\n if (typeStr === 'json') {\n typeStr = 'object'\n }\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parsrun/entity",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Single-source entity definitions for Pars - generates ArkType schemas and Drizzle tables",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "arktype": "^2.0.0",
29
- "@parsrun/core": "0.1.36"
29
+ "@parsrun/core": "0.1.38"
30
30
  },
31
31
  "devDependencies": {
32
32
  "drizzle-orm": "^0.44.0",
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { defineEntity, ref, enumField, jsonField, decimal } from './define.js'
3
+
4
+ describe('defineEntity', () => {
5
+ it('should create entity with basic fields', () => {
6
+ const Product = defineEntity({
7
+ name: 'products',
8
+ fields: {
9
+ name: 'string',
10
+ price: 'number',
11
+ },
12
+ })
13
+
14
+ expect(Product.name).toBe('products')
15
+ expect(Product.requiredFields).toContain('name')
16
+ expect(Product.requiredFields).toContain('price')
17
+ expect(Product.autoFields).toContain('id')
18
+ })
19
+
20
+ it('should handle tenant-scoped entities', () => {
21
+ const Order = defineEntity({
22
+ name: 'orders',
23
+ tenant: true,
24
+ fields: {
25
+ total: 'number',
26
+ },
27
+ })
28
+
29
+ expect(Order.requiredFields).toContain('tenantId')
30
+ })
31
+
32
+ it('should handle timestamps', () => {
33
+ const Post = defineEntity({
34
+ name: 'posts',
35
+ timestamps: true,
36
+ fields: {
37
+ title: 'string',
38
+ },
39
+ })
40
+
41
+ expect(Post.autoFields).toContain('insertedAt')
42
+ expect(Post.autoFields).toContain('updatedAt')
43
+ })
44
+
45
+ it('should handle soft delete', () => {
46
+ const User = defineEntity({
47
+ name: 'users',
48
+ softDelete: true,
49
+ fields: {
50
+ email: 'string.email',
51
+ },
52
+ })
53
+
54
+ expect(User.autoFields).toContain('deletedAt')
55
+ })
56
+
57
+ it('should handle optional fields', () => {
58
+ const Profile = defineEntity({
59
+ name: 'profiles',
60
+ fields: {
61
+ bio: { type: 'string', optional: true },
62
+ age: 'number',
63
+ },
64
+ })
65
+
66
+ expect(Profile.optionalFields).toContain('bio')
67
+ expect(Profile.requiredFields).toContain('age')
68
+ })
69
+
70
+ it('should handle fields with defaults', () => {
71
+ const Settings = defineEntity({
72
+ name: 'settings',
73
+ fields: {
74
+ theme: { type: 'string', default: 'light' },
75
+ },
76
+ })
77
+
78
+ expect(Settings.optionalFields).toContain('theme')
79
+ })
80
+ })
81
+
82
+ describe('helper functions', () => {
83
+ it('ref should create a reference field', () => {
84
+ const field = ref('users', { onDelete: 'cascade' })
85
+
86
+ expect(field.type).toBe('string.uuid')
87
+ expect(field.db?.references?.entity).toBe('users')
88
+ expect(field.db?.references?.onDelete).toBe('cascade')
89
+ })
90
+
91
+ it('enumField should create union type', () => {
92
+ const field = enumField(['draft', 'published', 'archived'] as const)
93
+
94
+ expect(field.type).toBe("'draft' | 'published' | 'archived'")
95
+ })
96
+
97
+ it('jsonField should create json field', () => {
98
+ const field = jsonField({ optional: true })
99
+
100
+ expect(field.type).toBe('json')
101
+ expect(field.optional).toBe(true)
102
+ })
103
+
104
+ it('decimal should create decimal field', () => {
105
+ const field = decimal(10, 2, { min: 0 })
106
+
107
+ expect(field.type).toBe('number')
108
+ expect(field.db?.precision).toBe(10)
109
+ expect(field.db?.scale).toBe(2)
110
+ expect(field.min).toBe(0)
111
+ })
112
+ })
package/src/define.ts CHANGED
@@ -16,6 +16,11 @@ function fieldToArkType(field: Field): string {
16
16
 
17
17
  let typeStr = def.type
18
18
 
19
+ // Handle json type - ArkType doesn't have 'json', use 'object' instead
20
+ if (typeStr === 'json') {
21
+ typeStr = 'object'
22
+ }
23
+
19
24
  // Handle min/max constraints
20
25
  if (def.min !== undefined || def.max !== undefined) {
21
26
  if (typeStr === 'string' || typeStr.startsWith('string')) {