@eide/foir-cli 0.1.43 → 0.1.45

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
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- findConfigFile,
4
- loadConfigFile
5
- } from "./chunk-L642MYIL.js";
6
2
 
7
3
  // src/cli.ts
8
4
  import { config } from "dotenv";
9
- import { resolve as resolve6, dirname as dirname6 } from "path";
5
+ import { resolve as resolve5, dirname as dirname5 } from "path";
10
6
  import { fileURLToPath as fileURLToPath2 } from "url";
11
7
  import { createRequire } from "module";
12
8
  import { Command } from "commander";
@@ -48,8 +44,8 @@ async function getCredentials() {
48
44
  }
49
45
  async function writeCredentials(credentials) {
50
46
  await ensureDir(getCredentialsDir());
51
- const path4 = getCredentialsPath();
52
- await fs.writeFile(path4, JSON.stringify(credentials, null, 2), {
47
+ const path3 = getCredentialsPath();
48
+ await fs.writeFile(path3, JSON.stringify(credentials, null, 2), {
53
49
  mode: 384
54
50
  });
55
51
  }
@@ -218,21 +214,6 @@ async function resolveProjectContext(options) {
218
214
  };
219
215
  }
220
216
  }
221
- try {
222
- const { loadConfigProject } = await import("./loader-7VE4OF73.js");
223
- const configProfile = await loadConfigProject();
224
- if (configProfile) {
225
- const project2 = await getProjectContext(configProfile);
226
- if (project2) {
227
- return {
228
- project: project2,
229
- source: "foir.config.ts",
230
- profileName: configProfile
231
- };
232
- }
233
- }
234
- } catch {
235
- }
236
217
  const defaultProfile = await getDefaultProfile();
