@cedarjs/internal 4.0.1-next.0 → 4.0.1-next.67

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.
@@ -28,15 +28,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var gqlormSchema_exports = {};
30
30
  __export(gqlormSchema_exports, {
31
+ buildBackendModelInfo: () => buildBackendModelInfo,
32
+ buildFrontendModelInfo: () => buildFrontendModelInfo,
31
33
  buildModelSchema: () => buildModelSchema,
32
- generateGqlormArtifacts: () => generateGqlormArtifacts
34
+ generateGqlormArtifacts: () => generateGqlormArtifacts,
35
+ generateGqlormBackendContent: () => generateGqlormBackendContent,
36
+ generateWebGqlormModelsContent: () => generateWebGqlormModelsContent,
37
+ getExistingSdlTypeNames: () => getExistingSdlTypeNames,
38
+ mapDmmfTypeToGraphql: () => mapDmmfTypeToGraphql
33
39
  });
34
40
  module.exports = __toCommonJS(gqlormSchema_exports);
35
41
  var import_node_fs = __toESM(require("node:fs"), 1);
36
42
  var import_node_path = __toESM(require("node:path"), 1);
37
43
  var import_project_config = require("@cedarjs/project-config");
38
- const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set(["RW_DataMigration"]);
39
- const SENSITIVE_PATTERNS = ["password", "secret", "token", "hash", "salt"];
44
+ var import_cedarPluralize = require("@cedarjs/utils/cedarPluralize");
45
+ const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set([
46
+ "RW_DataMigration",
47
+ "Cedar_DataMigration"
48
+ ]);
49
+ const SENSITIVE_PATTERNS = [
50
+ "password",
51
+ "secret",
52
+ "token",
53
+ "hash",
54
+ "salt",
55
+ "apikey",
56
+ "secretkey",
57
+ "encryptionkey",
58
+ "privatekey"
59
+ ];
60
+ const DEFAULT_GQLORM_BACKEND_CONFIG = {
61
+ membershipModel: "Membership",
62
+ membershipModelCamel: "membership",
63
+ membershipUserField: "userId",
64
+ membershipOrganizationField: "organizationId",
65
+ membershipModelExists: false
66
+ };
40
67
  function hasDirective(doc, directive) {
41
68
  if (!doc) {
42
69
  return false;
@@ -56,6 +83,172 @@ function emitSensitivityWarning(modelName, fieldName) {
56
83
  `
57
84
  );
58
85
  }
86
+ const DMMF_TYPE_TO_GRAPHQL = {
87
+ String: "String",
88
+ Int: "Int",
89
+ Float: "Float",
90
+ BigInt: "BigInt",
91
+ Boolean: "Boolean",
92
+ DateTime: "DateTime",
93
+ Json: "JSON",
94
+ Decimal: "String",
95
+ Bytes: "String"
96
+ };
97
+ function mapDmmfTypeToGraphql(type, kind) {
98
+ if (kind === "enum") {
99
+ return "String";
100
+ }
101
+ return DMMF_TYPE_TO_GRAPHQL[type] ?? "String";
102
+ }
103
+ function graphqlTypeToTsType(graphqlType) {
104
+ switch (graphqlType) {
105
+ case "Int":
106
+ case "Float":
107
+ case "BigInt":
108
+ return "number";
109
+ case "Boolean":
110
+ return "boolean";
111
+ default:
112
+ return "string";
113
+ }
114
+ }
115
+ function graphqlTypeToTsInterfaceType(graphqlType, isRequired) {
116
+ let tsType;
117
+ switch (graphqlType) {
118
+ case "Int":
119
+ case "Float":
120
+ case "BigInt":
121
+ tsType = "number";
122
+ break;
123
+ case "Boolean":
124
+ tsType = "boolean";
125
+ break;
126
+ case "DateTime":
127
+ tsType = "Date";
128
+ break;
129
+ default:
130
+ tsType = "string";
131
+ }
132
+ return isRequired ? tsType : `${tsType} | null`;
133
+ }
134
+ function dmmfTypeToFrontendTsType(type, kind, isRequired) {
135
+ let tsType;
136
+ if (kind === "enum") {
137
+ tsType = "string";
138
+ } else {
139
+ switch (type) {
140
+ case "String":
141
+ tsType = "string";
142
+ break;
143
+ case "Int":
144
+ case "Float":
145
+ tsType = "number";
146
+ break;
147
+ case "BigInt":
148
+ tsType = "bigint";
149
+ break;
150
+ case "Boolean":
151
+ tsType = "boolean";
152
+ break;
153
+ case "DateTime":
154
+ tsType = "string";
155
+ break;
156
+ case "Json":
157
+ tsType = "unknown";
158
+ break;
159
+ case "Bytes":
160
+ case "Decimal":
161
+ tsType = "string";
162
+ break;
163
+ default:
164
+ tsType = "unknown";
165
+ }
166
+ }
167
+ if (isRequired) {
168
+ return tsType;
169
+ }
170
+ return `${tsType} | null`;
171
+ }
172
+ function buildFrontendModelInfo(dmmf) {
173
+ const models = [];
174
+ for (const model of dmmf.datamodel.models) {
175
+ if (INTERNAL_MODEL_NAMES.has(model.name)) {
176
+ continue;
177
+ }
178
+ if (hasDirective(model.documentation, "hide")) {
179
+ continue;
180
+ }
181
+ const fields = [];
182
+ for (const field of model.fields) {
183
+ if (field.kind !== "scalar" && field.kind !== "enum") {
184
+ continue;
185
+ }
186
+ if (hasDirective(field.documentation, "hide")) {
187
+ continue;
188
+ }
189
+ const isShown = hasDirective(field.documentation, "show");
190
+ if (!isShown && isSensitiveField(field.name)) {
191
+ continue;
192
+ }
193
+ fields.push({
194
+ name: field.name,
195
+ tsType: dmmfTypeToFrontendTsType(
196
+ field.type,
197
+ field.kind,
198
+ field.isRequired
199
+ )
200
+ });
201
+ }
202
+ if (fields.length > 0) {
203
+ models.push({
204
+ modelName: model.name,
205
+ camelName: model.name.charAt(0).toLowerCase() + model.name.slice(1),
206
+ fields
207
+ });
208
+ }
209
+ }
210
+ return models;
211
+ }
212
+ function generateWebGqlormModelsContent(models) {
213
+ if (models.length === 0) {
214
+ return [
215
+ "// Auto-generated by Cedar \u2014 do not edit",
216
+ "// Regenerated on every codegen run. Source: api/db/schema.prisma",
217
+ "",
218
+ "declare module '@cedarjs/gqlorm/types/orm' {",
219
+ " interface GqlormTypeMap {}",
220
+ "}",
221
+ ""
222
+ ].join("\n");
223
+ }
224
+ const lines = [
225
+ "// Auto-generated by Cedar \u2014 do not edit",
226
+ "// Regenerated on every codegen run. Source: api/db/schema.prisma",
227
+ "",
228
+ "declare namespace GqlormScalar {"
229
+ ];
230
+ for (const model of models) {
231
+ lines.push(` interface ${model.modelName} {`);
232
+ for (const field of model.fields) {
233
+ lines.push(` ${field.name}: ${field.tsType}`);
234
+ }
235
+ lines.push(" }");
236
+ lines.push("");
237
+ }
238
+ lines.push("}");
239
+ lines.push("");
240
+ lines.push("declare module '@cedarjs/gqlorm/types/orm' {");
241
+ lines.push(" interface GqlormTypeMap {");
242
+ lines.push(" models: {");
243
+ for (const model of models) {
244
+ lines.push(` ${model.camelName}: GqlormScalar.${model.modelName}`);
245
+ }
246
+ lines.push(" }");
247
+ lines.push(" }");
248
+ lines.push("}");
249
+ lines.push("");
250
+ return lines.join("\n");
251
+ }
59
252
  function buildModelSchema(dmmf) {
60
253
  const schema = {};
61
254
  for (const model of dmmf.datamodel.models) {
@@ -90,7 +283,368 @@ function buildModelSchema(dmmf) {
90
283
  }
91
284
  return schema;
92
285
  }
286
+ function buildBackendModelInfo(dmmf) {
287
+ const models = [];
288
+ for (const model of dmmf.datamodel.models) {
289
+ if (INTERNAL_MODEL_NAMES.has(model.name)) {
290
+ continue;
291
+ }
292
+ if (hasDirective(model.documentation, "hide")) {
293
+ continue;
294
+ }
295
+ const fields = [];
296
+ for (const field of model.fields) {
297
+ if (field.kind !== "scalar" && field.kind !== "enum") {
298
+ continue;
299
+ }
300
+ if (hasDirective(field.documentation, "hide")) {
301
+ continue;
302
+ }
303
+ const isShown = hasDirective(field.documentation, "show");
304
+ if (!isShown && isSensitiveField(field.name)) {
305
+ continue;
306
+ }
307
+ fields.push({
308
+ name: field.name,
309
+ graphqlType: mapDmmfTypeToGraphql(field.type, field.kind),
310
+ isRequired: field.isRequired,
311
+ isId: field.isId
312
+ });
313
+ }
314
+ if (fields.length > 0) {
315
+ const camelName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
316
+ const pluralName = (0, import_cedarPluralize.pluralize)(camelName);
317
+ models.push({
318
+ modelName: model.name,
319
+ camelName,
320
+ pluralName,
321
+ fields,
322
+ idField: fields.find((f) => f.isId)
323
+ });
324
+ }
325
+ }
326
+ return models;
327
+ }
328
+ const TYPE_DEF_REGEX = /\btype\s+([A-Z]\w*)\s*\{/g;
329
+ const STRUCTURAL_TYPE_NAMES = /* @__PURE__ */ new Set(["Query", "Mutation", "Subscription"]);
330
+ function getExistingSdlTypeNames(graphqlDir) {
331
+ const typeNames = /* @__PURE__ */ new Set();
332
+ if (!import_node_fs.default.existsSync(graphqlDir)) {
333
+ return typeNames;
334
+ }
335
+ const sdlFiles = import_node_fs.default.readdirSync(graphqlDir).filter((file) => {
336
+ return /\.sdl\.(ts|js)$/.test(file) && !file.startsWith("__gqlorm__");
337
+ });
338
+ for (const file of sdlFiles) {
339
+ const content = import_node_fs.default.readFileSync(import_node_path.default.join(graphqlDir, file), "utf-8");
340
+ let match;
341
+ TYPE_DEF_REGEX.lastIndex = 0;
342
+ while ((match = TYPE_DEF_REGEX.exec(content)) !== null) {
343
+ const name = match[1];
344
+ if (!STRUCTURAL_TYPE_NAMES.has(name)) {
345
+ typeNames.add(name);
346
+ }
347
+ }
348
+ }
349
+ return typeNames;
350
+ }
351
+ function generateGqlormBackendContent(models, config = DEFAULT_GQLORM_BACKEND_CONFIG) {
352
+ if (models.length === 0) {
353
+ return "";
354
+ }
355
+ const anyModelNeedsOrgScoping = config.membershipModelExists && models.some(
356
+ (m) => m.camelName !== config.membershipModelCamel && m.fields.some((f) => f.name === config.membershipOrganizationField)
357
+ );
358
+ const anyModelNeedsAuth = models.some((m) => {
359
+ const hasUserField = m.fields.some(
360
+ (f) => f.name === config.membershipUserField
361
+ );
362
+ const hasOrgField = m.fields.some(
363
+ (f) => f.name === config.membershipOrganizationField
364
+ );
365
+ const isMembership = m.camelName === config.membershipModelCamel;
366
+ return hasUserField || hasOrgField && config.membershipModelExists && !isMembership;
367
+ });
368
+ const lines = [
369
+ "// This file is auto-generated by Cedar gqlorm codegen.",
370
+ "// Do not edit \u2014 it will be overwritten on every codegen run.",
371
+ "// To hide a model from gqlorm, add /// @gqlorm hide in schema.prisma.",
372
+ "",
373
+ "import gql from 'graphql-tag'",
374
+ ...anyModelNeedsAuth ? [
375
+ "import { AuthenticationError, ForbiddenError } from '@cedarjs/graphql-server'"
376
+ ] : [],
377
+ "",
378
+ "// Minimal context type used in auth checks",
379
+ "interface GqlormContext {",
380
+ " currentUser: Record<string, unknown> | null | undefined",
381
+ "}",
382
+ "",
383
+ "// Generated minimal interface \u2014 only visible models and fields, only the",
384
+ "// operations used by this file. No @gqlorm hide models, no sensitive fields.",
385
+ "// Scoped to avoid any dependency on the generated Prisma client path or",
386
+ "// @prisma/client (which is an empty shim in Prisma v7).",
387
+ "interface GqlormDb {"
388
+ ];
389
+ for (const model of models) {
390
+ const selectType = model.fields.map((f) => `${f.name}: true`).join("; ");
391
+ lines.push(` ${model.camelName}: {`);
392
+ lines.push(" findMany(args: {");
393
+ lines.push(" where?: Record<string, unknown>");
394
+ lines.push(` select: Partial<{ ${selectType} }>`);
395
+ lines.push(" }): Promise<");
396
+ lines.push(" Array<{");
397
+ for (const field of model.fields) {
398
+ const tsType = graphqlTypeToTsInterfaceType(
399
+ field.graphqlType,
400
+ field.isRequired
401
+ );
402
+ lines.push(` ${field.name}: ${tsType}`);
403
+ }
404
+ lines.push(" }>");
405
+ lines.push(" >");
406
+ if (model.idField) {
407
+ const idTsType = graphqlTypeToTsType(model.idField.graphqlType);
408
+ lines.push(" findUnique(args: {");
409
+ lines.push(` where: { ${model.idField.name}: ${idTsType} }`);
410
+ lines.push(` select: { ${selectType} }`);
411
+ lines.push(" }): Promise<{");
412
+ for (const field of model.fields) {
413
+ const tsType = graphqlTypeToTsInterfaceType(
414
+ field.graphqlType,
415
+ field.isRequired
416
+ );
417
+ lines.push(` ${field.name}: ${tsType}`);
418
+ }
419
+ lines.push(" } | null>");
420
+ }
421
+ if (anyModelNeedsOrgScoping && model.camelName === config.membershipModelCamel) {
422
+ lines.push(" findFirst(args: {");
423
+ lines.push(" where: Record<string, unknown>");
424
+ lines.push(" }): Promise<Record<string, unknown> | null>");
425
+ }
426
+ lines.push(" }");
427
+ }
428
+ const membershipAlreadyInModels = models.some(
429
+ (m) => m.camelName === config.membershipModelCamel
430
+ );
431
+ if (anyModelNeedsOrgScoping && !membershipAlreadyInModels) {
432
+ lines.push(` ${config.membershipModelCamel}: {`);
433
+ lines.push(" findMany(args: {");
434
+ lines.push(" where: Record<string, unknown>");
435
+ lines.push(` select: { ${config.membershipOrganizationField}: true }`);
436
+ lines.push(" }): Promise<");
437
+ lines.push(
438
+ ` Array<{ ${config.membershipOrganizationField}: unknown }>`
439
+ );
440
+ lines.push(" >");
441
+ lines.push(" findFirst(args: {");
442
+ lines.push(" where: Record<string, unknown>");
443
+ lines.push(" }): Promise<Record<string, unknown> | null>");
444
+ lines.push(" }");
445
+ }
446
+ lines.push("}");
447
+ lines.push("");
448
+ lines.push("export const schema = gql`");
449
+ for (const model of models) {
450
+ lines.push(` type ${model.modelName} {`);
451
+ for (const field of model.fields) {
452
+ const nullMark = field.isRequired ? "!" : "";
453
+ lines.push(` ${field.name}: ${field.graphqlType}${nullMark}`);
454
+ }
455
+ lines.push(" }");
456
+ lines.push("");
457
+ }
458
+ lines.push(" type Query {");
459
+ for (const model of models) {
460
+ const hasUserField = model.fields.some(
461
+ (f) => f.name === config.membershipUserField
462
+ );
463
+ const hasOrgField = model.fields.some(
464
+ (f) => f.name === config.membershipOrganizationField
465
+ );
466
+ const isMembershipModel = model.camelName === config.membershipModelCamel;
467
+ const needsAuth = hasUserField || hasOrgField && config.membershipModelExists && !isMembershipModel;
468
+ const authDirective = needsAuth ? "@requireAuth" : "@skipAuth";
469
+ lines.push(
470
+ ` ${model.pluralName}: [${model.modelName}!]! ${authDirective}`
471
+ );
472
+ if (model.idField) {
473
+ const idNullMark = model.idField.isRequired ? "!" : "";
474
+ lines.push(
475
+ ` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName} ${authDirective}`
476
+ );
477
+ }
478
+ }
479
+ lines.push(" }");
480
+ lines.push("`");
481
+ lines.push("");
482
+ lines.push(
483
+ "// db is passed in from graphql.ts by the Babel inject plugin, which imports it"
484
+ );
485
+ lines.push(
486
+ "// from 'src/lib/db' in a context where that alias resolves correctly."
487
+ );
488
+ lines.push("export function createGqlormResolvers(db: GqlormDb) {");
489
+ lines.push(" return {");
490
+ lines.push(" Query: {");
491
+ for (let i = 0; i < models.length; i++) {
492
+ const model = models[i];
493
+ const selectObj = model.fields.map((f) => `${f.name}: true`).join(", ");
494
+ const hasUserField = model.fields.some(
495
+ (f) => f.name === config.membershipUserField
496
+ );
497
+ const hasOrgField = model.fields.some(
498
+ (f) => f.name === config.membershipOrganizationField
499
+ );
500
+ const isMembershipModel = model.camelName === config.membershipModelCamel;
501
+ const useOrgScoping = hasOrgField && config.membershipModelExists && !isMembershipModel;
502
+ lines.push(
503
+ ` ${model.pluralName}: async (_root: unknown, _args: unknown, ${hasUserField || useOrgScoping ? "context" : "_context"}: GqlormContext) => {`
504
+ );
505
+ if (hasUserField || useOrgScoping) {
506
+ lines.push(" if (!context.currentUser) {");
507
+ lines.push(
508
+ ` throw new AuthenticationError("You don't have permission to do that.")`
509
+ );
510
+ lines.push(" }");
511
+ lines.push(" const currentUserId = context.currentUser['id']");
512
+ lines.push(
513
+ " if (currentUserId === undefined || currentUserId === null) {"
514
+ );
515
+ lines.push(
516
+ ` throw new AuthenticationError("Could not determine the current user's ID.")`
517
+ );
518
+ lines.push(" }");
519
+ lines.push(" const where: Record<string, unknown> = {}");
520
+ if (hasUserField) {
521
+ lines.push(" // Scope to the current user");
522
+ lines.push(
523
+ ` where['${config.membershipUserField}'] = currentUserId`
524
+ );
525
+ }
526
+ if (useOrgScoping) {
527
+ lines.push(" // Scope to the current user's organizations");
528
+ lines.push(
529
+ ` const memberships = await db.${config.membershipModelCamel}.findMany({`
530
+ );
531
+ lines.push(
532
+ ` where: { ${config.membershipUserField}: currentUserId },`
533
+ );
534
+ lines.push(
535
+ ` select: { ${config.membershipOrganizationField}: true },`
536
+ );
537
+ lines.push(" })");
538
+ lines.push(
539
+ ` const organizationIds = memberships.map((m) => m.${config.membershipOrganizationField})`
540
+ );
541
+ lines.push(
542
+ ` where['${config.membershipOrganizationField}'] = { in: organizationIds }`
543
+ );
544
+ }
545
+ lines.push(` return db.${model.camelName}.findMany({`);
546
+ lines.push(" where,");
547
+ lines.push(` select: { ${selectObj} },`);
548
+ lines.push(" })");
549
+ } else {
550
+ lines.push(` return db.${model.camelName}.findMany({`);
551
+ lines.push(` select: { ${selectObj} },`);
552
+ lines.push(" })");
553
+ }
554
+ lines.push(" },");
555
+ if (model.idField) {
556
+ const idFieldName = model.idField.name;
557
+ const tsType = graphqlTypeToTsType(model.idField.graphqlType);
558
+ lines.push(
559
+ ` ${model.camelName}: async (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }, ${hasUserField || useOrgScoping ? "context" : "_context"}: GqlormContext) => {`
560
+ );
561
+ if (hasUserField || useOrgScoping) {
562
+ lines.push(" if (!context.currentUser) {");
563
+ lines.push(
564
+ ` throw new AuthenticationError("You don't have permission to do that.")`
565
+ );
566
+ lines.push(" }");
567
+ lines.push(" const currentUserId = context.currentUser['id']");
568
+ lines.push(
569
+ " if (currentUserId === undefined || currentUserId === null) {"
570
+ );
571
+ lines.push(
572
+ ` throw new AuthenticationError("Could not determine the current user's ID.")`
573
+ );
574
+ lines.push(" }");
575
+ }
576
+ lines.push("");
577
+ lines.push(
578
+ ` const record = await db.${model.camelName}.findUnique({`
579
+ );
580
+ lines.push(` where: { ${idFieldName} },`);
581
+ lines.push(` select: { ${selectObj} },`);
582
+ lines.push(" })");
583
+ lines.push("");
584
+ lines.push(" if (!record) {");
585
+ lines.push(" return null");
586
+ lines.push(" }");
587
+ if (hasUserField) {
588
+ lines.push("");
589
+ lines.push(" // Verify the current user owns this record");
590
+ lines.push(
591
+ ` if (record.${config.membershipUserField} !== currentUserId) {`
592
+ );
593
+ lines.push(
594
+ ` throw new ForbiddenError('Not authorized to access this resource')`
595
+ );
596
+ lines.push(" }");
597
+ }
598
+ if (useOrgScoping) {
599
+ lines.push("");
600
+ lines.push(
601
+ " // Verify the current user belongs to the record's organization"
602
+ );
603
+ lines.push(
604
+ ` const membership = await db.${config.membershipModelCamel}.findFirst({`
605
+ );
606
+ lines.push(" where: {");
607
+ lines.push(` ${config.membershipUserField}: currentUserId,`);
608
+ lines.push(
609
+ ` ${config.membershipOrganizationField}: record.${config.membershipOrganizationField},`
610
+ );
611
+ lines.push(" },");
612
+ lines.push(" })");
613
+ lines.push(" if (!membership) {");
614
+ lines.push(
615
+ ` throw new ForbiddenError('Not authorized to access this resource')`
616
+ );
617
+ lines.push(" }");
618
+ }
619
+ lines.push("");
620
+ lines.push(" return record");
621
+ lines.push(" },");
622
+ }
623
+ if (i < models.length - 1) {
624
+ lines.push("");
625
+ }
626
+ }
627
+ lines.push(" },");
628
+ lines.push(" }");
629
+ lines.push("}");
630
+ lines.push("");
631
+ return lines.join("\n");
632
+ }
93
633
  async function generateGqlormArtifacts() {
634
+ if (!(0, import_project_config.getConfig)().experimental?.gqlorm?.enabled) {
635
+ const generatedBase = (0, import_project_config.getPaths)().generated.base;
636
+ const staleFiles = [
637
+ import_node_path.default.join(generatedBase, "gqlorm", "backend.ts"),
638
+ import_node_path.default.join(generatedBase, "gqlorm-schema.json"),
639
+ import_node_path.default.join(generatedBase, "types", "includes", "web-gqlorm-models.d.ts")
640
+ ];
641
+ for (const staleFile of staleFiles) {
642
+ if (import_node_fs.default.existsSync(staleFile)) {
643
+ import_node_fs.default.unlinkSync(staleFile);
644
+ }
645
+ }
646
+ return { files: [], errors: [] };
647
+ }
94
648
  const files = [];
95
649
  const errors = [];
96
650
  try {
@@ -98,14 +652,70 @@ async function generateGqlormArtifacts() {
98
652
  const mod = await import("@prisma/internals");
99
653
  const { getDMMF } = mod.default || mod;
100
654
  const dmmf = await getDMMF({ datamodel: schemas });
655
+ const paths = (0, import_project_config.getPaths)();
656
+ const generatedBase = paths.generated.base;
101
657
  const modelSchema = buildModelSchema(dmmf);
102
- const outputPath = import_node_path.default.join(
103
- (0, import_project_config.getPaths)().generated.base,
104
- "gqlorm-schema.json"
658
+ const frontendModels = buildFrontendModelInfo(dmmf);
659
+ const schemaOutputPath = import_node_path.default.join(generatedBase, "gqlorm-schema.json");
660
+ const webTypesOutputPath = import_node_path.default.join(
661
+ generatedBase,
662
+ "types",
663
+ "includes",
664
+ "web-gqlorm-models.d.ts"
665
+ );
666
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(schemaOutputPath), { recursive: true });
667
+ import_node_fs.default.writeFileSync(schemaOutputPath, JSON.stringify(modelSchema, null, 2));
668
+ files.push(schemaOutputPath);
669
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(webTypesOutputPath), { recursive: true });
670
+ import_node_fs.default.writeFileSync(
671
+ webTypesOutputPath,
672
+ generateWebGqlormModelsContent(frontendModels)
105
673
  );
106
- import_node_fs.default.mkdirSync(import_node_path.default.dirname(outputPath), { recursive: true });
107
- import_node_fs.default.writeFileSync(outputPath, JSON.stringify(modelSchema, null, 2));
108
- files.push(outputPath);
674
+ files.push(webTypesOutputPath);
675
+ const backendOutputDir = import_node_path.default.join(generatedBase, "gqlorm");
676
+ const backendOutputPath = import_node_path.default.join(backendOutputDir, "backend.ts");
677
+ const graphqlDir = paths.api.graphql;
678
+ const existingTypes = getExistingSdlTypeNames(graphqlDir);
679
+ const allModels = buildBackendModelInfo(dmmf);
680
+ const gqlormConfig = (0, import_project_config.getConfig)().experimental.gqlorm;
681
+ const membershipModel = gqlormConfig.membershipModel ?? "Membership";
682
+ const membershipModelCamel = membershipModel.charAt(0).toLowerCase() + membershipModel.slice(1);
683
+ const membershipUserField = gqlormConfig.membershipUserField ?? "userId";
684
+ const membershipOrganizationField = gqlormConfig.membershipOrganizationField ?? "organizationId";
685
+ const membershipModelExists = dmmf.datamodel.models.some(
686
+ (m) => m.name === membershipModel
687
+ );
688
+ const backendConfig = {
689
+ membershipModel,
690
+ membershipModelCamel,
691
+ membershipUserField,
692
+ membershipOrganizationField,
693
+ membershipModelExists
694
+ };
695
+ const gqlormModels = allModels.filter(
696
+ (m) => !existingTypes.has(m.modelName)
697
+ );
698
+ const anyModelHasOrgField = gqlormModels.some(
699
+ (m) => m.fields.some((f) => f.name === membershipOrganizationField)
700
+ );
701
+ if (anyModelHasOrgField && !membershipModelExists) {
702
+ console.warn(
703
+ `[gqlorm] One or more models have a \`${membershipOrganizationField}\` field, but the membership model "${membershipModel}" was not found in the schema. Organization-based access scoping will not be applied for these models. Add a \`${membershipModel}\` model to your schema.prisma or configure \`experimental.gqlorm.membershipModel\` in cedar.toml.`
704
+ );
705
+ }
706
+ if (gqlormModels.length > 0) {
707
+ const backendContent = generateGqlormBackendContent(
708
+ gqlormModels,
709
+ backendConfig
710
+ );
711
+ import_node_fs.default.mkdirSync(backendOutputDir, { recursive: true });
712
+ import_node_fs.default.writeFileSync(backendOutputPath, backendContent);
713
+ files.push(backendOutputPath);
714
+ } else {
715
+ if (import_node_fs.default.existsSync(backendOutputPath)) {
716
+ import_node_fs.default.unlinkSync(backendOutputPath);
717
+ }
718
+ }
109
719
  } catch (error) {
110
720
  errors.push({
111
721
  message: "Failed to generate gqlorm schema artifacts",
@@ -116,6 +726,12 @@ async function generateGqlormArtifacts() {
116
726
  }
117
727
  // Annotate the CommonJS export names for ESM import in node:
118
728
  0 && (module.exports = {
729
+ buildBackendModelInfo,
730
+ buildFrontendModelInfo,
119
731
  buildModelSchema,
120
- generateGqlormArtifacts
732
+ generateGqlormArtifacts,
733
+ generateGqlormBackendContent,
734
+ generateWebGqlormModelsContent,
735
+ getExistingSdlTypeNames,
736
+ mapDmmfTypeToGraphql
121
737
  });