seedorm 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/adapters/postgres/pg-connection.ts","../src/query/operators.ts","../src/adapters/postgres/pg-query-builder.ts","../src/adapters/postgres/postgres-adapter.ts"],"sourcesContent":["// ── Field & Schema Types ──\n\nexport enum FieldType {\n String = \"string\",\n Number = \"number\",\n Boolean = \"boolean\",\n Date = \"date\",\n Json = \"json\",\n Array = \"array\",\n}\n\nexport interface FieldDefinition {\n type: FieldType;\n required?: boolean;\n unique?: boolean;\n index?: boolean;\n default?: unknown;\n minLength?: number;\n maxLength?: number;\n min?: number;\n max?: number;\n enum?: unknown[];\n}\n\nexport interface SchemaDefinition {\n [field: string]: FieldType | FieldDefinition;\n}\n\nexport interface NormalizedField {\n type: FieldType;\n required: boolean;\n unique: boolean;\n index: boolean;\n default?: unknown;\n minLength?: number;\n maxLength?: number;\n min?: number;\n max?: number;\n enum?: unknown[];\n}\n\nexport interface NormalizedSchema {\n [field: string]: NormalizedField;\n}\n\n// ── Document ──\n\nexport interface Document {\n id: string;\n createdAt: string;\n updatedAt: string;\n [key: string]: unknown;\n}\n\n// ── Query / Filter ──\n\nexport interface FilterOperators {\n $eq?: unknown;\n $ne?: unknown;\n $gt?: number | string | Date;\n $gte?: number | string | Date;\n $lt?: number | string | Date;\n $lte?: number | string | Date;\n $in?: unknown[];\n $nin?: unknown[];\n $like?: string;\n $exists?: boolean;\n}\n\nexport type FieldFilter = FilterOperators | unknown;\n\nexport interface FilterQuery {\n [field: string]: FieldFilter;\n}\n\nexport interface SortOption {\n [field: string]: 1 | -1;\n}\n\nexport interface FindOptions {\n filter?: FilterQuery;\n sort?: SortOption;\n limit?: number;\n offset?: number;\n include?: string[];\n}\n\n// ── Relations ──\n\nexport enum RelationType {\n HasOne = \"hasOne\",\n HasMany = \"hasMany\",\n BelongsTo = \"belongsTo\",\n ManyToMany = \"manyToMany\",\n}\n\nexport interface RelationDefinition {\n type: RelationType;\n model: string;\n foreignKey: string;\n joinCollection?: string;\n relatedKey?: string;\n}\n\nexport interface RelationsDefinition {\n [name: string]: RelationDefinition;\n}\n\n// ── Model Definition ──\n\nexport interface ModelDefinition {\n name: string;\n collection: string;\n schema: SchemaDefinition;\n timestamps?: boolean;\n prefix?: string;\n relations?: RelationsDefinition;\n}\n\n// ── Storage Adapter ──\n\nexport interface StorageAdapter {\n connect(): Promise<void>;\n disconnect(): Promise<void>;\n\n insert(collection: string, doc: Document): Promise<Document>;\n findById(collection: string, id: string): Promise<Document | null>;\n find(collection: string, options: FindOptions): Promise<Document[]>;\n count(collection: string, filter?: FilterQuery): Promise<number>;\n update(\n collection: string,\n id: string,\n data: Partial<Document>,\n ): Promise<Document | null>;\n delete(collection: string, id: string): Promise<boolean>;\n deleteMany(collection: string, filter: FilterQuery): Promise<number>;\n\n createCollection(collection: string, schema: NormalizedSchema): Promise<void>;\n dropCollection(collection: string): Promise<void>;\n listCollections(): Promise<string[]>;\n}\n\n// ── Config ──\n\nexport enum AdapterType {\n Json = \"json\",\n Postgres = \"postgres\",\n MySQL = \"mysql\",\n}\n\nexport interface JsonAdapterConfig {\n adapter: AdapterType.Json;\n path?: string;\n}\n\nexport interface PostgresAdapterConfig {\n adapter: AdapterType.Postgres;\n url: string;\n}\n\nexport interface MySQLAdapterConfig {\n adapter: AdapterType.MySQL;\n url: string;\n}\n\nexport type AdapterConfig =\n | JsonAdapterConfig\n | PostgresAdapterConfig\n | MySQLAdapterConfig;\n\nexport interface SeedORMConfig {\n adapter: AdapterConfig;\n migrationsDir?: string;\n}\n\n// ── Migration ──\n\nexport interface MigrationStep {\n type:\n | \"createCollection\"\n | \"dropCollection\"\n | \"addField\"\n | \"dropField\"\n | \"alterField\"\n | \"addIndex\"\n | \"dropIndex\";\n collection: string;\n field?: string;\n schema?: NormalizedField;\n oldSchema?: NormalizedField;\n}\n\nexport interface Migration {\n id: string;\n name: string;\n timestamp: number;\n up: MigrationStep[];\n down: MigrationStep[];\n}\n\nexport interface MigrationRecord {\n id: string;\n name: string;\n appliedAt: string;\n}\n","export class SeedORMError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SeedORMError\";\n }\n}\n\nexport class ValidationError extends SeedORMError {\n public field: string;\n public reason: string;\n\n constructor(field: string, reason: string) {\n super(`Validation error on \"${field}\": ${reason}`);\n this.name = \"ValidationError\";\n this.field = field;\n this.reason = reason;\n }\n}\n\nexport class AdapterError extends SeedORMError {\n public adapter: string;\n\n constructor(adapter: string, message: string) {\n super(`[${adapter}] ${message}`);\n this.name = \"AdapterError\";\n this.adapter = adapter;\n }\n}\n\nexport class CollectionNotFoundError extends SeedORMError {\n constructor(collection: string) {\n super(`Collection \"${collection}\" not found`);\n this.name = \"CollectionNotFoundError\";\n }\n}\n\nexport class DocumentNotFoundError extends SeedORMError {\n constructor(collection: string, id: string) {\n super(`Document \"${id}\" not found in \"${collection}\"`);\n this.name = \"DocumentNotFoundError\";\n }\n}\n\nexport class UniqueConstraintError extends SeedORMError {\n public field: string;\n public value: unknown;\n\n constructor(collection: string, field: string, value: unknown) {\n super(\n `Unique constraint violation on \"${collection}.${field}\": value ${JSON.stringify(value)} already exists`,\n );\n this.name = \"UniqueConstraintError\";\n this.field = field;\n this.value = value;\n }\n}\n","import { AdapterError } from \"../../errors.js\";\n\n// pg is an optional peer dependency\nlet pgModule: typeof import(\"pg\") | null = null;\n\nasync function loadPg(): Promise<typeof import(\"pg\")> {\n if (pgModule) return pgModule;\n try {\n pgModule = await import(\"pg\");\n return pgModule;\n } catch {\n throw new AdapterError(\n \"postgres\",\n 'The \"pg\" package is required for PostgreSQL. Install it with: npm install pg',\n );\n }\n}\n\nexport class PgConnection {\n private pool: import(\"pg\").Pool | null = null;\n private url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n async connect(): Promise<void> {\n const pg = await loadPg();\n this.pool = new pg.Pool({ connectionString: this.url });\n // Test the connection\n const client = await this.pool.connect();\n client.release();\n }\n\n async disconnect(): Promise<void> {\n if (this.pool) {\n await this.pool.end();\n this.pool = null;\n }\n }\n\n async query<T extends Record<string, unknown> = Record<string, unknown>>(\n text: string,\n params?: unknown[],\n ): Promise<{ rows: T[]; rowCount: number }> {\n if (!this.pool) {\n throw new AdapterError(\"postgres\", \"Not connected\");\n }\n const result = await this.pool.query(text, params);\n return { rows: result.rows as T[], rowCount: result.rowCount ?? 0 };\n }\n}\n","import type { FilterOperators } from \"../types.js\";\n\ntype OperatorFn = (fieldValue: unknown, operand: unknown) => boolean;\n\nconst operators: Record<string, OperatorFn> = {\n $eq: (val, op) => val === op,\n\n $ne: (val, op) => val !== op,\n\n $gt: (val, op) => {\n if (typeof val === \"number\" && typeof op === \"number\") return val > op;\n if (typeof val === \"string\" && typeof op === \"string\") return val > op;\n return false;\n },\n\n $gte: (val, op) => {\n if (typeof val === \"number\" && typeof op === \"number\") return val >= op;\n if (typeof val === \"string\" && typeof op === \"string\") return val >= op;\n return false;\n },\n\n $lt: (val, op) => {\n if (typeof val === \"number\" && typeof op === \"number\") return val < op;\n if (typeof val === \"string\" && typeof op === \"string\") return val < op;\n return false;\n },\n\n $lte: (val, op) => {\n if (typeof val === \"number\" && typeof op === \"number\") return val <= op;\n if (typeof val === \"string\" && typeof op === \"string\") return val <= op;\n return false;\n },\n\n $in: (val, op) => {\n if (!Array.isArray(op)) return false;\n return op.includes(val);\n },\n\n $nin: (val, op) => {\n if (!Array.isArray(op)) return true;\n return !op.includes(val);\n },\n\n $like: (val, op) => {\n if (typeof val !== \"string\" || typeof op !== \"string\") return false;\n // Convert SQL-like pattern to regex: % → .*, _ → .\n const escaped = op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = escaped.replace(/%/g, \".*\").replace(/_/g, \".\");\n return new RegExp(`^${pattern}$`, \"i\").test(val);\n },\n\n $exists: (val, op) => {\n const exists = val !== undefined && val !== null;\n return op ? exists : !exists;\n },\n};\n\nexport function applyOperator(\n key: string,\n fieldValue: unknown,\n operand: unknown,\n): boolean {\n const fn = operators[key];\n if (!fn) throw new Error(`Unknown operator: ${key}`);\n return fn(fieldValue, operand);\n}\n\nexport function isOperatorObject(value: unknown): value is FilterOperators {\n if (value === null || typeof value !== \"object\" || Array.isArray(value))\n return false;\n return Object.keys(value as object).some((k) => k.startsWith(\"$\"));\n}\n\nexport const OPERATORS = Object.keys(operators);\n","import type { FilterQuery, FindOptions, SortOption } from \"../../types.js\";\nimport { isOperatorObject } from \"../../query/operators.js\";\n\ninterface BuiltQuery {\n text: string;\n params: unknown[];\n}\n\nexport function buildSelect(\n collection: string,\n options: FindOptions,\n): BuiltQuery {\n const params: unknown[] = [];\n let text = `SELECT * FROM \"${collection}\"`;\n\n if (options.filter && Object.keys(options.filter).length > 0) {\n const where = buildWhere(options.filter, params);\n text += ` WHERE ${where}`;\n }\n\n if (options.sort) {\n text += ` ORDER BY ${buildOrderBy(options.sort)}`;\n }\n\n if (options.limit !== undefined) {\n params.push(options.limit);\n text += ` LIMIT $${params.length}`;\n }\n\n if (options.offset !== undefined) {\n params.push(options.offset);\n text += ` OFFSET $${params.length}`;\n }\n\n return { text, params };\n}\n\nexport function buildCount(\n collection: string,\n filter?: FilterQuery,\n): BuiltQuery {\n const params: unknown[] = [];\n let text = `SELECT COUNT(*) as count FROM \"${collection}\"`;\n\n if (filter && Object.keys(filter).length > 0) {\n const where = buildWhere(filter, params);\n text += ` WHERE ${where}`;\n }\n\n return { text, params };\n}\n\nfunction buildWhere(filter: FilterQuery, params: unknown[]): string {\n const conditions: string[] = [];\n\n for (const [field, condition] of Object.entries(filter)) {\n if (isOperatorObject(condition)) {\n for (const [op, value] of Object.entries(\n condition as Record<string, unknown>,\n )) {\n conditions.push(buildOperator(field, op, value, params));\n }\n } else {\n params.push(condition);\n conditions.push(`\"${field}\" = $${params.length}`);\n }\n }\n\n return conditions.join(\" AND \");\n}\n\nfunction buildOperator(\n field: string,\n op: string,\n value: unknown,\n params: unknown[],\n): string {\n switch (op) {\n case \"$eq\":\n params.push(value);\n return `\"${field}\" = $${params.length}`;\n case \"$ne\":\n params.push(value);\n return `\"${field}\" != $${params.length}`;\n case \"$gt\":\n params.push(value);\n return `\"${field}\" > $${params.length}`;\n case \"$gte\":\n params.push(value);\n return `\"${field}\" >= $${params.length}`;\n case \"$lt\":\n params.push(value);\n return `\"${field}\" < $${params.length}`;\n case \"$lte\":\n params.push(value);\n return `\"${field}\" <= $${params.length}`;\n case \"$in\":\n params.push(value);\n return `\"${field}\" = ANY($${params.length})`;\n case \"$nin\":\n params.push(value);\n return `\"${field}\" != ALL($${params.length})`;\n case \"$like\":\n params.push(value);\n return `\"${field}\" ILIKE $${params.length}`;\n case \"$exists\":\n return value\n ? `\"${field}\" IS NOT NULL`\n : `\"${field}\" IS NULL`;\n default:\n throw new Error(`Unknown operator: ${op}`);\n }\n}\n\nfunction buildOrderBy(sort: SortOption): string {\n return Object.entries(sort)\n .map(([field, dir]) => `\"${field}\" ${dir === 1 ? \"ASC\" : \"DESC\"}`)\n .join(\", \");\n}\n","import {\n FieldType,\n type Document,\n type FilterQuery,\n type FindOptions,\n type NormalizedField,\n type NormalizedSchema,\n type StorageAdapter,\n} from \"../../types.js\";\nimport { CollectionNotFoundError } from \"../../errors.js\";\nimport { PgConnection } from \"./pg-connection.js\";\nimport { buildSelect, buildCount } from \"./pg-query-builder.js\";\n\nfunction fieldToSQLType(field: NormalizedField): string {\n switch (field.type) {\n case FieldType.String:\n return field.maxLength ? `VARCHAR(${field.maxLength})` : \"TEXT\";\n case FieldType.Number:\n return \"DOUBLE PRECISION\";\n case FieldType.Boolean:\n return \"BOOLEAN\";\n case FieldType.Date:\n return \"TIMESTAMPTZ\";\n case FieldType.Json:\n case FieldType.Array:\n return \"JSONB\";\n default:\n return \"TEXT\";\n }\n}\n\nexport class PostgresAdapter implements StorageAdapter {\n private conn: PgConnection;\n\n constructor(url: string) {\n this.conn = new PgConnection(url);\n }\n\n async connect(): Promise<void> {\n await this.conn.connect();\n }\n\n async disconnect(): Promise<void> {\n await this.conn.disconnect();\n }\n\n async createCollection(\n collection: string,\n schema: NormalizedSchema,\n ): Promise<void> {\n const columns = [\n `\"id\" TEXT PRIMARY KEY`,\n `\"createdAt\" TIMESTAMPTZ NOT NULL DEFAULT NOW()`,\n `\"updatedAt\" TIMESTAMPTZ NOT NULL DEFAULT NOW()`,\n ];\n\n for (const [name, def] of Object.entries(schema)) {\n let col = `\"${name}\" ${fieldToSQLType(def)}`;\n if (def.required) col += \" NOT NULL\";\n if (def.unique) col += \" UNIQUE\";\n columns.push(col);\n }\n\n await this.conn.query(\n `CREATE TABLE IF NOT EXISTS \"${collection}\" (${columns.join(\", \")})`,\n );\n\n // Create indexes\n for (const [name, def] of Object.entries(schema)) {\n if (def.index && !def.unique) {\n await this.conn.query(\n `CREATE INDEX IF NOT EXISTS \"idx_${collection}_${name}\" ON \"${collection}\" (\"${name}\")`,\n );\n }\n }\n }\n\n async dropCollection(collection: string): Promise<void> {\n await this.conn.query(`DROP TABLE IF EXISTS \"${collection}\" CASCADE`);\n }\n\n async listCollections(): Promise<string[]> {\n const { rows } = await this.conn.query<{ tablename: string }>(\n `SELECT tablename FROM pg_tables WHERE schemaname = 'public'`,\n );\n return rows.map((r) => r.tablename);\n }\n\n async insert(collection: string, doc: Document): Promise<Document> {\n const keys = Object.keys(doc);\n const cols = keys.map((k) => `\"${k}\"`).join(\", \");\n const placeholders = keys.map((_, i) => `$${i + 1}`).join(\", \");\n const values = keys.map((k) => {\n const v = doc[k];\n return typeof v === \"object\" && v !== null && !(v instanceof Date)\n ? JSON.stringify(v)\n : v;\n });\n\n const { rows } = await this.conn.query<Document>(\n `INSERT INTO \"${collection}\" (${cols}) VALUES (${placeholders}) RETURNING *`,\n values,\n );\n return rows[0]!;\n }\n\n async findById(\n collection: string,\n id: string,\n ): Promise<Document | null> {\n const { rows } = await this.conn.query<Document>(\n `SELECT * FROM \"${collection}\" WHERE \"id\" = $1`,\n [id],\n );\n return rows[0] ?? null;\n }\n\n async find(\n collection: string,\n options: FindOptions,\n ): Promise<Document[]> {\n const q = buildSelect(collection, options);\n const { rows } = await this.conn.query<Document>(q.text, q.params);\n return rows;\n }\n\n async count(collection: string, filter?: FilterQuery): Promise<number> {\n const q = buildCount(collection, filter);\n const { rows } = await this.conn.query<{ count: string }>(\n q.text,\n q.params,\n );\n return parseInt(rows[0]!.count, 10);\n }\n\n async update(\n collection: string,\n id: string,\n data: Partial<Document>,\n ): Promise<Document | null> {\n const entries = Object.entries(data).filter(([k]) => k !== \"id\");\n if (entries.length === 0) return this.findById(collection, id);\n\n const sets = entries.map(([k], i) => `\"${k}\" = $${i + 1}`).join(\", \");\n const values = entries.map(([, v]) =>\n typeof v === \"object\" && v !== null && !(v instanceof Date)\n ? JSON.stringify(v)\n : v,\n );\n values.push(id);\n\n const { rows } = await this.conn.query<Document>(\n `UPDATE \"${collection}\" SET ${sets} WHERE \"id\" = $${values.length} RETURNING *`,\n values,\n );\n return rows[0] ?? null;\n }\n\n async delete(collection: string, id: string): Promise<boolean> {\n const { rowCount } = await this.conn.query(\n `DELETE FROM \"${collection}\" WHERE \"id\" = $1`,\n [id],\n );\n return rowCount > 0;\n }\n\n async deleteMany(\n collection: string,\n filter: FilterQuery,\n ): Promise<number> {\n const q = buildSelect(collection, { filter });\n // Convert SELECT to DELETE\n const deleteText = q.text.replace(/^SELECT \\* FROM/, \"DELETE FROM\");\n const { rowCount } = await this.conn.query(deleteText, q.params);\n return rowCount;\n }\n}\n"],"mappings":"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","names":["FieldType","RelationType","AdapterType"]}
package/dist/index.cjs CHANGED
@@ -30,6 +30,36 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/types.ts
34
+ var FieldType, RelationType, AdapterType;
35
+ var init_types = __esm({
36
+ "src/types.ts"() {
37
+ "use strict";
38
+ FieldType = /* @__PURE__ */ ((FieldType2) => {
39
+ FieldType2["String"] = "string";
40
+ FieldType2["Number"] = "number";
41
+ FieldType2["Boolean"] = "boolean";
42
+ FieldType2["Date"] = "date";
43
+ FieldType2["Json"] = "json";
44
+ FieldType2["Array"] = "array";
45
+ return FieldType2;
46
+ })(FieldType || {});
47
+ RelationType = /* @__PURE__ */ ((RelationType2) => {
48
+ RelationType2["HasOne"] = "hasOne";
49
+ RelationType2["HasMany"] = "hasMany";
50
+ RelationType2["BelongsTo"] = "belongsTo";
51
+ RelationType2["ManyToMany"] = "manyToMany";
52
+ return RelationType2;
53
+ })(RelationType || {});
54
+ AdapterType = /* @__PURE__ */ ((AdapterType2) => {
55
+ AdapterType2["Json"] = "json";
56
+ AdapterType2["Postgres"] = "postgres";
57
+ AdapterType2["MySQL"] = "mysql";
58
+ return AdapterType2;
59
+ })(AdapterType || {});
60
+ }
61
+ });
62
+
33
63
  // src/errors.ts
