schemock 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/adapters/index.d.mts +1364 -0
  4. package/dist/adapters/index.d.ts +1364 -0
  5. package/dist/adapters/index.js +36988 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/adapters/index.mjs +36972 -0
  8. package/dist/adapters/index.mjs.map +1 -0
  9. package/dist/cli/index.d.mts +831 -0
  10. package/dist/cli/index.d.ts +831 -0
  11. package/dist/cli/index.js +4425 -0
  12. package/dist/cli/index.js.map +1 -0
  13. package/dist/cli/index.mjs +4401 -0
  14. package/dist/cli/index.mjs.map +1 -0
  15. package/dist/cli.js +6776 -0
  16. package/dist/index.d.mts +8 -0
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.js +39439 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +39367 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/middleware/index.d.mts +688 -0
  23. package/dist/middleware/index.d.ts +688 -0
  24. package/dist/middleware/index.js +921 -0
  25. package/dist/middleware/index.js.map +1 -0
  26. package/dist/middleware/index.mjs +899 -0
  27. package/dist/middleware/index.mjs.map +1 -0
  28. package/dist/react/index.d.mts +316 -0
  29. package/dist/react/index.d.ts +316 -0
  30. package/dist/react/index.js +466 -0
  31. package/dist/react/index.js.map +1 -0
  32. package/dist/react/index.mjs +456 -0
  33. package/dist/react/index.mjs.map +1 -0
  34. package/dist/runtime/index.d.mts +814 -0
  35. package/dist/runtime/index.d.ts +814 -0
  36. package/dist/runtime/index.js +1270 -0
  37. package/dist/runtime/index.js.map +1 -0
  38. package/dist/runtime/index.mjs +1246 -0
  39. package/dist/runtime/index.mjs.map +1 -0
  40. package/dist/schema/index.d.mts +838 -0
  41. package/dist/schema/index.d.ts +838 -0
  42. package/dist/schema/index.js +696 -0
  43. package/dist/schema/index.js.map +1 -0
  44. package/dist/schema/index.mjs +681 -0
  45. package/dist/schema/index.mjs.map +1 -0
  46. package/dist/types-C1MiZh1d.d.ts +96 -0
  47. package/dist/types-C2bd2vgy.d.mts +773 -0
  48. package/dist/types-C2bd2vgy.d.ts +773 -0
  49. package/dist/types-C9VMgu3E.d.mts +289 -0
  50. package/dist/types-DV2DS7wj.d.mts +96 -0
  51. package/dist/types-c2AN3vky.d.ts +289 -0
  52. package/package.json +116 -0
