@cedarjs/internal 4.0.0-canary.13809 → 4.0.0-canary.13810

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.
@@ -1,5 +1,25 @@
1
1
  import type * as DMMF from '@prisma/dmmf';
2
2
  type ModelSchema = Record<string, string[]>;
3
+ export interface BackendFieldInfo {
4
+ name: string;
5
+ graphqlType: string;
6
+ isRequired: boolean;
7
+ isId: boolean;
8
+ }
9
+ export interface BackendModelInfo {
10
+ modelName: string;
11
+ camelName: string;
12
+ pluralName: string;
13
+ fields: BackendFieldInfo[];
14
+ idField: BackendFieldInfo | undefined;
15
+ }
16
+ /**
17
+ * Map a DMMF field type to its GraphQL SDL equivalent.
18
+ *
19
+ * Enum fields (kind === 'enum') are mapped to String. Unknown scalar types
20
+ * fall back to String.
21
+ */
22
+ export declare function mapDmmfTypeToGraphql(type: string, kind: string): string;
3
23
  /**
4
24
  * Build a ModelSchema from a Prisma DMMF document.
5
25
  *
@@ -7,12 +27,57 @@ type ModelSchema = Record<string, string[]>;
7
27
  * with a mock DMMF object.
8
28
  */
9
29
  export declare function buildModelSchema(dmmf: DMMF.Document): ModelSchema;
