@eide/foir-cli 0.1.43 → 0.1.44

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/cli.js CHANGED
@@ -48,8 +48,8 @@ async function getCredentials() {
48
48
  }
49
49
  async function writeCredentials(credentials) {
50
50
  await ensureDir(getCredentialsDir());
51
- const path4 = getCredentialsPath();
52
- await fs.writeFile(path4, JSON.stringify(credentials, null, 2), {
51
+ const path3 = getCredentialsPath();
52
+ await fs.writeFile(path3, JSON.stringify(credentials, null, 2), {
53
53
  mode: 384
54
54
  });
55
55
  }
@@ -606,95 +606,98 @@ async function provisionApiKey(apiUrl, accessToken, projectId, tenantId) {
606
606
  }
607
607
  function registerSelectProjectCommand(program2, globalOpts) {
608
608
  program2.command("select-project").description("Choose which project to work with").option("--project-id <id>", "Project ID to select directly").option("--save-as <name>", "Save as a named profile").action(
609
- withErrorHandler(globalOpts, async (cmdOpts) => {
610
- const opts = globalOpts();
611
- const apiUrl = getApiUrl(opts);
612
- const credentials = await getCredentials();
613
- if (!credentials) {
614
- console.log("Not logged in. Run `foir login` first.");
615
- throw new Error("Not authenticated");
616
- }
617
- console.log("Fetching your projects...\n");
618
- const sessionContext = await fetchSessionContext(
619
- apiUrl,
620
- credentials.accessToken
621
- );
622
- const { availableTenants: tenants, availableProjects: projects } = sessionContext;
623
- if (projects.length === 0) {
624
- console.log("No projects found. Create one in the platform first.");
625
- throw new Error("No projects available");
626
- }
627
- const tenantNameMap = new Map(tenants.map((t) => [t.id, t.name]));
628
- let selectedProject;
629
- if (cmdOpts.projectId) {
630
- const found = projects.find((p) => p.id === cmdOpts.projectId);
631
- if (!found) {
632
- console.log(`Project with ID "${cmdOpts.projectId}" not found.`);
633
- console.log("Available projects:");
634
- for (const p of projects) {
635
- console.log(` - ${p.name} (${p.id})`);
636
- }
637
- throw new Error("Project not found");
609
+ withErrorHandler(
610
+ globalOpts,
611
+ async (cmdOpts) => {
612
+ const opts = globalOpts();
613
+ const apiUrl = getApiUrl(opts);
614
+ const credentials = await getCredentials();
615
+ if (!credentials) {
616
+ console.log("Not logged in. Run `foir login` first.");
617
+ throw new Error("Not authenticated");
638
618
  }
639
- selectedProject = found;
640
- } else {
641
- const byTenant = projects.reduce(
642
- (acc, p) => {
643
- const key = tenantNameMap.get(p.tenantId) ?? "Unknown";
644
- if (!acc[key]) acc[key] = [];
645
- acc[key].push(p);
646
- return acc;
647
- },
648
- {}
619
+ console.log("Fetching your projects...\n");
620
+ const sessionContext = await fetchSessionContext(
621
+ apiUrl,
622
+ credentials.accessToken
649
623
  );
650
- const choices = Object.entries(byTenant).flatMap(
651
- ([tenantName, tenantProjects]) => [
652
- new inquirer.Separator(`\u2500\u2500 ${tenantName} \u2500\u2500`),
653
- ...tenantProjects.map((p) => ({
654
- name: ` ${p.name}`,
655
- value: p.id,
656
- short: p.name
657
- }))
658
- ]
624
+ const { availableTenants: tenants, availableProjects: projects } = sessionContext;
625
+ if (projects.length === 0) {
626
+ console.log("No projects found. Create one in the platform first.");
627
+ throw new Error("No projects available");
628
+ }
629
+ const tenantNameMap = new Map(tenants.map((t) => [t.id, t.name]));
630
+ let selectedProject;
631
+ if (cmdOpts.projectId) {
632
+ const found = projects.find((p) => p.id === cmdOpts.projectId);
633
+ if (!found) {
634
+ console.log(`Project with ID "${cmdOpts.projectId}" not found.`);
635
+ console.log("Available projects:");
636
+ for (const p of projects) {
637
+ console.log(` - ${p.name} (${p.id})`);
638
+ }
639
+ throw new Error("Project not found");
640
+ }
641
+ selectedProject = found;
642
+ } else {
643
+ const byTenant = projects.reduce(
644
+ (acc, p) => {
645
+ const key = tenantNameMap.get(p.tenantId) ?? "Unknown";
646
+ if (!acc[key]) acc[key] = [];
647
+ acc[key].push(p);
648
+ return acc;
649
+ },
650
+ {}
651
+ );
652
+ const choices = Object.entries(byTenant).flatMap(
653
+ ([tenantName, tenantProjects]) => [
654
+ new inquirer.Separator(`\u2500\u2500 ${tenantName} \u2500\u2500`),
655
+ ...tenantProjects.map((p) => ({
656
+ name: ` ${p.name}`,
657
+ value: p.id,
658
+ short: p.name
659
+ }))
660
+ ]
661
+ );
662
+ const { projectId } = await inquirer.prompt([
663
+ {
664
+ type: "list",
665
+ name: "projectId",
666
+ message: "Select a project:",
667
+ choices
668
+ }
669
+ ]);
670
+ selectedProject = projects.find((p) => p.id === projectId);
671
+ }
672
+ console.log("\nProvisioning API key for CLI access...");
673
+ const { apiKey, apiKeyId } = await provisionApiKey(
674
+ apiUrl,
675
+ credentials.accessToken,
676
+ selectedProject.id,
677
+ selectedProject.tenantId
659
678
  );
660
- const { projectId } = await inquirer.prompt([
679
+ await writeProjectContext(
661
680
  {
662
- type: "list",
663
- name: "projectId",
664
- message: "Select a project:",
665
- choices
666
- }
667
- ]);
668
- selectedProject = projects.find((p) => p.id === projectId);
669
- }
670
- console.log("\nProvisioning API key for CLI access...");
671
- const { apiKey, apiKeyId } = await provisionApiKey(
672
- apiUrl,
673
- credentials.accessToken,
674
- selectedProject.id,
675
- selectedProject.tenantId
676
- );
677
- await writeProjectContext(
678
- {
679
- id: selectedProject.id,
680
- name: selectedProject.name,
681
- tenantId: selectedProject.tenantId,
682
- apiKey,
683
- apiKeyId
684
- },
685
- cmdOpts.saveAs
686
- );
687
- console.log(`
688
- \u2713 Selected project: ${selectedProject.name}`);
689
- console.log("\u2713 API key provisioned for CLI access");
690
- if (cmdOpts.saveAs) {
691
- console.log(
692
- ` Saved as profile "${cmdOpts.saveAs}". Use --project ${cmdOpts.saveAs} or set as default with \`foir profiles default ${cmdOpts.saveAs}\``
681
+ id: selectedProject.id,
682
+ name: selectedProject.name,
683
+ tenantId: selectedProject.tenantId,
684
+ apiKey,
685
+ apiKeyId
686
+ },
687
+ cmdOpts.saveAs
693
688
  );
694
- } else {
695
- console.log(" (stored in .foir/project.json for this repository)");
689
+ console.log(`
690
+ \u2713 Selected project: ${selectedProject.name}`);
691
+ console.log("\u2713 API key provisioned for CLI access");
692
+ if (cmdOpts.saveAs) {
693
+ console.log(
694
+ ` Saved as profile "${cmdOpts.saveAs}". Use --project ${cmdOpts.saveAs} or set as default with \`foir profiles default ${cmdOpts.saveAs}\``
695
+ );
696
+ } else {
697
+ console.log(" (stored in .foir/project.json for this repository)");
698
+ }
696
699
  }
697
- })
700
+ )
698
701
  );
699
702
  }
700
703
 
@@ -942,20 +945,7 @@ import { resolve } from "path";
942
945
  import chalk4 from "chalk";
943
946
 
944
947
  // src/config/pull-config.ts
945
- var DEFAULT_TYPES_DIR = "./src/generated/types";
946
- var DEFAULT_DOCS_DIR = "./src/generated/documents";
947
- var ALL_DOMAINS = {
948
- auth: true,
949
- authProviders: true,
950
- files: true,
951
- sync: true,
952
- notifications: true,
953
- operations: true,
954
- schedules: true,
955
- sharing: true,
956
- embeddings: true,
957
- analytics: true
958
- };
948
+ var DEFAULT_OUTPUT_DIR = "./src/generated";
959
949
  async function loadPullConfig(flags) {
960
950
  let fileConfig = {};
961
951
  const configPath = findConfigFile(flags.config);
@@ -963,52 +953,386 @@ async function loadPullConfig(flags) {
963
953
  const full = await loadConfigFile(configPath);
964
954
  fileConfig = full.pull ?? {};
965
955
  }
966
- const types = flags.out ?? fileConfig.output?.types ?? DEFAULT_TYPES_DIR;
967
- const documents = fileConfig.output?.documents ?? DEFAULT_DOCS_DIR;
968
- const swift = flags.swift ?? fileConfig.output?.swift;
969
- const targets = fileConfig.targets ?? [];
970
- const typesParent = types.replace(/\/[^/]+$/, "");
971
- const operations = fileConfig.output?.operations ?? `${typesParent}/operations`;
972
- const hooks = targets.includes("react") ? fileConfig.output?.hooks ?? `${typesParent}/hooks` : void 0;
973
- const loaders = targets.includes("remix") ? fileConfig.output?.loaders ?? `${typesParent}/loaders` : void 0;
974
- const output = {
975
- types,
976
- documents,
977
- operations,
978
- ...hooks ? { hooks } : {},
979
- ...loaders ? { loaders } : {},
980
- ...swift ? { swift } : {}
981
- };
982
- let domains;
983
- if (fileConfig.domains === false) {
984
- domains = {
985
- auth: false,
986
- authProviders: false,
987
- files: false,
988
- sync: false,
989
- notifications: false,
990
- operations: false,
991
- schedules: false,
992
- sharing: false,
993
- embeddings: false,
994
- analytics: false
995
- };
996
- } else if (typeof fileConfig.domains === "object") {
997
- domains = { ...ALL_DOMAINS, ...fileConfig.domains };
956
+ let outputDir;
957
+ if (flags.out) {
958
+ outputDir = flags.out;
959
+ } else if (typeof fileConfig.output === "string") {
960
+ outputDir = fileConfig.output;
961
+ } else if (typeof fileConfig.output === "object" && fileConfig.output?.types) {
962
+ const legacyTypes = fileConfig.output.types;
963
+ outputDir = legacyTypes.replace(/\/types\/?$/, "") || DEFAULT_OUTPUT_DIR;
998
964
  } else {
999
- domains = { ...ALL_DOMAINS };
965
+ outputDir = DEFAULT_OUTPUT_DIR;
1000
966
  }
1001
967
  const only = flags.only ? flags.only.split(",").map((s) => s.trim()) : fileConfig.only ?? [];
1002
968
  const includeInline = fileConfig.includeInline ?? true;
1003
969
  const prettier = flags.noPrettier ? false : fileConfig.prettier ?? true;
1004
970
  const dryRun = flags.dryRun ?? false;
1005
- return { output, targets, domains, only, includeInline, prettier, dryRun };
971
+ return {
972
+ output: { types: outputDir },
973
+ only,
974
+ includeInline,
975
+ prettier,
976
+ dryRun
977
+ };
1006
978
  }
1007
979
 
1008
980
  // src/graphql/generated.ts
1009
- var GetCustomerProfileSchemaDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetCustomerProfileSchema" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "customerProfileSchema" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fields" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "type" } }, { "kind": "Field", "name": { "kind": "Name", "value": "label" } }, { "kind": "Field", "name": { "kind": "Name", "value": "required" } }, { "kind": "Field", "name": { "kind": "Name", "value": "helpText" } }, { "kind": "Field", "name": { "kind": "Name", "value": "defaultValue" } }, { "kind": "Field", "name": { "kind": "Name", "value": "config" } }, { "kind": "Field", "name": { "kind": "Name", "value": "validation" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "rule" } }, { "kind": "Field", "name": { "kind": "Name", "value": "value" } }, { "kind": "Field", "name": { "kind": "Name", "value": "message" } }] } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "publicFields" } }, { "kind": "Field", "name": { "kind": "Name", "value": "version" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }] } }] };
1010
- var ModelsForCodegenDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "ModelsForCodegen" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "offset" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "models" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "search" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "offset" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "offset" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "pluralName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "category" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fields" } }, { "kind": "Field", "name": { "kind": "Name", "value": "config" } }, { "kind": "Field", "name": { "kind": "Name", "value": "hooks" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "total" } }] } }] } }] };
1011
- var GlobalSearchDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GlobalSearch" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } }, "type": { "kind": "ListType", "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Boolean" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "globalSearch" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "query" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "query" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "modelKeys" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "modelKeys" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeMedia" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "includeMedia" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "records" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "modelKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "naturalKey" } }, { "kind": "Field", "name": { "kind": "Name", "value": "subtitle" } }, { "kind": "Field", "name": { "kind": "Name", "value": "updatedAt" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "media" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "altText" } }, { "kind": "Field", "name": { "kind": "Name", "value": "fileUrl" } }] } }] } }] } }] };
981
+ var GetCustomerProfileSchemaDocument = {
982
+ kind: "Document",
983
+ definitions: [
984
+ {
985
+ kind: "OperationDefinition",
986
+ operation: "query",
987
+ name: { kind: "Name", value: "GetCustomerProfileSchema" },
988
+ selectionSet: {
989
+ kind: "SelectionSet",
990
+ selections: [
991
+ {
992
+ kind: "Field",
993
+ name: { kind: "Name", value: "customerProfileSchema" },
994
+ selectionSet: {
995
+ kind: "SelectionSet",
996
+ selections: [
997
+ { kind: "Field", name: { kind: "Name", value: "id" } },
998
+ {
999
+ kind: "Field",
1000
+ name: { kind: "Name", value: "fields" },
1001
+ selectionSet: {
1002
+ kind: "SelectionSet",
1003
+ selections: [
1004
+ { kind: "Field", name: { kind: "Name", value: "key" } },
1005
+ { kind: "Field", name: { kind: "Name", value: "type" } },
1006
+ { kind: "Field", name: { kind: "Name", value: "label" } },
1007
+ {
1008
+ kind: "Field",
1009
+ name: { kind: "Name", value: "required" }
1010
+ },
1011
+ {
1012
+ kind: "Field",
1013
+ name: { kind: "Name", value: "helpText" }
1014
+ },
1015
+ {
1016
+ kind: "Field",
1017
+ name: { kind: "Name", value: "defaultValue" }
1018
+ },
1019
+ {
1020
+ kind: "Field",
1021
+ name: { kind: "Name", value: "config" }
1022
+ },
1023
+ {
1024
+ kind: "Field",
1025
+ name: { kind: "Name", value: "validation" },
1026
+ selectionSet: {
1027
+ kind: "SelectionSet",
1028
+ selections: [
1029
+ {
1030
+ kind: "Field",
1031
+ name: { kind: "Name", value: "rule" }
1032
+ },
1033
+ {
1034
+ kind: "Field",
1035
+ name: { kind: "Name", value: "value" }
1036
+ },
1037
+ {
1038
+ kind: "Field",
1039
+ name: { kind: "Name", value: "message" }
1040
+ }
1041
+ ]
1042
+ }
1043
+ }
1044
+ ]
1045
+ }
1046
+ },
1047
+ {
1048
+ kind: "Field",
1049
+ name: { kind: "Name", value: "publicFields" }
1050
+ },
1051
+ { kind: "Field", name: { kind: "Name", value: "version" } },
1052
+ { kind: "Field", name: { kind: "Name", value: "createdAt" } },
1053
+ { kind: "Field", name: { kind: "Name", value: "updatedAt" } }
1054
+ ]
1055
+ }
1056
+ }
1057
+ ]
1058
+ }
1059
+ }
1060
+ ]
1061
+ };
1062
+ var ModelsForCodegenDocument = {
1063
+ kind: "Document",
1064
+ definitions: [
1065
+ {
1066
+ kind: "OperationDefinition",
1067
+ operation: "query",
1068
+ name: { kind: "Name", value: "ModelsForCodegen" },
1069
+ variableDefinitions: [
1070
+ {
1071
+ kind: "VariableDefinition",
1072
+ variable: {
1073
+ kind: "Variable",
1074
+ name: { kind: "Name", value: "search" }
1075
+ },
1076
+ type: { kind: "NamedType", name: { kind: "Name", value: "String" } }
1077
+ },
1078
+ {
1079
+ kind: "VariableDefinition",
1080
+ variable: {
1081
+ kind: "Variable",
1082
+ name: { kind: "Name", value: "limit" }
1083
+ },
1084
+ type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1085
+ },
1086
+ {
1087
+ kind: "VariableDefinition",
1088
+ variable: {
1089
+ kind: "Variable",
1090
+ name: { kind: "Name", value: "offset" }
1091
+ },
1092
+ type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1093
+ }
1094
+ ],
1095
+ selectionSet: {
1096
+ kind: "SelectionSet",
1097
+ selections: [
1098
+ {
1099
+ kind: "Field",
1100
+ name: { kind: "Name", value: "models" },
1101
+ arguments: [
1102
+ {
1103
+ kind: "Argument",
1104
+ name: { kind: "Name", value: "search" },
1105
+ value: {
1106
+ kind: "Variable",
1107
+ name: { kind: "Name", value: "search" }
1108
+ }
1109
+ },
1110
+ {
1111
+ kind: "Argument",
1112
+ name: { kind: "Name", value: "limit" },
1113
+ value: {
1114
+ kind: "Variable",
1115
+ name: { kind: "Name", value: "limit" }
1116
+ }
1117
+ },
1118
+ {
1119
+ kind: "Argument",
1120
+ name: { kind: "Name", value: "offset" },
1121
+ value: {
1122
+ kind: "Variable",
1123
+ name: { kind: "Name", value: "offset" }
1124
+ }
1125
+ }
1126
+ ],
1127
+ selectionSet: {
1128
+ kind: "SelectionSet",
1129
+ selections: [
1130
+ {
1131
+ kind: "Field",
1132
+ name: { kind: "Name", value: "items" },
1133
+ selectionSet: {
1134
+ kind: "SelectionSet",
1135
+ selections: [
1136
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1137
+ { kind: "Field", name: { kind: "Name", value: "key" } },
1138
+ { kind: "Field", name: { kind: "Name", value: "name" } },
1139
+ {
1140
+ kind: "Field",
1141
+ name: { kind: "Name", value: "pluralName" }
1142
+ },
1143
+ {
1144
+ kind: "Field",
1145
+ name: { kind: "Name", value: "description" }
1146
+ },
1147
+ {
1148
+ kind: "Field",
1149
+ name: { kind: "Name", value: "category" }
1150
+ },
1151
+ {
1152
+ kind: "Field",
1153
+ name: { kind: "Name", value: "fields" }
1154
+ },
1155
+ {
1156
+ kind: "Field",
1157
+ name: { kind: "Name", value: "config" }
1158
+ },
1159
+ { kind: "Field", name: { kind: "Name", value: "hooks" } },
1160
+ {
1161
+ kind: "Field",
1162
+ name: { kind: "Name", value: "createdAt" }
1163
+ },
1164
+ {
1165
+ kind: "Field",
1166
+ name: { kind: "Name", value: "updatedAt" }
1167
+ }
1168
+ ]
1169
+ }
1170
+ },
1171
+ { kind: "Field", name: { kind: "Name", value: "total" } }
1172
+ ]
1173
+ }
1174
+ }
1175
+ ]
1176
+ }
1177
+ }
1178
+ ]
1179
+ };
1180
+ var GlobalSearchDocument = {
1181
+ kind: "Document",
1182
+ definitions: [
1183
+ {
1184
+ kind: "OperationDefinition",
1185
+ operation: "query",
1186
+ name: { kind: "Name", value: "GlobalSearch" },
1187
+ variableDefinitions: [
1188
+ {
1189
+ kind: "VariableDefinition",
1190
+ variable: {
1191
+ kind: "Variable",
1192
+ name: { kind: "Name", value: "query" }
1193
+ },
1194
+ type: {
1195
+ kind: "NonNullType",
1196
+ type: {
1197
+ kind: "NamedType",
1198
+ name: { kind: "Name", value: "String" }
1199
+ }
1200
+ }
1201
+ },
1202
+ {
1203
+ kind: "VariableDefinition",
1204
+ variable: {
1205
+ kind: "Variable",
1206
+ name: { kind: "Name", value: "limit" }
1207
+ },
1208
+ type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1209
+ },
1210
+ {
1211
+ kind: "VariableDefinition",
1212
+ variable: {
1213
+ kind: "Variable",
1214
+ name: { kind: "Name", value: "modelKeys" }
1215
+ },
1216
+ type: {
1217
+ kind: "ListType",
1218
+ type: {
1219
+ kind: "NonNullType",
1220
+ type: {
1221
+ kind: "NamedType",
1222
+ name: { kind: "Name", value: "String" }
1223
+ }
1224
+ }
1225
+ }
1226
+ },
1227
+ {
1228
+ kind: "VariableDefinition",
1229
+ variable: {
1230
+ kind: "Variable",
1231
+ name: { kind: "Name", value: "includeMedia" }
1232
+ },
1233
+ type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
1234
+ }
1235
+ ],
1236
+ selectionSet: {
1237
+ kind: "SelectionSet",
1238
+ selections: [
1239
+ {
1240
+ kind: "Field",
1241
+ name: { kind: "Name", value: "globalSearch" },
1242
+ arguments: [
1243
+ {
1244
+ kind: "Argument",
1245
+ name: { kind: "Name", value: "query" },
1246
+ value: {
1247
+ kind: "Variable",
1248
+ name: { kind: "Name", value: "query" }
1249
+ }
1250
+ },
1251
+ {
1252
+ kind: "Argument",
1253
+ name: { kind: "Name", value: "limit" },
1254
+ value: {
1255
+ kind: "Variable",
1256
+ name: { kind: "Name", value: "limit" }
1257
+ }
1258
+ },
1259
+ {
1260
+ kind: "Argument",
1261
+ name: { kind: "Name", value: "modelKeys" },
1262
+ value: {
1263
+ kind: "Variable",
1264
+ name: { kind: "Name", value: "modelKeys" }
1265
+ }
1266
+ },
1267
+ {
1268
+ kind: "Argument",
1269
+ name: { kind: "Name", value: "includeMedia" },
1270
+ value: {
1271
+ kind: "Variable",
1272
+ name: { kind: "Name", value: "includeMedia" }
1273
+ }
1274
+ }
1275
+ ],
1276
+ selectionSet: {
1277
+ kind: "SelectionSet",
1278
+ selections: [
1279
+ {
1280
+ kind: "Field",
1281
+ name: { kind: "Name", value: "records" },
1282
+ selectionSet: {
1283
+ kind: "SelectionSet",
1284
+ selections: [
1285
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1286
+ {
1287
+ kind: "Field",
1288
+ name: { kind: "Name", value: "modelKey" }
1289
+ },
1290
+ { kind: "Field", name: { kind: "Name", value: "title" } },
1291
+ {
1292
+ kind: "Field",
1293
+ name: { kind: "Name", value: "naturalKey" }
1294
+ },
1295
+ {
1296
+ kind: "Field",
1297
+ name: { kind: "Name", value: "subtitle" }
1298
+ },
1299
+ {
1300
+ kind: "Field",
1301
+ name: { kind: "Name", value: "updatedAt" }
1302
+ }
1303
+ ]
1304
+ }
1305
+ },
1306
+ {
1307
+ kind: "Field",
1308
+ name: { kind: "Name", value: "media" },
1309
+ selectionSet: {
1310
+ kind: "SelectionSet",
1311
+ selections: [
1312
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1313
+ {
1314
+ kind: "Field",
1315
+ name: { kind: "Name", value: "fileName" }
1316
+ },
1317
+ {
1318
+ kind: "Field",
1319
+ name: { kind: "Name", value: "altText" }
1320
+ },
1321
+ {
1322
+ kind: "Field",
1323
+ name: { kind: "Name", value: "fileUrl" }
1324
+ }
1325
+ ]
1326
+ }
1327
+ }
1328
+ ]
1329
+ }
1330
+ }
1331
+ ]
1332
+ }
1333
+ }
1334
+ ]
1335
+ };
1012
1336
 