@@ -0,0 +1,1246 @@
1
+ import { http, HttpResponse } from 'msw';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/runtime/handlers.ts
14
+ var handlers_exports = {};
15
+ __export(handlers_exports, {
16
+ createHandlers: () => createHandlers
17
+ });
18
+ function createHandlers(schemas, adapter, options) {
19
+ const handlers = [];
20
+ const baseUrl = options?.baseUrl ?? "/api";
21
+ for (const schema of schemas) {
22
+ const entityPath = getEntityPath(schema, baseUrl);
23
+ handlers.push(createListHandler(schema, entityPath, adapter));
24
+ handlers.push(createGetHandler(schema, entityPath, adapter));
25
+ handlers.push(createCreateHandler(schema, entityPath, adapter));
26
+ handlers.push(createUpdateHandler(schema, entityPath, adapter, "PUT"));
27
+ handlers.push(createUpdateHandler(schema, entityPath, adapter, "PATCH"));
28
+ handlers.push(createDeleteHandler(schema, entityPath, adapter));
29
+ if (schema.api?.operations) {
30
+ handlers.push(
31
+ ...createCustomHandlers(schema, entityPath, adapter)
32
+ );
33
+ }
34
+ }
35
+ return handlers;
36
+ }
37
+ function getEntityPath(schema, baseUrl) {
38
+ if (schema.api?.basePath) {
39
+ return schema.api.basePath;
40
+ }
41
+ const plural = schema.name.endsWith("s") ? schema.name : `${schema.name}s`;
42
+ return `${baseUrl}/${plural}`;
43
+ }
44
+ function createListHandler(schema, path, adapter) {
45
+ return http.get(path, async ({ request }) => {
46
+ const url = new URL(request.url);
47
+ const ctx = {
48
+ entity: schema.name,
49
+ endpoint: path,
50
+ params: {},
51
+ filter: parseQueryFilter(url.searchParams),
52
+ limit: parseNumber(url.searchParams.get("limit")),
53
+ offset: parseNumber(url.searchParams.get("offset")),
54
+ orderBy: parseOrderBy(url.searchParams.get("orderBy"))
55
+ };
56
+ const result = await adapter.findMany(ctx);
57
+ if (result.error) {
58
+ return HttpResponse.json(
59
+ { error: result.error.message },
60
+ { status: 500 }
61
+ );
62
+ }
63
+ return HttpResponse.json({
64
+ data: result.data,
65
+ meta: result.meta
66
+ });
67
+ });
68
+ }
69
+ function createGetHandler(schema, path, adapter) {
70
+ return http.get(`${path}/:id`, async ({ params }) => {
71
+ const ctx = {
72
+ entity: schema.name,
73
+ endpoint: path,
74
+ params: { id: params.id }
75
+ };
76
+ const result = await adapter.findOne(ctx);
77
+ if (result.error) {
78
+ return HttpResponse.json(
79
+ { error: result.error.message },
80
+ { status: 404 }
81
+ );
82
+ }
83
+ if (!result.data) {
84
+ return HttpResponse.json(
85
+ { error: `${schema.name} not found` },
86
+ { status: 404 }
87
+ );
88
+ }
89
+ return HttpResponse.json({ data: result.data });
90
+ });
91
+ }
92
+ function createCreateHandler(schema, path, adapter) {
93
+ return http.post(path, async ({ request }) => {
94
+ const body = await request.json();
95
+ const ctx = {
96
+ entity: schema.name,
97
+ endpoint: path,
98
+ data: body
99
+ };
100
+ const result = await adapter.create(ctx);
101
+ if (result.error) {
102
+ return HttpResponse.json(
103
+ { error: result.error.message },
104
+ { status: 400 }
105
+ );
106
+ }
107
+ return HttpResponse.json({ data: result.data }, { status: 201 });
108
+ });
109
+ }
110
+ function createUpdateHandler(schema, path, adapter, method) {
111
+ const handler = method === "PUT" ? http.put : http.patch;
112
+ return handler(`${path}/:id`, async ({ params, request }) => {
113
+ const body = await request.json();
114
+ const ctx = {
115
+ entity: schema.name,
116
+ endpoint: path,
117
+ params: { id: params.id },
118
+ data: body
119
+ };
120
+ const result = await adapter.update(ctx);
121
+ if (result.error) {
122
+ return HttpResponse.json(
123
+ { error: result.error.message },
124
+ { status: 400 }
125
+ );
126
+ }
127
+ if (!result.data) {
128
+ return HttpResponse.json(
129
+ { error: `${schema.name} not found` },
130
+ { status: 404 }
131
+ );
132
+ }
133
+ return HttpResponse.json({ data: result.data });
134
+ });
135
+ }
136
+ function createDeleteHandler(schema, path, adapter) {
137
+ return http.delete(`${path}/:id`, async ({ params }) => {
138
+ const ctx = {
139
+ entity: schema.name,
140
+ endpoint: path,
141
+ params: { id: params.id }
142
+ };
143
+ const result = await adapter.delete(ctx);
144
+ if (result.error) {
145
+ return HttpResponse.json(
146
+ { error: result.error.message },
147
+ { status: 400 }
148
+ );
149
+ }
150
+ return new HttpResponse(null, { status: 204 });
151
+ });
152
+ }
153
+ function createCustomHandlers(schema, basePath, adapter) {
154
+ const handlers = [];
155
+ const operations = schema.api?.operations || {};
156
+ for (const [name, config] of Object.entries(operations)) {
157
+ if (typeof config === "boolean") continue;
158
+ if (!config || typeof config !== "object") continue;
159
+ const customConfig = config;
160
+ const method = customConfig.method?.toLowerCase() || "get";
161
+ const path = `${basePath}${customConfig.path || `/${name}`}`;
162
+ const httpMethod = http[method];
163
+ if (typeof httpMethod !== "function") continue;
164
+ handlers.push(
165
+ httpMethod(path, async ({ request, params }) => {
166
+ const ctx = {
167
+ entity: schema.name,
168
+ endpoint: path,
169
+ operation: name,
170
+ params
171
+ };
172
+ if (method === "post" || method === "put" || method === "patch") {
173
+ ctx.data = await request.json();
174
+ }
175
+ if (adapter.custom) {
176
+ const result = await adapter.custom(ctx);
177
+ if (result.error) {
178
+ return HttpResponse.json(
179
+ { error: result.error.message },
180
+ { status: 400 }
181
+ );
182
+ }
183
+ return HttpResponse.json({ data: result.data });
184
+ }
185
+ return HttpResponse.json(
186
+ { error: "Custom operations not supported by adapter" },
187
+ { status: 501 }
188
+ );
189
+ })
190
+ );
191
+ }
192
+ return handlers;
193
+ }
194
+ function parseQueryFilter(params) {
195
+ const filter = {};
196
+ let hasFilter = false;
197
+ for (const [key, value] of params.entries()) {
198
+ if (["limit", "offset", "orderBy", "select"].includes(key)) continue;
199
+ if (key.startsWith("filter[") && key.endsWith("]")) {
200
+ const field = key.slice(7, -1);
201
+ filter[field] = parseValue(value);
202
+ hasFilter = true;
203
+ } else if (!key.includes("[")) {
204
+ filter[key] = parseValue(value);
205
+ hasFilter = true;
206
+ }
207
+ }
208
+ return hasFilter ? filter : void 0;
209
+ }
210
+ function parseOrderBy(value) {
211
+ if (!value) return void 0;
212
+ const orderBy = {};
213
+ for (const part of value.split(",")) {
214
+ const [field, direction] = part.split(":");
215
+ if (field) {
216
+ orderBy[field] = direction === "desc" ? "desc" : "asc";
217
+ }
218
+ }
219
+ return Object.keys(orderBy).length > 0 ? orderBy : void 0;
220
+ }
221
+ function parseNumber(value) {
222
+ if (!value) return void 0;
223
+ const num = parseInt(value, 10);
224
+ return isNaN(num) ? void 0 : num;
225
+ }
226
+ function parseValue(value) {
227
+ if (value === "true") return true;
228
+ if (value === "false") return false;
229
+ const num = Number(value);
230
+ if (!isNaN(num)) return num;
231
+ return value;
232
+ }
233
+ var init_handlers = __esm({
234
+ "src/runtime/handlers.ts"() {
235
+ }
236
+ });
237
+
238
+ // src/runtime/resolver/registry.ts
239
+ var SchemaRegistry = class {
240
+ entities = /* @__PURE__ */ new Map();
241
+ views = /* @__PURE__ */ new Map();
242
+ /**
243
+ * Register an entity schema
244
+ * @param schema - The entity schema to register
245
+ */
246
+ register(schema) {
247
+ if (this.entities.has(schema.name)) {
248
+ console.warn(`Schema '${schema.name}' is already registered. Overwriting.`);
249
+ }
250
+ this.entities.set(schema.name, schema);
251
+ }
252
+ /**
253
+ * Register a view schema
254
+ * @param view - The view schema to register
255
+ */
256
+ registerView(view) {
257
+ if (this.views.has(view.name)) {
258
+ console.warn(`View '${view.name}' is already registered. Overwriting.`);
259
+ }
260
+ this.views.set(view.name, view);
261
+ }
262
+ /**
263
+ * Get an entity schema by name
264
+ * @param entityName - The entity name
265
+ * @returns EntitySchema or undefined if not found
266
+ */
267
+ get(entityName) {
268
+ return this.entities.get(entityName);
269
+ }
270
+ /**
271
+ * Get an entity schema by name, throws if not found
272
+ * @param entityName - The entity name
273
+ * @returns EntitySchema
274
+ * @throws Error if entity not found
275
+ */
276
+ getOrThrow(entityName) {
277
+ const schema = this.entities.get(entityName);
278
+ if (!schema) {
279
+ throw new Error(`Entity schema '${entityName}' not found. Did you forget to register it?`);
280
+ }
281
+ return schema;
282
+ }
283
+ /**
284
+ * Get a view schema by name
285
+ * @param viewName - The view name
286
+ * @returns ViewSchema or undefined if not found
287
+ */
288
+ getView(viewName) {
289
+ return this.views.get(viewName);
290
+ }
291
+ /**
292
+ * Get a view schema by name, throws if not found
293
+ * @param viewName - The view name
294
+ * @returns ViewSchema
295
+ * @throws Error if view not found
296
+ */
297
+ getViewOrThrow(viewName) {
298
+ const view = this.views.get(viewName);
299
+ if (!view) {
300
+ throw new Error(`View schema '${viewName}' not found. Did you forget to register it?`);
301
+ }
302
+ return view;
303
+ }
304
+ /**
305
+ * Get all registered entity schemas
306
+ * @returns Array of all entity schemas
307
+ */
308
+ getAll() {
309
+ return Array.from(this.entities.values());
310
+ }
311
+ /**
312
+ * Get all registered view schemas
313
+ * @returns Array of all view schemas
314
+ */
315
+ getAllViews() {
316
+ return Array.from(this.views.values());
317
+ }
318
+ /**
319
+ * Check if an entity schema is registered
320
+ * @param entityName - The entity name
321
+ * @returns true if registered
322
+ */
323
+ has(entityName) {
324
+ return this.entities.has(entityName);
325
+ }
326
+ /**
327
+ * Check if a view schema is registered
328
+ * @param viewName - The view name
329
+ * @returns true if registered
330
+ */
331
+ hasView(viewName) {
332
+ return this.views.has(viewName);
333
+ }
334
+ /**
335
+ * Get all relations for an entity
336
+ * @param entityName - The entity name
337
+ * @returns Array of relation definitions with their names
338
+ */
339
+ getRelationsFor(entityName) {
340
+ const schema = this.entities.get(entityName);
341
+ if (!schema || !schema.relations) {
342
+ return [];
343
+ }
344
+ return Object.entries(schema.relations).map(([name, relation]) => ({
345
+ name,
346
+ relation
347
+ }));
348
+ }
349
+ /**
350
+ * Get all entities that have a relation to a target entity
351
+ * @param targetEntityName - The target entity name
352
+ * @returns Array of entity names that reference the target
353
+ */
354
+ getEntitiesReferencingEntity(targetEntityName) {
355
+ const result = [];
356
+ for (const [entityName, schema] of this.entities) {
357
+ if (schema.relations) {
358
+ for (const relation of Object.values(schema.relations)) {
359
+ if (relation.target === targetEntityName) {
360
+ result.push(entityName);
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+ /**
369
+ * Get entity names in dependency order (topological sort)
370
+ * Entities with no dependencies come first
371
+ * @returns Array of entity names in order
372
+ */
373
+ getEntityOrder() {
374
+ const visited = /* @__PURE__ */ new Set();
375
+ const result = [];
376
+ const visit = (entityName) => {
377
+ if (visited.has(entityName)) return;
378
+ visited.add(entityName);
379
+ const schema = this.entities.get(entityName);
380
+ if (schema?.relations) {
381
+ for (const relation of Object.values(schema.relations)) {
382
+ if (this.entities.has(relation.target)) {
383
+ visit(relation.target);
384
+ }
385
+ }
386
+ }
387
+ result.push(entityName);
388
+ };
389
+ for (const entityName of this.entities.keys()) {
390
+ visit(entityName);
391
+ }
392
+ return result;
393
+ }
394
+ /**
395
+ * Clear all registered schemas
396
+ */
397
+ clear() {
398
+ this.entities.clear();
399
+ this.views.clear();
400
+ }
401
+ /**
402
+ * Get entity count
403
+ */
404
+ get entityCount() {
405
+ return this.entities.size;
406
+ }
407
+ /**
408
+ * Get view count
409
+ */
410
+ get viewCount() {
411
+ return this.views.size;
412
+ }
413
+ };
414
+ var registry = new SchemaRegistry();
415
+
416
+ // src/runtime/resolver/computed.ts
417
+ function topologicalSort(fields, allComputed) {
418
+ const result = [];
419
+ const visited = /* @__PURE__ */ new Set();
420
+ const visiting = /* @__PURE__ */ new Set();
421
+ const fieldNames = new Set(fields.map(([name]) => name));
422
+ function visit(name) {
423
+ if (visited.has(name)) return;
424
+ if (visiting.has(name)) {
425
+ throw new Error(`Circular dependency detected in computed fields: ${name}`);
426
+ }
427
+ visiting.add(name);
428
+ const computed = allComputed[name];
429
+ if (computed?.dependsOn) {
430
+ for (const dep of computed.dependsOn) {
431
+ if (allComputed[dep] && fieldNames.has(dep)) {
432
+ visit(dep);
433
+ }
434
+ }
435
+ }
436
+ visiting.delete(name);
437
+ visited.add(name);
438
+ const field = fields.find(([n]) => n === name);
439
+ if (field) {
440
+ result.push(field);
441
+ }
442
+ }
443
+ for (const [name] of fields) {
444
+ visit(name);
445
+ }
446
+ return result;
447
+ }
448
+ var computeCache = /* @__PURE__ */ new Map();
449
+ function clearComputeCache() {
450
+ computeCache.clear();
451
+ }
452
+ function resolveComputedField(entity, fieldName, computed, db, context) {
453
+ const entityId = entity.id;
454
+ const cacheKey = `${entityId}:${fieldName}`;
455
+ if (computeCache.has(cacheKey)) {
456
+ return computeCache.get(cacheKey);
457
+ }
458
+ let value;
459
+ if (context.mode === "seed" && computed.mock) {
460
+ value = computed.mock();
461
+ } else {
462
+ value = computed.resolve(entity, db, context);
463
+ }
464
+ if (value instanceof Promise) {
465
+ return value.then((resolved) => {
466
+ computeCache.set(cacheKey, resolved);
467
+ return resolved;
468
+ });
469
+ }
470
+ computeCache.set(cacheKey, value);
471
+ return value;
472
+ }
473
+ async function resolveComputedFields(entity, schema, db, context) {
474
+ if (!schema.computed || Object.keys(schema.computed).length === 0) {
475
+ return entity;
476
+ }
477
+ const computedFields = Object.entries(schema.computed);
478
+ const sorted = topologicalSort(computedFields, schema.computed);
479
+ for (const [fieldName, computed] of sorted) {
480
+ const value = await resolveComputedField(entity, fieldName, computed, db, context);
481
+ entity[fieldName] = value;
482
+ }
483
+ return entity;
484
+ }
485
+ function resolveComputedFieldsSync(entity, schema, context) {
486
+ if (!schema.computed || Object.keys(schema.computed).length === 0) {
487
+ return entity;
488
+ }
489
+ const computedFields = Object.entries(schema.computed);
490
+ const sorted = topologicalSort(computedFields, schema.computed);
491
+ for (const [fieldName, computed] of sorted) {
492
+ if (computed.mock) {
493
+ entity[fieldName] = computed.mock();
494
+ }
495
+ }
496
+ return entity;
497
+ }
498
+
499
+ // src/runtime/resolver/relation.ts
500
+ var DEFAULT_MAX_DEPTH = 3;
501
+ async function resolveRelation(entity, relationName, relation, db, registry2, options = {}, currentDepth = 0) {
502
+ const maxDepth = options.depth ?? DEFAULT_MAX_DEPTH;
503
+ if (currentDepth >= maxDepth) {
504
+ return relation.type === "hasMany" ? [] : null;
505
+ }
506
+ switch (relation.type) {
507
+ case "hasOne":
508
+ return resolveHasOne(entity, relation, db, registry2, options, currentDepth);
509
+ case "hasMany":
510
+ return resolveHasMany(entity, relation, db, registry2, options, currentDepth);
511
+ case "belongsTo":
512
+ return resolveBelongsTo(entity, relation, db, registry2, options, currentDepth);
513
+ default:
514
+ throw new Error(`Unknown relation type: ${relation.type}`);
515
+ }
516
+ }
517
+ async function resolveHasOne(entity, relation, db, registry2, options, currentDepth) {
518
+ const targetDb = db[relation.target];
519
+ if (!targetDb) {
520
+ throw new Error(`Target entity '${relation.target}' not found in database`);
521
+ }
522
+ const foreignKey = relation.foreignKey || `${entity.constructor.name.toLowerCase()}Id`;
523
+ const entityId = entity.id;
524
+ const related = targetDb.findFirst({
525
+ where: {
526
+ [foreignKey]: { equals: entityId }
527
+ }
528
+ });
529
+ if (!related) return null;
530
+ const targetSchema = registry2.get(relation.target);
531
+ if (targetSchema) {
532
+ await resolveNestedRelations(
533
+ related,
534
+ targetSchema,
535
+ db,
536
+ registry2,
537
+ options,
538
+ currentDepth + 1
539
+ );
540
+ }
541
+ return related;
542
+ }
543
+ async function resolveHasMany(entity, relation, db, registry2, options, currentDepth) {
544
+ const targetDb = db[relation.target];
545
+ if (!targetDb) {
546
+ throw new Error(`Target entity '${relation.target}' not found in database`);
547
+ }
548
+ const foreignKey = relation.foreignKey || `${entity.constructor.name.toLowerCase()}Id`;
549
+ const entityId = entity.id;
550
+ const query = {
551
+ where: {
552
+ [foreignKey]: { equals: entityId }
553
+ }
554
+ };
555
+ const limit = options.limit ?? relation.limit;
556
+ if (limit) {
557
+ query.take = limit;
558
+ }
559
+ const orderBy = options.orderBy ?? relation.orderBy;
560
+ if (orderBy) {
561
+ query.orderBy = orderBy;
562
+ }
563
+ const results = targetDb.findMany(query);
564
+ const targetSchema = registry2.get(relation.target);
565
+ if (targetSchema) {
566
+ for (const item of results) {
567
+ await resolveNestedRelations(
568
+ item,
569
+ targetSchema,
570
+ db,
571
+ registry2,
572
+ options,
573
+ currentDepth + 1
574
+ );
575
+ }
576
+ }
577
+ return results;
578
+ }
579
+ async function resolveBelongsTo(entity, relation, db, registry2, options, currentDepth) {
580
+ const foreignKey = relation.foreignKey || `${relation.target}Id`;
581
+ const foreignKeyValue = entity[foreignKey];
582
+ if (!foreignKeyValue) return null;
583
+ const targetDb = db[relation.target];
584
+ if (!targetDb) {
585
+ throw new Error(`Target entity '${relation.target}' not found in database`);
586
+ }
587
+ const related = targetDb.findFirst({
588
+ where: { id: { equals: foreignKeyValue } }
589
+ });
590
+ if (!related) return null;
591
+ const targetSchema = registry2.get(relation.target);
592
+ if (targetSchema) {
593
+ await resolveNestedRelations(
594
+ related,
595
+ targetSchema,
596
+ db,
597
+ registry2,
598
+ options,
599
+ currentDepth + 1
600
+ );
601
+ }
602
+ return related;
603
+ }
604
+ async function resolveNestedRelations(entity, schema, db, registry2, options, currentDepth) {
605
+ if (!schema.relations) return;
606
+ const include = options.include ?? [];
607
+ for (const [relationName, relation] of Object.entries(schema.relations)) {
608
+ const shouldLoad = relation.eager || include.includes(relationName) || include.some((inc) => inc.startsWith(`${relationName}.`));
609
+ if (shouldLoad) {
610
+ const nestedIncludes = include.filter((inc) => inc.startsWith(`${relationName}.`)).map((inc) => inc.replace(`${relationName}.`, ""));
611
+ entity[relationName] = await resolveRelation(
612
+ entity,
613
+ relationName,
614
+ relation,
615
+ db,
616
+ registry2,
617
+ { ...options, include: nestedIncludes },
618
+ currentDepth
619
+ );
620
+ }
621
+ }
622
+ }
623
+ async function resolveRelations(entity, schema, db, registry2, options = {}) {
624
+ await resolveNestedRelations(entity, schema, db, registry2, options, 0);
625
+ return entity;
626
+ }
627
+ async function eagerLoadRelations(entities, schema, db, registry2, options = {}) {
628
+ if (!schema.relations || entities.length === 0) {
629
+ return entities;
630
+ }
631
+ const include = options.include ?? [];
632
+ for (const [relationName, relation] of Object.entries(schema.relations)) {
633
+ const shouldLoad = relation.eager || include.includes(relationName) || include.some((inc) => inc.startsWith(`${relationName}.`));
634
+ if (!shouldLoad) continue;
635
+ if (relation.type === "belongsTo") {
636
+ await batchLoadBelongsTo(entities, relationName, relation, db, registry2, options);
637
+ } else if (relation.type === "hasOne") {
638
+ await batchLoadHasOne(entities, relationName, relation, db, registry2, options);
639
+ } else if (relation.type === "hasMany") {
640
+ for (const entity of entities) {
641
+ entity[relationName] = await resolveRelation(entity, relationName, relation, db, registry2, options);
642
+ }
643
+ }
644
+ }
645
+ return entities;
646
+ }
647
+ async function batchLoadBelongsTo(entities, relationName, relation, db, registry2, options) {
648
+ const foreignKey = relation.foreignKey || `${relation.target}Id`;
649
+ const targetDb = db[relation.target];
650
+ const foreignKeyValues = [...new Set(entities.map((e) => e[foreignKey]).filter(Boolean))];
651
+ if (foreignKeyValues.length === 0) {
652
+ entities.forEach((e) => e[relationName] = null);
653
+ return;
654
+ }
655
+ const relatedEntities = targetDb.findMany({
656
+ where: {
657
+ id: { in: foreignKeyValues }
658
+ }
659
+ });
660
+ const lookup = new Map(relatedEntities.map((r) => [r.id, r]));
661
+ for (const entity of entities) {
662
+ const fkValue = entity[foreignKey];
663
+ entity[relationName] = fkValue ? lookup.get(fkValue) ?? null : null;
664
+ }
665
+ const targetSchema = registry2.get(relation.target);
666
+ if (targetSchema) {
667
+ const nestedIncludes = (options.include ?? []).filter((inc) => inc.startsWith(`${relationName}.`)).map((inc) => inc.replace(`${relationName}.`, ""));
668
+ if (nestedIncludes.length > 0) {
669
+ await eagerLoadRelations(relatedEntities, targetSchema, db, registry2, {
670
+ ...options,
671
+ include: nestedIncludes
672
+ });
673
+ }
674
+ }
675
+ }
676
+ async function batchLoadHasOne(entities, relationName, relation, db, registry2, options) {
677
+ const foreignKey = relation.foreignKey || "id";
678
+ const targetDb = db[relation.target];
679
+ const entityIds = entities.map((e) => e.id).filter(Boolean);
680
+ if (entityIds.length === 0) {
681
+ entities.forEach((e) => e[relationName] = null);
682
+ return;
683
+ }
684
+ const relatedEntities = targetDb.findMany({
685
+ where: {
686
+ [foreignKey]: { in: entityIds }
687
+ }
688
+ });
689
+ const lookup = new Map(relatedEntities.map((r) => [r[foreignKey], r]));
690
+ for (const entity of entities) {
691
+ entity[relationName] = lookup.get(entity.id) ?? null;
692
+ }
693
+ const targetSchema = registry2.get(relation.target);
694
+ if (targetSchema) {
695
+ const nestedIncludes = (options.include ?? []).filter((inc) => inc.startsWith(`${relationName}.`)).map((inc) => inc.replace(`${relationName}.`, ""));
696
+ if (nestedIncludes.length > 0) {
697
+ await eagerLoadRelations(relatedEntities, targetSchema, db, registry2, {
698
+ ...options,
699
+ include: nestedIncludes
700
+ });
701
+ }
702
+ }
703
+ }
704
+
705
+ // src/schema/types.ts
706
+ function isComputedField(field) {
707
+ return typeof field === "object" && field !== null && "_computed" in field && field._computed === true;
708
+ }
709
+
710
+ // src/runtime/resolver/view.ts
711
+ function isEmbedDefinition(value) {
712
+ return typeof value === "object" && value !== null && "_embed" in value && value._embed === true;
713
+ }
714
+ var ViewResolver = class {
715
+ constructor(registry2, db) {
716
+ this.registry = registry2;
717
+ this.db = db;
718
+ }
719
+ /**
720
+ * Resolves a view schema with given parameters.
721
+ *
722
+ * @param view - The view schema to resolve
723
+ * @param params - URL parameters for the view
724
+ * @param context - Optional resolver context
725
+ * @returns The resolved view data
726
+ *
727
+ * @example
728
+ * ```typescript
729
+ * const userData = await viewResolver.resolve(UserFullView, { id: 'user-123' });
730
+ * ```
731
+ */
732
+ async resolve(view, params, context) {
733
+ clearComputeCache();
734
+ const resolverContext = {
735
+ mode: "resolve",
736
+ params,
737
+ ...context
738
+ };
739
+ return this.buildViewResult(view, { params, context: resolverContext });
740
+ }
741
+ /**
742
+ * Builds the view result by resolving all fields
743
+ */
744
+ async buildViewResult(view, options) {
745
+ const result = {};
746
+ for (const [fieldName, fieldDef] of Object.entries(view.fields)) {
747
+ if (isEmbedDefinition(fieldDef)) {
748
+ result[fieldName] = await this.resolveEmbed(fieldDef, result, options);
749
+ } else if (isComputedField(fieldDef)) {
750
+ result[fieldName] = await this.resolveViewComputedField(fieldDef, result, options);
751
+ } else if (this.isNestedObjectField(fieldDef)) {
752
+ result[fieldName] = await this.resolveNestedObject(fieldDef, result, options);
753
+ } else {
754
+ const paramValue = options.params[fieldName];
755
+ if (paramValue !== void 0) {
756
+ result[fieldName] = paramValue;
757
+ }
758
+ }
759
+ }
760
+ return result;
761
+ }
762
+ /**
763
+ * Check if a field definition is a nested object
764
+ */
765
+ isNestedObjectField(fieldDef) {
766
+ return typeof fieldDef === "object" && fieldDef !== null && !("type" in fieldDef) && !("_computed" in fieldDef) && !("_embed" in fieldDef);
767
+ }
768
+ /**
769
+ * Resolves an embedded entity field
770
+ */
771
+ async resolveEmbed(embed, parentData, options) {
772
+ const targetDb = this.db[embed.entity.name];
773
+ if (!targetDb) {
774
+ throw new Error(`Target entity '${embed.entity.name}' not found in database`);
775
+ }
776
+ const query = { where: {} };
777
+ const idParam = options.params.id;
778
+ if (idParam) {
779
+ const possibleForeignKeys = [
780
+ `${this.guessParentEntityName(options)}Id`,
781
+ "userId",
782
+ "authorId",
783
+ "parentId"
784
+ ];
785
+ for (const fk of possibleForeignKeys) {
786
+ if (embed.entity.fields && fk in embed.entity.fields) {
787
+ query.where[fk] = { equals: idParam };
788
+ break;
789
+ }
790
+ }
791
+ }
792
+ if (embed.config?.limit) {
793
+ query.take = embed.config.limit;
794
+ }
795
+ if (embed.config?.orderBy) {
796
+ query.orderBy = embed.config.orderBy;
797
+ }
798
+ const shouldReturnArray = !embed.config?.limit || embed.config.limit > 1;
799
+ if (shouldReturnArray) {
800
+ const results = targetDb.findMany(query);
801
+ return results;
802
+ } else {
803
+ const result = targetDb.findFirst(query);
804
+ return result;
805
+ }
806
+ }
807
+ /**
808
+ * Guess the parent entity name from the view endpoint
809
+ */
810
+ guessParentEntityName(options) {
811
+ return "user";
812
+ }
813
+ /**
814
+ * Resolves a computed field within a view context
815
+ */
816
+ async resolveViewComputedField(computed, currentData, options) {
817
+ if (options.context.mode === "seed" && computed.mock) {
818
+ return computed.mock();
819
+ }
820
+ return computed.resolve(currentData, this.db, options.context);
821
+ }
822
+ /**
823
+ * Resolves a nested object containing fields and computed values
824
+ */
825
+ async resolveNestedObject(nestedDef, parentData, options) {
826
+ const result = {};
827
+ for (const [fieldName, fieldDef] of Object.entries(nestedDef)) {
828
+ if (isComputedField(fieldDef)) {
829
+ result[fieldName] = await this.resolveViewComputedField(fieldDef, parentData, options);
830
+ } else {
831
+ result[fieldName] = fieldDef;
832
+ }
833
+ }
834
+ return result;
835
+ }
836
+ /**
837
+ * Resolves a view with mock data (for seeding)
838
+ */
839
+ async resolveMock(view, params) {
840
+ return this.resolve(view, params, { mode: "seed" });
841
+ }
842
+ };
843
+ function createViewResolver(registry2, db) {
844
+ return new ViewResolver(registry2, db);
845
+ }
846
+
847
+ // src/runtime/resolver/resolver.ts
848
+ var Resolver = class _Resolver {
849
+ constructor(registry2, db, context) {
850
+ this.registry = registry2;
851
+ this.db = db;
852
+ this.context = { mode: "resolve", ...context };
853
+ this.viewResolver = new ViewResolver(registry2, db);
854
+ }
855
+ context;
856
+ viewResolver;
857
+ /**
858
+ * Find a single entity by ID
859
+ *
860
+ * @param entityName - The entity type to find
861
+ * @param id - The entity ID
862
+ * @param options - Query options (relations, computed fields)
863
+ * @returns The entity or null if not found
864
+ *
865
+ * @example
866
+ * ```typescript
867
+ * const user = await resolver.findOne('user', '123', {
868
+ * include: ['profile'],
869
+ * computed: ['postCount'],
870
+ * });
871
+ * ```
872
+ */
873
+ async findOne(entityName, id, options = {}) {
874
+ clearComputeCache();
875
+ const schema = this.registry.getOrThrow(entityName);
876
+ const entityDb = this.db[entityName];
877
+ if (!entityDb) {
878
+ throw new Error(`Entity '${entityName}' not found in database`);
879
+ }
880
+ const entity = entityDb.findFirst({
881
+ where: { id: { equals: id } }
882
+ });
883
+ if (!entity) return null;
884
+ await resolveRelations(entity, schema, this.db, this.registry, options);
885
+ await resolveComputedFields(entity, schema, this.db, this.context);
886
+ return entity;
887
+ }
888
+ /**
889
+ * Find multiple entities with filtering and pagination
890
+ *
891
+ * @param entityName - The entity type to find
892
+ * @param options - Query options (filters, pagination, relations)
893
+ * @returns Array of entities
894
+ *
895
+ * @example
896
+ * ```typescript
897
+ * const users = await resolver.findMany('user', {
898
+ * where: { role: 'admin' },
899
+ * limit: 20,
900
+ * offset: 0,
901
+ * include: ['profile'],
902
+ * });
903
+ * ```
904
+ */
905
+ async findMany(entityName, options = {}) {
906
+ clearComputeCache();
907
+ const schema = this.registry.getOrThrow(entityName);
908
+ const entityDb = this.db[entityName];
909
+ if (!entityDb) {
910
+ throw new Error(`Entity '${entityName}' not found in database`);
911
+ }
912
+ const query = {};
913
+ if (options.where) {
914
+ query.where = this.buildWhereClause(options.where);
915
+ }
916
+ if (options.limit) {
917
+ query.take = options.limit;
918
+ }
919
+ if (options.offset) {
920
+ query.skip = options.offset;
921
+ }
922
+ if (options.orderBy) {
923
+ query.orderBy = options.orderBy;
924
+ }
925
+ const entities = entityDb.findMany(query);
926
+ await eagerLoadRelations(entities, schema, this.db, this.registry, options);
927
+ for (const entity of entities) {
928
+ await resolveComputedFields(entity, schema, this.db, this.context);
929
+ }
930
+ return entities;
931
+ }
932
+ /**
933
+ * Create a new entity
934
+ *
935
+ * @param entityName - The entity type to create
936
+ * @param data - The entity data
937
+ * @param options - Query options for the returned entity
938
+ * @returns The created entity with relations and computed fields
939
+ *
940
+ * @example
941
+ * ```typescript
942
+ * const user = await resolver.create('user', {
943
+ * name: 'John Doe',
944
+ * email: 'john@example.com',
945
+ * });
946
+ * ```
947
+ */
948
+ async create(entityName, data, options = {}) {
949
+ const schema = this.registry.getOrThrow(entityName);
950
+ const entityDb = this.db[entityName];
951
+ if (!entityDb) {
952
+ throw new Error(`Entity '${entityName}' not found in database`);
953
+ }
954
+ if (!entityDb.create) {
955
+ throw new Error(`Entity '${entityName}' does not support create operation`);
956
+ }
957
+ const entity = entityDb.create(data);
958
+ await resolveRelations(entity, schema, this.db, this.registry, options);
959
+ await resolveComputedFields(entity, schema, this.db, this.context);
960
+ return entity;
961
+ }
962
+ /**
963
+ * Update an existing entity
964
+ *
965
+ * @param entityName - The entity type to update
966
+ * @param id - The entity ID
967
+ * @param data - The update data
968
+ * @param options - Query options for the returned entity
969
+ * @returns The updated entity or null if not found
970
+ *
971
+ * @example
972
+ * ```typescript
973
+ * const user = await resolver.update('user', '123', {
974
+ * name: 'Jane Doe',
975
+ * });
976
+ * ```
977
+ */
978
+ async update(entityName, id, data, options = {}) {
979
+ const schema = this.registry.getOrThrow(entityName);
980
+ const entityDb = this.db[entityName];
981
+ if (!entityDb) {
982
+ throw new Error(`Entity '${entityName}' not found in database`);
983
+ }
984
+ if (!entityDb.update) {
985
+ throw new Error(`Entity '${entityName}' does not support update operation`);
986
+ }
987
+ const entity = entityDb.update({
988
+ where: { id: { equals: id } },
989
+ data
990
+ });
991
+ if (!entity) return null;
992
+ await resolveRelations(entity, schema, this.db, this.registry, options);
993
+ await resolveComputedFields(entity, schema, this.db, this.context);
994
+ return entity;
995
+ }
996
+ /**
997
+ * Delete an entity by ID
998
+ *
999
+ * @param entityName - The entity type to delete
1000
+ * @param id - The entity ID
1001
+ * @returns true if deleted, false if not found
1002
+ *
1003
+ * @example
1004
+ * ```typescript
1005
+ * const deleted = await resolver.delete('user', '123');
1006
+ * ```
1007
+ */
1008
+ delete(entityName, id) {
1009
+ const entityDb = this.db[entityName];
1010
+ if (!entityDb) {
1011
+ throw new Error(`Entity '${entityName}' not found in database`);
1012
+ }
1013
+ if (!entityDb.delete) {
1014
+ throw new Error(`Entity '${entityName}' does not support delete operation`);
1015
+ }
1016
+ const deleted = entityDb.delete({
1017
+ where: { id: { equals: id } }
1018
+ });
1019
+ return deleted !== null;
1020
+ }
1021
+ /**
1022
+ * Count entities matching a filter
1023
+ *
1024
+ * @param entityName - The entity type to count
1025
+ * @param options - Count options (filters)
1026
+ * @returns The count
1027
+ *
1028
+ * @example
1029
+ * ```typescript
1030
+ * const adminCount = await resolver.count('user', {
1031
+ * where: { role: 'admin' },
1032
+ * });
1033
+ * ```
1034
+ */
1035
+ count(entityName, options = {}) {
1036
+ const entityDb = this.db[entityName];
1037
+ if (!entityDb) {
1038
+ throw new Error(`Entity '${entityName}' not found in database`);
1039
+ }
1040
+ const query = options.where ? { where: this.buildWhereClause(options.where) } : {};
1041
+ return entityDb.count(query);
1042
+ }
1043
+ /**
1044
+ * Resolve a view with parameters
1045
+ *
1046
+ * @param viewName - The view name
1047
+ * @param params - View parameters
1048
+ * @returns The resolved view data
1049
+ *
1050
+ * @example
1051
+ * ```typescript
1052
+ * const userFull = await resolver.view('user-full', { id: '123' });
1053
+ * ```
1054
+ */
1055
+ async view(viewName, params) {
1056
+ clearComputeCache();
1057
+ const viewSchema = this.registry.getViewOrThrow(viewName);
1058
+ return this.viewResolver.resolve(viewSchema, params, this.context);
1059
+ }
1060
+ /**
1061
+ * Build a where clause from simple object to database format
1062
+ */
1063
+ buildWhereClause(where) {
1064
+ const clause = {};
1065
+ for (const [key, value] of Object.entries(where)) {
1066
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1067
+ clause[key] = value;
1068
+ } else if (Array.isArray(value)) {
1069
+ clause[key] = { in: value };
1070
+ } else {
1071
+ clause[key] = { equals: value };
1072
+ }
1073
+ }
1074
+ return clause;
1075
+ }
1076
+ /**
1077
+ * Get the current resolver context
1078
+ */
1079
+ getContext() {
1080
+ return { ...this.context };
1081
+ }
1082
+ /**
1083
+ * Create a new resolver with updated context
1084
+ */
1085
+ withContext(context) {
1086
+ return new _Resolver(this.registry, this.db, { ...this.context, ...context });
1087
+ }
1088
+ };
1089
+ function createResolver(registry2, db, context) {
1090
+ return new Resolver(registry2, db, context);
1091
+ }
1092
+
1093
+ // src/runtime/setup.ts
1094
+ var state = {
1095
+ initialized: false,
1096
+ adapter: null,
1097
+ schemas: [],
1098
+ worker: null,
1099
+ server: null
1100
+ };
1101
+ async function setup(options) {
1102
+ if (state.initialized) {
1103
+ console.warn("Schemock runtime already initialized");
1104
+ return;
1105
+ }
1106
+ if (options) {
1107
+ state.adapter = options.adapter;
1108
+ state.schemas = options.schemas;
1109
+ }
1110
+ if (options?.startMsw && typeof window !== "undefined" && options.schemas.length > 0) {
1111
+ await startMswWorker(options);
1112
+ }
1113
+ state.initialized = true;
1114
+ }
1115
+ async function startMswWorker(options) {
1116
+ try {
1117
+ const { setupWorker } = await import('msw/browser');
1118
+ const { createHandlers: createHandlers2 } = await Promise.resolve().then(() => (init_handlers(), handlers_exports));
1119
+ const handlers = createHandlers2(
1120
+ options.schemas,
1121
+ state.adapter,
1122
+ options.mswOptions
1123
+ );
1124
+ state.worker = setupWorker(...handlers);
1125
+ await state.worker.start({
1126
+ quiet: options.mswOptions?.quiet ?? true
1127
+ });
1128
+ } catch (error) {
1129
+ console.warn("MSW setup failed:", error);
1130
+ }
1131
+ }
1132
+ async function teardown() {
1133
+ if (state.worker) {
1134
+ await state.worker.stop();
1135
+ state.worker = null;
1136
+ }
1137
+ state.adapter = null;
1138
+ state.schemas = [];
1139
+ state.initialized = false;
1140
+ }
1141
+ function isInitialized() {
1142
+ return state.initialized;
1143
+ }
1144
+ function getAdapter() {
1145
+ return state.adapter;
1146
+ }
1147
+ function setAdapter(adapter) {
1148
+ state.adapter = adapter;
1149
+ }
1150
+
1151
+ // src/runtime/seed.ts
1152
+ function seed(counts) {
1153
+ const adapter = getAdapter();
1154
+ if (!adapter) {
1155
+ throw new Error(
1156
+ "No adapter configured. Call setup() first or use adapter.seed() directly."
1157
+ );
1158
+ }
1159
+ if (adapter.name !== "mock") {
1160
+ throw new Error(
1161
+ `seed() only works with MockAdapter. Current adapter: ${adapter.name}`
1162
+ );
1163
+ }
1164
+ adapter.seed(counts);
1165
+ }
1166
+ function reset() {
1167
+ const adapter = getAdapter();
1168
+ if (!adapter) {
1169
+ throw new Error("No adapter configured. Call setup() first.");
1170
+ }
1171
+ if (adapter.name !== "mock") {
1172
+ throw new Error(
1173
+ `reset() only works with MockAdapter. Current adapter: ${adapter.name}`
1174
+ );
1175
+ }
1176
+ adapter.reset();
1177
+ }
1178
+ function seedWithRelations(counts, relations) {
1179
+ const adapter = getAdapter();
1180
+ if (!adapter || adapter.name !== "mock") {
1181
+ throw new Error("seedWithRelations() requires MockAdapter");
1182
+ }
1183
+ const deps = /* @__PURE__ */ new Map();
1184
+ for (const entity of Object.keys(counts)) {
1185
+ deps.set(entity, /* @__PURE__ */ new Set());
1186
+ }
1187
+ for (const [entity, fks] of Object.entries(relations)) {
1188
+ for (const targetEntity of Object.values(fks)) {
1189
+ deps.get(entity)?.add(targetEntity);
1190
+ }
1191
+ }
1192
+ const sorted = [];
1193
+ const visited = /* @__PURE__ */ new Set();
1194
+ const visiting = /* @__PURE__ */ new Set();
1195
+ function visit(entity) {
1196
+ if (visited.has(entity)) return;
1197
+ if (visiting.has(entity)) {
1198
+ throw new Error(`Circular dependency detected: ${entity}`);
1199
+ }
1200
+ visiting.add(entity);
1201
+ for (const dep of deps.get(entity) || []) {
1202
+ visit(dep);
1203
+ }
1204
+ visiting.delete(entity);
1205
+ visited.add(entity);
1206
+ sorted.push(entity);
1207
+ }
1208
+ for (const entity of Object.keys(counts)) {
1209
+ visit(entity);
1210
+ }
1211
+ const entityIds = {};
1212
+ for (const entity of sorted) {
1213
+ const count = counts[entity];
1214
+ if (count === void 0) continue;
1215
+ const fks = relations[entity] || {};
1216
+ const parentIds = {};
1217
+ for (const [fkField, parentEntity] of Object.entries(fks)) {
1218
+ parentIds[fkField] = entityIds[parentEntity] || [];
1219
+ }
1220
+ entityIds[entity] = [];
1221
+ for (let i = 0; i < count; i++) {
1222
+ const data = {};
1223
+ for (const [fkField, ids] of Object.entries(parentIds)) {
1224
+ if (ids.length > 0) {
1225
+ data[fkField] = ids[Math.floor(Math.random() * ids.length)];
1226
+ }
1227
+ }
1228
+ const result = adapter.create({
1229
+ entity,
1230
+ data
1231
+ });
1232
+ result.then((res) => {
1233
+ if (res.data?.id) {
1234
+ entityIds[entity].push(res.data.id);
1235
+ }
1236
+ });
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ // src/runtime/index.ts
1242
+ init_handlers();
1243
+
1244
+ export { Resolver, SchemaRegistry, ViewResolver, clearComputeCache, createHandlers, createResolver, createViewResolver, eagerLoadRelations, getAdapter, isInitialized, registry, reset, resolveComputedField, resolveComputedFields, resolveComputedFieldsSync, resolveRelation, resolveRelations, seed, seedWithRelations, setAdapter, setup, teardown, topologicalSort };
1245
+ //# sourceMappingURL=index.mjs.map
1246
+ //# sourceMappingURL=index.mjs.map