34
64
  var SeedORMError, ValidationError, AdapterError, CollectionNotFoundError, DocumentNotFoundError, UniqueConstraintError;
35
65
  var init_errors = __esm({
@@ -293,16 +323,16 @@ __export(postgres_adapter_exports, {
293
323
  });
294
324
  function fieldToSQLType(field) {
295
325
  switch (field.type) {
296
- case "string":
326
+ case "string" /* String */:
297
327
  return field.maxLength ? `VARCHAR(${field.maxLength})` : "TEXT";
298
- case "number":
328
+ case "number" /* Number */:
299
329
  return "DOUBLE PRECISION";
300
- case "boolean":
330
+ case "boolean" /* Boolean */:
301
331
  return "BOOLEAN";
302
- case "date":
332
+ case "date" /* Date */:
303
333
  return "TIMESTAMPTZ";
304
- case "json":
305
- case "array":
334
+ case "json" /* Json */:
335
+ case "array" /* Array */:
306
336
  return "JSONB";
307
337
  default:
308
338
  return "TEXT";
@@ -312,6 +342,7 @@ var PostgresAdapter;
312
342
  var init_postgres_adapter = __esm({
313
343
  "src/adapters/postgres/postgres-adapter.ts"() {
314
344
  "use strict";
345
+ init_types();
315
346
  init_pg_connection();
316
347
  init_pg_query_builder();
317
348
  PostgresAdapter = class {
@@ -426,11 +457,14 @@ var init_postgres_adapter = __esm({
426
457
  var index_exports = {};
427
458
  __export(index_exports, {
428
459
  AdapterError: () => AdapterError,
460
+ AdapterType: () => AdapterType,
429
461
  CollectionNotFoundError: () => CollectionNotFoundError,
430
462
  DocumentNotFoundError: () => DocumentNotFoundError,
463
+ FieldType: () => FieldType,
431
464
  JsonAdapter: () => JsonAdapter,
432
465
  Model: () => Model,
433
466
  PostgresAdapter: () => PostgresAdapter,
467
+ RelationType: () => RelationType,
434
468
  SeedORM: () => SeedORM,
435
469
  SeedORMError: () => SeedORMError,
436
470
  UniqueConstraintError: () => UniqueConstraintError,
@@ -441,40 +475,43 @@ __export(index_exports, {
441
475
  module.exports = __toCommonJS(index_exports);
442
476
 
443
477
  // src/seedorm.ts
478
+ init_types();
444
479
  init_errors();
445
480
 
446
481
  // src/model/model.ts
447
482
  var import_nanoid = require("nanoid");
483
+ init_types();
448
484
  init_errors();
449
485
 
450
486
  // src/model/schema.ts
451
487
  init_errors();
452
488
 
453
489
  // src/model/field-types.ts
490
+ init_types();
454
491
  function validateFieldType(value, type) {
455
492
  if (value === void 0 || value === null) return null;
456
493
  switch (type) {
457
- case "string":
494
+ case "string" /* String */:
458
495
  if (typeof value !== "string") return `expected string, got ${typeof value}`;
459
496
  break;
460
- case "number":
497
+ case "number" /* Number */:
461
498
  if (typeof value !== "number" || Number.isNaN(value))
462
499
  return `expected number, got ${typeof value}`;
463
500
  break;
464
- case "boolean":
501
+ case "boolean" /* Boolean */:
465
502
  if (typeof value !== "boolean")
466
503
  return `expected boolean, got ${typeof value}`;
467
504
  break;
468
- case "date":
505
+ case "date" /* Date */:
469
506
  if (typeof value === "string") {
470
507
  if (Number.isNaN(Date.parse(value))) return `invalid date string`;
471
508
  } else if (!(value instanceof Date)) {
472
509
  return `expected date string or Date, got ${typeof value}`;
473
510
  }
474
511
  break;
475
- case "json":
512
+ case "json" /* Json */:
476
513
  break;
477
- case "array":
514
+ case "array" /* Array */:
478
515
  if (!Array.isArray(value)) return `expected array, got ${typeof value}`;
479
516
  break;
480
517
  default:
@@ -484,7 +521,7 @@ function validateFieldType(value, type) {
484
521
  }
485
522
  function coerceFieldValue(value, type) {
486
523
  if (value === void 0 || value === null) return value;
487
- if (type === "date" && value instanceof Date) {
524
+ if (type === "date" /* Date */ && value instanceof Date) {
488
525
  return value.toISOString();
489
526
  }
490
527
  return value;
@@ -578,15 +615,19 @@ var Model = class {
578
615
  collection;
579
616
  schema;
580
617
  prefix;
618
+ relations;
581
619
  adapter;
582
620
  timestamps;
583
- constructor(definition, adapter) {
621
+ db;
622
+ constructor(definition, adapter, db) {
584
623
  this.name = definition.name;
585
624
  this.collection = definition.collection;
586
625
  this.schema = normalizeSchema(definition.schema);
587
626
  this.prefix = definition.prefix ?? definition.collection.slice(0, 3);
588
627
  this.adapter = adapter;
589
628
  this.timestamps = definition.timestamps !== false;
629
+ this.relations = definition.relations ?? {};
630
+ this.db = db ?? null;
590
631
  }
591
632
  async init() {
592
633
  await this.adapter.createCollection(this.collection, this.schema);
@@ -594,6 +635,111 @@ var Model = class {
594
635
  generateId() {
595
636
  return `${this.prefix}_${(0, import_nanoid.nanoid)(12)}`;
596
637
  }
638
+ resolveRelation(relationName) {
639
+ const rel = this.relations[relationName];
640
+ if (!rel) {
641
+ throw new SeedORMError(`Unknown relation "${relationName}" on model "${this.name}"`);
642
+ }
643
+ if (!this.db) {
644
+ throw new SeedORMError("Cannot resolve relations without a SeedORM instance");
645
+ }
646
+ const relatedModel = this.db.getModel(rel.model);
647
+ if (!relatedModel) {
648
+ throw new SeedORMError(`Related model "${rel.model}" not found. Make sure it is defined before querying.`);
649
+ }
650
+ return { rel, relatedModel };
651
+ }
652
+ async populate(docs, includeList) {
653
+ if (includeList.length === 0 || docs.length === 0) return docs;
654
+ docs = docs.map((d) => ({ ...d }));
655
+ for (const relationName of includeList) {
656
+ const { rel, relatedModel } = this.resolveRelation(relationName);
657
+ switch (rel.type) {
658
+ case "hasMany" /* HasMany */: {
659
+ const parentIds = docs.map((d) => d.id);
660
+ const related = await this.adapter.find(relatedModel.collection, {
661
+ filter: { [rel.foreignKey]: { $in: parentIds } }
662
+ });
663
+ const grouped = /* @__PURE__ */ new Map();
664
+ for (const r of related) {
665
+ const fk = r[rel.foreignKey];
666
+ if (!grouped.has(fk)) grouped.set(fk, []);
667
+ grouped.get(fk).push(r);
668
+ }
669
+ for (const doc of docs) {
670
+ doc[relationName] = grouped.get(doc.id) ?? [];
671
+ }
672
+ break;
673
+ }
674
+ case "hasOne" /* HasOne */: {
675
+ const parentIds = docs.map((d) => d.id);
676
+ const related = await this.adapter.find(relatedModel.collection, {
677
+ filter: { [rel.foreignKey]: { $in: parentIds } }
678
+ });
679
+ const map = /* @__PURE__ */ new Map();
680
+ for (const r of related) {
681
+ const fk = r[rel.foreignKey];
682
+ if (!map.has(fk)) map.set(fk, r);
683
+ }
684
+ for (const doc of docs) {
685
+ doc[relationName] = map.get(doc.id) ?? null;
686
+ }
687
+ break;
688
+ }
689
+ case "belongsTo" /* BelongsTo */: {
690
+ const fkValues = [...new Set(docs.map((d) => d[rel.foreignKey]).filter(Boolean))];
691
+ if (fkValues.length === 0) {
692
+ for (const doc of docs) doc[relationName] = null;
693
+ break;
694
+ }
695
+ const related = await this.adapter.find(relatedModel.collection, {
696
+ filter: { id: { $in: fkValues } }
697
+ });
698
+ const map = /* @__PURE__ */ new Map();
699
+ for (const r of related) {
700
+ map.set(r.id, r);
701
+ }
702
+ for (const doc of docs) {
703
+ const fk = doc[rel.foreignKey];
704
+ doc[relationName] = map.get(fk) ?? null;
705
+ }
706
+ break;
707
+ }
708
+ case "manyToMany" /* ManyToMany */: {
709
+ if (!rel.joinCollection || !rel.relatedKey) {
710
+ throw new SeedORMError(
711
+ `manyToMany relation "${relationName}" requires joinCollection and relatedKey`
712
+ );
713
+ }
714
+ const parentIds = docs.map((d) => d.id);
715
+ const joinRows = await this.adapter.find(rel.joinCollection, {
716
+ filter: { [rel.foreignKey]: { $in: parentIds } }
717
+ });
718
+ const relatedIds = [...new Set(joinRows.map((r) => r[rel.relatedKey]))];
719
+ const relatedDocs = relatedIds.length > 0 ? await this.adapter.find(relatedModel.collection, {
720
+ filter: { id: { $in: relatedIds } }
721
+ }) : [];
722
+ const relatedMap = /* @__PURE__ */ new Map();
723
+ for (const r of relatedDocs) relatedMap.set(r.id, r);
724
+ const grouped = /* @__PURE__ */ new Map();
725
+ for (const row of joinRows) {
726
+ const parentId = row[rel.foreignKey];
727
+ const relatedId = row[rel.relatedKey];
728
+ const relatedDoc = relatedMap.get(relatedId);
729
+ if (relatedDoc) {
730
+ if (!grouped.has(parentId)) grouped.set(parentId, []);
731
+ grouped.get(parentId).push(relatedDoc);
732
+ }
733
+ }
734
+ for (const doc of docs) {
735
+ doc[relationName] = grouped.get(doc.id) ?? [];
736
+ }
737
+ break;
738
+ }
739
+ }
740
+ }
741
+ return docs;
742
+ }
597
743
  async create(data) {
598
744
  const validated = validateDocument(data, this.schema);
599
745
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -612,23 +758,32 @@ var Model = class {
612
758
  }
613
759
  return results;
614
760
  }
615
- async findById(id) {
616
- return this.adapter.findById(this.collection, id);
761
+ async findById(id, options) {
762
+ const doc = await this.adapter.findById(this.collection, id);
763
+ if (!doc || !options?.include?.length) return doc;
764
+ const [populated] = await this.populate([doc], options.include);
765
+ return populated ?? null;
617
766
  }
618
- async findByIdOrThrow(id) {
619
- const doc = await this.findById(id);
767
+ async findByIdOrThrow(id, options) {
768
+ const doc = await this.findById(id, options);
620
769
  if (!doc) throw new DocumentNotFoundError(this.collection, id);
621
770
  return doc;
622
771
  }
623
- async findOne(filter) {
772
+ async findOne(filter, options) {
624
773
  const results = await this.adapter.find(this.collection, {
625
774
  filter,
626
775
  limit: 1
627
776
  });
628
- return results[0] ?? null;
777
+ const doc = results[0] ?? null;
778
+ if (!doc || !options?.include?.length) return doc;
779
+ const [populated] = await this.populate([doc], options.include);
780
+ return populated ?? null;
629
781
  }
630
782
  async find(options = {}) {
631
- return this.adapter.find(this.collection, options);
783
+ const { include, ...adapterOptions } = options;
784
+ const docs = await this.adapter.find(this.collection, adapterOptions);
785
+ if (!include?.length) return docs;
786
+ return this.populate(docs, include);
632
787
  }
633
788
  async findAll() {
634
789
  return this.adapter.find(this.collection, {});
@@ -654,6 +809,41 @@ var Model = class {
654
809
  async deleteMany(filter) {
655
810
  return this.adapter.deleteMany(this.collection, filter);
656
811
  }
812
+ async associate(id, relationName, relatedId) {
813
+ const { rel } = this.resolveRelation(relationName);
814
+ if (rel.type !== "manyToMany" /* ManyToMany */) {
815
+ throw new SeedORMError(`associate() is only supported for manyToMany relations, got "${rel.type}"`);
816
+ }
817
+ if (!rel.joinCollection || !rel.relatedKey) {
818
+ throw new SeedORMError(
819
+ `manyToMany relation "${relationName}" requires joinCollection and relatedKey`
820
+ );
821
+ }
822
+ const now = (/* @__PURE__ */ new Date()).toISOString();
823
+ const joinDoc = {
824
+ id: `${this.prefix}rel_${(0, import_nanoid.nanoid)(12)}`,
825
+ [rel.foreignKey]: id,
826
+ [rel.relatedKey]: relatedId,
827
+ createdAt: now,
828
+ updatedAt: now
829
+ };
830
+ return this.adapter.insert(rel.joinCollection, joinDoc);
831
+ }
832
+ async dissociate(id, relationName, relatedId) {
833
+ const { rel } = this.resolveRelation(relationName);
834
+ if (rel.type !== "manyToMany" /* ManyToMany */) {
835
+ throw new SeedORMError(`dissociate() is only supported for manyToMany relations, got "${rel.type}"`);
836
+ }
837
+ if (!rel.joinCollection || !rel.relatedKey) {
838
+ throw new SeedORMError(
839
+ `manyToMany relation "${relationName}" requires joinCollection and relatedKey`
840
+ );
841
+ }
842
+ return this.adapter.deleteMany(rel.joinCollection, {
843
+ [rel.foreignKey]: id,
844
+ [rel.relatedKey]: relatedId
845
+ });
846
+ }
657
847
  };
658
848
 
659
849
  // src/adapters/json/json-adapter.ts
@@ -973,24 +1163,24 @@ var SeedORM = class {
973
1163
  connected = false;
974
1164
  constructor(config) {
975
1165
  this.config = {
976
- adapter: config?.adapter ?? { adapter: "json", path: "./data" },
1166
+ adapter: config?.adapter ?? { adapter: "json" /* Json */, path: "./data" },
977
1167
  migrationsDir: config?.migrationsDir ?? "./migrations"
978
1168
  };
979
1169
  }
980
1170
  async createAdapter(adapterConfig) {
981
1171
  switch (adapterConfig.adapter) {
982
- case "json": {
1172
+ case "json" /* Json */: {
983
1173
  const dbPath = path2.resolve(
984
1174
  adapterConfig.path ?? "./data",
985
1175
  "seedorm.json"
986
1176
  );
987
1177
  return new JsonAdapter(dbPath);
988
1178
  }
989
- case "postgres": {
1179
+ case "postgres" /* Postgres */: {
990
1180
  const { PostgresAdapter: PostgresAdapter2 } = await Promise.resolve().then(() => (init_postgres_adapter(), postgres_adapter_exports));
991
1181
  return new PostgresAdapter2(adapterConfig.url);
992
1182
  }
993
- case "mysql":
1183
+ case "mysql" /* MySQL */:
994
1184
  throw new SeedORMError(
995
1185
  'MySQL adapter requires the "mysql2" package. Install it with: npm install mysql2'
996
1186
  );
@@ -1024,7 +1214,7 @@ var SeedORM = class {
1024
1214
  "Not connected. Call db.connect() before defining models."
1025
1215
  );
1026
1216
  }
1027
- const model = new Model(definition, this.adapter);
1217
+ const model = new Model(definition, this.adapter, this);
1028
1218
  this.models.set(definition.name, model);
1029
1219
  return model;
1030
1220
  }
@@ -1044,15 +1234,19 @@ var SeedORM = class {
1044
1234
 
1045
1235
  // src/index.ts
1046
1236
  init_postgres_adapter();
1237
+ init_types();
1047
1238
  init_errors();
1048
1239
  // Annotate the CommonJS export names for ESM import in node:
1049
1240
  0 && (module.exports = {
1050
1241
  AdapterError,
1242
+ AdapterType,
1051
1243
  CollectionNotFoundError,
1052
1244
  DocumentNotFoundError,
1245
+ FieldType,
1053
1246
  JsonAdapter,
1054
1247
  Model,
1055
1248
  PostgresAdapter,
1249
+ RelationType,
1056
1250
  SeedORM,
1057
1251
  SeedORMError,
1058
1252
  UniqueConstraintError,