237
218
  if (defaultProfile) {
238
219
  const project2 = await getProjectContext(defaultProfile);
@@ -362,13 +343,13 @@ function withErrorHandler(optsFn, fn) {
362
343
  // src/commands/login.ts
363
344
  async function findAvailablePort(start, end) {
364
345
  for (let port = start; port <= end; port++) {
365
- const available = await new Promise((resolve7) => {
346
+ const available = await new Promise((resolve6) => {
366
347
  const server = http.createServer();
367
348
  server.listen(port, () => {
368
349
  server.close();
369
- resolve7(true);
350
+ resolve6(true);
370
351
  });
371
- server.on("error", () => resolve7(false));
352
+ server.on("error", () => resolve6(false));
372
353
  });
373
354
  if (available) return port;
374
355
  }
@@ -406,7 +387,7 @@ async function loginAction(globalOpts) {
406
387
  const state = crypto.randomBytes(16).toString("hex");
407
388
  const port = await findAvailablePort(9876, 9900);
408
389
  const redirectUri = `http://localhost:${port}/callback`;
409
- const authCode = await new Promise((resolve7, reject) => {
390
+ const authCode = await new Promise((resolve6, reject) => {
410
391
  const server = http.createServer((req, res) => {
411
392
  const url = new URL(req.url, `http://localhost:${port}`);
412
393
  if (url.pathname === "/callback") {
@@ -439,7 +420,7 @@ async function loginAction(globalOpts) {
439
420
  `<html><head><meta http-equiv="refresh" content="2;url=${mainUrl}"></head><body style="font-family:system-ui;text-align:center;padding:50px"><h1>Authentication successful!</h1><p>You can close this window.</p></body></html>`
440
421
  );
441
422
  server.close();
442
- resolve7(code);
423
+ resolve6(code);
443
424
  }
444
425
  });
445
426
  server.listen(port);
@@ -606,95 +587,98 @@ async function provisionApiKey(apiUrl, accessToken, projectId, tenantId) {
606
587
  }
607
588
  function registerSelectProjectCommand(program2, globalOpts) {
608
589
  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");
590
+ withErrorHandler(
591
+ globalOpts,
592
+ async (cmdOpts) => {
593
+ const opts = globalOpts();
594
+ const apiUrl = getApiUrl(opts);
595
+ const credentials = await getCredentials();
596
+ if (!credentials) {
597
+ console.log("Not logged in. Run `foir login` first.");
598
+ throw new Error("Not authenticated");
638
599
  }
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
- {}
600
+ console.log("Fetching your projects...\n");
601
+ const sessionContext = await fetchSessionContext(
602
+ apiUrl,
603
+ credentials.accessToken
649
604
  );
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
- ]
605
+ const { availableTenants: tenants, availableProjects: projects } = sessionContext;
606
+ if (projects.length === 0) {
607
+ console.log("No projects found. Create one in the platform first.");
608
+ throw new Error("No projects available");
609
+ }
610
+ const tenantNameMap = new Map(tenants.map((t) => [t.id, t.name]));
611
+ let selectedProject;
612
+ if (cmdOpts.projectId) {
613
+ const found = projects.find((p) => p.id === cmdOpts.projectId);
614
+ if (!found) {
615
+ console.log(`Project with ID "${cmdOpts.projectId}" not found.`);
616
+ console.log("Available projects:");
617
+ for (const p of projects) {
618
+ console.log(` - ${p.name} (${p.id})`);
619
+ }
620
+ throw new Error("Project not found");
621
+ }
622
+ selectedProject = found;
623
+ } else {
624
+ const byTenant = projects.reduce(
625
+ (acc, p) => {
626
+ const key = tenantNameMap.get(p.tenantId) ?? "Unknown";
627
+ if (!acc[key]) acc[key] = [];
628
+ acc[key].push(p);
629
+ return acc;
630
+ },
631
+ {}
632
+ );
633
+ const choices = Object.entries(byTenant).flatMap(
634
+ ([tenantName, tenantProjects]) => [
635
+ new inquirer.Separator(`\u2500\u2500 ${tenantName} \u2500\u2500`),
636
+ ...tenantProjects.map((p) => ({
637
+ name: ` ${p.name}`,
638
+ value: p.id,
639
+ short: p.name
640
+ }))
641
+ ]
642
+ );
643
+ const { projectId } = await inquirer.prompt([
644
+ {
645
+ type: "list",
646
+ name: "projectId",
647
+ message: "Select a project:",
648
+ choices
649
+ }
650
+ ]);
651
+ selectedProject = projects.find((p) => p.id === projectId);
652
+ }
653
+ console.log("\nProvisioning API key for CLI access...");
654
+ const { apiKey, apiKeyId } = await provisionApiKey(
655
+ apiUrl,
656
+ credentials.accessToken,
657
+ selectedProject.id,
658
+ selectedProject.tenantId
659
659
  );
660
- const { projectId } = await inquirer.prompt([
660
+ await writeProjectContext(
661
661
  {
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}\``
662
+ id: selectedProject.id,
663
+ name: selectedProject.name,
664
+ tenantId: selectedProject.tenantId,
665
+ apiKey,
666
+ apiKeyId
667
+ },
668
+ cmdOpts.saveAs
693
669
  );
694
- } else {
695
- console.log(" (stored in .foir/project.json for this repository)");
670
+ console.log(`
671
+ \u2713 Selected project: ${selectedProject.name}`);
672
+ console.log("\u2713 API key provisioned for CLI access");
673
+ if (cmdOpts.saveAs) {
674
+ console.log(
675
+ ` Saved as profile "${cmdOpts.saveAs}". Use --project ${cmdOpts.saveAs} or set as default with \`foir profiles default ${cmdOpts.saveAs}\``
676
+ );
677
+ } else {
678
+ console.log(" (stored in .foir/project.json for this repository)");
679
+ }
696
680
  }
697
- })
681
+ )
698
682
  );
699
683
  }
700
684
 
@@ -827,3981 +811,127 @@ function registerWhoamiCommand(program2, globalOpts) {
827
811
  console.log("Selected Project (this repo)");
828
812
  console.log("\u2500".repeat(40));
829
813
  console.log(`Name: ${resolved.project.name}`);
830
- console.log(`ID: ${resolved.project.id}`);
831
- console.log(`Tenant ID: ${resolved.project.tenantId}`);
832
- if (resolved.profileName) {
833
- console.log(
834
- `Profile: ${resolved.profileName} (from ${resolved.source})`
835
- );
836
- }
837
- } else {
838
- console.log("");
839
- console.log("No project selected for this repository.");
840
- console.log("Run `foir select-project` to choose a project.");
841
- }
842
- })
843
- );
844
- }
845
-
846
- // src/commands/media.ts
847
- import { promises as fs2 } from "fs";
848
- import { basename } from "path";
849
- import chalk3 from "chalk";
850
-
851
- // src/lib/client.ts
852
- import { GraphQLClient } from "graphql-request";
853
- async function createClient(options) {
854
- const apiUrl = getApiUrl(options);
855
- const endpoint = getGraphQLEndpoint(apiUrl);
856
- const headers = {
857
- "Content-Type": "application/json"
858
- };
859
- const envApiKey = process.env.FOIR_API_KEY;
860
- if (envApiKey) {
861
- headers["x-api-key"] = envApiKey;
862
- return new GraphQLClient(endpoint, { headers });
863
- }
864
- const credentials = await getCredentials();
865
- if (!credentials) {
866
- throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
867
- }
868
- if (isTokenExpired(credentials)) {
869
- throw new Error("Session expired. Run `foir login` to re-authenticate.");
870
- }
871
- headers["Authorization"] = `Bearer ${credentials.accessToken}`;
872
- const resolved = await resolveProjectContext(options);
873
- if (resolved) {
874
- headers["x-tenant-id"] = resolved.project.tenantId;
875
- headers["x-project-id"] = resolved.project.id;
876
- }
877
- return new GraphQLClient(endpoint, { headers });
878
- }
879
- async function getRestAuth(options) {
880
- const apiUrl = getApiUrl(options);
881
- const headers = {};
882
- const envApiKey = process.env.FOIR_API_KEY;
883
- if (envApiKey) {
884
- headers["x-api-key"] = envApiKey;
885
- return { apiUrl, headers };
886
- }
887
- const credentials = await getCredentials();
888
- if (!credentials) {
889
- throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
890
- }
891
- if (isTokenExpired(credentials)) {
892
- throw new Error("Session expired. Run `foir login` to re-authenticate.");
893
- }
894
- headers["Authorization"] = `Bearer ${credentials.accessToken}`;
895
- const resolved = await resolveProjectContext(options);
896
- if (resolved) {
897
- headers["x-tenant-id"] = resolved.project.tenantId;
898
- headers["x-project-id"] = resolved.project.id;
899
- }
900
- return { apiUrl, headers };
901
- }
902
-
903
- // src/commands/media.ts
904
- function registerMediaCommands(program2, globalOpts) {
905
- const media = program2.command("media").description("Media file operations");
906
- media.command("upload <filepath>").description("Upload a file").action(
907
- withErrorHandler(globalOpts, async (filepath) => {
908
- const opts = globalOpts();
909
- const { apiUrl, headers } = await getRestAuth(opts);
910
- const fileBuffer = await fs2.readFile(filepath);
911
- const fileName = basename(filepath);
912
- const formData = new FormData();
913
- formData.append("file", new Blob([fileBuffer]), fileName);
914
- const uploadUrl = `${apiUrl.replace(/\/$/, "")}/api/files/upload`;
915
- const response = await fetch(uploadUrl, {
916
- method: "POST",
917
- headers,
918
- body: formData
919
- });
920
- if (!response.ok) {
921
- const errorText = await response.text();
922
- throw new Error(`Upload failed (${response.status}): ${errorText}`);
923
- }
924
- const result = await response.json();
925
- if (opts.json || opts.jsonl) {
926
- formatOutput(result, opts);
927
- } else {
928
- success(`Uploaded ${fileName}`);
929
- if (result.url) {
930
- console.log(chalk3.bold(` URL: ${result.url}`));
931
- }
932
- if (result.storageKey) {
933
- console.log(chalk3.gray(` Key: ${result.storageKey}`));
934
- }
935
- }
936
- })
937
- );
938
- }
939
-
940
- // src/commands/pull.ts
941
- import { resolve } from "path";
942
- import chalk4 from "chalk";
943
-
944
- // 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
- };
959
- async function loadPullConfig(flags) {
960
- let fileConfig = {};
961
- const configPath = findConfigFile(flags.config);
962
- if (configPath) {
963
- const full = await loadConfigFile(configPath);
964
- fileConfig = full.pull ?? {};
965
- }
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 };
998
- } else {
999
- domains = { ...ALL_DOMAINS };
1000
- }
1001
- const only = flags.only ? flags.only.split(",").map((s) => s.trim()) : fileConfig.only ?? [];
1002
- const includeInline = fileConfig.includeInline ?? true;
1003
- const prettier = flags.noPrettier ? false : fileConfig.prettier ?? true;
1004
- const dryRun = flags.dryRun ?? false;
1005
- return { output, targets, domains, only, includeInline, prettier, dryRun };
1006
- }
1007
-
1008
- // 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" } }] } }] } }] } }] };
1012
-
1013
- // src/codegen/fetch-models.ts
1014
- function normalizeConfig(raw) {
1015
- return {
1016
- records: Boolean(raw.records ?? true),
1017
- inline: Boolean(raw.inline ?? false),
1018
- publicApi: Boolean(raw.publicApi ?? false),
1019
- versioning: Boolean(raw.versioning ?? false),
1020
- publishing: Boolean(raw.publishing ?? false),
1021
- variants: Boolean(raw.variants ?? false),
1022
- customerScoped: Boolean(raw.customerScoped ?? false),
1023
- sharing: raw.sharing ? {
1024
- enabled: Boolean(
1025
- raw.sharing.enabled ?? false
1026
- ),
1027
- requireAcceptance: Boolean(
1028
- raw.sharing.requireAcceptance ?? true
1029
- )
1030
- } : void 0,
1031
- embeddings: raw.embeddings
1032
- };
1033
- }
1034
- function normalizeField(raw) {
1035
- const field = raw;
1036
- const options = { ...field.options ?? {} };
1037
- const resolvedType = field.type === "composite" && field.config?.schema ? field.config.schema : field.type;
1038
- if (!options.itemType) {
1039
- const resolved = field.itemType ?? field.config?.itemType ?? field.config?.itemSchema;
1040
- if (resolved) {
1041
- options.itemType = resolved;
1042
- }
1043
- }
1044
- if (field.config?.minItems !== void 0 && options.minItems === void 0) {
1045
- options.minItems = field.config.minItems;
1046
- }
1047
- if (field.config?.maxItems !== void 0 && options.maxItems === void 0) {
1048
- options.maxItems = field.config.maxItems;
1049
- }
1050
- return {
1051
- key: field.key,
1052
- type: resolvedType,
1053
- label: field.label,
1054
- required: field.required,
1055
- helpText: field.helpText,
1056
- options: Object.keys(options).length > 0 ? options : void 0
1057
- };
1058
- }
1059
- async function fetchModelsForCodegen(client) {
1060
- const data = await client.request(ModelsForCodegenDocument, {
1061
- limit: 500
1062
- });
1063
- return data.models.items.map((item) => ({
1064
- key: item.key,
1065
- name: item.name,
1066
- pluralName: item.pluralName ?? void 0,
1067
- description: item.description ?? void 0,
1068
- category: item.category ?? void 0,
1069
- fields: (item.fields ?? []).map(
1070
- (f) => normalizeField(f)
1071
- ),
1072
- config: normalizeConfig(item.config ?? {}),
1073
- hooks: item.hooks ?? void 0
1074
- }));
1075
- }
1076
- function filterModels(models, options) {
1077
- let filtered = models;
1078
- if (options.only && options.only.length > 0) {
1079
- const onlySet = new Set(options.only);
1080
- filtered = filtered.filter((m) => onlySet.has(m.key));
1081
- }
1082
- if (!options.includeInline) {
1083
- filtered = filtered.filter((m) => m.config.records);
1084
- }
1085
- return filtered;
1086
- }
1087
-
1088
- // src/codegen/fetch-customer-profile-schema.ts
1089
- async function fetchCustomerProfileSchema(client) {
1090
- const data = await client.request(GetCustomerProfileSchemaDocument);
1091
- if (!data.customerProfileSchema) return null;
1092
- return {
1093
- id: data.customerProfileSchema.id,
1094
- version: data.customerProfileSchema.version,
1095
- fields: (data.customerProfileSchema.fields ?? []).map((f) => ({
1096
- key: f.key,
1097
- type: f.type,
1098
- label: f.label,
1099
- required: f.required ?? void 0,
1100
- helpText: f.helpText ?? void 0,
1101
- options: f.config
1102
- }))
1103
- };
1104
- }
1105
-
1106
- // src/codegen/field-mapping.ts
1107
- var PRIMITIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
1108
- "text",
1109
- "richtext",
1110
- "number",
1111
- "boolean",
1112
- "email",
1113
- "phone",
1114
- "url",
1115
- "date",
1116
- "image",
1117
- "video",
1118
- "file",
1119
- "currency",
1120
- "select",
1121
- "multiselect",
1122
- "json",
1123
- "list",
1124
- "reference",
1125
- "link",
1126
- "flexible",
1127
- "model"
1128
- ]);
1129
- function isPrimitiveFieldType(type) {
1130
- return PRIMITIVE_FIELD_TYPES.has(type);
1131
- }
1132
- var FIELD_TYPE_MAPPING = {
1133
- text: { outputType: "string", inputType: "string" },
1134
- richtext: {
1135
- outputType: "RichtextValue",
1136
- inputType: "RichtextValue",
1137
- needsImport: "field-types"
1138
- },
1139
- number: { outputType: "number", inputType: "number" },
1140
- boolean: { outputType: "boolean", inputType: "boolean" },
1141
- email: { outputType: "string", inputType: "string" },
1142
- phone: { outputType: "string", inputType: "string" },
1143
- url: { outputType: "string", inputType: "string" },
1144
- date: { outputType: "Date", inputType: "string" },
1145
- image: {
1146
- outputType: "ImageValue",
1147
- inputType: "string",
1148
- needsImport: "field-types"
1149
- },
1150
- video: {
1151
- outputType: "VideoValue",
1152
- inputType: "string",
1153
- needsImport: "field-types"
1154
- },
1155
- file: {
1156
- outputType: "FileValue",
1157
- inputType: "string",
1158
- needsImport: "field-types"
1159
- },
1160
- currency: {
1161
- outputType: "CurrencyValue",
1162
- inputType: "CurrencyValue",
1163
- needsImport: "field-types"
1164
- },
1165
- select: { outputType: "string", inputType: "string" },
1166
- multiselect: { outputType: "string[]", inputType: "string[]" },
1167
- json: { outputType: "unknown", inputType: "unknown" },
1168
- list: { outputType: "unknown[]", inputType: "unknown[]" },
1169
- flexible: {
1170
- outputType: "FlexibleFieldItem[]",
1171
- inputType: "FlexibleFieldItem[]",
1172
- needsImport: "field-types"
1173
- },
1174
- reference: {
1175
- outputType: "ReferenceValue",
1176
- inputType: "ReferenceValue",
1177
- needsImport: "field-types"
1178
- },
1179
- link: {
1180
- outputType: "LinkValue",
1181
- inputType: "LinkValue",
1182
- needsImport: "field-types"
1183
- },
1184
- model: { outputType: "string", inputType: "string" }
1185
- };
1186
- function getFieldType(field, mode = "output") {
1187
- if (!field?.type) return "unknown";
1188
- const mapping = FIELD_TYPE_MAPPING[field.type];
1189
- if (!mapping) {
1190
- if (isPrimitiveFieldType(field.type)) return "unknown";
1191
- return toPascalCase(field.type);
1192
- }
1193
- let tsType = mode === "output" ? mapping.outputType : mapping.inputType;
1194
- if (field.type === "select" && field.options?.options) {
1195
- const options = field.options.options;
1196
- if (options.length > 0) {
1197
- tsType = options.map((o) => `'${o.value}'`).join(" | ");
1198
- }
1199
- }
1200
- if (field.type === "multiselect" && field.options?.options) {
1201
- const options = field.options.options;
1202
- if (options.length > 0) {
1203
- tsType = `(${options.map((o) => `'${o.value}'`).join(" | ")})[]`;
1204
- }
1205
- }
1206
- if (field.type === "list" && field.options?.itemType) {
1207
- const itemType = field.options.itemType;
1208
- const itemMapping = FIELD_TYPE_MAPPING[itemType];
1209
- if (itemMapping) {
1210
- tsType = `${mode === "output" ? itemMapping.outputType : itemMapping.inputType}[]`;
1211
- } else if (!isPrimitiveFieldType(itemType)) {
1212
- tsType = `${toPascalCase(itemType)}[]`;
1213
- }
1214
- }
1215
- return tsType;
1216
- }
1217
- function getReferenceTypeModelRefs(fields) {
1218
- const refs = /* @__PURE__ */ new Set();
1219
- for (const field of fields) {
1220
- if (field.type === "reference" && field.options?.referenceTypes) {
1221
- for (const rt of field.options.referenceTypes) {
1222
- refs.add(rt);
1223
- }
1224
- }
1225
- }
1226
- return refs;
1227
- }
1228
- function getInlineSchemaReferences(fields) {
1229
- const refs = /* @__PURE__ */ new Set();
1230
- for (const field of fields) {
1231
- if (!isPrimitiveFieldType(field.type) && !FIELD_TYPE_MAPPING[field.type]) {
1232
- refs.add(field.type);
1233
- }
1234
- if (field.type === "list" && field.options?.itemType) {
1235
- const itemType = field.options.itemType;
1236
- if (!isPrimitiveFieldType(itemType) && !FIELD_TYPE_MAPPING[itemType]) {
1237
- refs.add(itemType);
1238
- }
1239
- }
1240
- }
1241
- return refs;
1242
- }
1243
- function toCamelCase(str) {
1244
- if (!str) return "unknown";
1245
- return str.replace(
1246
- /[-_]([a-z])/g,
1247
- (_, letter) => letter.toUpperCase()
1248
- );
1249
- }
1250
- function toPascalCase(str) {
1251
- if (!str) return "Unknown";
1252
- const camel = toCamelCase(str);
1253
- return camel.charAt(0).toUpperCase() + camel.slice(1);
1254
- }
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
- function sanitizeFieldName(key) {
1260
- if (!key) return "unknown";
1261
- const camel = toCamelCase(key);
1262
- if (/^[0-9]/.test(camel)) return `_${camel}`;
1263
- return camel;
1264
- }
1265
- function generateFieldDef(field) {
1266
- const parts = [];
1267
- parts.push(`key: '${field.key ?? "unknown"}'`);
1268
- parts.push(`type: '${field.type ?? "text"}'`);
1269
- parts.push(
1270
- `label: '${(field.label ?? field.key ?? "Unknown").replace(/'/g, "\\'")}'`
1271
- );
1272
- if (field.required) parts.push("required: true");
1273
- if (field.helpText)
1274
- parts.push(`helpText: '${field.helpText.replace(/'/g, "\\'")}'`);
1275
- if (field.type === "text" && field.options) {
1276
- if (field.options.maxLength)
1277
- parts.push(`maxLength: ${field.options.maxLength}`);
1278
- if (field.options.minLength)
1279
- parts.push(`minLength: ${field.options.minLength}`);
1280
- }
1281
- if (field.type === "number" && field.options) {
1282
- if (field.options.min !== void 0)
1283
- parts.push(`min: ${field.options.min}`);
1284
- if (field.options.max !== void 0)
1285
- parts.push(`max: ${field.options.max}`);
1286
- if (field.options.step !== void 0)
1287
- parts.push(`step: ${field.options.step}`);
1288
- }
1289
- if ((field.type === "select" || field.type === "multiselect") && field.options?.options) {
1290
- const options = field.options.options;
1291
- const optionsStr = options.filter((o) => o.value !== void 0).map((o) => {
1292
- const label = (o.label ?? o.value ?? "").replace(/'/g, "\\'");
1293
- const value = (o.value ?? "").replace(/'/g, "\\'");
1294
- return `{ label: '${label}', value: '${value}' }`;
1295
- }).join(", ");
1296
- parts.push(`options: [${optionsStr}]`);
1297
- }
1298
- if (field.type === "reference" && field.options?.referenceTypes) {
1299
- const refTypes = field.options.referenceTypes;
1300
- parts.push(`referenceTypes: [${refTypes.map((t) => `'${t}'`).join(", ")}]`);
1301
- if (field.options.multiple) parts.push("multiple: true");
1302
- }
1303
- if (field.type === "list" && field.options?.itemType) {
1304
- parts.push(`itemType: '${field.options.itemType}'`);
1305
- if (field.options.minItems !== void 0)
1306
- parts.push(`minItems: ${field.options.minItems}`);
1307
- if (field.options.maxItems !== void 0)
1308
- parts.push(`maxItems: ${field.options.maxItems}`);
1309
- }
1310
- if ((field.type === "image" || field.type === "video" || field.type === "file") && field.options) {
1311
- if (field.options.allowedTypes) {
1312
- const types = field.options.allowedTypes;
1313
- parts.push(`allowedTypes: [${types.map((t) => `'${t}'`).join(", ")}]`);
1314
- }
1315
- if (field.options.maxSize) parts.push(`maxSize: ${field.options.maxSize}`);
1316
- }
1317
- return `{ ${parts.join(", ")} }`;
1318
- }
1319
-
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
- // src/codegen/generators/model-types.ts
1753
- function isInlineOnlyModel(model) {
1754
- return model.config.inline && !model.config.records;
1755
- }
1756
- function generateModelTypes(model, allModels) {
1757
- const typeName = toPascalCase(model.key);
1758
- const configName = toCamelCase(model.key) + "Config";
1759
- const fields = model.fields ?? [];
1760
- const fieldTypeImports = getFieldTypeImportsForFields(fields);
1761
- const inlineSchemaRefs = getInlineSchemaReferences(fields);
1762
- const referenceModelRefs = getReferenceTypeModelRefs(fields);
1763
- let code = buildImportStatements(
1764
- model,
1765
- fieldTypeImports,
1766
- inlineSchemaRefs,
1767
- referenceModelRefs,
1768
- allModels
1769
- );
1770
- if (isInlineOnlyModel(model)) {
1771
- code += generateDataInterface(model, fields, typeName, allModels);
1772
- return code;
1773
- }
1774
- code += generateConfigObject(model, fields, configName);
1775
- code += "\n";
1776
- code += generateDataInterface(model, fields, typeName + "Data", allModels);
1777
- return code;
1778
- }
1779
- function buildImportStatements(model, fieldTypeImports, inlineSchemaRefs, referenceModelRefs, allModels) {
1780
- const imports = [];
1781
- if (!isInlineOnlyModel(model)) {
1782
- imports.push("import type { ModelConfig } from '../config.js';");
1783
- }
1784
- if (fieldTypeImports.size > 0) {
1785
- const types = Array.from(fieldTypeImports).sort().join(", ");
1786
- imports.push(`import type { ${types} } from '../field-types.js';`);
1787
- }
1788
- const allModelRefKeys = /* @__PURE__ */ new Set([
1789
- ...inlineSchemaRefs,
1790
- ...referenceModelRefs
1791
- ]);
1792
- for (const refKey of allModelRefKeys) {
1793
- if (refKey === model.key) continue;
1794
- const refModel = allModels.find((m) => m.key === refKey);
1795
- if (refModel) {
1796
- const refTypeName = isInlineOnlyModel(refModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
1797
- imports.push(`import type { ${refTypeName} } from './${refKey}.js';`);
1798
- }
1799
- }
1800
- return imports.length > 0 ? imports.join("\n") + "\n\n" : "";
1801
- }
1802
- function generateConfigObject(model, fields, configName) {
1803
- const lines = [];
1804
- lines.push("/**");
1805
- lines.push(` * ${model.name} Configuration`);
1806
- if (model.description) lines.push(` * ${model.description}`);
1807
- lines.push(` *`);
1808
- lines.push(` * @generated from model '${model.key}'`);
1809
- lines.push(" */");
1810
- const escapedName = (model.name ?? model.key).replace(/'/g, "\\'");
1811
- lines.push(`export const ${configName} = {`);
1812
- lines.push(` key: '${model.key}',`);
1813
- lines.push(` name: '${escapedName}',`);
1814
- if (model.description) {
1815
- lines.push(` description: '${model.description.replace(/'/g, "\\'")}',`);
1816
- }
1817
- lines.push("");
1818
- lines.push(" // Capability flags");
1819
- lines.push(` records: ${model.config.records},`);
1820
- lines.push(` inline: ${model.config.inline},`);
1821
- lines.push(` publicApi: ${model.config.publicApi},`);
1822
- lines.push(` versioning: ${model.config.versioning},`);
1823
- lines.push(` publishing: ${model.config.publishing},`);
1824
- lines.push(` variants: ${model.config.variants},`);
1825
- lines.push(` customerScoped: ${model.config.customerScoped},`);
1826
- if (model.config.embeddings?.enabled) {
1827
- lines.push("");
1828
- lines.push(" // Embeddings");
1829
- lines.push(
1830
- ` embeddings: ${JSON.stringify(model.config.embeddings, null, 4).replace(/\n/g, "\n ")},`
1831
- );
1832
- }
1833
- if (model.hooks && Object.keys(model.hooks).length > 0) {
1834
- lines.push("");
1835
- lines.push(" // Lifecycle hooks");
1836
- lines.push(
1837
- ` hooks: ${JSON.stringify(model.hooks, null, 4).replace(/\n/g, "\n ")},`
1838
- );
1839
- } else {
1840
- lines.push("");
1841
- lines.push(" hooks: {},");
1842
- }
1843
- lines.push("");
1844
- lines.push(" // Field definitions");
1845
- if (fields.length === 0) {
1846
- lines.push(" fieldDefs: [],");
1847
- } else {
1848
- lines.push(" fieldDefs: [");
1849
- for (const field of fields) {
1850
- lines.push(` ${generateFieldDef(field)},`);
1851
- }
1852
- lines.push(" ],");
1853
- }
1854
- lines.push("} as const satisfies ModelConfig;");
1855
- return lines.join("\n") + "\n";
1856
- }
1857
- function generateDataInterface(model, fields, interfaceName, allModels) {
1858
- const lines = [];
1859
- lines.push("/**");
1860
- lines.push(` * ${model.name} Data`);
1861
- lines.push(` * Field values only \u2014 no system fields`);
1862
- lines.push(` *`);
1863
- lines.push(` * @generated from model '${model.key}'`);
1864
- lines.push(" */");
1865
- lines.push(`export interface ${interfaceName} {`);
1866
- for (const field of fields) {
1867
- const fieldName = sanitizeFieldName(field.key);
1868
- let fieldType = getFieldType(field, "output");
1869
- const refModel = allModels.find((m) => m.key === field.type);
1870
- if (refModel && !isInlineOnlyModel(refModel)) {
1871
- fieldType = toPascalCase(field.type) + "Data";
1872
- }
1873
- if (field.type === "list" && field.options?.itemType) {
1874
- const itemRefModel = allModels.find(
1875
- (m) => m.key === field.options.itemType
1876
- );
1877
- if (itemRefModel && !isInlineOnlyModel(itemRefModel)) {
1878
- fieldType = toPascalCase(field.options.itemType) + "Data[]";
1879
- }
1880
- }
1881
- if (field.type === "reference" && field.options?.referenceTypes) {
1882
- const refTypes = field.options.referenceTypes;
1883
- const resolvedPreviewTypes = [];
1884
- for (const refKey of refTypes) {
1885
- const targetModel = allModels.find((m) => m.key === refKey);
1886
- if (targetModel) {
1887
- const targetTypeName = isInlineOnlyModel(targetModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
1888
- resolvedPreviewTypes.push(`Partial<${targetTypeName}>`);
1889
- }
1890
- }
1891
- if (resolvedPreviewTypes.length === refTypes.length && resolvedPreviewTypes.length > 0) {
1892
- fieldType = `ReferenceValue<${resolvedPreviewTypes.join(" | ")}>`;
1893
- }
1894
- }
1895
- const optional = field.required ? "" : "?";
1896
- const comment = field.helpText ? ` /** ${field.helpText} */
1897
- ` : "";
1898
- lines.push(comment + ` ${fieldName}${optional}: ${fieldType};`);
1899
- }
1900
- lines.push("}");
1901
- return lines.join("\n") + "\n";
1902
- }
1903
- function getFieldTypeImportsForFields(fields) {
1904
- const imports = /* @__PURE__ */ new Set();
1905
- for (const field of fields) {
1906
- const mapping = FIELD_TYPE_MAPPING[field.type];
1907
- if (mapping?.needsImport === "field-types") {
1908
- imports.add(mapping.outputType.replace(/\[\]$/, ""));
1909
- }
1910
- if (field.type === "list" && field.options?.itemType) {
1911
- const itemMapping = FIELD_TYPE_MAPPING[field.options.itemType];
1912
- if (itemMapping?.needsImport === "field-types") {
1913
- imports.add(itemMapping.outputType.replace(/\[\]$/, ""));
1914
- }
1915
- }
1916
- }
1917
- return imports;
1918
- }
1919
-
1920
- // src/codegen/generators/model-index.ts
1921
- function isInlineOnlyModel2(model) {
1922
- return model.config.inline && !model.config.records;
1923
- }
1924
- function generateModelIndex(models) {
1925
- let code = `/**
1926
- * Model Types and Configs \u2014 Generated re-exports
1927
- *
1928
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
1929
- */
1930
-
1931
- `;
1932
- for (const model of models) {
1933
- const typeName = toPascalCase(model.key);
1934
- const configName = toCamelCase(model.key) + "Config";
1935
- if (isInlineOnlyModel2(model)) {
1936
- code += `export type { ${typeName} } from './${model.key}.js';
1937
- `;
1938
- } else {
1939
- code += `export { ${configName} } from './${model.key}.js';
1940
- `;
1941
- code += `export type { ${typeName}Data } from './${model.key}.js';
1942
- `;
1943
- }
1944
- }
1945
- return code;
1946
- }
1947
-
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`;
3705
- const lines = [];
3706
- lines.push(`/**
3707
- * Typed operations for ${model.name ?? model.key}
3708
- *
3709
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
3710
- */
3711
-
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';
3723
- `);
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
- }
3766
- }
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
- }
3797
- }
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
- 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
- }
3821
- }
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
- }
3845
- }
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
- }`);
3856
- lines.push("");
3857
- lines.push(`export interface Update${typeName}Result {
3858
- updateRecord: UpdateRecordResult<${dataType}>;
3859
- }`);
3860
- lines.push("");
3861
- lines.push(`export const DELETE_${upperSnake} = \`
3862
- mutation Delete${typeName}($id: ID!) {
3863
- deleteRecord(id: $id) { id }
3864
- }
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)
3878
- }
3879
- \`;`);
3880
- lines.push("");
3881
- lines.push(`export const UNPUBLISH_${upperSnake} = \`
3882
- mutation Unpublish${typeName}($id: ID!) {
3883
- unpublishRecord(id: $id)
3884
- }
3885
- \`;`);
3886
- 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';`);
3954
- }
3955
- 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
- }
4055
- }
4056
- \`;
4057
-
4058
- export interface UpdateCustomerProfileVariables {
4059
- customerId?: string;
4060
- input: {
4061
- data: Partial<CustomerProfileData>;
4062
- };
4063
- }
4064
-
4065
- export interface UpdateCustomerProfileResult {
4066
- updateCustomerProfile: {
4067
- id: string;
4068
- customerId: string;
4069
- data: CustomerProfileData;
4070
- createdAt: string;
4071
- updatedAt: string;
4072
- };
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
- for (const model of models) {
4219
- lines.push(`export * from './${model.key}.js';`);
4220
- }
4221
- if (hasCustomerProfile) {
4222
- lines.push(`export * from './customer-profile.js';`);
4223
- }
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>;
4345
- }
4346
-
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>);
4352
- }
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>);
4359
- }
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>);
4366
- }
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>);
4373
- }
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>);
4380
- }
4381
-
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>);
4387
- }
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 });
4394
- }
4395
-
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
- }
4402
- `;
4403
- }
4404
-
4405
- // src/codegen/generators/remix-loaders-index.ts
4406
- function generateRemixLoadersIndex(models, hasCustomerProfile) {
4407
- 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';`);
4416
- }
4417
- if (hasCustomerProfile) {
4418
- lines.push(`export * from './customer-profile.js';`);
4419
- }
4420
- lines.push("");
4421
- return lines.join("\n");
4422
- }
4423
-
4424
- // src/codegen/generators/customer-profile-loaders.ts
4425
- function generateCustomerProfileLoaders() {
4426
- return `/**
4427
- * Remix / server-side loader functions for Customer Profiles
4428
- *
4429
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4430
- */
4431
-
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>);
814
+ console.log(`ID: ${resolved.project.id}`);
815
+ console.log(`Tenant ID: ${resolved.project.tenantId}`);
816
+ if (resolved.profileName) {
817
+ console.log(
818
+ `Profile: ${resolved.profileName} (from ${resolved.source})`
819
+ );
820
+ }
821
+ } else {
822
+ console.log("");
823
+ console.log("No project selected for this repository.");
824
+ console.log("Run `foir select-project` to choose a project.");
825
+ }
826
+ })
827
+ );
4480
828
  }
4481
829
 
4482
- export async function deleteMyProfile(
4483
- client: GraphQLClient
4484
- ): Promise<DeleteMyProfileResult> {
4485
- return client.request<DeleteMyProfileResult>(DELETE_MY_PROFILE);
4486
- }
4487
- `;
4488
- }
830
+ // src/commands/media.ts
831
+ import { promises as fs2 } from "fs";
832
+ import { basename } from "path";
833
+ import chalk3 from "chalk";
4489
834
 
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;
835
+ // src/lib/client.ts
836
+ import { GraphQLClient } from "graphql-request";
837
+ async function createClient(options) {
838
+ const apiUrl = getApiUrl(options);
839
+ const endpoint = getGraphQLEndpoint(apiUrl);
840
+ const headers = {
841
+ "Content-Type": "application/json"
842
+ };
843
+ const envApiKey = process.env.FOIR_API_KEY;
844
+ if (envApiKey) {
845
+ headers["x-api-key"] = envApiKey;
846
+ return new GraphQLClient(endpoint, { headers });
4499
847
  }
4500
- }
4501
-
4502
- // src/codegen/write-files.ts
4503
- import { mkdir, writeFile } from "fs/promises";
4504
- import { dirname as dirname2 } from "path";
4505
- async function writeGeneratedFile(filePath, content, usePrettier = true) {
4506
- await mkdir(dirname2(filePath), { recursive: true });
4507
- let formattedContent = content;
4508
- const isSwift = filePath.endsWith(".swift");
4509
- if (usePrettier && !isSwift) {
4510
- try {
4511
- const prettier = await import("prettier");
4512
- const parser = filePath.endsWith(".graphql") ? "graphql" : "typescript";
4513
- formattedContent = await prettier.format(content, {
4514
- parser,
4515
- semi: true,
4516
- singleQuote: true,
4517
- trailingComma: "es5",
4518
- printWidth: 100
4519
- });
4520
- } catch {
4521
- }
848
+ const credentials = await getCredentials();
849
+ if (!credentials) {
850
+ throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
851
+ }
852
+ if (isTokenExpired(credentials)) {
853
+ throw new Error("Session expired. Run `foir login` to re-authenticate.");
4522
854
  }
4523
- await writeFile(filePath, formattedContent, "utf-8");
855
+ headers["Authorization"] = `Bearer ${credentials.accessToken}`;
856
+ const resolved = await resolveProjectContext(options);
857
+ if (resolved) {
858
+ headers["x-tenant-id"] = resolved.project.tenantId;
859
+ headers["x-project-id"] = resolved.project.id;
860
+ }
861
+ return new GraphQLClient(endpoint, { headers });
4524
862
  }
4525
- async function writeFiles(files, usePrettier = true) {
4526
- await Promise.all(
4527
- files.map(
4528
- (file) => writeGeneratedFile(file.path, file.content, usePrettier)
4529
- )
4530
- );
863
+ async function getRestAuth(options) {
864
+ const apiUrl = getApiUrl(options);
865
+ const headers = {};
866
+ const envApiKey = process.env.FOIR_API_KEY;
867
+ if (envApiKey) {
868
+ headers["x-api-key"] = envApiKey;
869
+ return { apiUrl, headers };
870
+ }
871
+ const credentials = await getCredentials();
872
+ if (!credentials) {
873
+ throw new Error("Not authenticated. Run `foir login` or set FOIR_API_KEY.");
874
+ }
875
+ if (isTokenExpired(credentials)) {
876
+ throw new Error("Session expired. Run `foir login` to re-authenticate.");
877
+ }
878
+ headers["Authorization"] = `Bearer ${credentials.accessToken}`;
879
+ const resolved = await resolveProjectContext(options);
880
+ if (resolved) {
881
+ headers["x-tenant-id"] = resolved.project.tenantId;
882
+ headers["x-project-id"] = resolved.project.id;
883
+ }
884
+ return { apiUrl, headers };
4531
885
  }
4532
886
 
4533
- // src/commands/pull.ts
4534
- function registerPullCommand(program2, globalOpts) {
4535
- 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(
4538
- withErrorHandler(
4539
- globalOpts,
4540
- async (cmdOpts) => {
4541
- const opts = globalOpts();
4542
- const flags = {
4543
- config: cmdOpts.config,
4544
- only: cmdOpts.only,
4545
- noPrettier: cmdOpts.prettier === false,
4546
- dryRun: !!cmdOpts.dryRun,
4547
- out: cmdOpts.out,
4548
- swift: cmdOpts.swift
4549
- };
4550
- const config2 = await loadPullConfig(flags);
4551
- const client = await createClient(opts);
4552
- console.log(chalk4.dim("Fetching models\u2026"));
4553
- const [allModels, cpSchema, publicSchema] = await Promise.all([
4554
- fetchModelsForCodegen(client),
4555
- fetchCustomerProfileSchema(client),
4556
- fetchPublicSchema(client)
4557
- ]);
4558
- if (allModels.length === 0 && !cpSchema) {
4559
- console.log(chalk4.yellow("No models found. Nothing to generate."));
4560
- return;
4561
- }
4562
- const models = filterModels(allModels, {
4563
- only: config2.only.length > 0 ? config2.only : void 0,
4564
- includeInline: config2.includeInline
4565
- });
4566
- console.log(
4567
- chalk4.dim(
4568
- `Found ${allModels.length} model(s), generating for ${models.length}.`
4569
- )
4570
- );
4571
- 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;
4577
- const files = [];
4578
- const hasCustomerProfile = !!(cpSchema && cpSchema.fields.length > 0);
4579
- const publicModels = models.filter(
4580
- (m) => m.config.publicApi && m.config.records
4581
- );
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
- for (const model of models) {
4591
- files.push({
4592
- path: resolve(typesDir, "models", `${model.key}.ts`),
4593
- content: generateModelTypes(model, models)
4594
- });
4595
- }
4596
- files.push({
4597
- path: resolve(typesDir, "models", "index.ts"),
4598
- content: generateModelIndex(models)
4599
- });
4600
- if (hasCustomerProfile) {
4601
- files.push({
4602
- path: resolve(typesDir, "customer-profile.ts"),
4603
- content: generateCustomerProfileTypes(cpSchema)
4604
- });
4605
- }
4606
- files.push({
4607
- path: resolve(typesDir, "index.ts"),
4608
- content: generateMainIndex(hasCustomerProfile)
4609
- });
4610
- for (const model of publicModels) {
4611
- files.push({
4612
- path: resolve(docsDir, `${model.key}.graphql`),
4613
- content: generateModelDocuments(model)
4614
- });
4615
- }
4616
- const hasSharingModels = publicModels.some(
4617
- (m) => m.config.sharing?.enabled
4618
- );
4619
- if (hasSharingModels) {
4620
- files.push({
4621
- path: resolve(docsDir, "_shared.graphql"),
4622
- content: generateSharedFragments()
4623
- });
4624
- }
4625
- files.push({
4626
- path: resolve(docsDir, "customer-profile.graphql"),
4627
- content: generateCustomerProfileDocuments()
4628
- });
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
- files.push({
4644
- path: resolve(opsDir, "_common.ts"),
4645
- content: generateTypedOperationsCommon(typesRelPath)
4646
- });
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
- files.push({
4660
- path: resolve(opsDir, "index.ts"),
4661
- content: generateTypedOperationsIndex(
4662
- publicModels,
4663
- hasCustomerProfile
4664
- )
4665
- });
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
- if (config2.dryRun) {
4733
- console.log(
4734
- chalk4.bold("\nDry run \u2014 files that would be generated:\n")
4735
- );
4736
- for (const file of files) {
4737
- const rel = file.path.replace(cwd + "/", "");
4738
- console.log(` ${chalk4.green("+")} ${rel}`);
4739
- }
4740
- console.log(`
4741
- ${chalk4.dim(`${files.length} file(s) total`)}`);
4742
- return;
4743
- }
4744
- 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" : "";
4752
- console.log(
4753
- 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)` : ""})`
4756
- )
4757
- );
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}`));
887
+ // src/commands/media.ts
888
+ function registerMediaCommands(program2, globalOpts) {
889
+ const media = program2.command("media").description("Media file operations");
890
+ media.command("upload <filepath>").description("Upload a file").action(
891
+ withErrorHandler(globalOpts, async (filepath) => {
892
+ const opts = globalOpts();
893
+ const { apiUrl, headers } = await getRestAuth(opts);
894
+ const fileBuffer = await fs2.readFile(filepath);
895
+ const fileName = basename(filepath);
896
+ const formData = new FormData();
897
+ formData.append("file", new Blob([fileBuffer]), fileName);
898
+ const uploadUrl = `${apiUrl.replace(/\/$/, "")}/api/files/upload`;
899
+ const response = await fetch(uploadUrl, {
900
+ method: "POST",
901
+ headers,
902
+ body: formData
903
+ });
904
+ if (!response.ok) {
905
+ const errorText = await response.text();
906
+ throw new Error(`Upload failed (${response.status}): ${errorText}`);
907
+ }
908
+ const result = await response.json();
909
+ if (opts.json || opts.jsonl) {
910
+ formatOutput(result, opts);
911
+ } else {
912
+ success(`Uploaded ${fileName}`);
913
+ if (result.url) {
914
+ console.log(chalk3.bold(` URL: ${result.url}`));
4766
915
  }
4767
- if (config2.output.swift) {
4768
- console.log(
4769
- chalk4.dim(` Swift: ${resolve(cwd, config2.output.swift)}`)
4770
- );
916
+ if (result.storageKey) {
917
+ console.log(chalk3.gray(` Key: ${result.storageKey}`));
4771
918
  }
4772
919
  }
4773
- )
920
+ })
4774
921
  );
4775
922
  }
4776
- function generateMainIndex(includeCustomerProfile) {
4777
- let code = `/**
4778
- * Generated types and configs
4779
- *
4780
- * @generated by foir \u2014 DO NOT EDIT MANUALLY
4781
- */
4782
-
4783
- export * from './field-types.js';
4784
- export * from './config.js';
4785
- export * from './models/index.js';
4786
- `;
4787
- if (includeCustomerProfile) {
4788
- code += `export * from './customer-profile.js';
4789
- `;
4790
- }
4791
- return code;
4792
- }
4793
923
 
4794
924
  // src/commands/create-extension.ts
4795
- import chalk5 from "chalk";
925
+ import chalk4 from "chalk";
4796
926
  import inquirer2 from "inquirer";
4797
927
 
4798
928
  // src/scaffold/scaffold.ts
4799
929
  import * as fs4 from "fs";
4800
- import * as path3 from "path";
930
+ import * as path2 from "path";
4801
931
 
4802
932
  // src/scaffold/package-manager.ts
4803
933
  import * as fs3 from "fs";
4804
- import * as path2 from "path";
934
+ import * as path from "path";
4805
935
  function detectPackageManager() {
4806
936
  const lockFiles = [
4807
937
  { file: "pnpm-lock.yaml", manager: "pnpm" },
@@ -4811,11 +941,11 @@ function detectPackageManager() {
4811
941
  let dir = process.cwd();
4812
942
  while (true) {
4813
943
  for (const { file, manager } of lockFiles) {
4814
- if (fs3.existsSync(path2.join(dir, file))) {
944
+ if (fs3.existsSync(path.join(dir, file))) {
4815
945
  return getManagerInfo(manager);
4816
946
  }
4817
947
  }
4818
- const parentDir = path2.dirname(dir);
948
+ const parentDir = path.dirname(dir);
4819
949
  if (parentDir === dir) {
4820
950
  break;
4821
951
  }
@@ -4848,7 +978,7 @@ function getManagerInfo(manager) {
4848
978
 
4849
979
  // src/scaffold/scaffold.ts
4850
980
  async function scaffold(projectName, extensionType, apiUrl) {
4851
- const projectDir = path3.resolve(process.cwd(), projectName);
981
+ const projectDir = path2.resolve(process.cwd(), projectName);
4852
982
  if (fs4.existsSync(projectDir)) {
4853
983
  throw new Error(
4854
984
  `Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
@@ -4856,8 +986,8 @@ async function scaffold(projectName, extensionType, apiUrl) {
4856
986
  }
4857
987
  const files = getFiles(projectName, extensionType, apiUrl);
4858
988
  for (const [filePath, content] of Object.entries(files)) {
4859
- const fullPath = path3.join(projectDir, filePath);
4860
- const dir = path3.dirname(fullPath);
989
+ const fullPath = path2.join(projectDir, filePath);
990
+ const dir = path2.dirname(fullPath);
4861
991
  if (!fs4.existsSync(dir)) {
4862
992
  fs4.mkdirSync(dir, { recursive: true });
4863
993
  }
@@ -5315,8 +1445,8 @@ function registerCreateExtensionCommand(program2, globalOpts) {
5315
1445
  globalOpts,
5316
1446
  async (name, cmdOpts) => {
5317
1447
  console.log();
5318
- console.log(chalk5.bold(" Create Foir Extension"));
5319
- console.log(chalk5.gray(" ---------------------"));
1448
+ console.log(chalk4.bold(" Create Foir Extension"));
1449
+ console.log(chalk4.gray(" ---------------------"));
5320
1450
  console.log();
5321
1451
  let extensionName = name;
5322
1452
  if (!extensionName) {
@@ -5348,7 +1478,7 @@ function registerCreateExtensionCommand(program2, globalOpts) {
5348
1478
  const apiUrl = cmdOpts?.apiUrl ?? "http://localhost:4000/graphql";
5349
1479
  console.log();
5350
1480
  console.log(
5351
- ` Scaffolding ${chalk5.cyan(`"${extensionName}"`)} (${extensionType})...`
1481
+ ` Scaffolding ${chalk4.cyan(`"${extensionName}"`)} (${extensionType})...`
5352
1482
  );
5353
1483
  console.log();
5354
1484
  await scaffold(extensionName, extensionType, apiUrl);
@@ -5357,6 +1487,164 @@ function registerCreateExtensionCommand(program2, globalOpts) {
5357
1487
  );
5358
1488
  }
5359
1489
 
1490
+ // src/graphql/generated.ts
1491
+ var GlobalSearchDocument = {
1492
+ kind: "Document",
1493
+ definitions: [
1494
+ {
1495
+ kind: "OperationDefinition",
1496
+ operation: "query",
1497
+ name: { kind: "Name", value: "GlobalSearch" },
1498
+ variableDefinitions: [
1499
+ {
1500
+ kind: "VariableDefinition",
1501
+ variable: {
1502
+ kind: "Variable",
1503
+ name: { kind: "Name", value: "query" }
1504
+ },
1505
+ type: {
1506
+ kind: "NonNullType",
1507
+ type: {
1508
+ kind: "NamedType",
1509
+ name: { kind: "Name", value: "String" }
1510
+ }
1511
+ }
1512
+ },
1513
+ {
1514
+ kind: "VariableDefinition",
1515
+ variable: {
1516
+ kind: "Variable",
1517
+ name: { kind: "Name", value: "limit" }
1518
+ },
1519
+ type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }
1520
+ },
1521
+ {
1522
+ kind: "VariableDefinition",
1523
+ variable: {
1524
+ kind: "Variable",
1525
+ name: { kind: "Name", value: "modelKeys" }
1526
+ },
1527
+ type: {
1528
+ kind: "ListType",
1529
+ type: {
1530
+ kind: "NonNullType",
1531
+ type: {
1532
+ kind: "NamedType",
1533
+ name: { kind: "Name", value: "String" }
1534
+ }
1535
+ }
1536
+ }
1537
+ },
1538
+ {
1539
+ kind: "VariableDefinition",
1540
+ variable: {
1541
+ kind: "Variable",
1542
+ name: { kind: "Name", value: "includeMedia" }
1543
+ },
1544
+ type: { kind: "NamedType", name: { kind: "Name", value: "Boolean" } }
1545
+ }
1546
+ ],
1547
+ selectionSet: {
1548
+ kind: "SelectionSet",
1549
+ selections: [
1550
+ {
1551
+ kind: "Field",
1552
+ name: { kind: "Name", value: "globalSearch" },
1553
+ arguments: [
1554
+ {
1555
+ kind: "Argument",
1556
+ name: { kind: "Name", value: "query" },
1557
+ value: {
1558
+ kind: "Variable",
1559
+ name: { kind: "Name", value: "query" }
1560
+ }
1561
+ },
1562
+ {
1563
+ kind: "Argument",
1564
+ name: { kind: "Name", value: "limit" },
1565
+ value: {
1566
+ kind: "Variable",
1567
+ name: { kind: "Name", value: "limit" }
1568
+ }
1569
+ },
1570
+ {
1571
+ kind: "Argument",
1572
+ name: { kind: "Name", value: "modelKeys" },
1573
+ value: {
1574
+ kind: "Variable",
1575
+ name: { kind: "Name", value: "modelKeys" }
1576
+ }
1577
+ },
1578
+ {
1579
+ kind: "Argument",
1580
+ name: { kind: "Name", value: "includeMedia" },
1581
+ value: {
1582
+ kind: "Variable",
1583
+ name: { kind: "Name", value: "includeMedia" }
1584
+ }
1585
+ }
1586
+ ],
1587
+ selectionSet: {
1588
+ kind: "SelectionSet",
1589
+ selections: [
1590
+ {
1591
+ kind: "Field",
1592
+ name: { kind: "Name", value: "records" },
1593
+ selectionSet: {
1594
+ kind: "SelectionSet",
1595
+ selections: [
1596
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1597
+ {
1598
+ kind: "Field",
1599
+ name: { kind: "Name", value: "modelKey" }
1600
+ },
1601
+ { kind: "Field", name: { kind: "Name", value: "title" } },
1602
+ {
1603
+ kind: "Field",
1604
+ name: { kind: "Name", value: "naturalKey" }
1605
+ },
1606
+ {
1607
+ kind: "Field",
1608
+ name: { kind: "Name", value: "subtitle" }
1609
+ },
1610
+ {
1611
+ kind: "Field",
1612
+ name: { kind: "Name", value: "updatedAt" }
1613
+ }
1614
+ ]
1615
+ }
1616
+ },
1617
+ {
1618
+ kind: "Field",
1619
+ name: { kind: "Name", value: "media" },
1620
+ selectionSet: {
1621
+ kind: "SelectionSet",
1622
+ selections: [
1623
+ { kind: "Field", name: { kind: "Name", value: "id" } },
1624
+ {
1625
+ kind: "Field",
1626
+ name: { kind: "Name", value: "fileName" }
1627
+ },
1628
+ {
1629
+ kind: "Field",
1630
+ name: { kind: "Name", value: "altText" }
1631
+ },
1632
+ {
1633
+ kind: "Field",
1634
+ name: { kind: "Name", value: "fileUrl" }
1635
+ }
1636
+ ]
1637
+ }
1638
+ }
1639
+ ]
1640
+ }
1641
+ }
1642
+ ]
1643
+ }
1644
+ }
1645
+ ]
1646
+ };
1647
+
5360
1648
  // src/commands/search.ts
5361
1649
  function registerSearchCommands(program2, globalOpts) {
5362
1650
  program2.command("search <query>").description("Search across all records and media").option(
@@ -5426,9 +1714,9 @@ Media (${data.globalSearch.media.length}):`);
5426
1714
 
5427
1715
  // src/commands/init.ts
5428
1716
  import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
5429
- import { writeFile as writeFile2 } from "fs/promises";
5430
- import { resolve as resolve3, join as join4 } from "path";
5431
- import chalk6 from "chalk";
1717
+ import { writeFile } from "fs/promises";
1718
+ import { resolve as resolve2, join as join4 } from "path";
1719
+ import chalk5 from "chalk";
5432
1720
  import inquirer3 from "inquirer";
5433
1721
  var FIELD_DEFAULTS = {
5434
1722
  text: "",
@@ -5518,18 +1806,18 @@ function registerInitCommands(program2, globalOpts) {
5518
1806
  async (key, opts) => {
5519
1807
  const globalFlags = globalOpts();
5520
1808
  const template = generateModelTemplate(key);
5521
- const outDir = resolve3(opts.output);
1809
+ const outDir = resolve2(opts.output);
5522
1810
  if (!existsSync3(outDir)) {
5523
1811
  mkdirSync2(outDir, { recursive: true });
5524
1812
  }
5525
1813
  const ext = opts.ts ? "ts" : "json";
5526
1814
  const filePath = join4(outDir, `${key}.${ext}`);
5527
1815
  const content = opts.ts ? formatAsTypeScript(template) : JSON.stringify(template, null, 2) + "\n";
5528
- await writeFile2(filePath, content, "utf-8");
1816
+ await writeFile(filePath, content, "utf-8");
5529
1817
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
5530
1818
  success(`Created ${filePath}`);
5531
1819
  console.log(
5532
- chalk6.gray(
1820
+ chalk5.gray(
5533
1821
  `
5534
1822
  Edit the file, then run:
5535
1823
  foir models create -f ${filePath}`
@@ -5555,7 +1843,7 @@ Edit the file, then run:
5555
1843
  const models = result.models.items;
5556
1844
  if (models.length === 0) {
5557
1845
  console.log(
5558
- chalk6.yellow(
1846
+ chalk5.yellow(
5559
1847
  "No models found. Create models first with `foir models create`."
5560
1848
  )
5561
1849
  );
@@ -5568,7 +1856,7 @@ Edit the file, then run:
5568
1856
  const found = models.find((m) => m.key === key);
5569
1857
  if (!found) {
5570
1858
  console.error(
5571
- chalk6.red(`Model "${key}" not found.`),
1859
+ chalk5.red(`Model "${key}" not found.`),
5572
1860
  "Available:",
5573
1861
  models.map((m) => m.key).join(", ")
5574
1862
  );
@@ -5596,7 +1884,7 @@ Edit the file, then run:
5596
1884
  console.log("No models selected.");
5597
1885
  return;
5598
1886
  }
5599
- const outDir = resolve3(opts.output);
1887
+ const outDir = resolve2(opts.output);
5600
1888
  if (!existsSync3(outDir)) {
5601
1889
  mkdirSync2(outDir, { recursive: true });
5602
1890
  }
@@ -5606,7 +1894,7 @@ Edit the file, then run:
5606
1894
  const ext = opts.ts ? "ts" : "json";
5607
1895
  const filePath = join4(outDir, `${model.key}.${ext}`);
5608
1896
  const content = opts.ts ? formatAsTypeScript(seed) : JSON.stringify(seed, null, 2) + "\n";
5609
- await writeFile2(filePath, content, "utf-8");
1897
+ await writeFile(filePath, content, "utf-8");
5610
1898
  createdFiles.push(filePath);
5611
1899
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
5612
1900
  success(`Created ${filePath}`);
@@ -5614,7 +1902,7 @@ Edit the file, then run:
5614
1902
  }
5615
1903
  if (!(globalFlags.json || globalFlags.jsonl || globalFlags.quiet)) {
5616
1904
  console.log(
5617
- chalk6.gray(
1905
+ chalk5.gray(
5618
1906
  `
5619
1907
  Edit the files, then run:
5620
1908
  foir records create --dir ${outDir} --publish`
@@ -5629,7 +1917,7 @@ Edit the files, then run:
5629
1917
  }
5630
1918
 
5631
1919
  // src/commands/profiles.ts
5632
- import chalk7 from "chalk";
1920
+ import chalk6 from "chalk";
5633
1921
 
5634
1922
  // src/lib/input.ts
5635
1923
  import inquirer4 from "inquirer";
@@ -5637,9 +1925,9 @@ import inquirer4 from "inquirer";
5637
1925
  // src/lib/config-loader.ts
5638
1926
  import { readFile } from "fs/promises";
5639
1927
  import { pathToFileURL } from "url";
5640
- import { resolve as resolve4 } from "path";
1928
+ import { resolve as resolve3 } from "path";
5641
1929
  async function loadConfig(filePath) {
5642
- const absPath = resolve4(filePath);
1930
+ const absPath = resolve3(filePath);
5643
1931
  if (filePath.endsWith(".ts")) {
5644
1932
  const configModule = await import(pathToFileURL(absPath).href);
5645
1933
  return configModule.default;
@@ -5769,9 +2057,7 @@ function registerProfilesCommand(program2, globalOpts) {
5769
2057
  console.log(`Default profile: ${current}`);
5770
2058
  } else {
5771
2059
  console.log("No default profile set.");
5772
- console.log(
5773
- "Use `foir profiles default <name>` to set one."
5774
- );
2060
+ console.log("Use `foir profiles default <name>` to set one.");
5775
2061
  }
5776
2062
  }
5777
2063
  })
@@ -5799,7 +2085,9 @@ function registerProfilesCommand(program2, globalOpts) {
5799
2085
  opts
5800
2086
  );
5801
2087
  } else {
5802
- console.log(`Profile: ${name}${name === defaultName ? " (default)" : ""}`);
2088
+ console.log(
2089
+ `Profile: ${name}${name === defaultName ? " (default)" : ""}`
2090
+ );
5803
2091
  console.log("\u2500".repeat(40));
5804
2092
  console.log(`Name: ${project.name}`);
5805
2093
  console.log(`ID: ${project.id}`);
@@ -5809,9 +2097,7 @@ function registerProfilesCommand(program2, globalOpts) {
5809
2097
  const resolved = await resolveProjectContext(opts);
5810
2098
  if (!resolved) {
5811
2099
  console.log("No active project context.");
5812
- console.log(
5813
- "Run `foir select-project` to choose a project."
5814
- );
2100
+ console.log("Run `foir select-project` to choose a project.");
5815
2101
  return;
5816
2102
  }
5817
2103
  if (opts.json || opts.jsonl) {
@@ -5837,39 +2123,40 @@ function registerProfilesCommand(program2, globalOpts) {
5837
2123
  })
5838
2124
  );
5839
2125
  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}".`)
2126
+ withErrorHandler(
2127
+ globalOpts,
2128
+ async (name, cmdOpts) => {
2129
+ const opts = globalOpts();
2130
+ const project = await getProjectContext(name);
2131
+ if (!project) {
2132
+ throw new Error(
2133
+ `Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
2134
+ );
2135
+ }
2136
+ const confirmed = await confirmAction(
2137
+ `Delete profile "${name}" (${project.name})?`,
2138
+ { confirm: !!cmdOpts.confirm }
5862
2139
  );
2140
+ if (!confirmed) {
2141
+ console.log("Aborted.");
2142
+ return;
2143
+ }
2144
+ await deleteProfile(name);
2145
+ if (opts.json || opts.jsonl) {
2146
+ formatOutput({ deleted: name }, opts);
2147
+ } else {
2148
+ console.log(chalk6.green(`Deleted profile "${name}".`));
2149
+ }
5863
2150
  }
5864
- })
2151
+ )
5865
2152
  );
5866
2153
  }
5867
2154
 
5868
2155
  // src/commands/register-commands.ts
5869
2156
  import { readFileSync, readdirSync } from "fs";
5870
- import { resolve as resolve5, dirname as dirname5 } from "path";
2157
+ import { resolve as resolve4, dirname as dirname4 } from "path";
5871
2158
  import { fileURLToPath } from "url";
5872
- import chalk8 from "chalk";
2159
+ import chalk7 from "chalk";
5873
2160
 
5874
2161
  // ../command-registry/src/command-map.ts
5875
2162
  var COMMANDS = [
@@ -7468,13 +3755,13 @@ function createSchemaEngine(sdl) {
7468
3755
 
7469
3756
  // src/commands/register-commands.ts
7470
3757
  var __filename = fileURLToPath(import.meta.url);
7471
- var __dirname = dirname5(__filename);
3758
+ var __dirname = dirname4(__filename);
7472
3759
  function loadSchemaSDL() {
7473
- const bundledPath = resolve5(__dirname, "schema.graphql");
3760
+ const bundledPath = resolve4(__dirname, "schema.graphql");
7474
3761
  try {
7475
3762
  return readFileSync(bundledPath, "utf-8");
7476
3763
  } catch {
7477
- const monorepoPath = resolve5(
3764
+ const monorepoPath = resolve4(
7478
3765
  __dirname,
7479
3766
  "../../../graphql-core/schema.graphql"
7480
3767
  );
@@ -7630,11 +3917,11 @@ function registerDynamicCommands(program2, globalOpts) {
7630
3917
  );
7631
3918
  Object.assign(variables, coerced);
7632
3919
  if (flags.dir && entry.acceptsInput) {
7633
- const dirPath = resolve5(String(flags.dir));
3920
+ const dirPath = resolve4(String(flags.dir));
7634
3921
  const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
7635
3922
  if (files.length === 0) {
7636
3923
  console.error(
7637
- chalk8.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
3924
+ chalk7.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
7638
3925
  );
7639
3926
  return;
7640
3927
  }
@@ -7642,7 +3929,7 @@ function registerDynamicCommands(program2, globalOpts) {
7642
3929
  let updated = 0;
7643
3930
  let failed = 0;
7644
3931
  for (const file of files) {
7645
- const filePath = resolve5(dirPath, file);
3932
+ const filePath = resolve4(dirPath, file);
7646
3933
  const fileData = await parseInputData({ file: filePath });
7647
3934
  const argName = entry.inputArgName ?? "input";
7648
3935
  const fileVars = { ...variables, [argName]: fileData };
@@ -7679,19 +3966,19 @@ function registerDynamicCommands(program2, globalOpts) {
7679
3966
  } catch (updateErr) {
7680
3967
  failed++;
7681
3968
  const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
7682
- console.error(chalk8.red(`\u2717 ${label}:`), msg2);
3969
+ console.error(chalk7.red(`\u2717 ${label}:`), msg2);
7683
3970
  continue;
7684
3971
  }
7685
3972
  }
7686
3973
  failed++;
7687
3974
  const msg = err instanceof Error ? err.message : String(err);
7688
- console.error(chalk8.red(`\u2717 ${label}:`), msg);
3975
+ console.error(chalk7.red(`\u2717 ${label}:`), msg);
7689
3976
  }
7690
3977
  }
7691
3978
  if (!(opts.json || opts.jsonl || opts.quiet)) {
7692
3979
  console.log("");
7693
3980
  console.log(
7694
- chalk8.bold(
3981
+ chalk7.bold(
7695
3982
  `Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
7696
3983
  )
7697
3984
  );
@@ -7867,7 +4154,7 @@ function registerDynamicCommands(program2, globalOpts) {
7867
4154
  }
7868
4155
  } else if (!(opts.json || opts.jsonl || opts.quiet)) {
7869
4156
  console.error(
7870
- chalk8.yellow(
4157
+ chalk7.yellow(
7871
4158
  "\u26A0 Could not auto-publish: no version found in response"
7872
4159
  )
7873
4160
  );
@@ -7890,8 +4177,8 @@ function autoColumns(items) {
7890
4177
 
7891
4178
  // src/cli.ts
7892
4179
  var __filename2 = fileURLToPath2(import.meta.url);
7893
- var __dirname2 = dirname6(__filename2);
7894
- config({ path: resolve6(__dirname2, "../.env.local") });
4180
+ var __dirname2 = dirname5(__filename2);
4181
+ config({ path: resolve5(__dirname2, "../.env.local") });
7895
4182
  var require2 = createRequire(import.meta.url);
7896
4183
  var { version } = require2("../package.json");
7897
4184
  var program = new Command();
@@ -7913,7 +4200,6 @@ registerWhoamiCommand(program, getGlobalOpts);
7913
4200
  registerProfilesCommand(program, getGlobalOpts);
7914
4201
  registerMediaCommands(program, getGlobalOpts);
7915
4202
  registerSearchCommands(program, getGlobalOpts);
7916
- registerPullCommand(program, getGlobalOpts);
7917
4203
  registerCreateExtensionCommand(program, getGlobalOpts);
7918
4204
  registerInitCommands(program, getGlobalOpts);
7919
4205
  registerDynamicCommands(program, getGlobalOpts);