30
+ /**
31
+ * Build enriched model information from the DMMF, applying the same visibility
32
+ * rules as `buildModelSchema()` but also collecting type, nullability, and
33
+ * @id flag per field.
34
+ *
35
+ * **Note:** This function silently excludes sensitive fields without emitting
36
+ * warnings. It is designed to run after `buildModelSchema()` (which emits the
37
+ * warnings). In `generateGqlormArtifacts()` the call order is guaranteed, but
38
+ * callers using this function standalone should be aware that no warnings will
39
+ * be printed for auto-hidden sensitive fields.
40
+ *
41
+ * This is a pure function — safe for testing.
42
+ */
43
+ export declare function buildBackendModelInfo(dmmf: DMMF.Document): BackendModelInfo[];
44
+ /**
45
+ * Scan all SDL files in the given directory and return the set of GraphQL type
46
+ * names that are already defined by user-authored SDLs.
47
+ *
48
+ * This prevents gqlorm from generating duplicate type definitions that would
49
+ * cause merge conflicts in `makeMergedSchema`.
50
+ */
51
+ export declare function getExistingSdlTypeNames(graphqlDir: string): Set<string>;
52
+ /**
53
+ * Generate the full TypeScript source for `.cedar/gqlorm/backend.ts`.
54
+ *
55
+ * The generated file exports:
56
+ * - `schema`: a gql DocumentNode with type defs and Query fields
57
+ * - `createGqlormResolvers(db: GqlormDb)`: a factory function that takes a
58
+ * Prisma client-like object and returns a resolvers object
59
+ *
60
+ * The file does NOT import `db` directly. Instead, the Babel inject plugin
61
+ * imports `db` from `src/lib/db` in `graphql.ts` (where that alias resolves
62
+ * correctly) and passes it to `createGqlormResolvers`.
63
+ *
64
+ * The generated `GqlormDb` interface is scoped to exactly the visible models
65
+ * and fields — no hidden/sensitive fields, no @gqlorm hide models, no
66
+ * dependency on the generated Prisma client path or @prisma/client.
67
+ */
68
+ export declare function generateGqlormBackendContent(models: BackendModelInfo[]): string;
10
69
  /**
11
70
  * Generate gqlorm artifacts from the Prisma schema.
12
71
  *
13
72
  * Reads the project's Prisma schema via DMMF, applies visibility rules
14
- * (@gqlorm directives + sensitivity heuristics), and writes the resulting
15
- * ModelSchema to `.cedar/gqlorm-schema.json`.
73
+ * (@gqlorm directives + sensitivity heuristics), and writes:
74
+ *
75
+ * 1. `.cedar/gqlorm-schema.json` — the frontend ModelSchema (field names only)
76
+ * 2. `.cedar/gqlorm/backend.ts` — auto-generated GraphQL types and resolvers
77
+ * for models that don't already have manually-written SDL files
78
+ *
79
+ * Cedar targets Node.js 24, which strips TypeScript types natively without any
80
+ * flags, so backend.ts can be imported directly at runtime
16
81
  *
17
82
  * Returns the same `{ files, errors }` shape used by other generators so it
18
83
  * can be integrated into `generate.ts` without special handling.
@@ -1 +1 @@
1
- {"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AAgBzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAiC3C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,WAAW,CAkDjE;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAC9C,CAAC,CA+BD"}
1
+ {"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AAiBzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAM3C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,gBAAgB,EAAE,CAAA;IAC1B,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAA;CACtC;AAqDD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvE;AAsDD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,WAAW,CAkDjE;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,gBAAgB,EAAE,CA2D7E;AAkBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA6BvE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,gBAAgB,EAAE,GACzB,MAAM,CA0IR;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAC9C,CAAC,CA6DD"}
@@ -28,13 +28,18 @@ 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,
31
32
  buildModelSchema: () => buildModelSchema,
32
- generateGqlormArtifacts: () => generateGqlormArtifacts
33
+ generateGqlormArtifacts: () => generateGqlormArtifacts,
34
+ generateGqlormBackendContent: () => generateGqlormBackendContent,
35
+ getExistingSdlTypeNames: () => getExistingSdlTypeNames,
36
+ mapDmmfTypeToGraphql: () => mapDmmfTypeToGraphql
33
37
  });
34
38
  module.exports = __toCommonJS(gqlormSchema_exports);
35
39
  var import_node_fs = __toESM(require("node:fs"), 1);
36
40
  var import_node_path = __toESM(require("node:path"), 1);
37
41
  var import_project_config = require("@cedarjs/project-config");
42
+ var import_cedarPluralize = require("@cedarjs/utils/cedarPluralize");
38
43
  const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set(["RW_DataMigration"]);
39
44
  const SENSITIVE_PATTERNS = ["password", "secret", "token", "hash", "salt"];
40
45
  function hasDirective(doc, directive) {
@@ -56,6 +61,54 @@ function emitSensitivityWarning(modelName, fieldName) {
56
61
  `
57
62
  );
58
63
  }
64
+ const DMMF_TYPE_TO_GRAPHQL = {
65
+ String: "String",
66
+ Int: "Int",
67
+ Float: "Float",
68
+ BigInt: "BigInt",
69
+ Boolean: "Boolean",
70
+ DateTime: "DateTime",
71
+ Json: "JSON",
72
+ Decimal: "String",
73
+ Bytes: "String"
74
+ };
75
+ function mapDmmfTypeToGraphql(type, kind) {
76
+ if (kind === "enum") {
77
+ return "String";
78
+ }
79
+ return DMMF_TYPE_TO_GRAPHQL[type] ?? "String";
80
+ }
81
+ function graphqlTypeToTsType(graphqlType) {
82
+ switch (graphqlType) {
83
+ case "Int":
84
+ case "Float":
85
+ case "BigInt":
86
+ return "number";
87
+ case "Boolean":
88
+ return "boolean";
89
+ default:
90
+ return "string";
91
+ }
92
+ }
93
+ function graphqlTypeToTsInterfaceType(graphqlType, isRequired) {
94
+ let tsType;
95
+ switch (graphqlType) {
96
+ case "Int":
97
+ case "Float":
98
+ case "BigInt":
99
+ tsType = "number";
100
+ break;
101
+ case "Boolean":
102
+ tsType = "boolean";
103
+ break;
104
+ case "DateTime":
105
+ tsType = "Date";
106
+ break;
107
+ default:
108
+ tsType = "string";
109
+ }
110
+ return isRequired ? tsType : `${tsType} | null`;
111
+ }
59
112
  function buildModelSchema(dmmf) {
60
113
  const schema = {};
61
114
  for (const model of dmmf.datamodel.models) {
@@ -90,6 +143,185 @@ function buildModelSchema(dmmf) {
90
143
  }
91
144
  return schema;
92
145
  }
146
+ function buildBackendModelInfo(dmmf) {
147
+ const models = [];
148
+ for (const model of dmmf.datamodel.models) {
149
+ if (INTERNAL_MODEL_NAMES.has(model.name)) {
150
+ continue;
151
+ }
152
+ if (hasDirective(model.documentation, "hide")) {
153
+ continue;
154
+ }
155
+ const fields = [];
156
+ for (const field of model.fields) {
157
+ if (field.kind !== "scalar" && field.kind !== "enum") {
158
+ continue;
159
+ }
160
+ if (hasDirective(field.documentation, "hide")) {
161
+ continue;
162
+ }
163
+ const isShown = hasDirective(field.documentation, "show");
164
+ if (!isShown && isSensitiveField(field.name)) {
165
+ continue;
166
+ }
167
+ fields.push({
168
+ name: field.name,
169
+ graphqlType: mapDmmfTypeToGraphql(field.type, field.kind),
170
+ isRequired: field.isRequired,
171
+ isId: field.isId
172
+ });
173
+ }
174
+ if (fields.length > 0) {
175
+ const camelName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
176
+ const pluralName = (0, import_cedarPluralize.pluralize)(camelName);
177
+ models.push({
178
+ modelName: model.name,
179
+ camelName,
180
+ pluralName,
181
+ fields,
182
+ idField: fields.find((f) => f.isId)
183
+ });
184
+ }
185
+ }
186
+ return models;
187
+ }
188
+ const TYPE_DEF_REGEX = /\btype\s+([A-Z]\w*)\s*\{/g;
189
+ const STRUCTURAL_TYPE_NAMES = /* @__PURE__ */ new Set(["Query", "Mutation", "Subscription"]);
190
+ function getExistingSdlTypeNames(graphqlDir) {
191
+ const typeNames = /* @__PURE__ */ new Set();
192
+ if (!import_node_fs.default.existsSync(graphqlDir)) {
193
+ return typeNames;
194
+ }
195
+ const sdlFiles = import_node_fs.default.readdirSync(graphqlDir).filter((file) => {
196
+ return /\.sdl\.(ts|js)$/.test(file) && !file.startsWith("__gqlorm__");
197
+ });
198
+ for (const file of sdlFiles) {
199
+ const content = import_node_fs.default.readFileSync(import_node_path.default.join(graphqlDir, file), "utf-8");
200
+ let match;
201
+ TYPE_DEF_REGEX.lastIndex = 0;
202
+ while ((match = TYPE_DEF_REGEX.exec(content)) !== null) {
203
+ const name = match[1];
204
+ if (!STRUCTURAL_TYPE_NAMES.has(name)) {
205
+ typeNames.add(name);
206
+ }
207
+ }
208
+ }
209
+ return typeNames;
210
+ }
211
+ function generateGqlormBackendContent(models) {
212
+ if (models.length === 0) {
213
+ return "";
214
+ }
215
+ const lines = [
216
+ "// This file is auto-generated by Cedar gqlorm codegen.",
217
+ "// Do not edit \u2014 it will be overwritten on every codegen run.",
218
+ "// To hide a model from gqlorm, add /// @gqlorm hide in schema.prisma.",
219
+ "",
220
+ "import gql from 'graphql-tag'",
221
+ "",
222
+ "// Generated minimal interface \u2014 only visible models and fields, only the",
223
+ "// operations used by this file. No @gqlorm hide models, no sensitive fields.",
224
+ "// Scoped to avoid any dependency on the generated Prisma client path or",
225
+ "// @prisma/client (which is an empty shim in Prisma v7).",
226
+ "interface GqlormDb {"
227
+ ];
228
+ for (const model of models) {
229
+ const selectType = model.fields.map((f) => `${f.name}: true`).join("; ");
230
+ lines.push(` ${model.camelName}: {`);
231
+ lines.push(" findMany(args: {");
232
+ lines.push(` select: { ${selectType} }`);
233
+ lines.push(" }): Promise<");
234
+ lines.push(" Array<{");
235
+ for (const field of model.fields) {
236
+ const tsType = graphqlTypeToTsInterfaceType(
237
+ field.graphqlType,
238
+ field.isRequired
239
+ );
240
+ lines.push(` ${field.name}: ${tsType}`);
241
+ }
242
+ lines.push(" }>");
243
+ lines.push(" >");
244
+ if (model.idField) {
245
+ const idTsType = graphqlTypeToTsType(model.idField.graphqlType);
246
+ lines.push(" findUnique(args: {");
247
+ lines.push(` where: { ${model.idField.name}: ${idTsType} }`);
248
+ lines.push(` select: { ${selectType} }`);
249
+ lines.push(" }): Promise<{");
250
+ for (const field of model.fields) {
251
+ const tsType = graphqlTypeToTsInterfaceType(
252
+ field.graphqlType,
253
+ field.isRequired
254
+ );
255
+ lines.push(` ${field.name}: ${tsType}`);
256
+ }
257
+ lines.push(" } | null>");
258
+ }
259
+ lines.push(" }");
260
+ }
261
+ lines.push("}");
262
+ lines.push("");
263
+ lines.push("export const schema = gql`");
264
+ for (const model of models) {
265
+ lines.push(` type ${model.modelName} {`);
266
+ for (const field of model.fields) {
267
+ const nullMark = field.isRequired ? "!" : "";
268
+ lines.push(` ${field.name}: ${field.graphqlType}${nullMark}`);
269
+ }
270
+ lines.push(" }");
271
+ lines.push("");
272
+ }
273
+ lines.push(" type Query {");
274
+ for (const model of models) {
275
+ lines.push(` ${model.pluralName}: [${model.modelName}!]! @skipAuth`);
276
+ if (model.idField) {
277
+ const idNullMark = model.idField.isRequired ? "!" : "";
278
+ lines.push(
279
+ ` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName} @skipAuth`
280
+ );
281
+ }
282
+ }
283
+ lines.push(" }");
284
+ lines.push("`");
285
+ lines.push("");
286
+ lines.push(
287
+ "// db is passed in from graphql.ts by the Babel inject plugin, which imports it"
288
+ );
289
+ lines.push(
290
+ "// from 'src/lib/db' in a context where that alias resolves correctly."
291
+ );
292
+ lines.push("export function createGqlormResolvers(db: GqlormDb) {");
293
+ lines.push(" return {");
294
+ lines.push(" Query: {");
295
+ for (let i = 0; i < models.length; i++) {
296
+ const model = models[i];
297
+ const selectObj = model.fields.map((f) => `${f.name}: true`).join(", ");
298
+ lines.push(` ${model.pluralName}: () => {`);
299
+ lines.push(` return db.${model.camelName}.findMany({`);
300
+ lines.push(` select: { ${selectObj} },`);
301
+ lines.push(" })");
302
+ lines.push(" },");
303
+ if (model.idField) {
304
+ const idFieldName = model.idField.name;
305
+ const tsType = graphqlTypeToTsType(model.idField.graphqlType);
306
+ lines.push(
307
+ ` ${model.camelName}: (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }) => {`
308
+ );
309
+ lines.push(` return db.${model.camelName}.findUnique({`);
310
+ lines.push(` where: { ${idFieldName} },`);
311
+ lines.push(` select: { ${selectObj} },`);
312
+ lines.push(" })");
313
+ lines.push(" },");
314
+ }
315
+ if (i < models.length - 1) {
316
+ lines.push("");
317
+ }
318
+ }
319
+ lines.push(" },");
320
+ lines.push(" }");
321
+ lines.push("}");
322
+ lines.push("");
323
+ return lines.join("\n");
324
+ }
93
325
  async function generateGqlormArtifacts() {
94
326
  const files = [];
95
327
  const errors = [];
@@ -99,13 +331,31 @@ async function generateGqlormArtifacts() {
99
331
  const { getDMMF } = mod.default || mod;
100
332
  const dmmf = await getDMMF({ datamodel: schemas });
101
333
  const modelSchema = buildModelSchema(dmmf);
102
- const outputPath = import_node_path.default.join(
334
+ const schemaOutputPath = import_node_path.default.join(
103
335
  (0, import_project_config.getPaths)().generated.base,
104
336
  "gqlorm-schema.json"
105
337
  );
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);
338
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(schemaOutputPath), { recursive: true });
339
+ import_node_fs.default.writeFileSync(schemaOutputPath, JSON.stringify(modelSchema, null, 2));
340
+ files.push(schemaOutputPath);
341
+ const graphqlDir = (0, import_project_config.getPaths)().api.graphql;
342
+ const existingTypes = getExistingSdlTypeNames(graphqlDir);
343
+ const allModels = buildBackendModelInfo(dmmf);
344
+ const gqlormModels = allModels.filter(
345
+ (m) => !existingTypes.has(m.modelName)
346
+ );
347
+ const backendOutputDir = import_node_path.default.join((0, import_project_config.getPaths)().generated.base, "gqlorm");
348
+ const backendOutputPath = import_node_path.default.join(backendOutputDir, "backend.ts");
349
+ if (gqlormModels.length > 0) {
350
+ const backendContent = generateGqlormBackendContent(gqlormModels);
351
+ import_node_fs.default.mkdirSync(backendOutputDir, { recursive: true });
352
+ import_node_fs.default.writeFileSync(backendOutputPath, backendContent);
353
+ files.push(backendOutputPath);
354
+ } else {
355
+ if (import_node_fs.default.existsSync(backendOutputPath)) {
356
+ import_node_fs.default.unlinkSync(backendOutputPath);
357
+ }
358
+ }
109
359
  } catch (error) {
110
360
  errors.push({
111
361
  message: "Failed to generate gqlorm schema artifacts",
@@ -116,6 +366,10 @@ async function generateGqlormArtifacts() {
116
366
  }
117
367
  // Annotate the CommonJS export names for ESM import in node:
118
368
  0 && (module.exports = {
369
+ buildBackendModelInfo,
119
370
  buildModelSchema,
120
- generateGqlormArtifacts
371
+ generateGqlormArtifacts,
372
+ generateGqlormBackendContent,
373
+ getExistingSdlTypeNames,
374
+ mapDmmfTypeToGraphql
121
375
  });
@@ -1,5 +1,25 @@
1
1
  import type * as DMMF from '@prisma/dmmf';
2
2
  type ModelSchema = Record<string, string[]>;
3
+ export interface BackendFieldInfo {
4
+ name: string;
5
+ graphqlType: string;
6
+ isRequired: boolean;
7
+ isId: boolean;
8
+ }
9
+ export interface BackendModelInfo {
10
+ modelName: string;
11
+ camelName: string;
12
+ pluralName: string;
13
+ fields: BackendFieldInfo[];
14
+ idField: BackendFieldInfo | undefined;
15
+ }
16
+ /**
17
+ * Map a DMMF field type to its GraphQL SDL equivalent.
18
+ *
19
+ * Enum fields (kind === 'enum') are mapped to String. Unknown scalar types
20
+ * fall back to String.
21
+ */
22
+ export declare function mapDmmfTypeToGraphql(type: string, kind: string): string;
3
23
  /**
4
24
  * Build a ModelSchema from a Prisma DMMF document.
5
25
  *
@@ -7,12 +27,57 @@ type ModelSchema = Record<string, string[]>;
7
27
  * with a mock DMMF object.
8
28
  */
9
29
  export declare function buildModelSchema(dmmf: DMMF.Document): ModelSchema;
30
+ /**
31
+ * Build enriched model information from the DMMF, applying the same visibility
32
+ * rules as `buildModelSchema()` but also collecting type, nullability, and
33
+ * @id flag per field.
34
+ *
35
+ * **Note:** This function silently excludes sensitive fields without emitting
36
+ * warnings. It is designed to run after `buildModelSchema()` (which emits the
37
+ * warnings). In `generateGqlormArtifacts()` the call order is guaranteed, but
38
+ * callers using this function standalone should be aware that no warnings will
39
+ * be printed for auto-hidden sensitive fields.
40
+ *
41
+ * This is a pure function — safe for testing.
42
+ */
43
+ export declare function buildBackendModelInfo(dmmf: DMMF.Document): BackendModelInfo[];
44
+ /**
45
+ * Scan all SDL files in the given directory and return the set of GraphQL type
46
+ * names that are already defined by user-authored SDLs.
47
+ *
48
+ * This prevents gqlorm from generating duplicate type definitions that would
49
+ * cause merge conflicts in `makeMergedSchema`.
50
+ */
51
+ export declare function getExistingSdlTypeNames(graphqlDir: string): Set<string>;
52
+ /**
53
+ * Generate the full TypeScript source for `.cedar/gqlorm/backend.ts`.
54
+ *
55
+ * The generated file exports:
56
+ * - `schema`: a gql DocumentNode with type defs and Query fields
57
+ * - `createGqlormResolvers(db: GqlormDb)`: a factory function that takes a
58
+ * Prisma client-like object and returns a resolvers object
59
+ *
60
+ * The file does NOT import `db` directly. Instead, the Babel inject plugin
61
+ * imports `db` from `src/lib/db` in `graphql.ts` (where that alias resolves
62
+ * correctly) and passes it to `createGqlormResolvers`.
63
+ *
64
+ * The generated `GqlormDb` interface is scoped to exactly the visible models
65
+ * and fields — no hidden/sensitive fields, no @gqlorm hide models, no
66
+ * dependency on the generated Prisma client path or @prisma/client.
67
+ */
68
+ export declare function generateGqlormBackendContent(models: BackendModelInfo[]): string;
10
69
  /**
11
70
  * Generate gqlorm artifacts from the Prisma schema.
12
71
  *
13
72
  * Reads the project's Prisma schema via DMMF, applies visibility rules
14
- * (@gqlorm directives + sensitivity heuristics), and writes the resulting
15
- * ModelSchema to `.cedar/gqlorm-schema.json`.
73
+ * (@gqlorm directives + sensitivity heuristics), and writes:
74
+ *
75
+ * 1. `.cedar/gqlorm-schema.json` — the frontend ModelSchema (field names only)
76
+ * 2. `.cedar/gqlorm/backend.ts` — auto-generated GraphQL types and resolvers
77
+ * for models that don't already have manually-written SDL files
78
+ *
79
+ * Cedar targets Node.js 24, which strips TypeScript types natively without any
80
+ * flags, so backend.ts can be imported directly at runtime
16
81
  *
17
82
  * Returns the same `{ files, errors }` shape used by other generators so it
18
83
  * can be integrated into `generate.ts` without special handling.
@@ -1 +1 @@
1
- {"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AAgBzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAiC3C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,WAAW,CAkDjE;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAC9C,CAAC,CA+BD"}
1
+ {"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AAiBzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAM3C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,gBAAgB,EAAE,CAAA;IAC1B,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAA;CACtC;AAqDD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvE;AAsDD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,WAAW,CAkDjE;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,gBAAgB,EAAE,CA2D7E;AAkBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA6BvE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,gBAAgB,EAAE,GACzB,MAAM,CA0IR;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAC9C,CAAC,CA6DD"}
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getPaths, getPrismaSchemas } from "@cedarjs/project-config";
4
+ import { pluralize } from "@cedarjs/utils/cedarPluralize";
4
5
  const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set(["RW_DataMigration"]);
5
6
  const SENSITIVE_PATTERNS = ["password", "secret", "token", "hash", "salt"];
6
7
  function hasDirective(doc, directive) {
@@ -22,6 +23,54 @@ function emitSensitivityWarning(modelName, fieldName) {
22
23
  `
23
24
  );
24
25
  }
26
+ const DMMF_TYPE_TO_GRAPHQL = {
27
+ String: "String",
28
+ Int: "Int",
29
+ Float: "Float",
30
+ BigInt: "BigInt",
31
+ Boolean: "Boolean",
32
+ DateTime: "DateTime",
33
+ Json: "JSON",
34
+ Decimal: "String",
35
+ Bytes: "String"
36
+ };
37
+ function mapDmmfTypeToGraphql(type, kind) {
38
+ if (kind === "enum") {
39
+ return "String";
40
+ }
41
+ return DMMF_TYPE_TO_GRAPHQL[type] ?? "String";
42
+ }
43
+ function graphqlTypeToTsType(graphqlType) {
44
+ switch (graphqlType) {
45
+ case "Int":
46
+ case "Float":
47
+ case "BigInt":
48
+ return "number";
49
+ case "Boolean":
50
+ return "boolean";
51
+ default:
52
+ return "string";
53
+ }
54
+ }
55
+ function graphqlTypeToTsInterfaceType(graphqlType, isRequired) {
56
+ let tsType;
57
+ switch (graphqlType) {
58
+ case "Int":
59
+ case "Float":
60
+ case "BigInt":
61
+ tsType = "number";
62
+ break;
63
+ case "Boolean":
64
+ tsType = "boolean";
65
+ break;
66
+ case "DateTime":
67
+ tsType = "Date";
68
+ break;
69
+ default:
70
+ tsType = "string";
71
+ }
72
+ return isRequired ? tsType : `${tsType} | null`;
73
+ }
25
74
  function buildModelSchema(dmmf) {
26
75
  const schema = {};
27
76
  for (const model of dmmf.datamodel.models) {
@@ -56,6 +105,185 @@ function buildModelSchema(dmmf) {
56
105
  }
57
106
  return schema;
58
107
  }
108
+ function buildBackendModelInfo(dmmf) {
109
+ const models = [];
110
+ for (const model of dmmf.datamodel.models) {
111
+ if (INTERNAL_MODEL_NAMES.has(model.name)) {
112
+ continue;
113
+ }
114
+ if (hasDirective(model.documentation, "hide")) {
115
+ continue;
116
+ }
117
+ const fields = [];
118
+ for (const field of model.fields) {
119
+ if (field.kind !== "scalar" && field.kind !== "enum") {
120
+ continue;
121
+ }
122
+ if (hasDirective(field.documentation, "hide")) {
123
+ continue;
124
+ }
125
+ const isShown = hasDirective(field.documentation, "show");
126
+ if (!isShown && isSensitiveField(field.name)) {
127
+ continue;
128
+ }
129
+ fields.push({
130
+ name: field.name,
131
+ graphqlType: mapDmmfTypeToGraphql(field.type, field.kind),
132
+ isRequired: field.isRequired,
133
+ isId: field.isId
134
+ });
135
+ }
136
+ if (fields.length > 0) {
137
+ const camelName = model.name.charAt(0).toLowerCase() + model.name.slice(1);
138
+ const pluralName = pluralize(camelName);
139
+ models.push({
140
+ modelName: model.name,
141
+ camelName,
142
+ pluralName,
143
+ fields,
144
+ idField: fields.find((f) => f.isId)
145
+ });
146
+ }
147
+ }
148
+ return models;
149
+ }
150
+ const TYPE_DEF_REGEX = /\btype\s+([A-Z]\w*)\s*\{/g;
151
+ const STRUCTURAL_TYPE_NAMES = /* @__PURE__ */ new Set(["Query", "Mutation", "Subscription"]);
152
+ function getExistingSdlTypeNames(graphqlDir) {
153
+ const typeNames = /* @__PURE__ */ new Set();
154
+ if (!fs.existsSync(graphqlDir)) {
155
+ return typeNames;
156
+ }
157
+ const sdlFiles = fs.readdirSync(graphqlDir).filter((file) => {
158
+ return /\.sdl\.(ts|js)$/.test(file) && !file.startsWith("__gqlorm__");
159
+ });
160
+ for (const file of sdlFiles) {
161
+ const content = fs.readFileSync(path.join(graphqlDir, file), "utf-8");
162
+ let match;
163
+ TYPE_DEF_REGEX.lastIndex = 0;
164
+ while ((match = TYPE_DEF_REGEX.exec(content)) !== null) {
165
+ const name = match[1];
166
+ if (!STRUCTURAL_TYPE_NAMES.has(name)) {
167
+ typeNames.add(name);
168
+ }
169
+ }
170
+ }
171
+ return typeNames;
172
+ }
173
+ function generateGqlormBackendContent(models) {
174
+ if (models.length === 0) {
175
+ return "";
176
+ }
177
+ const lines = [
178
+ "// This file is auto-generated by Cedar gqlorm codegen.",
179
+ "// Do not edit \u2014 it will be overwritten on every codegen run.",
180
+ "// To hide a model from gqlorm, add /// @gqlorm hide in schema.prisma.",
181
+ "",
182
+ "import gql from 'graphql-tag'",
183
+ "",
184
+ "// Generated minimal interface \u2014 only visible models and fields, only the",
185
+ "// operations used by this file. No @gqlorm hide models, no sensitive fields.",
186
+ "// Scoped to avoid any dependency on the generated Prisma client path or",
187
+ "// @prisma/client (which is an empty shim in Prisma v7).",
188
+ "interface GqlormDb {"
189
+ ];
190
+ for (const model of models) {
191
+ const selectType = model.fields.map((f) => `${f.name}: true`).join("; ");
192
+ lines.push(` ${model.camelName}: {`);
193
+ lines.push(" findMany(args: {");
194
+ lines.push(` select: { ${selectType} }`);
195
+ lines.push(" }): Promise<");
196
+ lines.push(" Array<{");
197
+ for (const field of model.fields) {
198
+ const tsType = graphqlTypeToTsInterfaceType(
199
+ field.graphqlType,
200
+ field.isRequired
201
+ );
202
+ lines.push(` ${field.name}: ${tsType}`);
203
+ }
204
+ lines.push(" }>");
205
+ lines.push(" >");
206
+ if (model.idField) {
207
+ const idTsType = graphqlTypeToTsType(model.idField.graphqlType);
208
+ lines.push(" findUnique(args: {");
209
+ lines.push(` where: { ${model.idField.name}: ${idTsType} }`);
210
+ lines.push(` select: { ${selectType} }`);
211
+ lines.push(" }): Promise<{");
212
+ for (const field of model.fields) {
213
+ const tsType = graphqlTypeToTsInterfaceType(
214
+ field.graphqlType,
215
+ field.isRequired
216
+ );
217
+ lines.push(` ${field.name}: ${tsType}`);
218
+ }
219
+ lines.push(" } | null>");
220
+ }
221
+ lines.push(" }");
222
+ }
223
+ lines.push("}");
224
+ lines.push("");
225
+ lines.push("export const schema = gql`");
226
+ for (const model of models) {
227
+ lines.push(` type ${model.modelName} {`);
228
+ for (const field of model.fields) {
229
+ const nullMark = field.isRequired ? "!" : "";
230
+ lines.push(` ${field.name}: ${field.graphqlType}${nullMark}`);
231
+ }
232
+ lines.push(" }");
233
+ lines.push("");
234
+ }
235
+ lines.push(" type Query {");
236
+ for (const model of models) {
237
+ lines.push(` ${model.pluralName}: [${model.modelName}!]! @skipAuth`);
238
+ if (model.idField) {
239
+ const idNullMark = model.idField.isRequired ? "!" : "";
240
+ lines.push(
241
+ ` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName} @skipAuth`
242
+ );
243
+ }
244
+ }
245
+ lines.push(" }");
246
+ lines.push("`");
247
+ lines.push("");
248
+ lines.push(
249
+ "// db is passed in from graphql.ts by the Babel inject plugin, which imports it"
250
+ );
251
+ lines.push(
252
+ "// from 'src/lib/db' in a context where that alias resolves correctly."
253
+ );
254
+ lines.push("export function createGqlormResolvers(db: GqlormDb) {");
255
+ lines.push(" return {");
256
+ lines.push(" Query: {");
257
+ for (let i = 0; i < models.length; i++) {
258
+ const model = models[i];
259
+ const selectObj = model.fields.map((f) => `${f.name}: true`).join(", ");
260
+ lines.push(` ${model.pluralName}: () => {`);
261
+ lines.push(` return db.${model.camelName}.findMany({`);
262
+ lines.push(` select: { ${selectObj} },`);
263
+ lines.push(" })");
264
+ lines.push(" },");
265
+ if (model.idField) {
266
+ const idFieldName = model.idField.name;
267
+ const tsType = graphqlTypeToTsType(model.idField.graphqlType);
268
+ lines.push(
269
+ ` ${model.camelName}: (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }) => {`
270
+ );
271
+ lines.push(` return db.${model.camelName}.findUnique({`);
272
+ lines.push(` where: { ${idFieldName} },`);
273
+ lines.push(` select: { ${selectObj} },`);
274
+ lines.push(" })");
275
+ lines.push(" },");
276
+ }
277
+ if (i < models.length - 1) {
278
+ lines.push("");
279
+ }
280
+ }
281
+ lines.push(" },");
282
+ lines.push(" }");
283
+ lines.push("}");
284
+ lines.push("");
285
+ return lines.join("\n");
286
+ }
59
287
  async function generateGqlormArtifacts() {
60
288
  const files = [];
61
289
  const errors = [];
@@ -65,13 +293,31 @@ async function generateGqlormArtifacts() {
65
293
  const { getDMMF } = mod.default || mod;
66
294
  const dmmf = await getDMMF({ datamodel: schemas });
67
295
  const modelSchema = buildModelSchema(dmmf);
68
- const outputPath = path.join(
296
+ const schemaOutputPath = path.join(
69
297
  getPaths().generated.base,
70
298
  "gqlorm-schema.json"
71
299
  );
72
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
73
- fs.writeFileSync(outputPath, JSON.stringify(modelSchema, null, 2));
74
- files.push(outputPath);
300
+ fs.mkdirSync(path.dirname(schemaOutputPath), { recursive: true });
301
+ fs.writeFileSync(schemaOutputPath, JSON.stringify(modelSchema, null, 2));
302
+ files.push(schemaOutputPath);
303
+ const graphqlDir = getPaths().api.graphql;
304
+ const existingTypes = getExistingSdlTypeNames(graphqlDir);
305
+ const allModels = buildBackendModelInfo(dmmf);
306
+ const gqlormModels = allModels.filter(
307
+ (m) => !existingTypes.has(m.modelName)
308
+ );
309
+ const backendOutputDir = path.join(getPaths().generated.base, "gqlorm");
310
+ const backendOutputPath = path.join(backendOutputDir, "backend.ts");
311
+ if (gqlormModels.length > 0) {
312
+ const backendContent = generateGqlormBackendContent(gqlormModels);
313
+ fs.mkdirSync(backendOutputDir, { recursive: true });
314
+ fs.writeFileSync(backendOutputPath, backendContent);
315
+ files.push(backendOutputPath);
316
+ } else {
317
+ if (fs.existsSync(backendOutputPath)) {
318
+ fs.unlinkSync(backendOutputPath);
319
+ }
320
+ }
75
321
  } catch (error) {
76
322
  errors.push({
77
323
  message: "Failed to generate gqlorm schema artifacts",
@@ -81,6 +327,10 @@ async function generateGqlormArtifacts() {
81
327
  return { files, errors };
82
328
  }
83
329
  export {
330
+ buildBackendModelInfo,
84
331
  buildModelSchema,
85
- generateGqlormArtifacts
332
+ generateGqlormArtifacts,
333
+ generateGqlormBackendContent,
334
+ getExistingSdlTypeNames,
335
+ mapDmmfTypeToGraphql
86
336
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/internal",
3
- "version": "4.0.0-canary.13809+f4d5684a29",
3
+ "version": "4.0.0-canary.13810+aff439035d",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -159,12 +159,13 @@
159
159
  "@babel/plugin-transform-react-jsx": "7.28.6",
160
160
  "@babel/plugin-transform-typescript": "^7.26.8",
161
161
  "@babel/traverse": "7.29.0",
162
- "@cedarjs/babel-config": "4.0.0-canary.13809",
163
- "@cedarjs/cli-helpers": "4.0.0-canary.13809",
164
- "@cedarjs/graphql-server": "4.0.0-canary.13809",
165
- "@cedarjs/project-config": "4.0.0-canary.13809",
166
- "@cedarjs/router": "4.0.0-canary.13809",
167
- "@cedarjs/structure": "4.0.0-canary.13809",
162
+ "@cedarjs/babel-config": "4.0.0-canary.13810",
163
+ "@cedarjs/cli-helpers": "4.0.0-canary.13810",
164
+ "@cedarjs/graphql-server": "4.0.0-canary.13810",
165
+ "@cedarjs/project-config": "4.0.0-canary.13810",
166
+ "@cedarjs/router": "4.0.0-canary.13810",
167
+ "@cedarjs/structure": "4.0.0-canary.13810",
168
+ "@cedarjs/utils": "4.0.0-canary.13810",
168
169
  "@graphql-codegen/add": "6.0.0",
169
170
  "@graphql-codegen/cli": "6.2.1",
170
171
  "@graphql-codegen/client-preset": "5.2.4",
@@ -197,7 +198,7 @@
197
198
  },
198
199
  "devDependencies": {
199
200
  "@arethetypeswrong/cli": "0.18.2",
200
- "@cedarjs/framework-tools": "4.0.0-canary.13809",
201
+ "@cedarjs/framework-tools": "4.0.0-canary.13810",
201
202
  "concurrently": "9.2.1",
202
203
  "graphql-tag": "2.12.6",
203
204
  "publint": "0.3.18",
@@ -210,5 +211,5 @@
210
211
  "publishConfig": {
211
212
  "access": "public"
212
213
  },
213
- "gitHead": "f4d5684a29838388cab209670ad76ea613c6d190"
214
+ "gitHead": "aff439035df7d99cfefe30e24200c46bd9ab83c6"
214
215
  }