@cedarjs/internal 4.0.0-canary.13813 → 4.0.0-canary.13814
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/generate/gqlormSchema.d.ts +8 -1
- package/dist/cjs/generate/gqlormSchema.d.ts.map +1 -1
- package/dist/cjs/generate/gqlormSchema.js +212 -11
- package/dist/generate/gqlormSchema.d.ts +8 -1
- package/dist/generate/gqlormSchema.d.ts.map +1 -1
- package/dist/generate/gqlormSchema.js +212 -11
- package/package.json +10 -10
|
@@ -22,6 +22,13 @@ export interface BackendModelInfo {
|
|
|
22
22
|
fields: BackendFieldInfo[];
|
|
23
23
|
idField: BackendFieldInfo | undefined;
|
|
24
24
|
}
|
|
25
|
+
export interface GqlormBackendConfig {
|
|
26
|
+
membershipModel: string;
|
|
27
|
+
membershipModelCamel: string;
|
|
28
|
+
membershipUserField: string;
|
|
29
|
+
membershipOrganizationField: string;
|
|
30
|
+
membershipModelExists: boolean;
|
|
31
|
+
}
|
|
25
32
|
/**
|
|
26
33
|
* Map a DMMF field type to its GraphQL SDL equivalent.
|
|
27
34
|
*
|
|
@@ -76,7 +83,7 @@ export declare function getExistingSdlTypeNames(graphqlDir: string): Set<string>
|
|
|
76
83
|
* and fields — no hidden/sensitive fields, no @gqlorm hide models, no
|
|
77
84
|
* dependency on the generated Prisma client path or @prisma/client.
|
|
78
85
|
*/
|
|
79
|
-
export declare function generateGqlormBackendContent(models: BackendModelInfo[]): string;
|
|
86
|
+
export declare function generateGqlormBackendContent(models: BackendModelInfo[], config?: GqlormBackendConfig): string;
|
|
80
87
|
/**
|
|
81
88
|
* Generate gqlorm artifacts from the Prisma schema.
|
|
82
89
|
*
|
|
@@ -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;AA8BzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAE3C,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAMD,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;
|
|
1
|
+
{"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AA8BzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAE3C,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAMD,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;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,2BAA2B,EAAE,MAAM,CAAA;IACnC,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AA6DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvE;AAoGD,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAClB,iBAAiB,EAAE,CAiDrB;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,iBAAiB,EAAE,GAC1B,MAAM,CA2CR;AAED;;;;;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,EAC1B,MAAM,GAAE,mBAAmD,GAC1D,MAAM,CA0VR;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,CAwHD"}
|
|
@@ -57,6 +57,13 @@ const SENSITIVE_PATTERNS = [
|
|
|
57
57
|
"encryptionkey",
|
|
58
58
|
"privatekey"
|
|
59
59
|
];
|
|
60
|
+
const DEFAULT_GQLORM_BACKEND_CONFIG = {
|
|
61
|
+
membershipModel: "Membership",
|
|
62
|
+
membershipModelCamel: "membership",
|
|
63
|
+
membershipUserField: "userId",
|
|
64
|
+
membershipOrganizationField: "organizationId",
|
|
65
|
+
membershipModelExists: false
|
|
66
|
+
};
|
|
60
67
|
function hasDirective(doc, directive) {
|
|
61
68
|
if (!doc) {
|
|
62
69
|
return false;
|
|
@@ -341,16 +348,37 @@ function getExistingSdlTypeNames(graphqlDir) {
|
|
|
341
348
|
}
|
|
342
349
|
return typeNames;
|
|
343
350
|
}
|
|
344
|
-
function generateGqlormBackendContent(models) {
|
|
351
|
+
function generateGqlormBackendContent(models, config = DEFAULT_GQLORM_BACKEND_CONFIG) {
|
|
345
352
|
if (models.length === 0) {
|
|
346
353
|
return "";
|
|
347
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
|
+
});
|
|
348
368
|
const lines = [
|
|
349
369
|
"// This file is auto-generated by Cedar gqlorm codegen.",
|
|
350
370
|
"// Do not edit \u2014 it will be overwritten on every codegen run.",
|
|
351
371
|
"// To hide a model from gqlorm, add /// @gqlorm hide in schema.prisma.",
|
|
352
372
|
"",
|
|
353
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
|
+
"}",
|
|
354
382
|
"",
|
|
355
383
|
"// Generated minimal interface \u2014 only visible models and fields, only the",
|
|
356
384
|
"// operations used by this file. No @gqlorm hide models, no sensitive fields.",
|
|
@@ -362,7 +390,8 @@ function generateGqlormBackendContent(models) {
|
|
|
362
390
|
const selectType = model.fields.map((f) => `${f.name}: true`).join("; ");
|
|
363
391
|
lines.push(` ${model.camelName}: {`);
|
|
364
392
|
lines.push(" findMany(args: {");
|
|
365
|
-
lines.push(
|
|
393
|
+
lines.push(" where?: Record<string, unknown>");
|
|
394
|
+
lines.push(` select: Partial<{ ${selectType} }>`);
|
|
366
395
|
lines.push(" }): Promise<");
|
|
367
396
|
lines.push(" Array<{");
|
|
368
397
|
for (const field of model.fields) {
|
|
@@ -389,6 +418,29 @@ function generateGqlormBackendContent(models) {
|
|
|
389
418
|
}
|
|
390
419
|
lines.push(" } | null>");
|
|
391
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>");
|
|
392
444
|
lines.push(" }");
|
|
393
445
|
}
|
|
394
446
|
lines.push("}");
|
|
@@ -405,11 +457,22 @@ function generateGqlormBackendContent(models) {
|
|
|
405
457
|
}
|
|
406
458
|
lines.push(" type Query {");
|
|
407
459
|
for (const model of models) {
|
|
408
|
-
|
|
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
|
+
);
|
|
409
472
|
if (model.idField) {
|
|
410
473
|
const idNullMark = model.idField.isRequired ? "!" : "";
|
|
411
474
|
lines.push(
|
|
412
|
-
` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName}
|
|
475
|
+
` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName} ${authDirective}`
|
|
413
476
|
);
|
|
414
477
|
}
|
|
415
478
|
}
|
|
@@ -428,21 +491,133 @@ function generateGqlormBackendContent(models) {
|
|
|
428
491
|
for (let i = 0; i < models.length; i++) {
|
|
429
492
|
const model = models[i];
|
|
430
493
|
const selectObj = model.fields.map((f) => `${f.name}: true`).join(", ");
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
}
|
|
435
554
|
lines.push(" },");
|
|
436
555
|
if (model.idField) {
|
|
437
556
|
const idFieldName = model.idField.name;
|
|
438
557
|
const tsType = graphqlTypeToTsType(model.idField.graphqlType);
|
|
439
558
|
lines.push(
|
|
440
|
-
` ${model.camelName}: (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }) => {`
|
|
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({`
|
|
441
579
|
);
|
|
442
|
-
lines.push(` return db.${model.camelName}.findUnique({`);
|
|
443
580
|
lines.push(` where: { ${idFieldName} },`);
|
|
444
581
|
lines.push(` select: { ${selectObj} },`);
|
|
445
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");
|
|
446
621
|
lines.push(" },");
|
|
447
622
|
}
|
|
448
623
|
if (i < models.length - 1) {
|
|
@@ -489,11 +664,37 @@ async function generateGqlormArtifacts() {
|
|
|
489
664
|
const graphqlDir = paths.api.graphql;
|
|
490
665
|
const existingTypes = getExistingSdlTypeNames(graphqlDir);
|
|
491
666
|
const allModels = buildBackendModelInfo(dmmf);
|
|
667
|
+
const gqlormConfig = (0, import_project_config.getConfig)().experimental.gqlorm;
|
|
668
|
+
const membershipModel = gqlormConfig.membershipModel ?? "Membership";
|
|
669
|
+
const membershipModelCamel = membershipModel.charAt(0).toLowerCase() + membershipModel.slice(1);
|
|
670
|
+
const membershipUserField = gqlormConfig.membershipUserField ?? "userId";
|
|
671
|
+
const membershipOrganizationField = gqlormConfig.membershipOrganizationField ?? "organizationId";
|
|
672
|
+
const membershipModelExists = dmmf.datamodel.models.some(
|
|
673
|
+
(m) => m.name === membershipModel
|
|
674
|
+
);
|
|
675
|
+
const backendConfig = {
|
|
676
|
+
membershipModel,
|
|
677
|
+
membershipModelCamel,
|
|
678
|
+
membershipUserField,
|
|
679
|
+
membershipOrganizationField,
|
|
680
|
+
membershipModelExists
|
|
681
|
+
};
|
|
492
682
|
const gqlormModels = allModels.filter(
|
|
493
683
|
(m) => !existingTypes.has(m.modelName)
|
|
494
684
|
);
|
|
685
|
+
const anyModelHasOrgField = gqlormModels.some(
|
|
686
|
+
(m) => m.fields.some((f) => f.name === membershipOrganizationField)
|
|
687
|
+
);
|
|
688
|
+
if (anyModelHasOrgField && !membershipModelExists) {
|
|
689
|
+
console.warn(
|
|
690
|
+
`[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.`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
495
693
|
if (gqlormModels.length > 0) {
|
|
496
|
-
const backendContent = generateGqlormBackendContent(
|
|
694
|
+
const backendContent = generateGqlormBackendContent(
|
|
695
|
+
gqlormModels,
|
|
696
|
+
backendConfig
|
|
697
|
+
);
|
|
497
698
|
import_node_fs.default.mkdirSync(backendOutputDir, { recursive: true });
|
|
498
699
|
import_node_fs.default.writeFileSync(backendOutputPath, backendContent);
|
|
499
700
|
files.push(backendOutputPath);
|
|
@@ -22,6 +22,13 @@ export interface BackendModelInfo {
|
|
|
22
22
|
fields: BackendFieldInfo[];
|
|
23
23
|
idField: BackendFieldInfo | undefined;
|
|
24
24
|
}
|
|
25
|
+
export interface GqlormBackendConfig {
|
|
26
|
+
membershipModel: string;
|
|
27
|
+
membershipModelCamel: string;
|
|
28
|
+
membershipUserField: string;
|
|
29
|
+
membershipOrganizationField: string;
|
|
30
|
+
membershipModelExists: boolean;
|
|
31
|
+
}
|
|
25
32
|
/**
|
|
26
33
|
* Map a DMMF field type to its GraphQL SDL equivalent.
|
|
27
34
|
*
|
|
@@ -76,7 +83,7 @@ export declare function getExistingSdlTypeNames(graphqlDir: string): Set<string>
|
|
|
76
83
|
* and fields — no hidden/sensitive fields, no @gqlorm hide models, no
|
|
77
84
|
* dependency on the generated Prisma client path or @prisma/client.
|
|
78
85
|
*/
|
|
79
|
-
export declare function generateGqlormBackendContent(models: BackendModelInfo[]): string;
|
|
86
|
+
export declare function generateGqlormBackendContent(models: BackendModelInfo[], config?: GqlormBackendConfig): string;
|
|
80
87
|
/**
|
|
81
88
|
* Generate gqlorm artifacts from the Prisma schema.
|
|
82
89
|
*
|
|
@@ -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;AA8BzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAE3C,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAMD,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;
|
|
1
|
+
{"version":3,"file":"gqlormSchema.d.ts","sourceRoot":"","sources":["../../src/generate/gqlormSchema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,IAAI,MAAM,cAAc,CAAA;AA8BzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AAE3C,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAMD,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;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,2BAA2B,EAAE,MAAM,CAAA;IACnC,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AA6DD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvE;AAoGD,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAClB,iBAAiB,EAAE,CAiDrB;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,iBAAiB,EAAE,GAC1B,MAAM,CA2CR;AAED;;;;;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,EAC1B,MAAM,GAAE,mBAAmD,GAC1D,MAAM,CA0VR;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,CAwHD"}
|
|
@@ -17,6 +17,13 @@ const SENSITIVE_PATTERNS = [
|
|
|
17
17
|
"encryptionkey",
|
|
18
18
|
"privatekey"
|
|
19
19
|
];
|
|
20
|
+
const DEFAULT_GQLORM_BACKEND_CONFIG = {
|
|
21
|
+
membershipModel: "Membership",
|
|
22
|
+
membershipModelCamel: "membership",
|
|
23
|
+
membershipUserField: "userId",
|
|
24
|
+
membershipOrganizationField: "organizationId",
|
|
25
|
+
membershipModelExists: false
|
|
26
|
+
};
|
|
20
27
|
function hasDirective(doc, directive) {
|
|
21
28
|
if (!doc) {
|
|
22
29
|
return false;
|
|
@@ -301,16 +308,37 @@ function getExistingSdlTypeNames(graphqlDir) {
|
|
|
301
308
|
}
|
|
302
309
|
return typeNames;
|
|
303
310
|
}
|
|
304
|
-
function generateGqlormBackendContent(models) {
|
|
311
|
+
function generateGqlormBackendContent(models, config = DEFAULT_GQLORM_BACKEND_CONFIG) {
|
|
305
312
|
if (models.length === 0) {
|
|
306
313
|
return "";
|
|
307
314
|
}
|
|
315
|
+
const anyModelNeedsOrgScoping = config.membershipModelExists && models.some(
|
|
316
|
+
(m) => m.camelName !== config.membershipModelCamel && m.fields.some((f) => f.name === config.membershipOrganizationField)
|
|
317
|
+
);
|
|
318
|
+
const anyModelNeedsAuth = models.some((m) => {
|
|
319
|
+
const hasUserField = m.fields.some(
|
|
320
|
+
(f) => f.name === config.membershipUserField
|
|
321
|
+
);
|
|
322
|
+
const hasOrgField = m.fields.some(
|
|
323
|
+
(f) => f.name === config.membershipOrganizationField
|
|
324
|
+
);
|
|
325
|
+
const isMembership = m.camelName === config.membershipModelCamel;
|
|
326
|
+
return hasUserField || hasOrgField && config.membershipModelExists && !isMembership;
|
|
327
|
+
});
|
|
308
328
|
const lines = [
|
|
309
329
|
"// This file is auto-generated by Cedar gqlorm codegen.",
|
|
310
330
|
"// Do not edit \u2014 it will be overwritten on every codegen run.",
|
|
311
331
|
"// To hide a model from gqlorm, add /// @gqlorm hide in schema.prisma.",
|
|
312
332
|
"",
|
|
313
333
|
"import gql from 'graphql-tag'",
|
|
334
|
+
...anyModelNeedsAuth ? [
|
|
335
|
+
"import { AuthenticationError, ForbiddenError } from '@cedarjs/graphql-server'"
|
|
336
|
+
] : [],
|
|
337
|
+
"",
|
|
338
|
+
"// Minimal context type used in auth checks",
|
|
339
|
+
"interface GqlormContext {",
|
|
340
|
+
" currentUser: Record<string, unknown> | null | undefined",
|
|
341
|
+
"}",
|
|
314
342
|
"",
|
|
315
343
|
"// Generated minimal interface \u2014 only visible models and fields, only the",
|
|
316
344
|
"// operations used by this file. No @gqlorm hide models, no sensitive fields.",
|
|
@@ -322,7 +350,8 @@ function generateGqlormBackendContent(models) {
|
|
|
322
350
|
const selectType = model.fields.map((f) => `${f.name}: true`).join("; ");
|
|
323
351
|
lines.push(` ${model.camelName}: {`);
|
|
324
352
|
lines.push(" findMany(args: {");
|
|
325
|
-
lines.push(
|
|
353
|
+
lines.push(" where?: Record<string, unknown>");
|
|
354
|
+
lines.push(` select: Partial<{ ${selectType} }>`);
|
|
326
355
|
lines.push(" }): Promise<");
|
|
327
356
|
lines.push(" Array<{");
|
|
328
357
|
for (const field of model.fields) {
|
|
@@ -349,6 +378,29 @@ function generateGqlormBackendContent(models) {
|
|
|
349
378
|
}
|
|
350
379
|
lines.push(" } | null>");
|
|
351
380
|
}
|
|
381
|
+
if (anyModelNeedsOrgScoping && model.camelName === config.membershipModelCamel) {
|
|
382
|
+
lines.push(" findFirst(args: {");
|
|
383
|
+
lines.push(" where: Record<string, unknown>");
|
|
384
|
+
lines.push(" }): Promise<Record<string, unknown> | null>");
|
|
385
|
+
}
|
|
386
|
+
lines.push(" }");
|
|
387
|
+
}
|
|
388
|
+
const membershipAlreadyInModels = models.some(
|
|
389
|
+
(m) => m.camelName === config.membershipModelCamel
|
|
390
|
+
);
|
|
391
|
+
if (anyModelNeedsOrgScoping && !membershipAlreadyInModels) {
|
|
392
|
+
lines.push(` ${config.membershipModelCamel}: {`);
|
|
393
|
+
lines.push(" findMany(args: {");
|
|
394
|
+
lines.push(" where: Record<string, unknown>");
|
|
395
|
+
lines.push(` select: { ${config.membershipOrganizationField}: true }`);
|
|
396
|
+
lines.push(" }): Promise<");
|
|
397
|
+
lines.push(
|
|
398
|
+
` Array<{ ${config.membershipOrganizationField}: unknown }>`
|
|
399
|
+
);
|
|
400
|
+
lines.push(" >");
|
|
401
|
+
lines.push(" findFirst(args: {");
|
|
402
|
+
lines.push(" where: Record<string, unknown>");
|
|
403
|
+
lines.push(" }): Promise<Record<string, unknown> | null>");
|
|
352
404
|
lines.push(" }");
|
|
353
405
|
}
|
|
354
406
|
lines.push("}");
|
|
@@ -365,11 +417,22 @@ function generateGqlormBackendContent(models) {
|
|
|
365
417
|
}
|
|
366
418
|
lines.push(" type Query {");
|
|
367
419
|
for (const model of models) {
|
|
368
|
-
|
|
420
|
+
const hasUserField = model.fields.some(
|
|
421
|
+
(f) => f.name === config.membershipUserField
|
|
422
|
+
);
|
|
423
|
+
const hasOrgField = model.fields.some(
|
|
424
|
+
(f) => f.name === config.membershipOrganizationField
|
|
425
|
+
);
|
|
426
|
+
const isMembershipModel = model.camelName === config.membershipModelCamel;
|
|
427
|
+
const needsAuth = hasUserField || hasOrgField && config.membershipModelExists && !isMembershipModel;
|
|
428
|
+
const authDirective = needsAuth ? "@requireAuth" : "@skipAuth";
|
|
429
|
+
lines.push(
|
|
430
|
+
` ${model.pluralName}: [${model.modelName}!]! ${authDirective}`
|
|
431
|
+
);
|
|
369
432
|
if (model.idField) {
|
|
370
433
|
const idNullMark = model.idField.isRequired ? "!" : "";
|
|
371
434
|
lines.push(
|
|
372
|
-
` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName}
|
|
435
|
+
` ${model.camelName}(${model.idField.name}: ${model.idField.graphqlType}${idNullMark}): ${model.modelName} ${authDirective}`
|
|
373
436
|
);
|
|
374
437
|
}
|
|
375
438
|
}
|
|
@@ -388,21 +451,133 @@ function generateGqlormBackendContent(models) {
|
|
|
388
451
|
for (let i = 0; i < models.length; i++) {
|
|
389
452
|
const model = models[i];
|
|
390
453
|
const selectObj = model.fields.map((f) => `${f.name}: true`).join(", ");
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
454
|
+
const hasUserField = model.fields.some(
|
|
455
|
+
(f) => f.name === config.membershipUserField
|
|
456
|
+
);
|
|
457
|
+
const hasOrgField = model.fields.some(
|
|
458
|
+
(f) => f.name === config.membershipOrganizationField
|
|
459
|
+
);
|
|
460
|
+
const isMembershipModel = model.camelName === config.membershipModelCamel;
|
|
461
|
+
const useOrgScoping = hasOrgField && config.membershipModelExists && !isMembershipModel;
|
|
462
|
+
lines.push(
|
|
463
|
+
` ${model.pluralName}: async (_root: unknown, _args: unknown, ${hasUserField || useOrgScoping ? "context" : "_context"}: GqlormContext) => {`
|
|
464
|
+
);
|
|
465
|
+
if (hasUserField || useOrgScoping) {
|
|
466
|
+
lines.push(" if (!context.currentUser) {");
|
|
467
|
+
lines.push(
|
|
468
|
+
` throw new AuthenticationError("You don't have permission to do that.")`
|
|
469
|
+
);
|
|
470
|
+
lines.push(" }");
|
|
471
|
+
lines.push(" const currentUserId = context.currentUser['id']");
|
|
472
|
+
lines.push(
|
|
473
|
+
" if (currentUserId === undefined || currentUserId === null) {"
|
|
474
|
+
);
|
|
475
|
+
lines.push(
|
|
476
|
+
` throw new AuthenticationError("Could not determine the current user's ID.")`
|
|
477
|
+
);
|
|
478
|
+
lines.push(" }");
|
|
479
|
+
lines.push(" const where: Record<string, unknown> = {}");
|
|
480
|
+
if (hasUserField) {
|
|
481
|
+
lines.push(" // Scope to the current user");
|
|
482
|
+
lines.push(
|
|
483
|
+
` where['${config.membershipUserField}'] = currentUserId`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (useOrgScoping) {
|
|
487
|
+
lines.push(" // Scope to the current user's organizations");
|
|
488
|
+
lines.push(
|
|
489
|
+
` const memberships = await db.${config.membershipModelCamel}.findMany({`
|
|
490
|
+
);
|
|
491
|
+
lines.push(
|
|
492
|
+
` where: { ${config.membershipUserField}: currentUserId },`
|
|
493
|
+
);
|
|
494
|
+
lines.push(
|
|
495
|
+
` select: { ${config.membershipOrganizationField}: true },`
|
|
496
|
+
);
|
|
497
|
+
lines.push(" })");
|
|
498
|
+
lines.push(
|
|
499
|
+
` const organizationIds = memberships.map((m) => m.${config.membershipOrganizationField})`
|
|
500
|
+
);
|
|
501
|
+
lines.push(
|
|
502
|
+
` where['${config.membershipOrganizationField}'] = { in: organizationIds }`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
lines.push(` return db.${model.camelName}.findMany({`);
|
|
506
|
+
lines.push(" where,");
|
|
507
|
+
lines.push(` select: { ${selectObj} },`);
|
|
508
|
+
lines.push(" })");
|
|
509
|
+
} else {
|
|
510
|
+
lines.push(` return db.${model.camelName}.findMany({`);
|
|
511
|
+
lines.push(` select: { ${selectObj} },`);
|
|
512
|
+
lines.push(" })");
|
|
513
|
+
}
|
|
395
514
|
lines.push(" },");
|
|
396
515
|
if (model.idField) {
|
|
397
516
|
const idFieldName = model.idField.name;
|
|
398
517
|
const tsType = graphqlTypeToTsType(model.idField.graphqlType);
|
|
399
518
|
lines.push(
|
|
400
|
-
` ${model.camelName}: (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }) => {`
|
|
519
|
+
` ${model.camelName}: async (_root: unknown, { ${idFieldName} }: { ${idFieldName}: ${tsType} }, ${hasUserField || useOrgScoping ? "context" : "_context"}: GqlormContext) => {`
|
|
520
|
+
);
|
|
521
|
+
if (hasUserField || useOrgScoping) {
|
|
522
|
+
lines.push(" if (!context.currentUser) {");
|
|
523
|
+
lines.push(
|
|
524
|
+
` throw new AuthenticationError("You don't have permission to do that.")`
|
|
525
|
+
);
|
|
526
|
+
lines.push(" }");
|
|
527
|
+
lines.push(" const currentUserId = context.currentUser['id']");
|
|
528
|
+
lines.push(
|
|
529
|
+
" if (currentUserId === undefined || currentUserId === null) {"
|
|
530
|
+
);
|
|
531
|
+
lines.push(
|
|
532
|
+
` throw new AuthenticationError("Could not determine the current user's ID.")`
|
|
533
|
+
);
|
|
534
|
+
lines.push(" }");
|
|
535
|
+
}
|
|
536
|
+
lines.push("");
|
|
537
|
+
lines.push(
|
|
538
|
+
` const record = await db.${model.camelName}.findUnique({`
|
|
401
539
|
);
|
|
402
|
-
lines.push(` return db.${model.camelName}.findUnique({`);
|
|
403
540
|
lines.push(` where: { ${idFieldName} },`);
|
|
404
541
|
lines.push(` select: { ${selectObj} },`);
|
|
405
542
|
lines.push(" })");
|
|
543
|
+
lines.push("");
|
|
544
|
+
lines.push(" if (!record) {");
|
|
545
|
+
lines.push(" return null");
|
|
546
|
+
lines.push(" }");
|
|
547
|
+
if (hasUserField) {
|
|
548
|
+
lines.push("");
|
|
549
|
+
lines.push(" // Verify the current user owns this record");
|
|
550
|
+
lines.push(
|
|
551
|
+
` if (record.${config.membershipUserField} !== currentUserId) {`
|
|
552
|
+
);
|
|
553
|
+
lines.push(
|
|
554
|
+
` throw new ForbiddenError('Not authorized to access this resource')`
|
|
555
|
+
);
|
|
556
|
+
lines.push(" }");
|
|
557
|
+
}
|
|
558
|
+
if (useOrgScoping) {
|
|
559
|
+
lines.push("");
|
|
560
|
+
lines.push(
|
|
561
|
+
" // Verify the current user belongs to the record's organization"
|
|
562
|
+
);
|
|
563
|
+
lines.push(
|
|
564
|
+
` const membership = await db.${config.membershipModelCamel}.findFirst({`
|
|
565
|
+
);
|
|
566
|
+
lines.push(" where: {");
|
|
567
|
+
lines.push(` ${config.membershipUserField}: currentUserId,`);
|
|
568
|
+
lines.push(
|
|
569
|
+
` ${config.membershipOrganizationField}: record.${config.membershipOrganizationField},`
|
|
570
|
+
);
|
|
571
|
+
lines.push(" },");
|
|
572
|
+
lines.push(" })");
|
|
573
|
+
lines.push(" if (!membership) {");
|
|
574
|
+
lines.push(
|
|
575
|
+
` throw new ForbiddenError('Not authorized to access this resource')`
|
|
576
|
+
);
|
|
577
|
+
lines.push(" }");
|
|
578
|
+
}
|
|
579
|
+
lines.push("");
|
|
580
|
+
lines.push(" return record");
|
|
406
581
|
lines.push(" },");
|
|
407
582
|
}
|
|
408
583
|
if (i < models.length - 1) {
|
|
@@ -449,11 +624,37 @@ async function generateGqlormArtifacts() {
|
|
|
449
624
|
const graphqlDir = paths.api.graphql;
|
|
450
625
|
const existingTypes = getExistingSdlTypeNames(graphqlDir);
|
|
451
626
|
const allModels = buildBackendModelInfo(dmmf);
|
|
627
|
+
const gqlormConfig = getConfig().experimental.gqlorm;
|
|
628
|
+
const membershipModel = gqlormConfig.membershipModel ?? "Membership";
|
|
629
|
+
const membershipModelCamel = membershipModel.charAt(0).toLowerCase() + membershipModel.slice(1);
|
|
630
|
+
const membershipUserField = gqlormConfig.membershipUserField ?? "userId";
|
|
631
|
+
const membershipOrganizationField = gqlormConfig.membershipOrganizationField ?? "organizationId";
|
|
632
|
+
const membershipModelExists = dmmf.datamodel.models.some(
|
|
633
|
+
(m) => m.name === membershipModel
|
|
634
|
+
);
|
|
635
|
+
const backendConfig = {
|
|
636
|
+
membershipModel,
|
|
637
|
+
membershipModelCamel,
|
|
638
|
+
membershipUserField,
|
|
639
|
+
membershipOrganizationField,
|
|
640
|
+
membershipModelExists
|
|
641
|
+
};
|
|
452
642
|
const gqlormModels = allModels.filter(
|
|
453
643
|
(m) => !existingTypes.has(m.modelName)
|
|
454
644
|
);
|
|
645
|
+
const anyModelHasOrgField = gqlormModels.some(
|
|
646
|
+
(m) => m.fields.some((f) => f.name === membershipOrganizationField)
|
|
647
|
+
);
|
|
648
|
+
if (anyModelHasOrgField && !membershipModelExists) {
|
|
649
|
+
console.warn(
|
|
650
|
+
`[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.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
455
653
|
if (gqlormModels.length > 0) {
|
|
456
|
-
const backendContent = generateGqlormBackendContent(
|
|
654
|
+
const backendContent = generateGqlormBackendContent(
|
|
655
|
+
gqlormModels,
|
|
656
|
+
backendConfig
|
|
657
|
+
);
|
|
457
658
|
fs.mkdirSync(backendOutputDir, { recursive: true });
|
|
458
659
|
fs.writeFileSync(backendOutputPath, backendContent);
|
|
459
660
|
files.push(backendOutputPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/internal",
|
|
3
|
-
"version": "4.0.0-canary.
|
|
3
|
+
"version": "4.0.0-canary.13814+3150cd24f2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -159,13 +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.
|
|
163
|
-
"@cedarjs/cli-helpers": "4.0.0-canary.
|
|
164
|
-
"@cedarjs/graphql-server": "4.0.0-canary.
|
|
165
|
-
"@cedarjs/project-config": "4.0.0-canary.
|
|
166
|
-
"@cedarjs/router": "4.0.0-canary.
|
|
167
|
-
"@cedarjs/structure": "4.0.0-canary.
|
|
168
|
-
"@cedarjs/utils": "4.0.0-canary.
|
|
162
|
+
"@cedarjs/babel-config": "4.0.0-canary.13814",
|
|
163
|
+
"@cedarjs/cli-helpers": "4.0.0-canary.13814",
|
|
164
|
+
"@cedarjs/graphql-server": "4.0.0-canary.13814",
|
|
165
|
+
"@cedarjs/project-config": "4.0.0-canary.13814",
|
|
166
|
+
"@cedarjs/router": "4.0.0-canary.13814",
|
|
167
|
+
"@cedarjs/structure": "4.0.0-canary.13814",
|
|
168
|
+
"@cedarjs/utils": "4.0.0-canary.13814",
|
|
169
169
|
"@graphql-codegen/add": "6.0.0",
|
|
170
170
|
"@graphql-codegen/cli": "6.2.1",
|
|
171
171
|
"@graphql-codegen/client-preset": "5.2.4",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
},
|
|
199
199
|
"devDependencies": {
|
|
200
200
|
"@arethetypeswrong/cli": "0.18.2",
|
|
201
|
-
"@cedarjs/framework-tools": "4.0.0-canary.
|
|
201
|
+
"@cedarjs/framework-tools": "4.0.0-canary.13814",
|
|
202
202
|
"concurrently": "9.2.1",
|
|
203
203
|
"graphql-tag": "2.12.6",
|
|
204
204
|
"publint": "0.3.18",
|
|
@@ -211,5 +211,5 @@
|
|
|
211
211
|
"publishConfig": {
|
|
212
212
|
"access": "public"
|
|
213
213
|
},
|
|
214
|
-
"gitHead": "
|
|
214
|
+
"gitHead": "3150cd24f242d15164dfafe0e64928bf5e3e1aca"
|
|
215
215
|
}
|