1013
1337
  // src/codegen/fetch-models.ts
1014
1338
  function normalizeConfig(raw) {
@@ -1252,10 +1576,6 @@ function toPascalCase(str) {
1252
1576
  const camel = toCamelCase(str);
1253
1577
  return camel.charAt(0).toUpperCase() + camel.slice(1);
1254
1578
  }
1255
- function toUpperSnakeCase(str) {
1256
- if (!str) return "UNKNOWN";
1257
- return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
1258
- }
1259
1579
  function sanitizeFieldName(key) {
1260
1580
  if (!key) return "unknown";
1261
1581
  const camel = toCamelCase(key);
@@ -1317,438 +1637,6 @@ function generateFieldDef(field) {
1317
1637
  return `{ ${parts.join(", ")} }`;
1318
1638
  }
1319
1639
 
1320
- // src/codegen/generators/field-types.ts
1321
- function generateFieldTypesFile() {
1322
- return `/**
1323
- * Field Types and Definitions
1324
- *
1325
- * Value types, filter types, and field definition types.
1326
- *
1327
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
1328
- */
1329
-
1330
- // =============================================================================
1331
- // JSON-SAFE BASE TYPE
1332
- // =============================================================================
1333
-
1334
- /** Recursive JSON-serializable value type. Safe for TanStack Router, Remix, RSC, and other serialization boundaries. */
1335
- export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
1336
-
1337
- // =============================================================================
1338
- // VALUE TYPES
1339
- // =============================================================================
1340
-
1341
- /** Rich text content (Lexical JSON format) */
1342
- export type RichtextValue = JsonValue;
1343
-
1344
- /** Currency value with amount and ISO 4217 code */
1345
- export interface CurrencyValue {
1346
- amount: number;
1347
- currency: string;
1348
- }
1349
-
1350
- /** Image reference with metadata */
1351
- export interface ImageValue {
1352
- id: string;
1353
- url: string;
1354
- alt?: string;
1355
- width?: number;
1356
- height?: number;
1357
- }
1358
-
1359
- /** Video reference with metadata */
1360
- export interface VideoValue {
1361
- id: string;
1362
- url: string;
1363
- thumbnail?: string;
1364
- duration?: number;
1365
- }
1366
-
1367
- /** File reference with metadata */
1368
- export interface FileValue {
1369
- id: string;
1370
- url: string;
1371
- name: string;
1372
- size: number;
1373
- mimeType: string;
1374
- }
1375
-
1376
- /** Link value (internal reference or external URL) */
1377
- export interface LinkValue {
1378
- type: 'entity' | 'url';
1379
- entity?: LinkRecordReference;
1380
- url?: string;
1381
- target?: '_self' | '_blank';
1382
- }
1383
-
1384
- /** Record reference for internal links */
1385
- export interface LinkRecordReference {
1386
- modelKey: string;
1387
- naturalKey: string;
1388
- }
1389
-
1390
- /** Record reference value (generic TPreview for typed preview data when reference target is known) */
1391
- export interface ReferenceValue<TPreview = Record<string, JsonValue>> {
1392
- _type: 'reference';
1393
- _schema: string;
1394
- naturalKey: string;
1395
- _preview?: TPreview;
1396
- }
1397
-
1398
- /** Composite/inline value */
1399
- export interface CompositeValue {
1400
- _type: 'composite';
1401
- _schema: string;
1402
- fields: Record<string, JsonValue>;
1403
- }
1404
-
1405
- /** A single item in a flexible field array */
1406
- export interface FlexibleFieldItem {
1407
- _id: string;
1408
- _key: string;
1409
- _type: string;
1410
- _label: string;
1411
- _required?: boolean;
1412
- _helpText?: string;
1413
- _config?: Record<string, JsonValue>;
1414
- value: JsonValue;
1415
- }
1416
-
1417
- // =============================================================================
1418
- // FILTER TYPES
1419
- // =============================================================================
1420
-
1421
- export interface TextFilter {
1422
- eq?: string;
1423
- ne?: string;
1424
- contains?: string;
1425
- startsWith?: string;
1426
- endsWith?: string;
1427
- in?: string[];
1428
- notIn?: string[];
1429
- isNull?: boolean;
1430
- }
1431
-
1432
- export interface NumberFilter {
1433
- eq?: number;
1434
- ne?: number;
1435
- gt?: number;
1436
- gte?: number;
1437
- lt?: number;
1438
- lte?: number;
1439
- in?: number[];
1440
- notIn?: number[];
1441
- isNull?: boolean;
1442
- }
1443
-
1444
- export interface BooleanFilter {
1445
- eq?: boolean;
1446
- ne?: boolean;
1447
- isNull?: boolean;
1448
- }
1449
-
1450
- export interface DateFilter {
1451
- eq?: string;
1452
- ne?: string;
1453
- gt?: string;
1454
- gte?: string;
1455
- lt?: string;
1456
- lte?: string;
1457
- isNull?: boolean;
1458
- }
1459
-
1460
- export interface SelectFilter<T extends string = string> {
1461
- eq?: T;
1462
- ne?: T;
1463
- in?: T[];
1464
- notIn?: T[];
1465
- isNull?: boolean;
1466
- }
1467
-
1468
- export interface MultiselectFilter<T extends string = string> {
1469
- contains?: T;
1470
- containsAny?: T[];
1471
- containsAll?: T[];
1472
- isNull?: boolean;
1473
- }
1474
-
1475
- export interface ReferenceFilter {
1476
- eq?: string;
1477
- ne?: string;
1478
- in?: string[];
1479
- notIn?: string[];
1480
- isNull?: boolean;
1481
- }
1482
-
1483
- export interface FilterInput {
1484
- field: string;
1485
- operator: string;
1486
- value: JsonValue;
1487
- }
1488
-
1489
- export interface SortInput {
1490
- field: string;
1491
- direction: 'ASC' | 'DESC';
1492
- }
1493
-
1494
- // =============================================================================
1495
- // RESOLVE TYPES
1496
- // =============================================================================
1497
-
1498
- /** Variant context for record resolution */
1499
- export interface VariantContext {
1500
- locale?: string;
1501
- device?: string;
1502
- region?: string;
1503
- contexts?: Record<string, JsonValue>;
1504
- }
1505
-
1506
- /** Reference resolution options */
1507
- export interface ReferenceResolutionOptions {
1508
- maxDepth?: number;
1509
- resolveMedia?: boolean;
1510
- resolveReferences?: boolean;
1511
- }
1512
-
1513
- /** Resolved record metadata */
1514
- export interface ResolvedRecord {
1515
- id: string;
1516
- modelKey: string;
1517
- naturalKey: string | null;
1518
- metadata?: Record<string, JsonValue>;
1519
- }
1520
-
1521
- /** Resolved variant info */
1522
- export interface ResolvedVariant {
1523
- id: string;
1524
- variantKey: string;
1525
- }
1526
-
1527
- /** Resolved field with value */
1528
- export interface ResolvedField {
1529
- key: string;
1530
- type: string;
1531
- label?: string;
1532
- required?: boolean;
1533
- value: JsonValue;
1534
- }
1535
-
1536
- /** Resolved content */
1537
- export interface ResolvedContent {
1538
- fields: ResolvedField[];
1539
- }
1540
-
1541
- /** Resolution context output */
1542
- export interface ResolutionContext {
1543
- locale: string;
1544
- contexts: Record<string, JsonValue>;
1545
- }
1546
-
1547
- /** Base resolved record content */
1548
- export interface ResolvedRecordContentBase {
1549
- record: ResolvedRecord;
1550
- variant: ResolvedVariant;
1551
- content: ResolvedContent;
1552
- resolvedWith: ResolutionContext;
1553
- }
1554
-
1555
- // =============================================================================
1556
- // FIELD DEFINITION TYPES
1557
- // =============================================================================
1558
-
1559
- export interface BaseFieldDef {
1560
- key: string;
1561
- label: string;
1562
- required?: boolean;
1563
- helpText?: string;
1564
- defaultValue?: JsonValue;
1565
- }
1566
-
1567
- export interface TextFieldDef extends BaseFieldDef {
1568
- type: 'text';
1569
- maxLength?: number;
1570
- minLength?: number;
1571
- pattern?: string;
1572
- }
1573
-
1574
- export interface NumberFieldDef extends BaseFieldDef {
1575
- type: 'number';
1576
- min?: number;
1577
- max?: number;
1578
- step?: number;
1579
- }
1580
-
1581
- export interface BooleanFieldDef extends BaseFieldDef {
1582
- type: 'boolean';
1583
- }
1584
-
1585
- export interface DateFieldDef extends BaseFieldDef {
1586
- type: 'date';
1587
- }
1588
-
1589
- export interface RichtextFieldDef extends BaseFieldDef {
1590
- type: 'richtext';
1591
- }
1592
-
1593
- export interface ImageFieldDef extends BaseFieldDef {
1594
- type: 'image';
1595
- allowedTypes?: string[];
1596
- maxSize?: number;
1597
- }
1598
-
1599
- export interface VideoFieldDef extends BaseFieldDef {
1600
- type: 'video';
1601
- allowedTypes?: string[];
1602
- maxSize?: number;
1603
- }
1604
-
1605
- export interface FileFieldDef extends BaseFieldDef {
1606
- type: 'file';
1607
- allowedTypes?: string[];
1608
- maxSize?: number;
1609
- }
1610
-
1611
- export interface SelectFieldDef extends BaseFieldDef {
1612
- type: 'select';
1613
- options: Array<{ label: string; value: string }>;
1614
- }
1615
-
1616
- export interface MultiselectFieldDef extends BaseFieldDef {
1617
- type: 'multiselect';
1618
- options: Array<{ label: string; value: string }>;
1619
- }
1620
-
1621
- export interface LinkFieldDef extends BaseFieldDef {
1622
- type: 'link';
1623
- }
1624
-
1625
- export interface ReferenceFieldDef extends BaseFieldDef {
1626
- type: 'reference';
1627
- referenceTypes?: string[];
1628
- multiple?: boolean;
1629
- }
1630
-
1631
- export interface ListFieldDef extends BaseFieldDef {
1632
- type: 'list';
1633
- itemType?: string;
1634
- minItems?: number;
1635
- maxItems?: number;
1636
- }
1637
-
1638
- export interface JsonFieldDef extends BaseFieldDef {
1639
- type: 'json';
1640
- }
1641
-
1642
- export interface FlexibleFieldDef extends BaseFieldDef {
1643
- type: 'flexible';
1644
- }
1645
-
1646
- /** Field def for inline model types (type is the model's key, e.g. 'seo', 'hero-banner') */
1647
- export interface InlineModelFieldDef extends BaseFieldDef {
1648
- type: string;
1649
- }
1650
-
1651
- export type FieldDef =
1652
- | TextFieldDef
1653
- | NumberFieldDef
1654
- | BooleanFieldDef
1655
- | DateFieldDef
1656
- | RichtextFieldDef
1657
- | ImageFieldDef
1658
- | VideoFieldDef
1659
- | FileFieldDef
1660
- | SelectFieldDef
1661
- | MultiselectFieldDef
1662
- | LinkFieldDef
1663
- | ReferenceFieldDef
1664
- | ListFieldDef
1665
- | JsonFieldDef
1666
- | FlexibleFieldDef
1667
- | InlineModelFieldDef;
1668
- `;
1669
- }
1670
-
1671
- // src/codegen/generators/config.ts
1672
- function generateConfigFile() {
1673
- return `/**
1674
- * Model Configuration Type
1675
- *
1676
- * Strongly-typed model definitions for the unified data layer.
1677
- *
1678
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
1679
- */
1680
-
1681
- import type { FieldDef, JsonValue } from './field-types.js';
1682
-
1683
- /**
1684
- * Model configuration
1685
- *
1686
- * Defines the complete configuration for a model including
1687
- * its schema, capabilities, and lifecycle hooks.
1688
- *
1689
- * @example
1690
- * export const blogPostConfig = {
1691
- * key: 'blog-post',
1692
- * name: 'Blog Post',
1693
- * records: true,
1694
- * inline: false,
1695
- * publicApi: true,
1696
- * versioning: true,
1697
- * publishing: true,
1698
- * variants: false,
1699
- * customerScoped: false,
1700
- * fieldDefs: [
1701
- * { key: 'title', type: 'text', label: 'Title', required: true },
1702
- * { key: 'content', type: 'richtext', label: 'Content' },
1703
- * ],
1704
- * } as const satisfies ModelConfig;
1705
- */
1706
- export interface ModelConfig {
1707
- /** Unique identifier (kebab-case) */
1708
- key: string;
1709
- /** Display name */
1710
- name: string;
1711
- /** Description */
1712
- description?: string;
1713
-
1714
- // Capability flags (from model config)
1715
- /** Can create standalone records */
1716
- records: boolean;
1717
- /** Available as inline field type in other models */
1718
- inline: boolean;
1719
- /** Exposed via public GraphQL API */
1720
- publicApi: boolean;
1721
- /** Version history enabled */
1722
- versioning: boolean;
1723
- /** Publishing workflow enabled */
1724
- publishing: boolean;
1725
- /** Market/device/locale variants enabled */
1726
- variants: boolean;
1727
- /** Customer-level record isolation */
1728
- customerScoped: boolean;
1729
-
1730
- /** Embedding configuration */
1731
- embeddings?: {
1732
- enabled: boolean;
1733
- fields: Array<{ fieldPath: string; weight?: number }>;
1734
- };
1735
-
1736
- /** Lifecycle hooks configuration */
1737
- hooks?: Record<string, JsonValue>;
1738
-
1739
- /** Field definitions */
1740
- fieldDefs: readonly FieldDef[];
1741
- }
1742
-
1743
- /**
1744
- * Helper to create a type-safe model config
1745
- */
1746
- export function defineModel<T extends ModelConfig>(config: T): T {
1747
- return config;
1748
- }
1749
- `;
1750
- }
1751
-
1752
1640
  // src/codegen/generators/model-types.ts
1753
1641
  function isInlineOnlyModel(model) {
1754
1642
  return model.config.inline && !model.config.records;
@@ -1778,12 +1666,9 @@ function generateModelTypes(model, allModels) {
1778
1666
  }
1779
1667
  function buildImportStatements(model, fieldTypeImports, inlineSchemaRefs, referenceModelRefs, allModels) {
1780
1668
  const imports = [];
1781
- if (!isInlineOnlyModel(model)) {
1782
- imports.push("import type { ModelConfig } from '../config.js';");
1783
- }
1784
1669
  if (fieldTypeImports.size > 0) {
1785
1670
  const types = Array.from(fieldTypeImports).sort().join(", ");
1786
- imports.push(`import type { ${types} } from '../field-types.js';`);
1671
+ imports.push(`import type { ${types} } from '@eide/foir-client';`);
1787
1672
  }
1788
1673
  const allModelRefKeys = /* @__PURE__ */ new Set([
1789
1674
  ...inlineSchemaRefs,
@@ -1851,7 +1736,7 @@ function generateConfigObject(model, fields, configName) {
1851
1736
  }
1852
1737
  lines.push(" ],");
1853
1738
  }
1854
- lines.push("} as const satisfies ModelConfig;");
1739
+ lines.push("} as const;");
1855
1740
  return lines.join("\n") + "\n";
1856
1741
  }
1857
1742
  function generateDataInterface(model, fields, interfaceName, allModels) {
@@ -1940,2563 +1825,499 @@ function generateModelIndex(models) {
1940
1825
  `;
1941
1826
  code += `export type { ${typeName}Data } from './${model.key}.js';
1942
1827
  `;
1828
+ if (model.config.publicApi) {
1829
+ code += `export type { ${typeName}Where, ${typeName}SortField } from './${model.key}.filters.js';
1830
+ `;
1831
+ }
1943
1832
  }
1944
1833
  }
1945
1834
  return code;
1946
1835
  }
1947
1836
 
1948
- // src/codegen/generators/documents.ts
1949
- function generateModelDocuments(model) {
1950
- const typeName = toPascalCase(model.key);
1951
- const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
1952
- const displayName = model.name ?? model.key;
1953
- return `# Generated GraphQL operations for ${displayName}
1954
- # @generated by foir \u2014 DO NOT EDIT MANUALLY
1955
-
1956
- fragment ${typeName}Fields on Record {
1957
- id
1958
- modelKey
1959
- naturalKey
1960
- data
1961
- metadata
1962
- publishedVersionNumber
1963
- publishedAt
1964
- versionNumber
1965
- changeDescription
1966
- createdAt
1967
- updatedAt
1968
- }
1969
-
1970
- query Get${typeName}($id: ID!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
1971
- record(id: $id) {
1972
- ...${typeName}Fields
1973
- resolved(locale: $locale, preview: $preview, fields: $fields) {
1974
- content
1975
- record { id modelKey naturalKey }
1976
- version { id versionNumber }
1977
- }
1978
- }
1979
- }
1980
-
1981
- query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
1982
- recordByKey(modelKey: "${model.key}", naturalKey: $naturalKey) {
1983
- ...${typeName}Fields
1984
- resolved(locale: $locale, preview: $preview, fields: $fields) {
1985
- content
1986
- record { id modelKey naturalKey }
1987
- version { id versionNumber }
1988
- }
1989
- }
1990
- }
1991
-
1992
- query List${pluralName}(
1993
- $limit: Int
1994
- $offset: Int
1995
- $filters: [FilterInput!]
1996
- $sort: SortInput
1997
- $locale: String
1998
- $preview: Boolean
1999
- $fields: FieldSelectionInput
2000
- ) {
2001
- records(
2002
- modelKey: "${model.key}"
2003
- limit: $limit
2004
- offset: $offset
2005
- filters: $filters
2006
- sort: $sort
2007
- ) {
2008
- items {
2009
- ...${typeName}Fields
2010
- resolved(locale: $locale, preview: $preview, fields: $fields) {
2011
- content
2012
- record { id modelKey naturalKey }
2013
- version { id versionNumber }
2014
- }
2015
- }
2016
- total
2017
- }
2018
- }
2019
-
2020
- mutation Create${typeName}($input: CreateRecordInput!) {
2021
- createRecord(input: $input) {
2022
- record {
2023
- ...${typeName}Fields
2024
- }
2025
- }
2026
- }
2027
-
2028
- mutation Update${typeName}($input: UpdateRecordInput!) {
2029
- updateRecord(input: $input) {
2030
- record {
2031
- ...${typeName}Fields
2032
- }
2033
- matched
2034
- }
2035
- }
2036
-
2037
- mutation Delete${typeName}($id: ID!) {
2038
- deleteRecord(id: $id) {
2039
- id
2040
- }
2041
- }
2042
-
2043
- mutation Publish${typeName}Version($versionId: ID!) {
2044
- publishVersion(versionId: $versionId)
2045
- }
2046
-
2047
- mutation Unpublish${typeName}($id: ID!) {
2048
- unpublishRecord(id: $id)
2049
- }
2050
- ${model.config.sharing?.enabled ? generateSharingOperations(model.key, typeName, pluralName) : ""}`;
2051
- }
2052
- function generateSharedFragments() {
2053
- return `# Shared fragments used across multiple model documents
2054
- # @generated by foir \u2014 DO NOT EDIT MANUALLY
2055
-
2056
- fragment ShareFields on Share {
2057
- id
2058
- resourceType
2059
- recordId
2060
- fileId
2061
- permission
2062
- status
2063
- sharedWithCustomerId
2064
- acceptedAt
2065
- declinedAt
2066
- expiresAt
2067
- createdAt
2068
- createdBy
2069
- revokedAt
2070
- }
2071
- `;
2072
- }
2073
- function generateSharingOperations(modelKey, typeName, pluralName) {
2074
- return `# Sharing operations
2075
-
2076
- mutation Share${typeName}($recordId: ID!, $sharedWithCustomerId: ID!, $permission: SharePermission!) {
2077
- shareRecord(recordId: $recordId, sharedWithCustomerId: $sharedWithCustomerId, permission: $permission) {
2078
- ...ShareFields
2079
- }
2080
- }
2081
-
2082
- mutation Accept${typeName}Share($shareId: ID!) {
2083
- acceptShare(shareId: $shareId) {
2084
- ...ShareFields
2085
- }
2086
- }
2087
-
2088
- mutation Decline${typeName}Share($shareId: ID!) {
2089
- declineShare(shareId: $shareId) {
2090
- ...ShareFields
2091
- }
2092
- }
2093
-
2094
- mutation Revoke${typeName}Share($shareId: ID!) {
2095
- revokeShare(shareId: $shareId) {
2096
- ...ShareFields
2097
- }
2098
- }
2099
-
2100
- query ${typeName}Shares($resourceId: ID!, $status: ShareStatus) {
2101
- shares(resourceType: RECORD, resourceId: $resourceId, status: $status) {
2102
- ...ShareFields
2103
- }
2104
- }
2105
-
2106
- query ${pluralName}SharedWithMe($status: ShareStatus) {
2107
- sharedWithMe(resourceType: RECORD, modelKey: "${modelKey}", status: $status) {
2108
- ...ShareFields
2109
- record {
2110
- ...${typeName}Fields
2111
- }
2112
- }
2113
- }
2114
- `;
2115
- }
2116
-
2117
- // src/codegen/swift-field-mapping.ts
2118
- var SWIFT_FIELD_TYPE_MAPPING = {
2119
- text: {
2120
- type: "String",
2121
- alwaysOptional: false,
2122
- defaultValue: '""',
2123
- castExpression: "as? String"
2124
- },
2125
- richtext: {
2126
- type: "String",
2127
- alwaysOptional: true,
2128
- defaultValue: '""',
2129
- castExpression: "as? String"
2130
- },
2131
- number: {
2132
- type: "Double",
2133
- alwaysOptional: true,
2134
- defaultValue: "0",
2135
- castExpression: "as? Double"
2136
- },
2137
- boolean: {
2138
- type: "Bool",
2139
- alwaysOptional: true,
2140
- defaultValue: "false",
2141
- castExpression: "as? Bool"
2142
- },
2143
- email: {
2144
- type: "String",
2145
- alwaysOptional: true,
2146
- defaultValue: '""',
2147
- castExpression: "as? String"
2148
- },
2149
- phone: {
2150
- type: "String",
2151
- alwaysOptional: true,
2152
- defaultValue: '""',
2153
- castExpression: "as? String"
2154
- },
2155
- url: {
2156
- type: "String",
2157
- alwaysOptional: true,
2158
- defaultValue: '""',
2159
- castExpression: "as? String"
2160
- },
2161
- date: {
2162
- type: "String",
2163
- alwaysOptional: true,
2164
- defaultValue: '""',
2165
- castExpression: "as? String"
2166
- },
2167
- image: {
2168
- type: "ImageValue",
2169
- alwaysOptional: true,
2170
- defaultValue: 'ImageValue(id: "", url: "")',
2171
- castExpression: "as? [String: Any]",
2172
- needsSharedType: true
2173
- },
2174
- video: {
2175
- type: "VideoValue",
2176
- alwaysOptional: true,
2177
- defaultValue: 'VideoValue(id: "", url: "")',
2178
- castExpression: "as? [String: Any]",
2179
- needsSharedType: true
2180
- },
2181
- file: {
2182
- type: "FileValue",
2183
- alwaysOptional: true,
2184
- defaultValue: 'FileValue(id: "", url: "", name: "", size: 0, mimeType: "")',
2185
- // fromSyncData handles fileId→id mapping
2186
- castExpression: "as? [String: Any]",
2187
- needsSharedType: true
2188
- },
2189
- currency: {
2190
- type: "CurrencyValue",
2191
- alwaysOptional: true,
2192
- defaultValue: 'CurrencyValue(amount: 0, currency: "")',
2193
- castExpression: "as? [String: Any]",
2194
- needsSharedType: true
2195
- },
2196
- select: {
2197
- type: "String",
2198
- alwaysOptional: true,
2199
- defaultValue: '""',
2200
- castExpression: "as? String"
2201
- },
2202
- multiselect: {
2203
- type: "[String]",
2204
- alwaysOptional: true,
2205
- defaultValue: "[]",
2206
- castExpression: "as? [String]"
2207
- },
2208
- json: {
2209
- type: "Any",
2210
- alwaysOptional: true,
2211
- defaultValue: "nil",
2212
- castExpression: ""
2213
- },
2214
- list: {
2215
- type: "[Any]",
2216
- alwaysOptional: true,
2217
- defaultValue: "[]",
2218
- castExpression: "as? [Any]"
2219
- },
2220
- flexible: {
2221
- type: "[[String: Any]]",
2222
- alwaysOptional: true,
2223
- defaultValue: "[]",
2224
- castExpression: "as? [[String: Any]]"
2225
- },
2226
- reference: {
2227
- type: "String",
2228
- alwaysOptional: true,
2229
- defaultValue: '""',
2230
- castExpression: "as? String"
2231
- },
2232
- link: {
2233
- type: "LinkValue",
2234
- alwaysOptional: true,
2235
- defaultValue: 'LinkValue(type: "")',
2236
- castExpression: "as? [String: Any]",
2237
- needsSharedType: true
2238
- },
2239
- model: {
2240
- type: "String",
2241
- alwaysOptional: true,
2242
- defaultValue: '""',
2243
- castExpression: "as? String"
2244
- }
2245
- };
2246
- function getSwiftFieldType(field) {
2247
- const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
2248
- if (!mapping) {
2249
- return {
2250
- type: "Any",
2251
- isOptional: true,
2252
- mapping: void 0
2253
- };
2254
- }
2255
- const isOptional = mapping.alwaysOptional || !field.required;
2256
- return { type: mapping.type, isOptional, mapping };
2257
- }
2258
-
2259
- // src/codegen/generators/swift-types.ts
2260
- function generateSwiftModelFile(model) {
2261
- const typeName = toPascalCase(model.key);
2262
- const fields = model.fields ?? [];
2263
- const lines = [];
2264
- lines.push("//");
2265
- lines.push(`// ${typeName}.swift`);
2266
- lines.push("//");
2267
- lines.push(`// Generated from model '${model.key}'`);
2268
- lines.push("//");
2269
- lines.push("// @generated by foir \u2014 DO NOT EDIT MANUALLY");
2270
- lines.push("//");
2271
- lines.push("");
2272
- lines.push("import Foundation");
2273
- lines.push("");
2274
- lines.push(generateFieldsEnum(typeName, fields));
2275
- lines.push("");
2276
- lines.push(generateDataStruct(typeName, fields));
2277
- lines.push("");
2278
- lines.push(generateSerializationExtension(typeName, fields));
2279
- lines.push("");
2280
- lines.push(generateConfigEnum(typeName, model));
2281
- return lines.join("\n");
2282
- }
2283
- function generateFieldsEnum(typeName, fields) {
2284
- const lines = [];
2285
- lines.push(`// MARK: - ${typeName} Field Keys`);
2286
- lines.push("");
2287
- lines.push(`enum ${typeName}Fields {`);
2288
- for (const field of fields) {
2289
- lines.push(` static let ${field.key} = "${field.key}"`);
2290
- }
2291
- lines.push("}");
2292
- return lines.join("\n");
2293
- }
2294
- function generateDataStruct(typeName, fields) {
2295
- const lines = [];
2296
- lines.push(`// MARK: - ${typeName} Data`);
2297
- lines.push("");
2298
- lines.push(`struct ${typeName}Data {`);
2299
- for (const field of fields) {
2300
- const { type, isOptional } = getSwiftFieldType(field);
2301
- const optionalSuffix = isOptional ? "?" : "";
2302
- lines.push(` var ${field.key}: ${type}${optionalSuffix}`);
2303
- }
2304
- lines.push("}");
2305
- return lines.join("\n");
2306
- }
2307
- function generateSerializationExtension(typeName, fields) {
2308
- const lines = [];
2309
- lines.push(`// MARK: - ${typeName} Serialization`);
2310
- lines.push("");
2311
- lines.push(`extension ${typeName}Data {`);
2312
- lines.push(" func toSyncData() -> [String: Any] {");
2313
- const requiredFields = fields.filter((f) => {
2314
- const { isOptional } = getSwiftFieldType(f);
2315
- return !isOptional;
2316
- });
2317
- const optionalFields = fields.filter((f) => {
2318
- const { isOptional } = getSwiftFieldType(f);
2319
- return isOptional;
2320
- });
2321
- if (requiredFields.length > 0) {
2322
- if (optionalFields.length === 0) {
2323
- lines.push(` return [`);
2324
- requiredFields.forEach((f, i) => {
2325
- const comma = i < requiredFields.length - 1 ? "," : "";
2326
- lines.push(
2327
- ` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`
2328
- );
2329
- });
2330
- lines.push(" ]");
2331
- } else {
2332
- lines.push(` var data: [String: Any] = [`);
2333
- requiredFields.forEach((f, i) => {
2334
- const comma = i < requiredFields.length - 1 ? "," : "";
2335
- lines.push(
2336
- ` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`
2337
- );
2338
- });
2339
- lines.push(" ]");
2340
- for (const f of optionalFields) {
2341
- lines.push(
2342
- ` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`
2343
- );
2344
- }
2345
- lines.push(" return data");
2346
- }
2347
- } else {
2348
- lines.push(" var data: [String: Any] = [:]");
2349
- for (const f of optionalFields) {
2350
- lines.push(
2351
- ` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`
2352
- );
2353
- }
2354
- lines.push(" return data");
2355
- }
2356
- lines.push(" }");
2357
- lines.push("");
2358
- lines.push(
2359
- " static func fromSyncData(_ data: [String: Any]) -> " + typeName + "Data {"
2360
- );
2361
- lines.push(` ${typeName}Data(`);
2362
- fields.forEach((field, i) => {
2363
- const comma = i < fields.length - 1 ? "," : "";
2364
- const { isOptional, mapping } = getSwiftFieldType(field);
2365
- lines.push(
2366
- ` ${field.key}: ${fromSyncDataExpr(field, typeName, isOptional, mapping)}${comma}`
2367
- );
2368
- });
2369
- lines.push(" )");
2370
- lines.push(" }");
2371
- lines.push("}");
2372
- return lines.join("\n");
2373
- }
2374
- function toSyncValueExpr(field) {
2375
- const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
2376
- if (mapping?.needsSharedType) {
2377
- return `${field.key}.toSyncData()`;
2378
- }
2379
- return field.key;
2380
- }
2381
- function toSyncValueExprForOptional(field) {
2382
- const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
2383
- if (mapping?.needsSharedType) {
2384
- return `${field.key}.toSyncData()`;
2385
- }
2386
- return field.key;
2387
- }
2388
- function fromSyncDataExpr(field, typeName, isOptional, mapping) {
2389
- const accessor = `data[${typeName}Fields.${field.key}]`;
2390
- if (!mapping) {
2391
- return isOptional ? `${accessor}` : `${accessor} ?? nil`;
2392
- }
2393
- if (mapping.needsSharedType) {
2394
- const dictCast = `${accessor} as? [String: Any]`;
2395
- if (isOptional) {
2396
- return `(${dictCast}).map { ${mapping.type}.fromSyncData($0) }`;
2397
- }
2398
- return `${mapping.type}.fromSyncData(${dictCast} ?? [:])`;
2399
- }
2400
- if (field.type === "json") {
2401
- return isOptional ? accessor : `${accessor}`;
2402
- }
2403
- if (isOptional) {
2404
- return `${accessor} ${mapping.castExpression}`;
2405
- }
2406
- return `${accessor} ${mapping.castExpression} ?? ${mapping.defaultValue}`;
2407
- }
2408
- function generateConfigEnum(typeName, model) {
2409
- const lines = [];
2410
- lines.push(`// MARK: - ${typeName} Config`);
2411
- lines.push("");
2412
- lines.push(`enum ${typeName}Config {`);
2413
- const escapedName = (model.name ?? model.key).replace(/"/g, '\\"');
2414
- lines.push(` static let key = "${model.key}"`);
2415
- lines.push(` static let name = "${escapedName}"`);
2416
- lines.push(` static let customerScoped = ${model.config.customerScoped}`);
2417
- lines.push(` static let publicApi = ${model.config.publicApi}`);
2418
- lines.push(` static let versioning = ${model.config.versioning}`);
2419
- lines.push(` static let publishing = ${model.config.publishing}`);
2420
- lines.push(` static let variants = ${model.config.variants}`);
2421
- lines.push(
2422
- ` static let sharingEnabled = ${model.config.sharing?.enabled ?? false}`
2423
- );
2424
- lines.push(
2425
- ` static let sharingRequireAcceptance = ${model.config.sharing?.requireAcceptance ?? true}`
2426
- );
2427
- lines.push("}");
2428
- lines.push("");
2429
- return lines.join("\n");
2430
- }
2431
-
2432
- // src/codegen/generators/swift-field-types.ts
2433
- function generateSwiftFieldTypesFile() {
2434
- return `//
2435
- // FieldTypes.swift
2436
- //
2437
- // Shared value types for platform sync data.
2438
- //
2439
- // @generated by foir \u2014 DO NOT EDIT MANUALLY
2440
- //
2441
-
2442
- import Foundation
2443
-
2444
- // MARK: - Image
2445
-
2446
- struct ImageValue {
2447
- let id: String
2448
- let url: String
2449
- var alt: String?
2450
- var width: Int?
2451
- var height: Int?
2452
- var dominantColor: String?
2453
- var blurhash: String?
2454
-
2455
- func toSyncData() -> [String: Any] {
2456
- var data: [String: Any] = ["fileId": id, "source": "internal"]
2457
- if let alt { data["altText"] = alt }
2458
- if let width { data["width"] = width }
2459
- if let height { data["height"] = height }
2460
- if let dominantColor { data["dominantColor"] = dominantColor }
2461
- if let blurhash { data["blurhash"] = blurhash }
2462
- return data
2463
- }
2464
-
2465
- static func fromSyncData(_ data: [String: Any]) -> ImageValue {
2466
- ImageValue(
2467
- id: data["fileId"] as? String ?? data["id"] as? String ?? "",
2468
- url: data["url"] as? String ?? "",
2469
- alt: data["altText"] as? String ?? data["alt"] as? String,
2470
- width: data["width"] as? Int,
2471
- height: data["height"] as? Int,
2472
- dominantColor: data["dominantColor"] as? String,
2473
- blurhash: data["blurhash"] as? String
2474
- )
2475
- }
2476
- }
2477
-
2478
- // MARK: - Video
2479
-
2480
- struct VideoValue {
2481
- let id: String
2482
- let url: String
2483
- var thumbnail: String?
2484
- var duration: Double?
2485
-
2486
- func toSyncData() -> [String: Any] {
2487
- var data: [String: Any] = ["fileId": id, "source": "internal"]
2488
- if let thumbnail { data["thumbnailUrl"] = thumbnail }
2489
- if let duration { data["duration"] = duration }
2490
- return data
2491
- }
2492
-
2493
- static func fromSyncData(_ data: [String: Any]) -> VideoValue {
2494
- VideoValue(
2495
- id: data["fileId"] as? String ?? data["id"] as? String ?? "",
2496
- url: data["url"] as? String ?? "",
2497
- thumbnail: data["thumbnailUrl"] as? String ?? data["thumbnail"] as? String,
2498
- duration: data["duration"] as? Double
2499
- )
2500
- }
2501
- }
2502
-
2503
- // MARK: - File
2504
-
2505
- struct FileValue {
2506
- let id: String
2507
- let url: String
2508
- let name: String
2509
- let size: Int
2510
- let mimeType: String
2511
-
2512
- func toSyncData() -> [String: Any] {
2513
- [
2514
- "fileId": id,
2515
- "source": "internal",
2516
- "filename": name,
2517
- "fileSize": size,
2518
- "mimeType": mimeType,
2519
- ]
2520
- }
2521
-
2522
- static func fromSyncData(_ data: [String: Any]) -> FileValue {
2523
- FileValue(
2524
- id: data["fileId"] as? String ?? data["id"] as? String ?? "",
2525
- url: data["url"] as? String ?? "",
2526
- name: data["filename"] as? String ?? data["name"] as? String ?? "",
2527
- size: data["fileSize"] as? Int ?? data["size"] as? Int ?? 0,
2528
- mimeType: data["mimeType"] as? String ?? ""
2529
- )
2530
- }
2531
- }
2532
-
2533
- // MARK: - Currency
2534
-
2535
- struct CurrencyValue {
2536
- let amount: Double
2537
- let currency: String
2538
-
2539
- func toSyncData() -> [String: Any] {
2540
- ["amount": amount, "currency": currency]
2541
- }
2542
-
2543
- static func fromSyncData(_ data: [String: Any]) -> CurrencyValue {
2544
- CurrencyValue(
2545
- amount: data["amount"] as? Double ?? 0,
2546
- currency: data["currency"] as? String ?? ""
2547
- )
2548
- }
2549
- }
2550
-
2551
- // MARK: - Link
2552
-
2553
- struct LinkValue {
2554
- let type: String
2555
- var entityModelKey: String?
2556
- var entityNaturalKey: String?
2557
- var url: String?
2558
- var target: String?
2559
-
2560
- func toSyncData() -> [String: Any] {
2561
- var data: [String: Any] = ["type": type]
2562
- if let entityModelKey { data["entityModelKey"] = entityModelKey }
2563
- if let entityNaturalKey { data["entityNaturalKey"] = entityNaturalKey }
2564
- if let url { data["url"] = url }
2565
- if let target { data["target"] = target }
2566
- return data
2567
- }
2568
-
2569
- static func fromSyncData(_ data: [String: Any]) -> LinkValue {
2570
- LinkValue(
2571
- type: data["type"] as? String ?? "",
2572
- entityModelKey: data["entityModelKey"] as? String,
2573
- entityNaturalKey: data["entityNaturalKey"] as? String,
2574
- url: data["url"] as? String,
2575
- target: data["target"] as? String
2576
- )
2577
- }
2578
- }
2579
- `;
2580
- }
2581
-
2582
- // src/codegen/generators/swift-model-keys.ts
2583
- function generateSwiftModelKeys(models) {
2584
- const lines = [];
2585
- lines.push("//");
2586
- lines.push("// ModelKeys.swift");
2587
- lines.push("//");
2588
- lines.push("// All model key constants.");
2589
- lines.push("//");
2590
- lines.push("// @generated by foir \u2014 DO NOT EDIT MANUALLY");
2591
- lines.push("//");
2592
- lines.push("");
2593
- lines.push("import Foundation");
2594
- lines.push("");
2595
- lines.push("enum ModelKeys {");
2596
- for (const model of models) {
2597
- const propName = toCamelCase(model.key);
2598
- lines.push(` static let ${propName} = "${model.key}"`);
2599
- }
2600
- lines.push("}");
2601
- lines.push("");
2602
- return lines.join("\n");
2603
- }
2604
-
2605
- // src/codegen/generators/customer-profile-types.ts
2606
- function generateCustomerProfileTypes(schema) {
2607
- const fields = schema.fields ?? [];
2608
- const fieldTypeImports = getFieldTypeImportsForFields2(fields);
2609
- let code = `/**
2610
- * Customer Profile Types
2611
- *
2612
- * @generated by foir from customer profile schema (version ${schema.version}) \u2014 DO NOT EDIT MANUALLY
2613
- */
2614
-
2615
- `;
2616
- if (fieldTypeImports.size > 0) {
2617
- const types = Array.from(fieldTypeImports).sort().join(", ");
2618
- code += `import type { ${types} } from '../field-types.js';
2619
-
2620
- `;
2621
- }
2622
- code += `/**
2623
- * Customer Profile Data
2624
- * Typed field values for customer profiles
2625
- *
2626
- * @generated from customer profile schema (version ${schema.version})
2627
- */
2628
- `;
2629
- code += `export interface CustomerProfileData {
2630
- `;
2631
- for (const field of fields) {
2632
- const fieldName = sanitizeFieldName(field.key);
2633
- const fieldType = getFieldType(field, "output");
2634
- const optional = field.required ? "" : "?";
2635
- if (field.helpText) {
2636
- code += ` /** ${field.helpText} */
2637
- `;
2638
- }
2639
- code += ` ${fieldName}${optional}: ${fieldType};
2640
- `;
2641
- }
2642
- code += `}
2643
- `;
2644
- return code;
2645
- }
2646
- function getFieldTypeImportsForFields2(fields) {
2647
- const imports = /* @__PURE__ */ new Set();
2648
- for (const field of fields) {
2649
- const mapping = FIELD_TYPE_MAPPING[field.type];
2650
- if (mapping?.needsImport === "field-types") {
2651
- imports.add(mapping.outputType);
2652
- }
2653
- }
2654
- return imports;
2655
- }
2656
-
2657
- // src/codegen/generators/swift-customer-profile.ts
2658
- function generateSwiftCustomerProfileFile(schema) {
2659
- const typeName = "CustomerProfile";
2660
- const fields = schema.fields ?? [];
2661
- const lines = [];
2662
- lines.push("//");
2663
- lines.push("// CustomerProfile.swift");
2664
- lines.push("//");
2665
- lines.push(
2666
- `// Generated from customer profile schema (version ${schema.version})`
2667
- );
2668
- lines.push("//");
2669
- lines.push("// @generated by foir \u2014 DO NOT EDIT MANUALLY");
2670
- lines.push("//");
2671
- lines.push("");
2672
- lines.push("import Foundation");
2673
- lines.push("");
2674
- lines.push(generateFieldsEnum2(typeName, fields));
2675
- lines.push("");
2676
- lines.push(generateDataStruct2(typeName, fields));
2677
- lines.push("");
2678
- lines.push(generateSerializationExtension2(typeName, fields));
2679
- return lines.join("\n");
2680
- }
2681
- function generateFieldsEnum2(typeName, fields) {
2682
- const lines = [];
2683
- lines.push(`// MARK: - ${typeName} Field Keys`);
2684
- lines.push("");
2685
- lines.push(`enum ${typeName}Fields {`);
2686
- for (const field of fields) {
2687
- lines.push(` static let ${field.key} = "${field.key}"`);
2688
- }
2689
- lines.push("}");
2690
- return lines.join("\n");
2691
- }
2692
- function generateDataStruct2(typeName, fields) {
2693
- const lines = [];
2694
- lines.push(`// MARK: - ${typeName} Data`);
2695
- lines.push("");
2696
- lines.push(`struct ${typeName}Data {`);
2697
- for (const field of fields) {
2698
- const { type, isOptional } = getSwiftFieldType(field);
2699
- const optionalSuffix = isOptional ? "?" : "";
2700
- lines.push(` var ${field.key}: ${type}${optionalSuffix}`);
2701
- }
2702
- lines.push("}");
2703
- return lines.join("\n");
2704
- }
2705
- function generateSerializationExtension2(typeName, fields) {
2706
- const lines = [];
2707
- lines.push(`// MARK: - ${typeName} Serialization`);
2708
- lines.push("");
2709
- lines.push(`extension ${typeName}Data {`);
2710
- lines.push(" func toSyncData() -> [String: Any] {");
2711
- const requiredFields = fields.filter((f) => {
2712
- const { isOptional } = getSwiftFieldType(f);
2713
- return !isOptional;
2714
- });
2715
- const optionalFields = fields.filter((f) => {
2716
- const { isOptional } = getSwiftFieldType(f);
2717
- return isOptional;
2718
- });
2719
- if (requiredFields.length > 0) {
2720
- if (optionalFields.length === 0) {
2721
- lines.push(" return [");
2722
- requiredFields.forEach((f, i) => {
2723
- const comma = i < requiredFields.length - 1 ? "," : "";
2724
- lines.push(
2725
- ` ${typeName}Fields.${f.key}: ${toSyncValueExpr2(f)}${comma}`
2726
- );
2727
- });
2728
- lines.push(" ]");
2729
- } else {
2730
- lines.push(" var data: [String: Any] = [");
2731
- requiredFields.forEach((f, i) => {
2732
- const comma = i < requiredFields.length - 1 ? "," : "";
2733
- lines.push(
2734
- ` ${typeName}Fields.${f.key}: ${toSyncValueExpr2(f)}${comma}`
2735
- );
2736
- });
2737
- lines.push(" ]");
2738
- for (const f of optionalFields) {
2739
- lines.push(
2740
- ` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional2(f)} }`
2741
- );
2742
- }
2743
- lines.push(" return data");
2744
- }
2745
- } else {
2746
- lines.push(" var data: [String: Any] = [:]");
2747
- for (const f of optionalFields) {
2748
- lines.push(
2749
- ` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional2(f)} }`
2750
- );
2751
- }
2752
- lines.push(" return data");
2753
- }
2754
- lines.push(" }");
2755
- lines.push("");
2756
- lines.push(
2757
- " static func fromSyncData(_ data: [String: Any]) -> " + typeName + "Data {"
2758
- );
2759
- lines.push(` ${typeName}Data(`);
2760
- fields.forEach((field, i) => {
2761
- const comma = i < fields.length - 1 ? "," : "";
2762
- const { isOptional, mapping } = getSwiftFieldType(field);
2763
- lines.push(
2764
- ` ${field.key}: ${fromSyncDataExpr2(field, typeName, isOptional, mapping)}${comma}`
2765
- );
2766
- });
2767
- lines.push(" )");
2768
- lines.push(" }");
2769
- lines.push("}");
2770
- lines.push("");
2771
- return lines.join("\n");
2772
- }
2773
- function toSyncValueExpr2(field) {
2774
- const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
2775
- if (mapping?.needsSharedType) {
2776
- return `${field.key}.toSyncData()`;
2777
- }
2778
- return field.key;
2779
- }
2780
- function toSyncValueExprForOptional2(field) {
2781
- const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
2782
- if (mapping?.needsSharedType) {
2783
- return `${field.key}.toSyncData()`;
2784
- }
2785
- return field.key;
2786
- }
2787
- function fromSyncDataExpr2(field, typeName, isOptional, mapping) {
2788
- const accessor = `data[${typeName}Fields.${field.key}]`;
2789
- if (!mapping) {
2790
- return isOptional ? `${accessor}` : `${accessor} ?? nil`;
2791
- }
2792
- if (mapping.needsSharedType) {
2793
- const dictCast = `${accessor} as? [String: Any]`;
2794
- if (isOptional) {
2795
- return `(${dictCast}).map { ${mapping.type}.fromSyncData($0) }`;
2796
- }
2797
- return `${mapping.type}.fromSyncData(${dictCast} ?? [:])`;
2798
- }
2799
- if (field.type === "json") {
2800
- return isOptional ? accessor : `${accessor}`;
2801
- }
2802
- if (isOptional) {
2803
- return `${accessor} ${mapping.castExpression}`;
2804
- }
2805
- return `${accessor} ${mapping.castExpression} ?? ${mapping.defaultValue}`;
2806
- }
2807
-
2808
- // src/codegen/generators/customer-profile-documents.ts
2809
- function generateCustomerProfileDocuments() {
2810
- return `# Generated GraphQL operations for Customer Profiles
2811
- # @generated by foir \u2014 DO NOT EDIT MANUALLY
2812
-
2813
- fragment CustomerProfileFields on CustomerProfile {
2814
- id
2815
- customerId
2816
- data
2817
- createdAt
2818
- updatedAt
2819
- }
2820
-
2821
- query GetMyProfile {
2822
- myProfile {
2823
- ...CustomerProfileFields
2824
- resolved
2825
- }
2826
- }
2827
-
2828
- query GetCustomerProfile($customerId: ID!) {
2829
- customerProfile(customerId: $customerId) {
2830
- ...CustomerProfileFields
2831
- resolved
2832
- }
2833
- }
2834
-
2835
- mutation SetMyProfile($data: JSON!) {
2836
- setMyProfile(data: $data) {
2837
- ...CustomerProfileFields
2838
- }
2839
- }
2840
-
2841
- mutation UpdateCustomerProfile($customerId: ID, $data: JSON!) {
2842
- updateCustomerProfile(customerId: $customerId, input: { data: $data }) {
2843
- ...CustomerProfileFields
2844
- }
2845
- }
2846
- `;
2847
- }
2848
-
2849
- // src/codegen/generators/static-documents.ts
2850
- var HEADER = "# @generated by foir \u2014 DO NOT EDIT MANUALLY";
2851
- function authDocument() {
2852
- return `# Customer authentication operations
2853
- ${HEADER}
2854
-
2855
- mutation CustomerLogin($email: String!, $password: String!) {
2856
- customerLogin(email: $email, password: $password) {
2857
- success
2858
- accessToken
2859
- refreshToken
2860
- user { id email status }
2861
- }
2862
- }
2863
-
2864
- mutation CustomerRegister($email: String!, $password: String!) {
2865
- customerRegister(email: $email, password: $password) {
2866
- success
2867
- accessToken
2868
- refreshToken
2869
- user { id email status }
2870
- emailVerificationRequired
2871
- }
2872
- }
2873
-
2874
- mutation CustomerRequestOTP($email: String!) {
2875
- customerRequestOTP(email: $email) {
2876
- success
2877
- expiresAt
2878
- message
2879
- }
2880
- }
2881
-
2882
- mutation CustomerLoginOTP($email: String!, $otp: String!) {
2883
- customerLoginOTP(email: $email, otp: $otp) {
2884
- success
2885
- accessToken
2886
- refreshToken
2887
- user { id email status }
2888
- }
2889
- }
2890
-
2891
- mutation CustomerRefreshToken($refreshToken: String!) {
2892
- customerRefreshToken(refreshToken: $refreshToken) {
2893
- success
2894
- accessToken
2895
- refreshToken
2896
- }
2897
- }
2898
-
2899
- mutation CustomerRequestPasswordReset($email: String!) {
2900
- customerRequestPasswordReset(email: $email) {
2901
- success
2902
- message
2903
- }
2904
- }
2905
-
2906
- mutation CustomerResetPassword($token: String!, $newPassword: String!) {
2907
- customerResetPassword(token: $token, newPassword: $newPassword) {
2908
- success
2909
- message
2910
- }
2911
- }
2912
-
2913
- mutation CustomerUpdatePassword($currentPassword: String!, $newPassword: String!) {
2914
- customerUpdatePassword(currentPassword: $currentPassword, newPassword: $newPassword) {
2915
- success
2916
- message
2917
- }
2918
- }
2919
-
2920
- mutation CustomerVerifyEmail($token: String!) {
2921
- customerVerifyEmail(token: $token) {
2922
- success
2923
- user { id email }
2924
- message
2925
- }
2926
- }
2927
-
2928
- mutation CustomerResendVerificationEmail {
2929
- customerResendVerificationEmail {
2930
- success
2931
- message
2932
- }
2933
- }
2934
-
2935
- mutation CustomerLogout {
2936
- customerLogout {
2937
- success
2938
- message
2939
- }
2940
- }
2941
-
2942
- query AuthConfig($tenantId: ID) {
2943
- authConfig(tenantId: $tenantId) {
2944
- authMethods
2945
- passwordPolicy {
2946
- minLength
2947
- requireUppercase
2948
- requireLowercase
2949
- requireNumbers
2950
- requireSpecialChars
2951
- requireSpecial
2952
- }
2953
- publicRegistrationEnabled
2954
- }
2955
- }
2956
-
2957
- query CurrentUser {
2958
- currentUser {
2959
- id
2960
- email
2961
- emailVerified
2962
- status
2963
- userType
2964
- }
2965
- }
2966
- `;
2967
- }
2968
- function authProvidersDocument() {
2969
- return `# Auth provider operations
2970
- ${HEADER}
2971
-
2972
- query AuthProviders {
2973
- authProviders {
2974
- id
2975
- key
2976
- name
2977
- type
2978
- enabled
2979
- isDefault
2980
- priority
2981
- }
2982
- }
2983
-
2984
- query DefaultAuthProvider {
2985
- defaultAuthProvider {
2986
- id
2987
- key
2988
- name
2989
- type
2990
- enabled
2991
- isDefault
2992
- priority
2993
- }
2994
- }
2995
-
2996
- mutation CustomerLoginWithProvider($input: ProviderLoginInput!) {
2997
- customerLoginWithProvider(input: $input) {
2998
- method
2999
- providerId
3000
- providerKey
3001
- redirectUrl
3002
- accessToken
3003
- refreshToken
3004
- user { id email userType }
3005
- otpSent
3006
- email
3007
- expiresAt
3008
- state
3009
- }
3010
- }
3011
-
3012
- mutation CustomerProviderCallback($input: ProviderCallbackInput!) {
3013
- customerProviderCallback(input: $input) {
3014
- accessToken
3015
- refreshToken
3016
- user { id email userType }
3017
- isNewCustomer
3018
- providerAccessToken
3019
- providerAccessTokenExpiresIn
3020
- }
3021
- }
3022
-
3023
- mutation CustomerProviderVerifyOTP($input: ProviderOTPVerifyInput!) {
3024
- customerProviderVerifyOTP(input: $input) {
3025
- accessToken
3026
- refreshToken
3027
- user { id email userType }
3028
- isNewCustomer
3029
- providerAccessToken
3030
- providerAccessTokenExpiresIn
3031
- }
3032
- }
3033
- `;
3034
- }
3035
- function filesDocument() {
3036
- return `# File management operations
3037
- ${HEADER}
3038
-
3039
- query GetFile($id: ID!) {
3040
- file(id: $id) {
3041
- id
3042
- filename
3043
- mimeType
3044
- size
3045
- url
3046
- source
3047
- status
3048
- metadata
3049
- width
3050
- height
3051
- blurhash
3052
- dominantColor
3053
- duration
3054
- thumbnailUrl
3055
- previewUrl
3056
- altText
3057
- caption
3058
- description
3059
- isImage
3060
- isVideo
3061
- createdAt
3062
- updatedAt
3063
- }
3064
- }
3065
-
3066
- mutation CreateFileUpload(
3067
- $filename: String!
3068
- $mimeType: String!
3069
- $size: Int!
3070
- $folder: String
3071
- $metadata: JSON
3072
- ) {
3073
- createFileUpload(
3074
- filename: $filename
3075
- mimeType: $mimeType
3076
- size: $size
3077
- folder: $folder
3078
- metadata: $metadata
3079
- ) {
3080
- uploadId
3081
- uploadUrl
3082
- expiresAt
3083
- }
3084
- }
3085
-
3086
- mutation ConfirmFileUpload($uploadId: ID!) {
3087
- confirmFileUpload(uploadId: $uploadId) {
3088
- id
3089
- filename
3090
- mimeType
3091
- size
3092
- url
3093
- source
3094
- status
3095
- createdAt
3096
- }
3097
- }
3098
- `;
3099
- }
3100
- function syncDocument() {
3101
- return `# Sync engine operations (Layer 1: delta sync protocol)
3102
- ${HEADER}
3103
-
3104
- query SyncPull($modelKey: String!, $since: String!, $limit: Int) {
3105
- syncPull(modelKey: $modelKey, since: $since, limit: $limit) {
3106
- items {
3107
- id
3108
- modelKey
3109
- naturalKey
3110
- data
3111
- metadata
3112
- syncVersion
3113
- updatedAt
3114
- deleted
3115
- }
3116
- cursor
3117
- hasMore
3118
- }
3119
- }
3120
-
3121
- mutation SyncPush($items: [SyncPushItemInput!]!) {
3122
- syncPush(items: $items) {
3123
- items {
3124
- clientId
3125
- serverId
3126
- syncVersion
3127
- status
3128
- serverData
3129
- serverSyncVersion
3130
- error
3131
- }
3132
- }
3133
- }
3134
-
3135
- subscription RecordChanged($modelKey: String!) {
3136
- recordChanged(modelKey: $modelKey) {
3137
- type
3138
- recordId
3139
- modelKey
3140
- naturalKey
3141
- syncVersion
3142
- data
3143
- updatedBy
3144
- timestamp
3145
- }
3146
- }
3147
- `;
3148
- }
3149
- function notificationsDocument() {
3150
- return `# Customer notification operations
3151
- ${HEADER}
3152
-
3153
- query CustomerNotifications(
3154
- $unreadOnly: Boolean
3155
- $category: String
3156
- $limit: Int
3157
- $offset: Int
3158
- ) {
3159
- customerNotifications(
3160
- unreadOnly: $unreadOnly
3161
- category: $category
3162
- limit: $limit
3163
- offset: $offset
3164
- ) {
3165
- items {
3166
- id
3167
- type
3168
- category
3169
- title
3170
- message
3171
- actionUrl
3172
- imageUrl
3173
- metadata
3174
- alertChannels
3175
- isRead
3176
- readAt
3177
- createdAt
3178
- }
3179
- total
3180
- unreadCount
3181
- hasMore
3182
- }
3183
- }
3184
-
3185
- query CustomerUnreadCount($category: String) {
3186
- customerUnreadCount(category: $category)
3187
- }
3188
-
3189
- query NotificationPreferences {
3190
- notificationPreferences {
3191
- id
3192
- category
3193
- channel
3194
- enabled
3195
- }
3196
- }
3197
-
3198
- mutation SendNotification($input: SendNotificationInput!) {
3199
- sendNotification(input: $input) {
3200
- id
3201
- type
3202
- category
3203
- title
3204
- message
3205
- actionUrl
3206
- imageUrl
3207
- metadata
3208
- alertChannels
3209
- isRead
3210
- readAt
3211
- createdAt
3212
- }
3213
- }
3214
-
3215
- mutation SendBulkNotifications($input: SendBulkNotificationsInput!) {
3216
- sendBulkNotifications(input: $input) {
3217
- sent
3218
- failed
3219
- }
3220
- }
3221
-
3222
- mutation MarkCustomerNotificationRead($id: ID!) {
3223
- markCustomerNotificationRead(id: $id) {
3224
- id
3225
- isRead
3226
- readAt
3227
- }
3228
- }
3229
-
3230
- mutation MarkAllCustomerNotificationsRead($category: String) {
3231
- markAllCustomerNotificationsRead(category: $category)
3232
- }
3233
-
3234
- mutation RegisterDeviceToken($input: RegisterDeviceTokenInput!) {
3235
- registerDeviceToken(input: $input) {
3236
- id
3237
- platform
3238
- token
3239
- deviceName
3240
- isActive
3241
- createdAt
3242
- }
3243
- }
3244
-
3245
- mutation UnregisterDeviceToken($token: String!) {
3246
- unregisterDeviceToken(token: $token)
3247
- }
3248
-
3249
- mutation UpdateNotificationPreference($input: UpdateNotificationPreferenceInput!) {
3250
- updateNotificationPreference(input: $input) {
3251
- id
3252
- category
3253
- channel
3254
- enabled
3255
- }
3256
- }
3257
- `;
3258
- }
3259
- function operationsDocument() {
3260
- return `# Operation execution operations
3261
- ${HEADER}
3262
-
3263
- query GetOperationExecution($id: ID!) {
3264
- operationExecution(id: $id) {
3265
- id
3266
- operationKey
3267
- status
3268
- result
3269
- error
3270
- startedAt
3271
- completedAt
3272
- durationMs
3273
- metadata
3274
- createdAt
3275
- }
3276
- }
3277
-
3278
- query ListOperationExecutions(
3279
- $operationKey: String
3280
- $status: OperationExecutionStatus
3281
- $limit: Int
3282
- $offset: Int
3283
- ) {
3284
- operationExecutions(
3285
- operationKey: $operationKey
3286
- status: $status
3287
- limit: $limit
3288
- offset: $offset
3289
- ) {
3290
- items {
3291
- id
3292
- operationKey
3293
- status
3294
- durationMs
3295
- startedAt
3296
- completedAt
3297
- metadata
3298
- createdAt
3299
- }
3300
- total
3301
- }
3302
- }
3303
-
3304
- mutation ExecuteOperation($input: ExecuteOperationInput!) {
3305
- executeOperation(input: $input) {
3306
- success
3307
- result
3308
- error {
3309
- code
3310
- message
3311
- }
3312
- executionId
3313
- durationMs
3314
- metadata
3315
- }
3316
- }
3317
-
3318
- mutation CancelOperationExecution($id: ID!) {
3319
- cancelOperationExecution(id: $id) {
3320
- id
3321
- status
3322
- }
3323
- }
3324
- `;
3325
- }
3326
- function schedulesDocument() {
3327
- return `# Schedule management operations
3328
- ${HEADER}
3329
-
3330
- query GetSchedule($key: String!) {
3331
- schedule(key: $key) {
3332
- id
3333
- key
3334
- name
3335
- description
3336
- cron
3337
- cronDescription
3338
- timezone
3339
- targetType
3340
- isActive
3341
- lastRunAt
3342
- lastRunStatus
3343
- nextRunAt
3344
- runCount
3345
- failureCount
3346
- createdAt
3347
- updatedAt
3348
- }
3349
- }
3350
-
3351
- query ListSchedules(
3352
- $targetType: ScheduleTargetType
3353
- $isActive: Boolean
3354
- $limit: Int
3355
- $offset: Int
3356
- ) {
3357
- schedules(
3358
- targetType: $targetType
3359
- isActive: $isActive
3360
- limit: $limit
3361
- offset: $offset
3362
- ) {
3363
- items {
3364
- id
3365
- key
3366
- name
3367
- cron
3368
- cronDescription
3369
- timezone
3370
- isActive
3371
- lastRunAt
3372
- lastRunStatus
3373
- nextRunAt
3374
- runCount
3375
- failureCount
3376
- createdAt
3377
- }
3378
- total
3379
- }
3380
- }
3381
-
3382
- mutation CreateSchedule($input: CreateScheduleInput!) {
3383
- createSchedule(input: $input) {
3384
- id
3385
- key
3386
- name
3387
- cron
3388
- isActive
3389
- createdAt
3390
- }
3391
- }
3392
-
3393
- mutation UpdateSchedule($key: String!, $input: UpdateScheduleInput!) {
3394
- updateSchedule(key: $key, input: $input) {
3395
- id
3396
- key
3397
- name
3398
- cron
3399
- isActive
3400
- updatedAt
3401
- }
3402
- }
3403
-
3404
- mutation DeleteSchedule($key: String!) {
3405
- deleteSchedule(key: $key)
3406
- }
3407
-
3408
- mutation PauseSchedule($key: String!) {
3409
- pauseSchedule(key: $key) {
3410
- id
3411
- key
3412
- isActive
3413
- }
3414
- }
3415
-
3416
- mutation ResumeSchedule($key: String!) {
3417
- resumeSchedule(key: $key) {
3418
- id
3419
- key
3420
- isActive
3421
- }
3422
- }
3423
-
3424
- mutation TriggerSchedule($key: String!) {
3425
- triggerSchedule(key: $key) {
3426
- success
3427
- jobId
3428
- error
3429
- }
3430
- }
3431
- `;
3432
- }
3433
- function sharingDocument() {
3434
- return `# Sharing operations
3435
- ${HEADER}
3436
-
3437
- fragment ShareFields on Share {
3438
- id
3439
- resourceType
3440
- recordId
3441
- fileId
3442
- permission
3443
- status
3444
- sharedWithCustomerId
3445
- acceptedAt
3446
- declinedAt
3447
- expiresAt
3448
- createdAt
3449
- createdBy
3450
- revokedAt
3451
- }
3452
-
3453
- query GetShares($resourceType: ShareResourceType!, $resourceId: ID!, $status: ShareStatus) {
3454
- shares(resourceType: $resourceType, resourceId: $resourceId, status: $status) {
3455
- ...ShareFields
3456
- }
3457
- }
3458
-
3459
- query SharedWithMe(
3460
- $resourceType: ShareResourceType
3461
- $modelKey: String
3462
- $status: ShareStatus
3463
- $limit: Int
3464
- $offset: Int
3465
- ) {
3466
- sharedWithMe(
3467
- resourceType: $resourceType
3468
- modelKey: $modelKey
3469
- status: $status
3470
- limit: $limit
3471
- offset: $offset
3472
- ) {
3473
- ...ShareFields
3474
- }
3475
- }
3476
-
3477
- mutation ShareRecord(
3478
- $recordId: ID!
3479
- $sharedWithCustomerId: ID!
3480
- $permission: SharePermission!
3481
- $expiresAt: DateTime
3482
- ) {
3483
- shareRecord(
3484
- recordId: $recordId
3485
- sharedWithCustomerId: $sharedWithCustomerId
3486
- permission: $permission
3487
- expiresAt: $expiresAt
3488
- ) {
3489
- ...ShareFields
3490
- }
3491
- }
3492
-
3493
- mutation ShareFile(
3494
- $fileId: ID!
3495
- $sharedWithCustomerId: ID!
3496
- $permission: SharePermission!
3497
- $expiresAt: DateTime
3498
- ) {
3499
- shareFile(
3500
- fileId: $fileId
3501
- sharedWithCustomerId: $sharedWithCustomerId
3502
- permission: $permission
3503
- expiresAt: $expiresAt
3504
- ) {
3505
- ...ShareFields
3506
- }
3507
- }
3508
-
3509
- mutation AcceptShare($shareId: ID!) {
3510
- acceptShare(shareId: $shareId) {
3511
- ...ShareFields
3512
- }
3513
- }
3514
-
3515
- mutation DeclineShare($shareId: ID!) {
3516
- declineShare(shareId: $shareId) {
3517
- ...ShareFields
3518
- }
3519
- }
3520
-
3521
- mutation RevokeShare($shareId: ID!) {
3522
- revokeShare(shareId: $shareId) {
3523
- ...ShareFields
3524
- }
3525
- }
3526
-
3527
- mutation UpdateSharePermission($shareId: ID!, $permission: SharePermission!) {
3528
- updateSharePermission(shareId: $shareId, permission: $permission) {
3529
- ...ShareFields
3530
- }
3531
- }
3532
- `;
3533
- }
3534
- function embeddingsDocument() {
3535
- return `# Vector embedding operations (search, write, delete)
3536
- ${HEADER}
3537
-
3538
- query SearchEmbeddings($input: SearchEmbeddingsInput!) {
3539
- searchEmbeddings(input: $input) {
3540
- recordId
3541
- modelKey
3542
- naturalKey
3543
- key
3544
- similarity
3545
- metadata
3546
- }
3547
- }
3548
-
3549
- mutation WriteEmbeddings($input: WriteEmbeddingsInput!) {
3550
- writeEmbeddings(input: $input) {
3551
- written
3552
- errors {
3553
- recordId
3554
- key
3555
- message
3556
- }
3557
- }
3558
- }
3559
-
3560
- mutation DeleteEmbeddings($input: DeleteEmbeddingsInput!) {
3561
- deleteEmbeddings(input: $input) {
3562
- deleted
3563
- }
3564
- }
3565
- `;
3566
- }
3567
- function analyticsDocument() {
3568
- return `# Analytics & conversion tracking operations
3569
- ${HEADER}
3570
-
3571
- mutation RecordConversion($input: RecordConversionInput!) {
3572
- recordConversion(input: $input) {
3573
- success
3574
- }
3575
- }
3576
- `;
3577
- }
3578
- function generateStaticDocuments(domains) {
3579
- const files = [];
3580
- if (domains.auth)
3581
- files.push({ filename: "auth.graphql", content: authDocument() });
3582
- if (domains.authProviders)
3583
- files.push({
3584
- filename: "auth-providers.graphql",
3585
- content: authProvidersDocument()
3586
- });
3587
- if (domains.files)
3588
- files.push({ filename: "files.graphql", content: filesDocument() });
3589
- if (domains.sync)
3590
- files.push({ filename: "sync.graphql", content: syncDocument() });
3591
- if (domains.notifications)
3592
- files.push({
3593
- filename: "notifications.graphql",
3594
- content: notificationsDocument()
3595
- });
3596
- if (domains.operations)
3597
- files.push({
3598
- filename: "operations.graphql",
3599
- content: operationsDocument()
3600
- });
3601
- if (domains.schedules)
3602
- files.push({ filename: "schedules.graphql", content: schedulesDocument() });
3603
- if (domains.sharing)
3604
- files.push({ filename: "sharing.graphql", content: sharingDocument() });
3605
- if (domains.embeddings)
3606
- files.push({
3607
- filename: "embeddings.graphql",
3608
- content: embeddingsDocument()
3609
- });
3610
- if (domains.analytics)
3611
- files.push({ filename: "analytics.graphql", content: analyticsDocument() });
3612
- return files;
3613
- }
3614
-
3615
- // src/codegen/generators/typed-operations-common.ts
3616
- function generateTypedOperationsCommon(typesRelPath) {
3617
- return `/**
3618
- * Shared types for typed GraphQL operations.
3619
- *
3620
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
3621
- */
3622
-
3623
- import type { JsonValue } from '${typesRelPath}/field-types.js';
3624
-
3625
- /** A record with strongly-typed data. */
3626
- export interface BaseRecord<T> {
3627
- id: string;
3628
- modelKey: string;
3629
- naturalKey: string | null;
3630
- data: T;
3631
- metadata: Record<string, JsonValue> | null;
3632
- publishedVersionNumber: number | null;
3633
- publishedAt: string | null;
3634
- versionNumber: number | null;
3635
- changeDescription: string | null;
3636
- createdAt: string;
3637
- updatedAt: string;
3638
- }
3639
-
3640
- /** Resolved content wrapping strongly-typed data. */
3641
- export interface ResolvedContent<T> {
3642
- content: T;
3643
- record: { id: string; modelKey: string; naturalKey: string | null };
3644
- version: { id: string; versionNumber: number } | null;
3645
- }
3646
-
3647
- /** Paginated list result. */
3648
- export interface PaginatedResult<T> {
3649
- items: (BaseRecord<T> & { resolved: ResolvedContent<T> | null })[];
3650
- total: number;
3651
- }
3652
-
3653
- /** Result of a createRecord mutation. */
3654
- export interface CreateRecordResult<T> {
3655
- record: BaseRecord<T>;
3656
- }
3657
-
3658
- /** Result of an updateRecord mutation. */
3659
- export interface UpdateRecordResult<T> {
3660
- record: BaseRecord<T>;
3661
- matched: boolean;
3662
- }
3663
-
3664
- /** Result of a deleteRecord mutation. */
3665
- export interface DeleteRecordResult {
3666
- id: string;
3667
- }
3668
-
3669
- /** Share resource type. */
3670
- export interface ShareResult {
3671
- id: string;
3672
- resourceType: string;
3673
- permission: string;
3674
- status: string;
3675
- acceptedAt: string | null;
3676
- declinedAt: string | null;
3677
- expiresAt: string | null;
3678
- createdAt: string;
3679
- revokedAt: string | null;
3680
- }
3681
-
3682
- /** A share that includes the shared record. */
3683
- export interface ShareWithRecord<T> extends ShareResult {
3684
- record: BaseRecord<T>;
3685
- }
3686
-
3687
- /** Field selection for resolved content \u2014 pick or omit specific fields. */
3688
- export interface FieldSelection<T = Record<string, unknown>> {
3689
- /** Include only these field keys (mutually exclusive with omit) */
3690
- pick?: (keyof T & string)[];
3691
- /** Exclude these field keys (mutually exclusive with omit) */
3692
- omit?: (keyof T & string)[];
3693
- }
3694
- `;
3695
- }
3696
-
3697
- // src/codegen/generators/typed-operations.ts
3698
- import path from "path";
3699
- function generateTypedOperations(model, typesRelPath) {
3700
- const typeName = toPascalCase(model.key);
3701
- const upperSnake = toUpperSnakeCase(model.key);
3702
- const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
3703
- const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
3704
- const dataType = `${typeName}Data`;
1837
+ // src/codegen/generators/client-factory.ts
1838
+ function generateClientFactory(models, hasCustomerProfile) {
1839
+ const publicModels = models.filter(
1840
+ (m) => m.config.publicApi && m.config.records
1841
+ );
3705
1842
  const lines = [];
3706
1843
  lines.push(`/**
3707
- * Typed operations for ${model.name ?? model.key}
1844
+ * Typed Foir client for this project.
3708
1845
  *
3709
1846
  * @generated by foir \u2014 DO NOT EDIT MANUALLY
3710
1847
  */
3711
1848
 
3712
- import type { JsonValue } from '${typesRelPath}/field-types.js';
3713
- import type { ${dataType} } from '${typesRelPath}/models/${model.key}.js';
3714
- import type {
3715
- BaseRecord,
3716
- ResolvedContent,
3717
- PaginatedResult,
3718
- CreateRecordResult,
3719
- UpdateRecordResult,
3720
- DeleteRecordResult,
3721
- FieldSelection,${model.config.sharing?.enabled ? "\n ShareResult,\n ShareWithRecord," : ""}
3722
- } from './_common.js';
1849
+ import {
1850
+ createBaseClient,
1851
+ createModelAccessor,
1852
+ createAuthClient,
1853
+ createFilesClient,
1854
+ createNotificationsClient,
1855
+ createSharingClient,
1856
+ createSearchClient,
1857
+ createProfileClient,
1858
+ createOperationsClient,
1859
+ type ClientConfig,
1860
+ type ModelAccessor,
1861
+ type FoirClient,
1862
+ } from '@eide/foir-client';
3723
1863
  `);
3724
- lines.push(`export type ${typeName}Record = BaseRecord<${dataType}>;`);
3725
- lines.push("");
3726
- lines.push(`export const GET_${upperSnake} = \`
3727
- query Get${typeName}($id: ID!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
3728
- record(id: $id) {
3729
- id modelKey naturalKey data metadata
3730
- publishedVersionNumber publishedAt versionNumber changeDescription
3731
- createdAt updatedAt
3732
- resolved(locale: $locale, preview: $preview, fields: $fields) {
3733
- content
3734
- record { id modelKey naturalKey }
3735
- version { id versionNumber }
3736
- }
3737
- }
3738
- }
3739
- \`;`);
3740
- lines.push("");
3741
- lines.push(`export interface Get${typeName}Variables {
3742
- id: string;
3743
- locale?: string;
3744
- preview?: boolean;
3745
- fields?: FieldSelection<${dataType}>;
3746
- }`);
3747
- lines.push("");
3748
- lines.push(`export interface Get${typeName}Result {
3749
- record: ${typeName}Record & {
3750
- resolved: ResolvedContent<${dataType}> | null;
3751
- } | null;
3752
- }`);
3753
- lines.push("");
3754
- lines.push(`export const GET_${upperSnake}_BY_KEY = \`
3755
- query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
3756
- recordByKey(modelKey: "${model.key}", naturalKey: $naturalKey) {
3757
- id modelKey naturalKey data metadata
3758
- publishedVersionNumber publishedAt versionNumber changeDescription
3759
- createdAt updatedAt
3760
- resolved(locale: $locale, preview: $preview, fields: $fields) {
3761
- content
3762
- record { id modelKey naturalKey }
3763
- version { id versionNumber }
3764
- }
3765
- }
1864
+ for (const model of publicModels) {
1865
+ const typeName = toPascalCase(model.key);
1866
+ const dataType = `${typeName}Data`;
1867
+ const whereType = `${typeName}Where`;
1868
+ const sortType = `${typeName}SortField`;
1869
+ lines.push(
1870
+ `import type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
1871
+ );
3766
1872
  }
3767
- \`;`);
3768
- lines.push("");
3769
- lines.push(`export interface Get${typeName}ByKeyVariables {
3770
- naturalKey: string;
3771
- locale?: string;
3772
- preview?: boolean;
3773
- fields?: FieldSelection<${dataType}>;
3774
- }`);
3775
- lines.push("");
3776
- lines.push(`export interface Get${typeName}ByKeyResult {
3777
- recordByKey: ${typeName}Record & {
3778
- resolved: ResolvedContent<${dataType}> | null;
3779
- } | null;
3780
- }`);
3781
- lines.push("");
3782
- lines.push(`export const LIST_${pluralUpperSnake} = \`
3783
- query List${pluralName}($limit: Int, $offset: Int, $filters: [FilterInput!], $sort: SortInput, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
3784
- records(modelKey: "${model.key}", limit: $limit, offset: $offset, filters: $filters, sort: $sort) {
3785
- items {
3786
- id modelKey naturalKey data metadata
3787
- publishedVersionNumber publishedAt versionNumber changeDescription
3788
- createdAt updatedAt
3789
- resolved(locale: $locale, preview: $preview, fields: $fields) {
3790
- content
3791
- record { id modelKey naturalKey }
3792
- version { id versionNumber }
3793
- }
3794
- }
3795
- total
3796
- }
1873
+ if (hasCustomerProfile) {
1874
+ lines.push(
1875
+ `import type { CustomerProfileData } from './models/customer-profile.js';`
1876
+ );
3797
1877
  }
3798
- \`;`);
3799
- lines.push("");
3800
- lines.push(`export interface List${pluralName}Variables {
3801
- limit?: number;
3802
- offset?: number;
3803
- filters?: Array<{ field: string; operator: string; value: JsonValue }>;
3804
- sort?: { field: string; direction: 'ASC' | 'DESC' };
3805
- locale?: string;
3806
- preview?: boolean;
3807
- fields?: FieldSelection<${dataType}>;
3808
- }`);
3809
1878
  lines.push("");
3810
- lines.push(`export interface List${pluralName}Result {
3811
- records: PaginatedResult<${dataType}>;
3812
- }`);
3813
- lines.push("");
3814
- lines.push(`export const CREATE_${upperSnake} = \`
3815
- mutation Create${typeName}($input: CreateRecordInput!) {
3816
- createRecord(input: $input) {
3817
- record {
3818
- id modelKey naturalKey data metadata createdAt updatedAt
3819
- }
3820
- }
1879
+ lines.push("export interface TypedClient extends FoirClient {");
1880
+ for (const model of publicModels) {
1881
+ const typeName = toPascalCase(model.key);
1882
+ const accessorName = toCamelCase(model.key);
1883
+ const dataType = `${typeName}Data`;
1884
+ const whereType = `${typeName}Where`;
1885
+ const sortType = `${typeName}SortField`;
1886
+ lines.push(` ${accessorName}: ModelAccessor<${dataType}, ${whereType}, ${sortType}>;`);
3821
1887
  }
3822
- \`;`);
3823
- lines.push("");
3824
- lines.push(`export interface Create${typeName}Variables {
3825
- input: {
3826
- modelKey: string;
3827
- naturalKey?: string;
3828
- data: Partial<${dataType}>;
3829
- metadata?: Record<string, JsonValue>;
3830
- };
3831
- }`);
3832
- lines.push("");
3833
- lines.push(`export interface Create${typeName}Result {
3834
- createRecord: CreateRecordResult<${dataType}>;
3835
- }`);
3836
- lines.push("");
3837
- lines.push(`export const UPDATE_${upperSnake} = \`
3838
- mutation Update${typeName}($input: UpdateRecordInput!) {
3839
- updateRecord(input: $input) {
3840
- record {
3841
- id modelKey naturalKey data metadata createdAt updatedAt
3842
- }
3843
- matched
3844
- }
1888
+ if (hasCustomerProfile) {
1889
+ lines.push(
1890
+ ` profile: ReturnType<typeof createProfileClient<CustomerProfileData>>;`
1891
+ );
3845
1892
  }
3846
- \`;`);
3847
- lines.push("");
3848
- lines.push(`export interface Update${typeName}Variables {
3849
- input: {
3850
- id: string;
3851
- data?: Partial<${dataType}>;
3852
- metadata?: Record<string, JsonValue>;
3853
- changeDescription?: string;
3854
- };
3855
- }`);
1893
+ lines.push("}");
3856
1894
  lines.push("");
3857
- lines.push(`export interface Update${typeName}Result {
3858
- updateRecord: UpdateRecordResult<${dataType}>;
3859
- }`);
1895
+ lines.push("export function createClient(config: ClientConfig): TypedClient {");
1896
+ lines.push(" const base = createBaseClient(config);");
3860
1897
  lines.push("");
3861
- lines.push(`export const DELETE_${upperSnake} = \`
3862
- mutation Delete${typeName}($id: ID!) {
3863
- deleteRecord(id: $id) { id }
1898
+ lines.push(" return {");
1899
+ lines.push(" auth: createAuthClient(base),");
1900
+ lines.push(" files: createFilesClient(base),");
1901
+ lines.push(" notifications: createNotificationsClient(base),");
1902
+ lines.push(" sharing: createSharingClient(base),");
1903
+ lines.push(" search: createSearchClient(base),");
1904
+ if (hasCustomerProfile) {
1905
+ lines.push(" profile: createProfileClient<CustomerProfileData>(base),");
1906
+ } else {
1907
+ lines.push(" profile: createProfileClient(base),");
3864
1908
  }
3865
- \`;`);
3866
- lines.push("");
3867
- lines.push(`export interface Delete${typeName}Variables {
3868
- id: string;
3869
- }`);
3870
- lines.push("");
3871
- lines.push(`export interface Delete${typeName}Result {
3872
- deleteRecord: DeleteRecordResult;
3873
- }`);
3874
- lines.push("");
3875
- lines.push(`export const PUBLISH_${upperSnake}_VERSION = \`
3876
- mutation Publish${typeName}Version($versionId: ID!) {
3877
- publishVersion(versionId: $versionId)
1909
+ lines.push(" operations: createOperationsClient(base),");
1910
+ for (const model of publicModels) {
1911
+ const typeName = toPascalCase(model.key);
1912
+ const accessorName = toCamelCase(model.key);
1913
+ const dataType = `${typeName}Data`;
1914
+ const whereType = `${typeName}Where`;
1915
+ const sortType = `${typeName}SortField`;
1916
+ lines.push(
1917
+ ` ${accessorName}: createModelAccessor<${dataType}, ${whereType}, ${sortType}>(base, '${model.key}'),`
1918
+ );
3878
1919
  }
3879
- \`;`);
1920
+ lines.push(` model<TData = unknown>(modelKey: string) {`);
1921
+ lines.push(` return createModelAccessor<TData>(base, modelKey);`);
1922
+ lines.push(` },`);
1923
+ lines.push(" request: base.request,");
1924
+ lines.push(" setCustomerToken: base.setCustomerToken,");
1925
+ lines.push(" setLocale: base.setLocale,");
1926
+ lines.push(" setPreview: base.setPreview,");
1927
+ lines.push(" } as TypedClient;");
1928
+ lines.push("}");
3880
1929
  lines.push("");
3881
- lines.push(`export const UNPUBLISH_${upperSnake} = \`
3882
- mutation Unpublish${typeName}($id: ID!) {
3883
- unpublishRecord(id: $id)
3884
- }
3885
- \`;`);
1930
+ lines.push("// Re-export types from @eide/foir-client for convenience");
1931
+ lines.push(`export type {
1932
+ ClientConfig,
1933
+ TypedRecord,
1934
+ TypedList,
1935
+ ModelAccessor,
1936
+ JsonValue,
1937
+ ImageValue,
1938
+ VideoValue,
1939
+ FileValue,
1940
+ LinkValue,
1941
+ ReferenceValue,
1942
+ FlexibleFieldItem,
1943
+ RichtextValue,
1944
+ CurrencyValue,
1945
+ SelectMap,
1946
+ ApplySelect,
1947
+ FoirClient,
1948
+ } from '@eide/foir-client';`);
3886
1949
  lines.push("");
3887
- if (model.config.sharing?.enabled) {
3888
- lines.push(`// --- Sharing operations ---`);
3889
- lines.push("");
3890
- lines.push(`export const SHARE_${upperSnake} = \`
3891
- mutation Share${typeName}($recordId: ID!, $sharedWithCustomerId: ID!, $permission: SharePermission!) {
3892
- shareRecord(recordId: $recordId, sharedWithCustomerId: $sharedWithCustomerId, permission: $permission) {
3893
- id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
3894
- }
3895
- }
3896
- \`;`);
3897
- lines.push("");
3898
- lines.push(`export interface Share${typeName}Variables {
3899
- recordId: string;
3900
- sharedWithCustomerId: string;
3901
- permission: 'VIEW' | 'EDIT' | 'ADMIN';
3902
- }`);
3903
- lines.push("");
3904
- lines.push(`export interface Share${typeName}Result {
3905
- shareRecord: ShareResult;
3906
- }`);
3907
- lines.push("");
3908
- lines.push(`export const ${upperSnake}_SHARES = \`
3909
- query ${typeName}Shares($resourceId: ID!, $status: ShareStatus) {
3910
- shares(resourceType: RECORD, resourceId: $resourceId, status: $status) {
3911
- id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
3912
- }
3913
- }
3914
- \`;`);
3915
- lines.push("");
3916
- lines.push(`export const ${pluralUpperSnake}_SHARED_WITH_ME = \`
3917
- query ${pluralName}SharedWithMe($status: ShareStatus) {
3918
- sharedWithMe(resourceType: RECORD, modelKey: "${model.key}", status: $status) {
3919
- id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
3920
- record {
3921
- id modelKey naturalKey data metadata
3922
- publishedVersionNumber publishedAt versionNumber changeDescription
3923
- createdAt updatedAt
3924
- }
3925
- }
3926
- }
3927
- \`;`);
3928
- lines.push("");
3929
- lines.push(`export interface ${pluralName}SharedWithMeResult {
3930
- sharedWithMe: ShareWithRecord<${dataType}>[];
3931
- }`);
3932
- lines.push("");
3933
- }
3934
- return lines.join("\n");
3935
- }
3936
- function computeTypesRelPath(opsDir, typesDir) {
3937
- const rel = path.relative(opsDir, typesDir).replace(/\\/g, "/");
3938
- return rel.startsWith(".") ? rel : `./${rel}`;
3939
- }
3940
-
3941
- // src/codegen/generators/typed-operations-index.ts
3942
- function generateTypedOperationsIndex(models, hasCustomerProfile) {
3943
- const lines = [];
3944
- lines.push(`/**
3945
- * Typed GraphQL operations.
3946
- *
3947
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
3948
- */
3949
-
3950
- export * from './_common.js';
3951
- `);
3952
- for (const model of models) {
3953
- lines.push(`export * from './${model.key}.js';`);
1950
+ for (const model of publicModels) {
1951
+ const typeName = toPascalCase(model.key);
1952
+ const dataType = `${typeName}Data`;
1953
+ const whereType = `${typeName}Where`;
1954
+ const sortType = `${typeName}SortField`;
1955
+ lines.push(
1956
+ `export type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
1957
+ );
3954
1958
  }
3955
1959
  if (hasCustomerProfile) {
3956
- lines.push(`export * from './customer-profile.js';`);
3957
- }
3958
- lines.push("");
3959
- return lines.join("\n");
3960
- }
3961
-
3962
- // src/codegen/generators/customer-profile-operations.ts
3963
- function generateCustomerProfileOperations(typesRelPath) {
3964
- return `/**
3965
- * Typed operations for Customer Profiles
3966
- *
3967
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
3968
- */
3969
-
3970
- import type { CustomerProfileData } from '${typesRelPath}/customer-profile.js';
3971
-
3972
- export const GET_MY_PROFILE = \`
3973
- query GetMyProfile {
3974
- myProfile {
3975
- id
3976
- customerId
3977
- data
3978
- createdAt
3979
- updatedAt
3980
- }
3981
- }
3982
- \`;
3983
-
3984
- export interface GetMyProfileResult {
3985
- myProfile: {
3986
- id: string;
3987
- customerId: string;
3988
- data: CustomerProfileData;
3989
- createdAt: string;
3990
- updatedAt: string;
3991
- } | null;
3992
- }
3993
-
3994
- export const GET_CUSTOMER_PROFILE = \`
3995
- query GetCustomerProfile($customerId: ID!) {
3996
- customerProfile(customerId: $customerId) {
3997
- id
3998
- customerId
3999
- data
4000
- createdAt
4001
- updatedAt
4002
- }
4003
- }
4004
- \`;
4005
-
4006
- export interface GetCustomerProfileVariables {
4007
- customerId: string;
4008
- }
4009
-
4010
- export interface GetCustomerProfileResult {
4011
- customerProfile: {
4012
- id: string;
4013
- customerId: string;
4014
- data: CustomerProfileData;
4015
- createdAt: string;
4016
- updatedAt: string;
4017
- } | null;
4018
- }
4019
-
4020
- export const SET_MY_PROFILE = \`
4021
- mutation SetMyProfile($data: JSON!) {
4022
- setMyProfile(data: $data) {
4023
- id
4024
- customerId
4025
- data
4026
- createdAt
4027
- updatedAt
4028
- }
4029
- }
4030
- \`;
4031
-
4032
- export interface SetMyProfileVariables {
4033
- data: Partial<CustomerProfileData>;
4034
- }
4035
-
4036
- export interface SetMyProfileResult {
4037
- setMyProfile: {
4038
- id: string;
4039
- customerId: string;
4040
- data: CustomerProfileData;
4041
- createdAt: string;
4042
- updatedAt: string;
4043
- };
4044
- }
4045
-
4046
- export const UPDATE_CUSTOMER_PROFILE = \`
4047
- mutation UpdateCustomerProfile($customerId: ID, $input: CustomerProfileInput!) {
4048
- updateCustomerProfile(customerId: $customerId, input: $input) {
4049
- id
4050
- customerId
4051
- data
4052
- createdAt
4053
- updatedAt
4054
- }
1960
+ lines.push(
1961
+ `export type { CustomerProfileData } from './models/customer-profile.js';`
1962
+ );
4055
1963
  }
4056
- \`;
4057
-
4058
- export interface UpdateCustomerProfileVariables {
4059
- customerId?: string;
4060
- input: {
4061
- data: Partial<CustomerProfileData>;
4062
- };
1964
+ return lines.join("\n") + "\n";
4063
1965
  }
4064
1966
 
4065
- export interface UpdateCustomerProfileResult {
4066
- updateCustomerProfile: {
4067
- id: string;
4068
- customerId: string;
4069
- data: CustomerProfileData;
4070
- createdAt: string;
4071
- updatedAt: string;
1967
+ // src/codegen/generators/schema-manifest.ts
1968
+ function generateSchemaManifest(models, cpSchema) {
1969
+ const manifest = {
1970
+ $schema: "https://foir.io/schema/v1.json",
1971
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1972
+ models: {}
4072
1973
  };
4073
- }
4074
-
4075
- export const DELETE_MY_PROFILE = \`
4076
- mutation DeleteMyProfile {
4077
- deleteMyProfile
4078
- }
4079
- \`;
4080
-
4081
- export interface DeleteMyProfileResult {
4082
- deleteMyProfile: boolean;
4083
- }
4084
- `;
4085
- }
4086
-
4087
- // src/codegen/generators/react-hooks.ts
4088
- function generateReactHooks(model) {
4089
- const typeName = toPascalCase(model.key);
4090
- const upperSnake = toUpperSnakeCase(model.key);
4091
- const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
4092
- const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
4093
- const lines = [];
4094
- lines.push(`/**
4095
- * React Apollo hooks for ${model.name ?? model.key}
4096
- *
4097
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4098
- */
4099
-
4100
- import { useQuery, useMutation } from '@apollo/client';
4101
- import type { QueryHookOptions, MutationHookOptions } from '@apollo/client';
4102
- import { gql } from '@apollo/client';
4103
- import {
4104
- GET_${upperSnake},
4105
- GET_${upperSnake}_BY_KEY,
4106
- LIST_${pluralUpperSnake},
4107
- CREATE_${upperSnake},
4108
- UPDATE_${upperSnake},
4109
- DELETE_${upperSnake},
4110
- PUBLISH_${upperSnake}_VERSION,
4111
- UNPUBLISH_${upperSnake},
4112
- } from '../operations/${model.key}.js';
4113
- import type {
4114
- ${typeName}Record,
4115
- Get${typeName}Variables,
4116
- Get${typeName}Result,
4117
- Get${typeName}ByKeyVariables,
4118
- Get${typeName}ByKeyResult,
4119
- List${pluralName}Variables,
4120
- List${pluralName}Result,
4121
- Create${typeName}Variables,
4122
- Create${typeName}Result,
4123
- Update${typeName}Variables,
4124
- Update${typeName}Result,
4125
- Delete${typeName}Variables,
4126
- Delete${typeName}Result,
4127
- } from '../operations/${model.key}.js';
4128
- `);
4129
- lines.push(`export type { ${typeName}Record };`);
4130
- lines.push("");
4131
- lines.push(`export function useGet${typeName}(
4132
- id: string | null | undefined,
4133
- options?: Omit<QueryHookOptions<Get${typeName}Result, Get${typeName}Variables>, 'variables' | 'skip'>
4134
- ) {
4135
- return useQuery<Get${typeName}Result, Get${typeName}Variables>(
4136
- gql\`\${GET_${upperSnake}}\`,
4137
- { ...options, variables: { id: id! }, skip: !id }
4138
- );
4139
- }`);
4140
- lines.push("");
4141
- lines.push(`export function useGet${typeName}ByKey(
4142
- naturalKey: string | null | undefined,
4143
- options?: Omit<QueryHookOptions<Get${typeName}ByKeyResult, Get${typeName}ByKeyVariables>, 'variables' | 'skip'>
4144
- ) {
4145
- return useQuery<Get${typeName}ByKeyResult, Get${typeName}ByKeyVariables>(
4146
- gql\`\${GET_${upperSnake}_BY_KEY}\`,
4147
- { ...options, variables: { naturalKey: naturalKey! }, skip: !naturalKey }
4148
- );
4149
- }`);
4150
- lines.push("");
4151
- lines.push(`export function useList${pluralName}(
4152
- variables?: List${pluralName}Variables,
4153
- options?: Omit<QueryHookOptions<List${pluralName}Result, List${pluralName}Variables>, 'variables'>
4154
- ) {
4155
- return useQuery<List${pluralName}Result, List${pluralName}Variables>(
4156
- gql\`\${LIST_${pluralUpperSnake}}\`,
4157
- { ...options, variables }
4158
- );
4159
- }`);
4160
- lines.push("");
4161
- lines.push(`export function useCreate${typeName}(
4162
- options?: MutationHookOptions<Create${typeName}Result, Create${typeName}Variables>
4163
- ) {
4164
- return useMutation<Create${typeName}Result, Create${typeName}Variables>(
4165
- gql\`\${CREATE_${upperSnake}}\`,
4166
- options
4167
- );
4168
- }`);
4169
- lines.push("");
4170
- lines.push(`export function useUpdate${typeName}(
4171
- options?: MutationHookOptions<Update${typeName}Result, Update${typeName}Variables>
4172
- ) {
4173
- return useMutation<Update${typeName}Result, Update${typeName}Variables>(
4174
- gql\`\${UPDATE_${upperSnake}}\`,
4175
- options
4176
- );
4177
- }`);
4178
- lines.push("");
4179
- lines.push(`export function useDelete${typeName}(
4180
- options?: MutationHookOptions<Delete${typeName}Result, Delete${typeName}Variables>
4181
- ) {
4182
- return useMutation<Delete${typeName}Result, Delete${typeName}Variables>(
4183
- gql\`\${DELETE_${upperSnake}}\`,
4184
- options
4185
- );
4186
- }`);
4187
- lines.push("");
4188
- lines.push(`export function usePublish${typeName}Version(
4189
- options?: MutationHookOptions<{ publishVersion: boolean }, { versionId: string }>
4190
- ) {
4191
- return useMutation<{ publishVersion: boolean }, { versionId: string }>(
4192
- gql\`\${PUBLISH_${upperSnake}_VERSION}\`,
4193
- options
4194
- );
4195
- }`);
4196
- lines.push("");
4197
- lines.push(`export function useUnpublish${typeName}(
4198
- options?: MutationHookOptions<{ unpublishRecord: boolean }, { id: string }>
4199
- ) {
4200
- return useMutation<{ unpublishRecord: boolean }, { id: string }>(
4201
- gql\`\${UNPUBLISH_${upperSnake}}\`,
4202
- options
4203
- );
4204
- }`);
4205
- lines.push("");
4206
- return lines.join("\n");
4207
- }
4208
-
4209
- // src/codegen/generators/react-hooks-index.ts
4210
- function generateReactHooksIndex(models, hasCustomerProfile) {
4211
- const lines = [];
4212
- lines.push(`/**
4213
- * React Apollo hooks for all models.
4214
- *
4215
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4216
- */
4217
- `);
4218
1974
  for (const model of models) {
4219
- lines.push(`export * from './${model.key}.js';`);
1975
+ manifest.models[model.key] = {
1976
+ name: model.name,
1977
+ pluralName: model.pluralName,
1978
+ description: model.description,
1979
+ category: model.category,
1980
+ config: model.config,
1981
+ fields: model.fields.map((f) => ({
1982
+ key: f.key,
1983
+ type: f.type,
1984
+ label: f.label,
1985
+ required: f.required,
1986
+ helpText: f.helpText,
1987
+ options: f.options
1988
+ }))
1989
+ };
4220
1990
  }
4221
- if (hasCustomerProfile) {
4222
- lines.push(`export * from './customer-profile.js';`);
1991
+ if (cpSchema && cpSchema.fields.length > 0) {
1992
+ manifest.customerProfile = {
1993
+ fields: cpSchema.fields.map((f) => ({
1994
+ key: f.key,
1995
+ type: f.type,
1996
+ label: f.label,
1997
+ required: f.required,
1998
+ helpText: f.helpText,
1999
+ options: f.options
2000
+ }))
2001
+ };
4223
2002
  }
4224
- lines.push("");
4225
- return lines.join("\n");
4226
- }
4227
-
4228
- // src/codegen/generators/customer-profile-hooks.ts
4229
- function generateCustomerProfileHooks() {
4230
- return `/**
4231
- * React Apollo hooks for Customer Profiles
4232
- *
4233
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4234
- */
4235
-
4236
- import { useQuery, useMutation } from '@apollo/client';
4237
- import type { QueryHookOptions, MutationHookOptions } from '@apollo/client';
4238
- import { gql } from '@apollo/client';
4239
- import {
4240
- GET_MY_PROFILE,
4241
- GET_CUSTOMER_PROFILE,
4242
- SET_MY_PROFILE,
4243
- UPDATE_CUSTOMER_PROFILE,
4244
- DELETE_MY_PROFILE,
4245
- } from '../operations/customer-profile.js';
4246
- import type {
4247
- GetMyProfileResult,
4248
- GetCustomerProfileVariables,
4249
- GetCustomerProfileResult,
4250
- SetMyProfileVariables,
4251
- SetMyProfileResult,
4252
- UpdateCustomerProfileVariables,
4253
- UpdateCustomerProfileResult,
4254
- DeleteMyProfileResult,
4255
- } from '../operations/customer-profile.js';
4256
-
4257
- export function useMyProfile(
4258
- options?: QueryHookOptions<GetMyProfileResult>
4259
- ) {
4260
- return useQuery<GetMyProfileResult>(
4261
- gql\`\${GET_MY_PROFILE}\`,
4262
- options
4263
- );
4264
- }
4265
-
4266
- export function useCustomerProfile(
4267
- customerId: string | null | undefined,
4268
- options?: Omit<QueryHookOptions<GetCustomerProfileResult, GetCustomerProfileVariables>, 'variables' | 'skip'>
4269
- ) {
4270
- return useQuery<GetCustomerProfileResult, GetCustomerProfileVariables>(
4271
- gql\`\${GET_CUSTOMER_PROFILE}\`,
4272
- { ...options, variables: { customerId: customerId! }, skip: !customerId }
4273
- );
4274
- }
4275
-
4276
- export function useSetMyProfile(
4277
- options?: MutationHookOptions<SetMyProfileResult, SetMyProfileVariables>
4278
- ) {
4279
- return useMutation<SetMyProfileResult, SetMyProfileVariables>(
4280
- gql\`\${SET_MY_PROFILE}\`,
4281
- options
4282
- );
4283
- }
4284
-
4285
- export function useUpdateCustomerProfile(
4286
- options?: MutationHookOptions<UpdateCustomerProfileResult, UpdateCustomerProfileVariables>
4287
- ) {
4288
- return useMutation<UpdateCustomerProfileResult, UpdateCustomerProfileVariables>(
4289
- gql\`\${UPDATE_CUSTOMER_PROFILE}\`,
4290
- options
4291
- );
4292
- }
4293
-
4294
- export function useDeleteMyProfile(
4295
- options?: MutationHookOptions<DeleteMyProfileResult>
4296
- ) {
4297
- return useMutation<DeleteMyProfileResult>(
4298
- gql\`\${DELETE_MY_PROFILE}\`,
4299
- options
4300
- );
4301
- }
4302
- `;
4303
- }
4304
-
4305
- // src/codegen/generators/remix-loaders.ts
4306
- function generateRemixLoaders(model) {
4307
- const typeName = toPascalCase(model.key);
4308
- const upperSnake = toUpperSnakeCase(model.key);
4309
- const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
4310
- const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
4311
- return `/**
4312
- * Remix / server-side loader functions for ${model.name ?? model.key}
4313
- *
4314
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4315
- */
4316
-
4317
- import {
4318
- GET_${upperSnake},
4319
- GET_${upperSnake}_BY_KEY,
4320
- LIST_${pluralUpperSnake},
4321
- CREATE_${upperSnake},
4322
- UPDATE_${upperSnake},
4323
- DELETE_${upperSnake},
4324
- PUBLISH_${upperSnake}_VERSION,
4325
- UNPUBLISH_${upperSnake},
4326
- } from '../operations/${model.key}.js';
4327
- import type {
4328
- Get${typeName}Variables,
4329
- Get${typeName}Result,
4330
- Get${typeName}ByKeyVariables,
4331
- Get${typeName}ByKeyResult,
4332
- List${pluralName}Variables,
4333
- List${pluralName}Result,
4334
- Create${typeName}Variables,
4335
- Create${typeName}Result,
4336
- Update${typeName}Variables,
4337
- Update${typeName}Result,
4338
- Delete${typeName}Variables,
4339
- Delete${typeName}Result,
4340
- } from '../operations/${model.key}.js';
4341
-
4342
- /** A minimal GraphQL client interface (works with graphql-request, urql, or custom). */
4343
- export interface GraphQLClient {
4344
- request<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
2003
+ return JSON.stringify(manifest, null, 2) + "\n";
4345
2004
  }
4346
2005
 
4347
- export async function get${typeName}(
4348
- client: GraphQLClient,
4349
- variables: Get${typeName}Variables
4350
- ): Promise<Get${typeName}Result> {
4351
- return client.request<Get${typeName}Result>(GET_${upperSnake}, variables as Record<string, unknown>);
2006
+ // src/codegen/generators/model-filters.ts
2007
+ function isInlineOnlyModel3(model) {
2008
+ return model.config.inline && !model.config.records;
4352
2009
  }
4353
-
4354
- export async function get${typeName}ByKey(
4355
- client: GraphQLClient,
4356
- variables: Get${typeName}ByKeyVariables
4357
- ): Promise<Get${typeName}ByKeyResult> {
4358
- return client.request<Get${typeName}ByKeyResult>(GET_${upperSnake}_BY_KEY, variables as Record<string, unknown>);
2010
+ function getFilterType(fieldType) {
2011
+ switch (fieldType) {
2012
+ case "text":
2013
+ case "richtext":
2014
+ case "email":
2015
+ case "phone":
2016
+ case "url":
2017
+ case "select":
2018
+ return "StringFilter";
2019
+ case "number":
2020
+ case "currency":
2021
+ return "NumberFilter";
2022
+ case "boolean":
2023
+ return "BooleanFilter";
2024
+ case "date":
2025
+ return "DateFilter";
2026
+ default:
2027
+ return null;
2028
+ }
4359
2029
  }
4360
-
4361
- export async function list${pluralName}(
4362
- client: GraphQLClient,
4363
- variables?: List${pluralName}Variables
4364
- ): Promise<List${pluralName}Result> {
4365
- return client.request<List${pluralName}Result>(LIST_${pluralUpperSnake}, variables as Record<string, unknown>);
2030
+ function isSortable(fieldType) {
2031
+ return ["text", "number", "date", "boolean", "email", "url", "select"].includes(fieldType);
4366
2032
  }
4367
-
4368
- export async function create${typeName}(
4369
- client: GraphQLClient,
4370
- variables: Create${typeName}Variables
4371
- ): Promise<Create${typeName}Result> {
4372
- return client.request<Create${typeName}Result>(CREATE_${upperSnake}, variables as Record<string, unknown>);
2033
+ function generateModelWhere(model) {
2034
+ if (isInlineOnlyModel3(model)) return "";
2035
+ const typeName = toPascalCase(model.key);
2036
+ const whereName = `${typeName}Where`;
2037
+ const fields = model.fields ?? [];
2038
+ const filterImports = /* @__PURE__ */ new Set();
2039
+ const filterFields = [];
2040
+ for (const field of fields) {
2041
+ const filterType = getFilterType(field.type);
2042
+ if (filterType) {
2043
+ filterImports.add(filterType);
2044
+ const fieldName = sanitizeFieldName(field.key);
2045
+ filterFields.push(` ${fieldName}?: ${filterType};`);
2046
+ }
2047
+ }
2048
+ filterImports.add("DateFilter");
2049
+ filterFields.push(" createdAt?: DateFilter;");
2050
+ filterFields.push(" updatedAt?: DateFilter;");
2051
+ const imports = Array.from(filterImports).sort().join(", ");
2052
+ const lines = [];
2053
+ lines.push(`import type { ${imports} } from '@eide/foir-client';`);
2054
+ lines.push("");
2055
+ lines.push(`export interface ${whereName} {`);
2056
+ lines.push(...filterFields);
2057
+ lines.push(` AND?: ${whereName}[];`);
2058
+ lines.push(` OR?: ${whereName}[];`);
2059
+ lines.push(` NOT?: ${whereName};`);
2060
+ lines.push("}");
2061
+ return lines.join("\n") + "\n";
4373
2062
  }
4374
-
4375
- export async function update${typeName}(
4376
- client: GraphQLClient,
4377
- variables: Update${typeName}Variables
4378
- ): Promise<Update${typeName}Result> {
4379
- return client.request<Update${typeName}Result>(UPDATE_${upperSnake}, variables as Record<string, unknown>);
2063
+ function generateModelSortField(model) {
2064
+ if (isInlineOnlyModel3(model)) return "";
2065
+ const typeName = toPascalCase(model.key);
2066
+ const sortName = `${typeName}SortField`;
2067
+ const fields = model.fields ?? [];
2068
+ const sortableFields = [];
2069
+ for (const field of fields) {
2070
+ if (isSortable(field.type)) {
2071
+ sortableFields.push(`'${field.key}'`);
2072
+ }
2073
+ }
2074
+ sortableFields.push(`'createdAt'`);
2075
+ sortableFields.push(`'updatedAt'`);
2076
+ const unique = [...new Set(sortableFields)];
2077
+ return `export type ${sortName} = ${unique.join(" | ")};
2078
+ `;
4380
2079
  }
4381
2080
 
4382
- export async function delete${typeName}(
4383
- client: GraphQLClient,
4384
- variables: Delete${typeName}Variables
4385
- ): Promise<Delete${typeName}Result> {
4386
- return client.request<Delete${typeName}Result>(DELETE_${upperSnake}, variables as Record<string, unknown>);
2081
+ // src/codegen/generators/model-zod.ts
2082
+ function isInlineOnlyModel4(model) {
2083
+ return model.config.inline && !model.config.records;
4387
2084
  }
4388
-
4389
- export async function publish${typeName}Version(
4390
- client: GraphQLClient,
4391
- versionId: string
4392
- ): Promise<{ publishVersion: boolean }> {
4393
- return client.request<{ publishVersion: boolean }>(PUBLISH_${upperSnake}_VERSION, { versionId });
2085
+ function getZodExpression(field, allModels) {
2086
+ const imports = /* @__PURE__ */ new Set();
2087
+ let expr;
2088
+ switch (field.type) {
2089
+ case "text":
2090
+ case "email":
2091
+ case "phone":
2092
+ case "url": {
2093
+ imports.add("textField");
2094
+ const opts = [];
2095
+ if (field.options?.maxLength) opts.push(`maxLength: ${field.options.maxLength}`);
2096
+ if (field.options?.minLength) opts.push(`minLength: ${field.options.minLength}`);
2097
+ if (field.options?.pattern) opts.push(`pattern: '${field.options.pattern}'`);
2098
+ expr = opts.length > 0 ? `textField({ ${opts.join(", ")} })` : `textField()`;
2099
+ break;
2100
+ }
2101
+ case "number": {
2102
+ imports.add("numberField");
2103
+ const opts = [];
2104
+ if (field.options?.min !== void 0) opts.push(`min: ${field.options.min}`);
2105
+ if (field.options?.max !== void 0) opts.push(`max: ${field.options.max}`);
2106
+ expr = opts.length > 0 ? `numberField({ ${opts.join(", ")} })` : `numberField()`;
2107
+ break;
2108
+ }
2109
+ case "boolean":
2110
+ imports.add("booleanField");
2111
+ expr = "booleanField()";
2112
+ break;
2113
+ case "date":
2114
+ imports.add("dateField");
2115
+ expr = "dateField()";
2116
+ break;
2117
+ case "richtext":
2118
+ imports.add("richtextValueSchema");
2119
+ expr = "richtextValueSchema";
2120
+ break;
2121
+ case "image":
2122
+ imports.add("imageValueSchema");
2123
+ expr = "imageValueSchema";
2124
+ break;
2125
+ case "video":
2126
+ imports.add("videoValueSchema");
2127
+ expr = "videoValueSchema";
2128
+ break;
2129
+ case "file":
2130
+ imports.add("fileValueSchema");
2131
+ expr = "fileValueSchema";
2132
+ break;
2133
+ case "currency":
2134
+ imports.add("currencyValueSchema");
2135
+ expr = "currencyValueSchema";
2136
+ break;
2137
+ case "link":
2138
+ imports.add("linkValueSchema");
2139
+ expr = "linkValueSchema";
2140
+ break;
2141
+ case "reference":
2142
+ imports.add("referenceValueSchema");
2143
+ expr = "referenceValueSchema";
2144
+ break;
2145
+ case "select": {
2146
+ if (field.options?.options) {
2147
+ imports.add("selectField");
2148
+ const options = field.options.options;
2149
+ const values = options.map((o) => `'${o.value}'`).join(", ");
2150
+ expr = `selectField([${values}])`;
2151
+ } else {
2152
+ expr = "z.string()";
2153
+ }
2154
+ break;
2155
+ }
2156
+ case "multiselect": {
2157
+ if (field.options?.options) {
2158
+ imports.add("multiselectField");
2159
+ const options = field.options.options;
2160
+ const values = options.map((o) => `'${o.value}'`).join(", ");
2161
+ expr = `multiselectField([${values}])`;
2162
+ } else {
2163
+ expr = "z.array(z.string())";
2164
+ }
2165
+ break;
2166
+ }
2167
+ case "flexible":
2168
+ imports.add("flexibleFieldItemSchema");
2169
+ expr = "z.array(flexibleFieldItemSchema)";
2170
+ break;
2171
+ case "json":
2172
+ imports.add("jsonValueSchema");
2173
+ expr = "jsonValueSchema";
2174
+ break;
2175
+ case "list": {
2176
+ if (field.options?.itemType) {
2177
+ const itemType = field.options.itemType;
2178
+ const refModel = allModels.find((m) => m.key === itemType);
2179
+ if (refModel) {
2180
+ const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2181
+ expr = `z.array(${refSchemaName})`;
2182
+ } else {
2183
+ imports.add("jsonValueSchema");
2184
+ expr = "z.array(jsonValueSchema)";
2185
+ }
2186
+ } else {
2187
+ imports.add("jsonValueSchema");
2188
+ expr = "z.array(jsonValueSchema)";
2189
+ }
2190
+ break;
2191
+ }
2192
+ default: {
2193
+ const refModel = allModels.find((m) => m.key === field.type);
2194
+ if (refModel) {
2195
+ const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2196
+ expr = refSchemaName;
2197
+ } else {
2198
+ imports.add("jsonValueSchema");
2199
+ expr = "jsonValueSchema";
2200
+ }
2201
+ }
2202
+ }
2203
+ if (!field.required) {
2204
+ expr = `${expr}.optional()`;
2205
+ }
2206
+ return { expression: expr, imports };
4394
2207
  }
2208
+ function generateModelZodSchema(model, allModels) {
2209
+ const typeName = toPascalCase(model.key);
2210
+ const schemaName = isInlineOnlyModel4(model) ? `${typeName}Schema` : `${typeName}DataSchema`;
2211
+ const fields = model.fields ?? [];
2212
+ if (fields.length === 0) {
2213
+ return `import { z } from '@eide/foir-client/validation';
4395
2214
 
4396
- export async function unpublish${typeName}(
4397
- client: GraphQLClient,
4398
- id: string
4399
- ): Promise<{ unpublishRecord: boolean }> {
4400
- return client.request<{ unpublishRecord: boolean }>(UNPUBLISH_${upperSnake}, { id });
4401
- }
2215
+ export const ${schemaName} = z.object({});
4402
2216
  `;
4403
- }
4404
-
4405
- // src/codegen/generators/remix-loaders-index.ts
4406
- function generateRemixLoadersIndex(models, hasCustomerProfile) {
2217
+ }
2218
+ const allImports = /* @__PURE__ */ new Set();
2219
+ const fieldLines = [];
2220
+ const inlineSchemaImports = [];
2221
+ for (const field of fields) {
2222
+ const { expression, imports } = getZodExpression(field, allModels);
2223
+ for (const imp of imports) allImports.add(imp);
2224
+ const fieldName = sanitizeFieldName(field.key);
2225
+ fieldLines.push(` ${fieldName}: ${expression},`);
2226
+ if (!isPrimitiveFieldType(field.type) && field.type !== "list") {
2227
+ const refModel = allModels.find((m) => m.key === field.type);
2228
+ if (refModel && refModel.key !== model.key) {
2229
+ const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2230
+ inlineSchemaImports.push(
2231
+ `import { ${refSchemaName} } from './${field.type}.zod.js';`
2232
+ );
2233
+ }
2234
+ }
2235
+ if (field.type === "list" && field.options?.itemType) {
2236
+ const itemType = field.options.itemType;
2237
+ const refModel = allModels.find((m) => m.key === itemType);
2238
+ if (refModel && refModel.key !== model.key) {
2239
+ const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
2240
+ inlineSchemaImports.push(
2241
+ `import { ${refSchemaName} } from './${itemType}.zod.js';`
2242
+ );
2243
+ }
2244
+ }
2245
+ }
4407
2246
  const lines = [];
4408
- lines.push(`/**
4409
- * Remix / server-side loader functions for all models.
4410
- *
4411
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4412
- */
4413
- `);
4414
- for (const model of models) {
4415
- lines.push(`export * from './${model.key}.js';`);
2247
+ lines.push(`/**`);
2248
+ lines.push(` * Zod validation schema for ${model.name}`);
2249
+ lines.push(` *`);
2250
+ lines.push(` * @generated by foir \u2014 DO NOT EDIT MANUALLY`);
2251
+ lines.push(` */`);
2252
+ lines.push("");
2253
+ lines.push(`import { z } from '@eide/foir-client/validation';`);
2254
+ if (allImports.size > 0) {
2255
+ const sorted = Array.from(allImports).sort();
2256
+ lines.push(`import { ${sorted.join(", ")} } from '@eide/foir-client/validation';`);
4416
2257
  }
4417
- if (hasCustomerProfile) {
4418
- lines.push(`export * from './customer-profile.js';`);
2258
+ const uniqueInlineImports = [...new Set(inlineSchemaImports)];
2259
+ for (const imp of uniqueInlineImports) {
2260
+ lines.push(imp);
4419
2261
  }
4420
2262
  lines.push("");
4421
- return lines.join("\n");
2263
+ lines.push(`export const ${schemaName} = z.object({`);
2264
+ lines.push(...fieldLines);
2265
+ lines.push("});");
2266
+ lines.push("");
2267
+ lines.push(`export type ${isInlineOnlyModel4(model) ? typeName : typeName + "Data"}Validated = z.infer<typeof ${schemaName}>;`);
2268
+ return lines.join("\n") + "\n";
4422
2269
  }
4423
2270
 
4424
- // src/codegen/generators/customer-profile-loaders.ts
4425
- function generateCustomerProfileLoaders() {
4426
- return `/**
4427
- * Remix / server-side loader functions for Customer Profiles
2271
+ // src/codegen/generators/customer-profile-types.ts
2272
+ function generateCustomerProfileTypes(schema) {
2273
+ const fields = schema.fields ?? [];
2274
+ const fieldTypeImports = getFieldTypeImportsForFields2(fields);
2275
+ let code = `/**
2276
+ * Customer Profile Types
4428
2277
  *
4429
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
2278
+ * @generated by foir from customer profile schema (version ${schema.version}) \u2014 DO NOT EDIT MANUALLY
4430
2279
  */
4431
2280
 
4432
- import {
4433
- GET_MY_PROFILE,
4434
- GET_CUSTOMER_PROFILE,
4435
- SET_MY_PROFILE,
4436
- UPDATE_CUSTOMER_PROFILE,
4437
- DELETE_MY_PROFILE,
4438
- } from '../operations/customer-profile.js';
4439
- import type {
4440
- GetMyProfileResult,
4441
- GetCustomerProfileVariables,
4442
- GetCustomerProfileResult,
4443
- SetMyProfileVariables,
4444
- SetMyProfileResult,
4445
- UpdateCustomerProfileVariables,
4446
- UpdateCustomerProfileResult,
4447
- DeleteMyProfileResult,
4448
- } from '../operations/customer-profile.js';
4449
-
4450
- /** A minimal GraphQL client interface (works with graphql-request, urql, or custom). */
4451
- export interface GraphQLClient {
4452
- request<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
4453
- }
4454
-
4455
- export async function getMyProfile(
4456
- client: GraphQLClient
4457
- ): Promise<GetMyProfileResult> {
4458
- return client.request<GetMyProfileResult>(GET_MY_PROFILE);
4459
- }
4460
-
4461
- export async function getCustomerProfile(
4462
- client: GraphQLClient,
4463
- variables: GetCustomerProfileVariables
4464
- ): Promise<GetCustomerProfileResult> {
4465
- return client.request<GetCustomerProfileResult>(GET_CUSTOMER_PROFILE, variables as Record<string, unknown>);
4466
- }
4467
-
4468
- export async function setMyProfile(
4469
- client: GraphQLClient,
4470
- variables: SetMyProfileVariables
4471
- ): Promise<SetMyProfileResult> {
4472
- return client.request<SetMyProfileResult>(SET_MY_PROFILE, variables as Record<string, unknown>);
4473
- }
4474
-
4475
- export async function updateCustomerProfile(
4476
- client: GraphQLClient,
4477
- variables: UpdateCustomerProfileVariables
4478
- ): Promise<UpdateCustomerProfileResult> {
4479
- return client.request<UpdateCustomerProfileResult>(UPDATE_CUSTOMER_PROFILE, variables as Record<string, unknown>);
4480
- }
2281
+ `;
2282
+ if (fieldTypeImports.size > 0) {
2283
+ const types = Array.from(fieldTypeImports).sort().join(", ");
2284
+ code += `import type { ${types} } from '../field-types.js';
4481
2285
 
4482
- export async function deleteMyProfile(
4483
- client: GraphQLClient
4484
- ): Promise<DeleteMyProfileResult> {
4485
- return client.request<DeleteMyProfileResult>(DELETE_MY_PROFILE);
4486
- }
4487
2286
  `;
2287
+ }
2288
+ code += `/**
2289
+ * Customer Profile Data
2290
+ * Typed field values for customer profiles
2291
+ *
2292
+ * @generated from customer profile schema (version ${schema.version})
2293
+ */
2294
+ `;
2295
+ code += `export interface CustomerProfileData {
2296
+ `;
2297
+ for (const field of fields) {
2298
+ const fieldName = sanitizeFieldName(field.key);
2299
+ const fieldType = getFieldType(field, "output");
2300
+ const optional = field.required ? "" : "?";
2301
+ if (field.helpText) {
2302
+ code += ` /** ${field.helpText} */
2303
+ `;
2304
+ }
2305
+ code += ` ${fieldName}${optional}: ${fieldType};
2306
+ `;
2307
+ }
2308
+ code += `}
2309
+ `;
2310
+ return code;
4488
2311
  }
4489
-
4490
- // src/codegen/generators/public-schema-content.ts
4491
- async function fetchPublicSchema(client) {
4492
- try {
4493
- const res = await client.request(
4494
- `query { _service { sdl } }`
4495
- );
4496
- return res._service?.sdl ?? null;
4497
- } catch {
4498
- return null;
2312
+ function getFieldTypeImportsForFields2(fields) {
2313
+ const imports = /* @__PURE__ */ new Set();
2314
+ for (const field of fields) {
2315
+ const mapping = FIELD_TYPE_MAPPING[field.type];
2316
+ if (mapping?.needsImport === "field-types") {
2317
+ imports.add(mapping.outputType);
2318
+ }
4499
2319
  }
2320
+ return imports;
4500
2321
  }
4501
2322
 
4502
2323
  // src/codegen/write-files.ts
@@ -4533,8 +2354,8 @@ async function writeFiles(files, usePrettier = true) {
4533
2354
  // src/commands/pull.ts
4534
2355
  function registerPullCommand(program2, globalOpts) {
4535
2356
  program2.command("pull").description(
4536
- "Generate TypeScript types, GraphQL documents, and Swift types from platform models"
4537
- ).option("--config <path>", "Path to config file").option("--only <models>", "Comma-separated model keys to generate").option("--no-prettier", "Skip Prettier formatting").option("--dry-run", "Show what would be generated without writing").option("--out <dir>", "Override output directory for types").option("--swift <dir>", "Generate Swift files to directory").action(
2357
+ "Generate typed client, model types, and validation schemas from platform models"
2358
+ ).option("--config <path>", "Path to config file").option("--only <models>", "Comma-separated model keys to generate").option("--no-prettier", "Skip Prettier formatting").option("--dry-run", "Show what would be generated without writing").option("--out <dir>", "Override output directory").action(
4538
2359
  withErrorHandler(
4539
2360
  globalOpts,
4540
2361
  async (cmdOpts) => {
@@ -4544,16 +2365,14 @@ function registerPullCommand(program2, globalOpts) {
4544
2365
  only: cmdOpts.only,
4545
2366
  noPrettier: cmdOpts.prettier === false,
4546
2367
  dryRun: !!cmdOpts.dryRun,
4547
- out: cmdOpts.out,
4548
- swift: cmdOpts.swift
2368
+ out: cmdOpts.out
4549
2369
  };
4550
2370
  const config2 = await loadPullConfig(flags);
4551
2371
  const client = await createClient(opts);
4552
2372
  console.log(chalk4.dim("Fetching models\u2026"));
4553
- const [allModels, cpSchema, publicSchema] = await Promise.all([
2373
+ const [allModels, cpSchema] = await Promise.all([
4554
2374
  fetchModelsForCodegen(client),
4555
- fetchCustomerProfileSchema(client),
4556
- fetchPublicSchema(client)
2375
+ fetchCustomerProfileSchema(client)
4557
2376
  ]);
4558
2377
  if (allModels.length === 0 && !cpSchema) {
4559
2378
  console.log(chalk4.yellow("No models found. Nothing to generate."));
@@ -4569,166 +2388,62 @@ function registerPullCommand(program2, globalOpts) {
4569
2388
  )
4570
2389
  );
4571
2390
  const cwd = process.cwd();
4572
- const typesDir = resolve(cwd, config2.output.types);
4573
- const docsDir = resolve(cwd, config2.output.documents);
4574
- const opsDir = resolve(cwd, config2.output.operations);
4575
- const hooksDir = config2.output.hooks ? resolve(cwd, config2.output.hooks) : null;
4576
- const loadersDir = config2.output.loaders ? resolve(cwd, config2.output.loaders) : null;
2391
+ const outDir = resolve(cwd, config2.output.types);
2392
+ const modelsDir = resolve(outDir, "models");
4577
2393
  const files = [];
4578
2394
  const hasCustomerProfile = !!(cpSchema && cpSchema.fields.length > 0);
4579
2395
  const publicModels = models.filter(
4580
2396
  (m) => m.config.publicApi && m.config.records
4581
2397
  );
4582
- files.push({
4583
- path: resolve(typesDir, "field-types.ts"),
4584
- content: generateFieldTypesFile()
4585
- });
4586
- files.push({
4587
- path: resolve(typesDir, "config.ts"),
4588
- content: generateConfigFile()
4589
- });
4590
2398
  for (const model of models) {
4591
2399
  files.push({
4592
- path: resolve(typesDir, "models", `${model.key}.ts`),
2400
+ path: resolve(modelsDir, `${model.key}.ts`),
4593
2401
  content: generateModelTypes(model, models)
4594
2402
  });
4595
- }
4596
- files.push({
4597
- path: resolve(typesDir, "models", "index.ts"),
4598
- content: generateModelIndex(models)
4599
- });
4600
- if (hasCustomerProfile) {
4601
2403
  files.push({
4602
- path: resolve(typesDir, "customer-profile.ts"),
4603
- content: generateCustomerProfileTypes(cpSchema)
2404
+ path: resolve(modelsDir, `${model.key}.zod.ts`),
2405
+ content: generateModelZodSchema(model, models)
4604
2406
  });
4605
2407
  }
4606
- files.push({
4607
- path: resolve(typesDir, "index.ts"),
4608
- content: generateMainIndex(hasCustomerProfile)
4609
- });
4610
2408
  for (const model of publicModels) {
4611
- files.push({
4612
- path: resolve(docsDir, `${model.key}.graphql`),
4613
- content: generateModelDocuments(model)
4614
- });
2409
+ const whereCode = generateModelWhere(model);
2410
+ const sortCode = generateModelSortField(model);
2411
+ if (whereCode || sortCode) {
2412
+ files.push({
2413
+ path: resolve(modelsDir, `${model.key}.filters.ts`),
2414
+ content: `/**
2415
+ * Filter & sort types for ${model.name}
2416
+ *
2417
+ * @generated by foir \u2014 DO NOT EDIT MANUALLY
2418
+ */
2419
+
2420
+ ${whereCode}
2421
+ ${sortCode}`
2422
+ });
2423
+ }
4615
2424
  }
4616
- const hasSharingModels = publicModels.some(
4617
- (m) => m.config.sharing?.enabled
4618
- );
4619
- if (hasSharingModels) {
2425
+ if (hasCustomerProfile) {
4620
2426
  files.push({
4621
- path: resolve(docsDir, "_shared.graphql"),
4622
- content: generateSharedFragments()
2427
+ path: resolve(modelsDir, "customer-profile.ts"),
2428
+ content: generateCustomerProfileTypes(cpSchema)
4623
2429
  });
4624
2430
  }
4625
2431
  files.push({
4626
- path: resolve(docsDir, "customer-profile.graphql"),
4627
- content: generateCustomerProfileDocuments()
2432
+ path: resolve(modelsDir, "index.ts"),
2433
+ content: generateModelIndex(models)
4628
2434
  });
4629
- const staticDocs = generateStaticDocuments(config2.domains);
4630
- for (const doc of staticDocs) {
4631
- files.push({
4632
- path: resolve(docsDir, doc.filename),
4633
- content: doc.content
4634
- });
4635
- }
4636
- if (publicSchema) {
4637
- files.push({
4638
- path: resolve(docsDir, "public-schema.graphql"),
4639
- content: publicSchema
4640
- });
4641
- }
4642
- const typesRelPath = computeTypesRelPath(opsDir, typesDir);
4643
2435
  files.push({
4644
- path: resolve(opsDir, "_common.ts"),
4645
- content: generateTypedOperationsCommon(typesRelPath)
2436
+ path: resolve(outDir, "client.ts"),
2437
+ content: generateClientFactory(publicModels, hasCustomerProfile)
4646
2438
  });
4647
- for (const model of publicModels) {
4648
- files.push({
4649
- path: resolve(opsDir, `${model.key}.ts`),
4650
- content: generateTypedOperations(model, typesRelPath)
4651
- });
4652
- }
4653
- if (hasCustomerProfile) {
4654
- files.push({
4655
- path: resolve(opsDir, "customer-profile.ts"),
4656
- content: generateCustomerProfileOperations(typesRelPath)
4657
- });
4658
- }
4659
2439
  files.push({
4660
- path: resolve(opsDir, "index.ts"),
4661
- content: generateTypedOperationsIndex(
4662
- publicModels,
4663
- hasCustomerProfile
4664
- )
2440
+ path: resolve(outDir, "schema.json"),
2441
+ content: generateSchemaManifest(models, cpSchema)
2442
+ });
2443
+ files.push({
2444
+ path: resolve(outDir, "index.ts"),
2445
+ content: generateRootIndex(hasCustomerProfile)
4665
2446
  });
4666
- if (hooksDir) {
4667
- for (const model of publicModels) {
4668
- files.push({
4669
- path: resolve(hooksDir, `${model.key}.ts`),
4670
- content: generateReactHooks(model)
4671
- });
4672
- }
4673
- if (hasCustomerProfile) {
4674
- files.push({
4675
- path: resolve(hooksDir, "customer-profile.ts"),
4676
- content: generateCustomerProfileHooks()
4677
- });
4678
- }
4679
- files.push({
4680
- path: resolve(hooksDir, "index.ts"),
4681
- content: generateReactHooksIndex(
4682
- publicModels,
4683
- hasCustomerProfile
4684
- )
4685
- });
4686
- }
4687
- if (loadersDir) {
4688
- for (const model of publicModels) {
4689
- files.push({
4690
- path: resolve(loadersDir, `${model.key}.ts`),
4691
- content: generateRemixLoaders(model)
4692
- });
4693
- }
4694
- if (hasCustomerProfile) {
4695
- files.push({
4696
- path: resolve(loadersDir, "customer-profile.ts"),
4697
- content: generateCustomerProfileLoaders()
4698
- });
4699
- }
4700
- files.push({
4701
- path: resolve(loadersDir, "index.ts"),
4702
- content: generateRemixLoadersIndex(
4703
- publicModels,
4704
- hasCustomerProfile
4705
- )
4706
- });
4707
- }
4708
- if (config2.output.swift) {
4709
- const swiftDir = resolve(cwd, config2.output.swift);
4710
- files.push({
4711
- path: resolve(swiftDir, "FieldTypes.swift"),
4712
- content: generateSwiftFieldTypesFile()
4713
- });
4714
- files.push({
4715
- path: resolve(swiftDir, "ModelKeys.swift"),
4716
- content: generateSwiftModelKeys(models)
4717
- });
4718
- for (const model of models) {
4719
- const swiftTypeName = toPascalCase(model.key);
4720
- files.push({
4721
- path: resolve(swiftDir, `${swiftTypeName}.swift`),
4722
- content: generateSwiftModelFile(model)
4723
- });
4724
- }
4725
- if (hasCustomerProfile) {
4726
- files.push({
4727
- path: resolve(swiftDir, "CustomerProfile.swift"),
4728
- content: generateSwiftCustomerProfileFile(cpSchema)
4729
- });
4730
- }
4731
- }
4732
2447
  if (config2.dryRun) {
4733
2448
  console.log(
4734
2449
  chalk4.bold("\nDry run \u2014 files that would be generated:\n")
@@ -4742,50 +2457,31 @@ ${chalk4.dim(`${files.length} file(s) total`)}`);
4742
2457
  return;
4743
2458
  }
4744
2459
  await writeFiles(files, config2.prettier);
4745
- const modelCount = models.length;
4746
- const docCount = publicModels.length + staticDocs.length;
4747
- const opsCount = publicModels.length + (hasCustomerProfile ? 1 : 0) + 2;
4748
- const hookCount = hooksDir ? publicModels.length + (hasCustomerProfile ? 1 : 0) + 1 : 0;
4749
- const loaderCount = loadersDir ? publicModels.length + (hasCustomerProfile ? 1 : 0) + 1 : 0;
4750
- const swiftCount = config2.output.swift ? models.length + 2 : 0;
4751
- const cpSuffix = hasCustomerProfile ? ", customer profile" : "";
2460
+ const cpSuffix = hasCustomerProfile ? " + customer profile" : "";
4752
2461
  console.log(
4753
2462
  chalk4.green(`
4754
- Generated ${files.length} file(s)`) + chalk4.dim(
4755
- ` (${modelCount} type(s), ${docCount} document(s), ${opsCount} operation(s)${cpSuffix}${hookCount > 0 ? `, ${hookCount} hook(s)` : ""}${loaderCount > 0 ? `, ${loaderCount} loader(s)` : ""}${swiftCount > 0 ? `, ${swiftCount} Swift file(s)` : ""})`
2463
+ \u2713 Generated ${files.length} file(s)`) + chalk4.dim(
2464
+ ` (${models.length} model(s), ${publicModels.length} typed accessor(s)${cpSuffix})`
4756
2465
  )
4757
2466
  );
4758
- console.log(chalk4.dim(` Types: ${typesDir}`));
4759
- console.log(chalk4.dim(` Documents: ${docsDir}`));
4760
- console.log(chalk4.dim(` Operations: ${opsDir}`));
4761
- if (hooksDir) {
4762
- console.log(chalk4.dim(` Hooks: ${hooksDir}`));
4763
- }
4764
- if (loadersDir) {
4765
- console.log(chalk4.dim(` Loaders: ${loadersDir}`));
4766
- }
4767
- if (config2.output.swift) {
4768
- console.log(
4769
- chalk4.dim(` Swift: ${resolve(cwd, config2.output.swift)}`)
4770
- );
4771
- }
2467
+ console.log(chalk4.dim(` Output: ${outDir}`));
4772
2468
  }
4773
2469
  )
4774
2470
  );
4775
2471
  }
4776
- function generateMainIndex(includeCustomerProfile) {
2472
+ function generateRootIndex(includeCustomerProfile) {
4777
2473
  let code = `/**
4778
- * Generated types and configs
2474
+ * Generated Foir client and model types.
4779
2475
  *
4780
2476
  * @generated by foir \u2014 DO NOT EDIT MANUALLY
4781
2477
  */
4782
2478
 
4783
- export * from './field-types.js';
4784
- export * from './config.js';
2479
+ export { createClient } from './client.js';
2480
+ export type { TypedClient } from './client.js';
4785
2481
  export * from './models/index.js';
4786
2482
  `;
4787
2483
  if (includeCustomerProfile) {
4788
- code += `export * from './customer-profile.js';
2484
+ code += `export type { CustomerProfileData } from './models/customer-profile.js';
4789
2485
  `;
4790
2486
  }
4791
2487
  return code;
@@ -4797,11 +2493,11 @@ import inquirer2 from "inquirer";
4797
2493
 
4798
2494
  // src/scaffold/scaffold.ts
4799
2495
  import * as fs4 from "fs";
4800
- import * as path3 from "path";
2496
+ import * as path2 from "path";
4801
2497
 
4802
2498
  // src/scaffold/package-manager.ts
4803
2499
  import * as fs3 from "fs";
4804
- import * as path2 from "path";
2500
+ import * as path from "path";
4805
2501
  function detectPackageManager() {
4806
2502
  const lockFiles = [
4807
2503
  { file: "pnpm-lock.yaml", manager: "pnpm" },
@@ -4811,11 +2507,11 @@ function detectPackageManager() {
4811
2507
  let dir = process.cwd();
4812
2508
  while (true) {
4813
2509
  for (const { file, manager } of lockFiles) {
4814
- if (fs3.existsSync(path2.join(dir, file))) {
2510
+ if (fs3.existsSync(path.join(dir, file))) {
4815
2511
  return getManagerInfo(manager);
4816
2512
  }
4817
2513
  }
4818
- const parentDir = path2.dirname(dir);
2514
+ const parentDir = path.dirname(dir);
4819
2515
  if (parentDir === dir) {
4820
2516
  break;
4821
2517
  }
@@ -4848,7 +2544,7 @@ function getManagerInfo(manager) {
4848
2544
 
4849
2545
  // src/scaffold/scaffold.ts
4850
2546
  async function scaffold(projectName, extensionType, apiUrl) {
4851
- const projectDir = path3.resolve(process.cwd(), projectName);
2547
+ const projectDir = path2.resolve(process.cwd(), projectName);
4852
2548
  if (fs4.existsSync(projectDir)) {
4853
2549
  throw new Error(
4854
2550
  `Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
@@ -4856,8 +2552,8 @@ async function scaffold(projectName, extensionType, apiUrl) {
4856
2552
  }
4857
2553
  const files = getFiles(projectName, extensionType, apiUrl);
4858
2554
  for (const [filePath, content] of Object.entries(files)) {
4859
- const fullPath = path3.join(projectDir, filePath);
4860
- const dir = path3.dirname(fullPath);
2555
+ const fullPath = path2.join(projectDir, filePath);
2556
+ const dir = path2.dirname(fullPath);
4861
2557
  if (!fs4.existsSync(dir)) {
4862
2558
  fs4.mkdirSync(dir, { recursive: true });
4863
2559
  }
@@ -5769,9 +3465,7 @@ function registerProfilesCommand(program2, globalOpts) {
5769
3465
  console.log(`Default profile: ${current}`);
5770
3466
  } else {
5771
3467
  console.log("No default profile set.");
5772
- console.log(
5773
- "Use `foir profiles default <name>` to set one."
5774
- );
3468
+ console.log("Use `foir profiles default <name>` to set one.");
5775
3469
  }
5776
3470
  }
5777
3471
  })
@@ -5799,7 +3493,9 @@ function registerProfilesCommand(program2, globalOpts) {
5799
3493
  opts
5800
3494
  );
5801
3495
  } else {
5802
- console.log(`Profile: ${name}${name === defaultName ? " (default)" : ""}`);
3496
+ console.log(
3497
+ `Profile: ${name}${name === defaultName ? " (default)" : ""}`
3498
+ );
5803
3499
  console.log("\u2500".repeat(40));
5804
3500
  console.log(`Name: ${project.name}`);
5805
3501
  console.log(`ID: ${project.id}`);
@@ -5809,9 +3505,7 @@ function registerProfilesCommand(program2, globalOpts) {
5809
3505
  const resolved = await resolveProjectContext(opts);
5810
3506
  if (!resolved) {
5811
3507
  console.log("No active project context.");
5812
- console.log(
5813
- "Run `foir select-project` to choose a project."
5814
- );
3508
+ console.log("Run `foir select-project` to choose a project.");
5815
3509
  return;
5816
3510
  }
5817
3511
  if (opts.json || opts.jsonl) {
@@ -5837,31 +3531,32 @@ function registerProfilesCommand(program2, globalOpts) {
5837
3531
  })
5838
3532
  );
5839
3533
  profiles.command("delete <name>").description("Delete a named profile").option("--confirm", "Skip confirmation prompt").action(
5840
- withErrorHandler(globalOpts, async (name, cmdOpts) => {
5841
- const opts = globalOpts();
5842
- const project = await getProjectContext(name);
5843
- if (!project) {
5844
- throw new Error(
5845
- `Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
5846
- );
5847
- }
5848
- const confirmed = await confirmAction(
5849
- `Delete profile "${name}" (${project.name})?`,
5850
- { confirm: !!cmdOpts.confirm }
5851
- );
5852
- if (!confirmed) {
5853
- console.log("Aborted.");
5854
- return;
5855
- }
5856
- await deleteProfile(name);
5857
- if (opts.json || opts.jsonl) {
5858
- formatOutput({ deleted: name }, opts);
5859
- } else {
5860
- console.log(
5861
- chalk7.green(`Deleted profile "${name}".`)
3534
+ withErrorHandler(
3535
+ globalOpts,
3536
+ async (name, cmdOpts) => {
3537
+ const opts = globalOpts();
3538
+ const project = await getProjectContext(name);
3539
+ if (!project) {
3540
+ throw new Error(
3541
+ `Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
3542
+ );
3543
+ }
3544
+ const confirmed = await confirmAction(
3545
+ `Delete profile "${name}" (${project.name})?`,
3546
+ { confirm: !!cmdOpts.confirm }
5862
3547
  );
3548
+ if (!confirmed) {
3549
+ console.log("Aborted.");
3550
+ return;
3551
+ }
3552
+ await deleteProfile(name);
3553
+ if (opts.json || opts.jsonl) {
3554
+ formatOutput({ deleted: name }, opts);
3555
+ } else {
3556
+ console.log(chalk7.green(`Deleted profile "${name}".`));
3557
+ }
5863
3558
  }
5864
- })
3559
+ )
5865
3560
  );
5866
3561
  }
5867
3562