seedorm 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1063 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/errors.ts
34
+ var SeedORMError, ValidationError, AdapterError, CollectionNotFoundError, DocumentNotFoundError, UniqueConstraintError;
35
+ var init_errors = __esm({
36
+ "src/errors.ts"() {
37
+ "use strict";
38
+ SeedORMError = class extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "SeedORMError";
42
+ }
43
+ };
44
+ ValidationError = class extends SeedORMError {
45
+ field;
46
+ reason;
47
+ constructor(field, reason) {
48
+ super(`Validation error on "${field}": ${reason}`);
49
+ this.name = "ValidationError";
50
+ this.field = field;
51
+ this.reason = reason;
52
+ }
53
+ };
54
+ AdapterError = class extends SeedORMError {
55
+ adapter;
56
+ constructor(adapter, message) {
57
+ super(`[${adapter}] ${message}`);
58
+ this.name = "AdapterError";
59
+ this.adapter = adapter;
60
+ }
61
+ };
62
+ CollectionNotFoundError = class extends SeedORMError {
63
+ constructor(collection) {
64
+ super(`Collection "${collection}" not found`);
65
+ this.name = "CollectionNotFoundError";
66
+ }
67
+ };
68
+ DocumentNotFoundError = class extends SeedORMError {
69
+ constructor(collection, id) {
70
+ super(`Document "${id}" not found in "${collection}"`);
71
+ this.name = "DocumentNotFoundError";
72
+ }
73
+ };
74
+ UniqueConstraintError = class extends SeedORMError {
75
+ field;
76
+ value;
77
+ constructor(collection, field, value) {
78
+ super(
79
+ `Unique constraint violation on "${collection}.${field}": value ${JSON.stringify(value)} already exists`
80
+ );
81
+ this.name = "UniqueConstraintError";
82
+ this.field = field;
83
+ this.value = value;
84
+ }
85
+ };
86
+ }
87
+ });
88
+
89
+ // src/query/operators.ts
90
+ function applyOperator(key, fieldValue, operand) {
91
+ const fn = operators[key];
92
+ if (!fn) throw new Error(`Unknown operator: ${key}`);
93
+ return fn(fieldValue, operand);
94
+ }
95
+ function isOperatorObject(value) {
96
+ if (value === null || typeof value !== "object" || Array.isArray(value))
97
+ return false;
98
+ return Object.keys(value).some((k) => k.startsWith("$"));
99
+ }
100
+ var operators, OPERATORS;
101
+ var init_operators = __esm({
102
+ "src/query/operators.ts"() {
103
+ "use strict";
104
+ operators = {
105
+ $eq: (val, op) => val === op,
106
+ $ne: (val, op) => val !== op,
107
+ $gt: (val, op) => {
108
+ if (typeof val === "number" && typeof op === "number") return val > op;
109
+ if (typeof val === "string" && typeof op === "string") return val > op;
110
+ return false;
111
+ },
112
+ $gte: (val, op) => {
113
+ if (typeof val === "number" && typeof op === "number") return val >= op;
114
+ if (typeof val === "string" && typeof op === "string") return val >= op;
115
+ return false;
116
+ },
117
+ $lt: (val, op) => {
118
+ if (typeof val === "number" && typeof op === "number") return val < op;
119
+ if (typeof val === "string" && typeof op === "string") return val < op;
120
+ return false;
121
+ },
122
+ $lte: (val, op) => {
123
+ if (typeof val === "number" && typeof op === "number") return val <= op;
124
+ if (typeof val === "string" && typeof op === "string") return val <= op;
125
+ return false;
126
+ },
127
+ $in: (val, op) => {
128
+ if (!Array.isArray(op)) return false;
129
+ return op.includes(val);
130
+ },
131
+ $nin: (val, op) => {
132
+ if (!Array.isArray(op)) return true;
133
+ return !op.includes(val);
134
+ },
135
+ $like: (val, op) => {
136
+ if (typeof val !== "string" || typeof op !== "string") return false;
137
+ const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
138
+ const pattern = escaped.replace(/%/g, ".*").replace(/_/g, ".");
139
+ return new RegExp(`^${pattern}$`, "i").test(val);
140
+ },
141
+ $exists: (val, op) => {
142
+ const exists = val !== void 0 && val !== null;
143
+ return op ? exists : !exists;
144
+ }
145
+ };
146
+ OPERATORS = Object.keys(operators);
147
+ }
148
+ });
149
+
150
+ // src/adapters/postgres/pg-connection.ts
151
+ async function loadPg() {
152
+ if (pgModule) return pgModule;
153
+ try {
154
+ pgModule = await import("pg");
155
+ return pgModule;
156
+ } catch {
157
+ throw new AdapterError(
158
+ "postgres",
159
+ 'The "pg" package is required for PostgreSQL. Install it with: npm install pg'
160
+ );
161
+ }
162
+ }
163
+ var pgModule, PgConnection;
164
+ var init_pg_connection = __esm({
165
+ "src/adapters/postgres/pg-connection.ts"() {
166
+ "use strict";
167
+ init_errors();
168
+ pgModule = null;
169
+ PgConnection = class {
170
+ pool = null;
171
+ url;
172
+ constructor(url) {
173
+ this.url = url;
174
+ }
175
+ async connect() {
176
+ const pg = await loadPg();
177
+ this.pool = new pg.Pool({ connectionString: this.url });
178
+ const client = await this.pool.connect();
179
+ client.release();
180
+ }
181
+ async disconnect() {
182
+ if (this.pool) {
183
+ await this.pool.end();
184
+ this.pool = null;
185
+ }
186
+ }
187
+ async query(text, params) {
188
+ if (!this.pool) {
189
+ throw new AdapterError("postgres", "Not connected");
190
+ }
191
+ const result = await this.pool.query(text, params);
192
+ return { rows: result.rows, rowCount: result.rowCount ?? 0 };
193
+ }
194
+ };
195
+ }
196
+ });
197
+
198
+ // src/adapters/postgres/pg-query-builder.ts
199
+ function buildSelect(collection, options) {
200
+ const params = [];
201
+ let text = `SELECT * FROM "${collection}"`;
202
+ if (options.filter && Object.keys(options.filter).length > 0) {
203
+ const where = buildWhere(options.filter, params);
204
+ text += ` WHERE ${where}`;
205
+ }
206
+ if (options.sort) {
207
+ text += ` ORDER BY ${buildOrderBy(options.sort)}`;
208
+ }
209
+ if (options.limit !== void 0) {
210
+ params.push(options.limit);
211
+ text += ` LIMIT $${params.length}`;
212
+ }
213
+ if (options.offset !== void 0) {
214
+ params.push(options.offset);
215
+ text += ` OFFSET $${params.length}`;
216
+ }
217
+ return { text, params };
218
+ }
219
+ function buildCount(collection, filter) {
220
+ const params = [];
221
+ let text = `SELECT COUNT(*) as count FROM "${collection}"`;
222
+ if (filter && Object.keys(filter).length > 0) {
223
+ const where = buildWhere(filter, params);
224
+ text += ` WHERE ${where}`;
225
+ }
226
+ return { text, params };
227
+ }
228
+ function buildWhere(filter, params) {
229
+ const conditions = [];
230
+ for (const [field, condition] of Object.entries(filter)) {
231
+ if (isOperatorObject(condition)) {
232
+ for (const [op, value] of Object.entries(
233
+ condition
234
+ )) {
235
+ conditions.push(buildOperator(field, op, value, params));
236
+ }
237
+ } else {
238
+ params.push(condition);
239
+ conditions.push(`"${field}" = $${params.length}`);
240
+ }
241
+ }
242
+ return conditions.join(" AND ");
243
+ }
244
+ function buildOperator(field, op, value, params) {
245
+ switch (op) {
246
+ case "$eq":
247
+ params.push(value);
248
+ return `"${field}" = $${params.length}`;
249
+ case "$ne":
250
+ params.push(value);
251
+ return `"${field}" != $${params.length}`;
252
+ case "$gt":
253
+ params.push(value);
254
+ return `"${field}" > $${params.length}`;
255
+ case "$gte":
256
+ params.push(value);
257
+ return `"${field}" >= $${params.length}`;
258
+ case "$lt":
259
+ params.push(value);
260
+ return `"${field}" < $${params.length}`;
261
+ case "$lte":
262
+ params.push(value);
263
+ return `"${field}" <= $${params.length}`;
264
+ case "$in":
265
+ params.push(value);
266
+ return `"${field}" = ANY($${params.length})`;
267
+ case "$nin":
268
+ params.push(value);
269
+ return `"${field}" != ALL($${params.length})`;
270
+ case "$like":
271
+ params.push(value);
272
+ return `"${field}" ILIKE $${params.length}`;
273
+ case "$exists":
274
+ return value ? `"${field}" IS NOT NULL` : `"${field}" IS NULL`;
275
+ default:
276
+ throw new Error(`Unknown operator: ${op}`);
277
+ }
278
+ }
279
+ function buildOrderBy(sort) {
280
+ return Object.entries(sort).map(([field, dir]) => `"${field}" ${dir === 1 ? "ASC" : "DESC"}`).join(", ");
281
+ }
282
+ var init_pg_query_builder = __esm({
283
+ "src/adapters/postgres/pg-query-builder.ts"() {
284
+ "use strict";
285
+ init_operators();
286
+ }
287
+ });
288
+
289
+ // src/adapters/postgres/postgres-adapter.ts
290
+ var postgres_adapter_exports = {};
291
+ __export(postgres_adapter_exports, {
292
+ PostgresAdapter: () => PostgresAdapter
293
+ });
294
+ function fieldToSQLType(field) {
295
+ switch (field.type) {
296
+ case "string":
297
+ return field.maxLength ? `VARCHAR(${field.maxLength})` : "TEXT";
298
+ case "number":
299
+ return "DOUBLE PRECISION";
300
+ case "boolean":
301
+ return "BOOLEAN";
302
+ case "date":
303
+ return "TIMESTAMPTZ";
304
+ case "json":
305
+ case "array":
306
+ return "JSONB";
307
+ default:
308
+ return "TEXT";
309
+ }
310
+ }
311
+ var PostgresAdapter;
312
+ var init_postgres_adapter = __esm({
313
+ "src/adapters/postgres/postgres-adapter.ts"() {
314
+ "use strict";
315
+ init_pg_connection();
316
+ init_pg_query_builder();
317
+ PostgresAdapter = class {
318
+ conn;
319
+ constructor(url) {
320
+ this.conn = new PgConnection(url);
321
+ }
322
+ async connect() {
323
+ await this.conn.connect();
324
+ }
325
+ async disconnect() {
326
+ await this.conn.disconnect();
327
+ }
328
+ async createCollection(collection, schema) {
329
+ const columns = [
330
+ `"id" TEXT PRIMARY KEY`,
331
+ `"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
332
+ `"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()`
333
+ ];
334
+ for (const [name, def] of Object.entries(schema)) {
335
+ let col = `"${name}" ${fieldToSQLType(def)}`;
336
+ if (def.required) col += " NOT NULL";
337
+ if (def.unique) col += " UNIQUE";
338
+ columns.push(col);
339
+ }
340
+ await this.conn.query(
341
+ `CREATE TABLE IF NOT EXISTS "${collection}" (${columns.join(", ")})`
342
+ );
343
+ for (const [name, def] of Object.entries(schema)) {
344
+ if (def.index && !def.unique) {
345
+ await this.conn.query(
346
+ `CREATE INDEX IF NOT EXISTS "idx_${collection}_${name}" ON "${collection}" ("${name}")`
347
+ );
348
+ }
349
+ }
350
+ }
351
+ async dropCollection(collection) {
352
+ await this.conn.query(`DROP TABLE IF EXISTS "${collection}" CASCADE`);
353
+ }
354
+ async listCollections() {
355
+ const { rows } = await this.conn.query(
356
+ `SELECT tablename FROM pg_tables WHERE schemaname = 'public'`
357
+ );
358
+ return rows.map((r) => r.tablename);
359
+ }
360
+ async insert(collection, doc) {
361
+ const keys = Object.keys(doc);
362
+ const cols = keys.map((k) => `"${k}"`).join(", ");
363
+ const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
364
+ const values = keys.map((k) => {
365
+ const v = doc[k];
366
+ return typeof v === "object" && v !== null && !(v instanceof Date) ? JSON.stringify(v) : v;
367
+ });
368
+ const { rows } = await this.conn.query(
369
+ `INSERT INTO "${collection}" (${cols}) VALUES (${placeholders}) RETURNING *`,
370
+ values
371
+ );
372
+ return rows[0];
373
+ }
374
+ async findById(collection, id) {
375
+ const { rows } = await this.conn.query(
376
+ `SELECT * FROM "${collection}" WHERE "id" = $1`,
377
+ [id]
378
+ );
379
+ return rows[0] ?? null;
380
+ }
381
+ async find(collection, options) {
382
+ const q = buildSelect(collection, options);
383
+ const { rows } = await this.conn.query(q.text, q.params);
384
+ return rows;
385
+ }
386
+ async count(collection, filter) {
387
+ const q = buildCount(collection, filter);
388
+ const { rows } = await this.conn.query(
389
+ q.text,
390
+ q.params
391
+ );
392
+ return parseInt(rows[0].count, 10);
393
+ }
394
+ async update(collection, id, data) {
395
+ const entries = Object.entries(data).filter(([k]) => k !== "id");
396
+ if (entries.length === 0) return this.findById(collection, id);
397
+ const sets = entries.map(([k], i) => `"${k}" = $${i + 1}`).join(", ");
398
+ const values = entries.map(
399
+ ([, v]) => typeof v === "object" && v !== null && !(v instanceof Date) ? JSON.stringify(v) : v
400
+ );
401
+ values.push(id);
402
+ const { rows } = await this.conn.query(
403
+ `UPDATE "${collection}" SET ${sets} WHERE "id" = $${values.length} RETURNING *`,
404
+ values
405
+ );
406
+ return rows[0] ?? null;
407
+ }
408
+ async delete(collection, id) {
409
+ const { rowCount } = await this.conn.query(
410
+ `DELETE FROM "${collection}" WHERE "id" = $1`,
411
+ [id]
412
+ );
413
+ return rowCount > 0;
414
+ }
415
+ async deleteMany(collection, filter) {
416
+ const q = buildSelect(collection, { filter });
417
+ const deleteText = q.text.replace(/^SELECT \* FROM/, "DELETE FROM");
418
+ const { rowCount } = await this.conn.query(deleteText, q.params);
419
+ return rowCount;
420
+ }
421
+ };
422
+ }
423
+ });
424
+
425
+ // src/index.ts
426
+ var index_exports = {};
427
+ __export(index_exports, {
428
+ AdapterError: () => AdapterError,
429
+ CollectionNotFoundError: () => CollectionNotFoundError,
430
+ DocumentNotFoundError: () => DocumentNotFoundError,
431
+ JsonAdapter: () => JsonAdapter,
432
+ Model: () => Model,
433
+ PostgresAdapter: () => PostgresAdapter,
434
+ SeedORM: () => SeedORM,
435
+ SeedORMError: () => SeedORMError,
436
+ UniqueConstraintError: () => UniqueConstraintError,
437
+ ValidationError: () => ValidationError,
438
+ normalizeSchema: () => normalizeSchema,
439
+ validateDocument: () => validateDocument
440
+ });
441
+ module.exports = __toCommonJS(index_exports);
442
+
443
+ // src/seedorm.ts
444
+ init_errors();
445
+
446
+ // src/model/model.ts
447
+ var import_nanoid = require("nanoid");
448
+ init_errors();
449
+
450
+ // src/model/schema.ts
451
+ init_errors();
452
+
453
+ // src/model/field-types.ts
454
+ function validateFieldType(value, type) {
455
+ if (value === void 0 || value === null) return null;
456
+ switch (type) {
457
+ case "string":
458
+ if (typeof value !== "string") return `expected string, got ${typeof value}`;
459
+ break;
460
+ case "number":
461
+ if (typeof value !== "number" || Number.isNaN(value))
462
+ return `expected number, got ${typeof value}`;
463
+ break;
464
+ case "boolean":
465
+ if (typeof value !== "boolean")
466
+ return `expected boolean, got ${typeof value}`;
467
+ break;
468
+ case "date":
469
+ if (typeof value === "string") {
470
+ if (Number.isNaN(Date.parse(value))) return `invalid date string`;
471
+ } else if (!(value instanceof Date)) {
472
+ return `expected date string or Date, got ${typeof value}`;
473
+ }
474
+ break;
475
+ case "json":
476
+ break;
477
+ case "array":
478
+ if (!Array.isArray(value)) return `expected array, got ${typeof value}`;
479
+ break;
480
+ default:
481
+ return `unknown type: ${type}`;
482
+ }
483
+ return null;
484
+ }
485
+ function coerceFieldValue(value, type) {
486
+ if (value === void 0 || value === null) return value;
487
+ if (type === "date" && value instanceof Date) {
488
+ return value.toISOString();
489
+ }
490
+ return value;
491
+ }
492
+
493
+ // src/model/schema.ts
494
+ function normalizeSchema(schema) {
495
+ const normalized = {};
496
+ for (const [field, def] of Object.entries(schema)) {
497
+ if (typeof def === "string") {
498
+ normalized[field] = {
499
+ type: def,
500
+ required: false,
501
+ unique: false,
502
+ index: false
503
+ };
504
+ } else {
505
+ const d = def;
506
+ normalized[field] = {
507
+ type: d.type,
508
+ required: d.required ?? false,
509
+ unique: d.unique ?? false,
510
+ index: d.index ?? d.unique ?? false,
511
+ ...d.default !== void 0 && { default: d.default },
512
+ ...d.minLength !== void 0 && { minLength: d.minLength },
513
+ ...d.maxLength !== void 0 && { maxLength: d.maxLength },
514
+ ...d.min !== void 0 && { min: d.min },
515
+ ...d.max !== void 0 && { max: d.max },
516
+ ...d.enum !== void 0 && { enum: d.enum }
517
+ };
518
+ }
519
+ }
520
+ return normalized;
521
+ }
522
+ function validateDocument(data, schema, isUpdate = false) {
523
+ const result = {};
524
+ for (const [field, def] of Object.entries(schema)) {
525
+ let value = data[field];
526
+ if (value === void 0 && def.default !== void 0 && !isUpdate) {
527
+ value = typeof def.default === "function" ? def.default() : def.default;
528
+ }
529
+ if (!isUpdate && def.required && (value === void 0 || value === null)) {
530
+ throw new ValidationError(field, "field is required");
531
+ }
532
+ if (value === void 0) continue;
533
+ const typeErr = validateFieldType(value, def.type);
534
+ if (typeErr) throw new ValidationError(field, typeErr);
535
+ if (def.type === "string" && typeof value === "string") {
536
+ if (def.minLength !== void 0 && value.length < def.minLength) {
537
+ throw new ValidationError(
538
+ field,
539
+ `minimum length is ${def.minLength}, got ${value.length}`
540
+ );
541
+ }
542
+ if (def.maxLength !== void 0 && value.length > def.maxLength) {
543
+ throw new ValidationError(
544
+ field,
545
+ `maximum length is ${def.maxLength}, got ${value.length}`
546
+ );
547
+ }
548
+ }
549
+ if (def.type === "number" && typeof value === "number") {
550
+ if (def.min !== void 0 && value < def.min) {
551
+ throw new ValidationError(field, `minimum value is ${def.min}`);
552
+ }
553
+ if (def.max !== void 0 && value > def.max) {
554
+ throw new ValidationError(field, `maximum value is ${def.max}`);
555
+ }
556
+ }
557
+ if (def.enum && !def.enum.includes(value)) {
558
+ throw new ValidationError(
559
+ field,
560
+ `value must be one of: ${def.enum.join(", ")}`
561
+ );
562
+ }
563
+ result[field] = coerceFieldValue(value, def.type);
564
+ }
565
+ if (isUpdate) {
566
+ for (const [key, val] of Object.entries(data)) {
567
+ if (!(key in schema) && val !== void 0) {
568
+ result[key] = val;
569
+ }
570
+ }
571
+ }
572
+ return result;
573
+ }
574
+
575
+ // src/model/model.ts
576
+ var Model = class {
577
+ name;
578
+ collection;
579
+ schema;
580
+ prefix;
581
+ adapter;
582
+ timestamps;
583
+ constructor(definition, adapter) {
584
+ this.name = definition.name;
585
+ this.collection = definition.collection;
586
+ this.schema = normalizeSchema(definition.schema);
587
+ this.prefix = definition.prefix ?? definition.collection.slice(0, 3);
588
+ this.adapter = adapter;
589
+ this.timestamps = definition.timestamps !== false;
590
+ }
591
+ async init() {
592
+ await this.adapter.createCollection(this.collection, this.schema);
593
+ }
594
+ generateId() {
595
+ return `${this.prefix}_${(0, import_nanoid.nanoid)(12)}`;
596
+ }
597
+ async create(data) {
598
+ const validated = validateDocument(data, this.schema);
599
+ const now = (/* @__PURE__ */ new Date()).toISOString();
600
+ const doc = {
601
+ ...validated,
602
+ id: this.generateId(),
603
+ createdAt: now,
604
+ updatedAt: now
605
+ };
606
+ return this.adapter.insert(this.collection, doc);
607
+ }
608
+ async createMany(items) {
609
+ const results = [];
610
+ for (const item of items) {
611
+ results.push(await this.create(item));
612
+ }
613
+ return results;
614
+ }
615
+ async findById(id) {
616
+ return this.adapter.findById(this.collection, id);
617
+ }
618
+ async findByIdOrThrow(id) {
619
+ const doc = await this.findById(id);
620
+ if (!doc) throw new DocumentNotFoundError(this.collection, id);
621
+ return doc;
622
+ }
623
+ async findOne(filter) {
624
+ const results = await this.adapter.find(this.collection, {
625
+ filter,
626
+ limit: 1
627
+ });
628
+ return results[0] ?? null;
629
+ }
630
+ async find(options = {}) {
631
+ return this.adapter.find(this.collection, options);
632
+ }
633
+ async findAll() {
634
+ return this.adapter.find(this.collection, {});
635
+ }
636
+ async count(filter) {
637
+ return this.adapter.count(this.collection, filter);
638
+ }
639
+ async update(id, data) {
640
+ const validated = validateDocument(data, this.schema, true);
641
+ if (this.timestamps) {
642
+ validated.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
643
+ }
644
+ return this.adapter.update(this.collection, id, validated);
645
+ }
646
+ async updateOrThrow(id, data) {
647
+ const doc = await this.update(id, data);
648
+ if (!doc) throw new DocumentNotFoundError(this.collection, id);
649
+ return doc;
650
+ }
651
+ async delete(id) {
652
+ return this.adapter.delete(this.collection, id);
653
+ }
654
+ async deleteMany(filter) {
655
+ return this.adapter.deleteMany(this.collection, filter);
656
+ }
657
+ };
658
+
659
+ // src/adapters/json/json-adapter.ts
660
+ init_errors();
661
+
662
+ // src/query/filter.ts
663
+ init_operators();
664
+ function matchesFilter(doc, filter) {
665
+ for (const [field, condition] of Object.entries(filter)) {
666
+ const value = doc[field];
667
+ if (isOperatorObject(condition)) {
668
+ for (const [op, operand] of Object.entries(
669
+ condition
670
+ )) {
671
+ if (!applyOperator(op, value, operand)) return false;
672
+ }
673
+ } else {
674
+ if (value !== condition) return false;
675
+ }
676
+ }
677
+ return true;
678
+ }
679
+ function sortDocuments(docs, sort) {
680
+ const entries = Object.entries(sort);
681
+ return [...docs].sort((a, b) => {
682
+ for (const [field, dir] of entries) {
683
+ const av = a[field];
684
+ const bv = b[field];
685
+ if (av === bv) continue;
686
+ if (av === void 0 || av === null) return dir;
687
+ if (bv === void 0 || bv === null) return -dir;
688
+ if (av < bv) return -dir;
689
+ if (av > bv) return dir;
690
+ }
691
+ return 0;
692
+ });
693
+ }
694
+ function applyFindOptions(docs, options) {
695
+ let result = docs;
696
+ if (options.filter) {
697
+ result = result.filter((doc) => matchesFilter(doc, options.filter));
698
+ }
699
+ if (options.sort) {
700
+ result = sortDocuments(result, options.sort);
701
+ }
702
+ if (options.offset) {
703
+ result = result.slice(options.offset);
704
+ }
705
+ if (options.limit !== void 0) {
706
+ result = result.slice(0, options.limit);
707
+ }
708
+ return result;
709
+ }
710
+ function countWithFilter(docs, filter) {
711
+ if (!filter) return docs.length;
712
+ return docs.filter((doc) => matchesFilter(doc, filter)).length;
713
+ }
714
+
715
+ // src/adapters/json/file-engine.ts
716
+ var fs = __toESM(require("fs"), 1);
717
+ var path = __toESM(require("path"), 1);
718
+ var import_write_file_atomic = __toESM(require("write-file-atomic"), 1);
719
+ var FileEngine = class {
720
+ filePath;
721
+ data = {};
722
+ writeQueue = Promise.resolve();
723
+ dirty = false;
724
+ constructor(filePath) {
725
+ this.filePath = path.resolve(filePath);
726
+ }
727
+ async load() {
728
+ try {
729
+ const raw = fs.readFileSync(this.filePath, "utf-8");
730
+ this.data = JSON.parse(raw);
731
+ } catch (err) {
732
+ if (err.code === "ENOENT") {
733
+ this.data = {};
734
+ await this.flush();
735
+ } else {
736
+ throw err;
737
+ }
738
+ }
739
+ }
740
+ getData() {
741
+ return this.data;
742
+ }
743
+ getCollection(name) {
744
+ if (!this.data[name]) {
745
+ this.data[name] = [];
746
+ }
747
+ return this.data[name];
748
+ }
749
+ hasCollection(name) {
750
+ return name in this.data;
751
+ }
752
+ createCollection(name) {
753
+ if (!this.data[name]) {
754
+ this.data[name] = [];
755
+ }
756
+ }
757
+ dropCollection(name) {
758
+ delete this.data[name];
759
+ }
760
+ listCollections() {
761
+ return Object.keys(this.data);
762
+ }
763
+ markDirty() {
764
+ this.dirty = true;
765
+ }
766
+ async flush() {
767
+ this.writeQueue = this.writeQueue.then(async () => {
768
+ const dir = path.dirname(this.filePath);
769
+ if (!fs.existsSync(dir)) {
770
+ fs.mkdirSync(dir, { recursive: true });
771
+ }
772
+ await (0, import_write_file_atomic.default)(
773
+ this.filePath,
774
+ JSON.stringify(this.data, null, 2) + "\n"
775
+ );
776
+ this.dirty = false;
777
+ });
778
+ return this.writeQueue;
779
+ }
780
+ async flushIfDirty() {
781
+ if (this.dirty) {
782
+ await this.flush();
783
+ }
784
+ }
785
+ };
786
+
787
+ // src/adapters/json/indexer.ts
788
+ init_errors();
789
+ var Indexer = class {
790
+ indexes = /* @__PURE__ */ new Map();
791
+ // collection → field → index
792
+ setupIndex(collection, field, unique, docs) {
793
+ if (!this.indexes.has(collection)) {
794
+ this.indexes.set(collection, /* @__PURE__ */ new Map());
795
+ }
796
+ const colIndexes = this.indexes.get(collection);
797
+ const idx = { field, unique, map: /* @__PURE__ */ new Map() };
798
+ for (const doc of docs) {
799
+ const val = doc[field];
800
+ if (val === void 0 || val === null) continue;
801
+ if (!idx.map.has(val)) {
802
+ idx.map.set(val, /* @__PURE__ */ new Set());
803
+ }
804
+ idx.map.get(val).add(doc.id);
805
+ }
806
+ colIndexes.set(field, idx);
807
+ }
808
+ onInsert(collection, doc) {
809
+ const colIndexes = this.indexes.get(collection);
810
+ if (!colIndexes) return;
811
+ for (const [, idx] of colIndexes) {
812
+ const val = doc[idx.field];
813
+ if (val === void 0 || val === null) continue;
814
+ if (idx.unique && idx.map.has(val) && idx.map.get(val).size > 0) {
815
+ throw new UniqueConstraintError(collection, idx.field, val);
816
+ }
817
+ if (!idx.map.has(val)) {
818
+ idx.map.set(val, /* @__PURE__ */ new Set());
819
+ }
820
+ idx.map.get(val).add(doc.id);
821
+ }
822
+ }
823
+ onUpdate(collection, oldDoc, newDoc) {
824
+ const colIndexes = this.indexes.get(collection);
825
+ if (!colIndexes) return;
826
+ for (const [, idx] of colIndexes) {
827
+ const oldVal = oldDoc[idx.field];
828
+ const newVal = newDoc[idx.field];
829
+ if (oldVal === newVal) continue;
830
+ if (oldVal !== void 0 && oldVal !== null) {
831
+ idx.map.get(oldVal)?.delete(oldDoc.id);
832
+ }
833
+ if (newVal !== void 0 && newVal !== null) {
834
+ if (idx.unique && idx.map.has(newVal) && idx.map.get(newVal).size > 0) {
835
+ throw new UniqueConstraintError(collection, idx.field, newVal);
836
+ }
837
+ if (!idx.map.has(newVal)) {
838
+ idx.map.set(newVal, /* @__PURE__ */ new Set());
839
+ }
840
+ idx.map.get(newVal).add(newDoc.id);
841
+ }
842
+ }
843
+ }
844
+ onDelete(collection, doc) {
845
+ const colIndexes = this.indexes.get(collection);
846
+ if (!colIndexes) return;
847
+ for (const [, idx] of colIndexes) {
848
+ const val = doc[idx.field];
849
+ if (val !== void 0 && val !== null) {
850
+ idx.map.get(val)?.delete(doc.id);
851
+ }
852
+ }
853
+ }
854
+ findByValue(collection, field, value) {
855
+ return this.indexes.get(collection)?.get(field)?.map.get(value);
856
+ }
857
+ dropCollection(collection) {
858
+ this.indexes.delete(collection);
859
+ }
860
+ };
861
+
862
+ // src/adapters/json/json-adapter.ts
863
+ var JsonAdapter = class {
864
+ engine;
865
+ indexer = new Indexer();
866
+ schemas = /* @__PURE__ */ new Map();
867
+ constructor(filePath) {
868
+ this.engine = new FileEngine(filePath);
869
+ }
870
+ async connect() {
871
+ await this.engine.load();
872
+ }
873
+ async disconnect() {
874
+ await this.engine.flushIfDirty();
875
+ }
876
+ async createCollection(collection, schema) {
877
+ this.engine.createCollection(collection);
878
+ this.schemas.set(collection, schema);
879
+ for (const [field, def] of Object.entries(schema)) {
880
+ if (def.index || def.unique) {
881
+ this.indexer.setupIndex(
882
+ collection,
883
+ field,
884
+ def.unique,
885
+ this.engine.getCollection(collection)
886
+ );
887
+ }
888
+ }
889
+ await this.engine.flush();
890
+ }
891
+ async dropCollection(collection) {
892
+ this.engine.dropCollection(collection);
893
+ this.indexer.dropCollection(collection);
894
+ this.schemas.delete(collection);
895
+ await this.engine.flush();
896
+ }
897
+ async listCollections() {
898
+ return this.engine.listCollections();
899
+ }
900
+ async insert(collection, doc) {
901
+ const docs = this.getCollectionOrThrow(collection);
902
+ this.indexer.onInsert(collection, doc);
903
+ docs.push(doc);
904
+ this.engine.markDirty();
905
+ await this.engine.flush();
906
+ return doc;
907
+ }
908
+ async findById(collection, id) {
909
+ const docs = this.getCollectionOrThrow(collection);
910
+ return docs.find((d) => d.id === id) ?? null;
911
+ }
912
+ async find(collection, options) {
913
+ const docs = this.getCollectionOrThrow(collection);
914
+ return applyFindOptions(docs, options);
915
+ }
916
+ async count(collection, filter) {
917
+ const docs = this.getCollectionOrThrow(collection);
918
+ return countWithFilter(docs, filter);
919
+ }
920
+ async update(collection, id, data) {
921
+ const docs = this.getCollectionOrThrow(collection);
922
+ const index = docs.findIndex((d) => d.id === id);
923
+ if (index === -1) return null;
924
+ const oldDoc = docs[index];
925
+ const newDoc = { ...oldDoc, ...data, id: oldDoc.id };
926
+ this.indexer.onUpdate(collection, oldDoc, newDoc);
927
+ docs[index] = newDoc;
928
+ this.engine.markDirty();
929
+ await this.engine.flush();
930
+ return newDoc;
931
+ }
932
+ async delete(collection, id) {
933
+ const docs = this.getCollectionOrThrow(collection);
934
+ const index = docs.findIndex((d) => d.id === id);
935
+ if (index === -1) return false;
936
+ this.indexer.onDelete(collection, docs[index]);
937
+ docs.splice(index, 1);
938
+ this.engine.markDirty();
939
+ await this.engine.flush();
940
+ return true;
941
+ }
942
+ async deleteMany(collection, filter) {
943
+ const docs = this.getCollectionOrThrow(collection);
944
+ const matching = applyFindOptions(docs, { filter });
945
+ for (const doc of matching) {
946
+ this.indexer.onDelete(collection, doc);
947
+ }
948
+ const ids = new Set(matching.map((d) => d.id));
949
+ const remaining = docs.filter((d) => !ids.has(d.id));
950
+ const deleted = docs.length - remaining.length;
951
+ docs.length = 0;
952
+ docs.push(...remaining);
953
+ if (deleted > 0) {
954
+ this.engine.markDirty();
955
+ await this.engine.flush();
956
+ }
957
+ return deleted;
958
+ }
959
+ getCollectionOrThrow(collection) {
960
+ if (!this.engine.hasCollection(collection)) {
961
+ throw new CollectionNotFoundError(collection);
962
+ }
963
+ return this.engine.getCollection(collection);
964
+ }
965
+ };
966
+
967
+ // src/seedorm.ts
968
+ var path2 = __toESM(require("path"), 1);
969
+ var SeedORM = class {
970
+ config;
971
+ adapter = null;
972
+ models = /* @__PURE__ */ new Map();
973
+ connected = false;
974
+ constructor(config) {
975
+ this.config = {
976
+ adapter: config?.adapter ?? { adapter: "json", path: "./data" },
977
+ migrationsDir: config?.migrationsDir ?? "./migrations"
978
+ };
979
+ }
980
+ async createAdapter(adapterConfig) {
981
+ switch (adapterConfig.adapter) {
982
+ case "json": {
983
+ const dbPath = path2.resolve(
984
+ adapterConfig.path ?? "./data",
985
+ "seedorm.json"
986
+ );
987
+ return new JsonAdapter(dbPath);
988
+ }
989
+ case "postgres": {
990
+ const { PostgresAdapter: PostgresAdapter2 } = await Promise.resolve().then(() => (init_postgres_adapter(), postgres_adapter_exports));
991
+ return new PostgresAdapter2(adapterConfig.url);
992
+ }
993
+ case "mysql":
994
+ throw new SeedORMError(
995
+ 'MySQL adapter requires the "mysql2" package. Install it with: npm install mysql2'
996
+ );
997
+ default:
998
+ throw new SeedORMError(
999
+ `Unknown adapter: ${adapterConfig.adapter}`
1000
+ );
1001
+ }
1002
+ }
1003
+ async connect() {
1004
+ if (this.connected) return;
1005
+ this.adapter = await this.createAdapter(this.config.adapter);
1006
+ await this.adapter.connect();
1007
+ this.connected = true;
1008
+ for (const model of this.models.values()) {
1009
+ await model.init();
1010
+ }
1011
+ }
1012
+ async disconnect() {
1013
+ if (!this.connected || !this.adapter) return;
1014
+ await this.adapter.disconnect();
1015
+ this.connected = false;
1016
+ this.adapter = null;
1017
+ }
1018
+ model(definition) {
1019
+ if (this.models.has(definition.name)) {
1020
+ return this.models.get(definition.name);
1021
+ }
1022
+ if (!this.adapter) {
1023
+ throw new SeedORMError(
1024
+ "Not connected. Call db.connect() before defining models."
1025
+ );
1026
+ }
1027
+ const model = new Model(definition, this.adapter);
1028
+ this.models.set(definition.name, model);
1029
+ return model;
1030
+ }
1031
+ getModel(name) {
1032
+ return this.models.get(name);
1033
+ }
1034
+ getAdapter() {
1035
+ if (!this.adapter) {
1036
+ throw new SeedORMError("Not connected.");
1037
+ }
1038
+ return this.adapter;
1039
+ }
1040
+ getConfig() {
1041
+ return this.config;
1042
+ }
1043
+ };
1044
+
1045
+ // src/index.ts
1046
+ init_postgres_adapter();
1047
+ init_errors();
1048
+ // Annotate the CommonJS export names for ESM import in node:
1049
+ 0 && (module.exports = {
1050
+ AdapterError,
1051
+ CollectionNotFoundError,
1052
+ DocumentNotFoundError,
1053
+ JsonAdapter,
1054
+ Model,
1055
+ PostgresAdapter,
1056
+ SeedORM,
1057
+ SeedORMError,
1058
+ UniqueConstraintError,
1059
+ ValidationError,
1060
+ normalizeSchema,
1061
+ validateDocument
1062
+ });
1063
+ //# sourceMappingURL=index.cjs.map