@eide/foir-cli 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-L642MYIL.js +47 -0
- package/dist/cli.js +1127 -3487
- package/dist/config/types.d.ts +21 -30
- package/dist/loader-7VE4OF73.js +10 -0
- package/dist/schema.graphql +30 -11
- package/package.json +20 -19
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findConfigFile,
|
|
4
|
+
loadConfigFile
|
|
5
|
+
} from "./chunk-L642MYIL.js";
|
|
2
6
|
|
|
3
7
|
// src/cli.ts
|
|
4
8
|
import { config } from "dotenv";
|
|
5
|
-
import { resolve as
|
|
9
|
+
import { resolve as resolve6, dirname as dirname6 } from "path";
|
|
6
10
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
11
|
import { createRequire } from "module";
|
|
8
12
|
import { Command } from "commander";
|
|
@@ -44,8 +48,8 @@ async function getCredentials() {
|
|
|
44
48
|
}
|
|
45
49
|
async function writeCredentials(credentials) {
|
|
46
50
|
await ensureDir(getCredentialsDir());
|
|
47
|
-
const
|
|
48
|
-
await fs.writeFile(
|
|
51
|
+
const path3 = getCredentialsPath();
|
|
52
|
+
await fs.writeFile(path3, JSON.stringify(credentials, null, 2), {
|
|
49
53
|
mode: 384
|
|
50
54
|
});
|
|
51
55
|
}
|
|
@@ -95,9 +99,19 @@ function getProjectContextDir() {
|
|
|
95
99
|
function getProjectContextPath() {
|
|
96
100
|
return join(getProjectContextDir(), "project.json");
|
|
97
101
|
}
|
|
98
|
-
|
|
102
|
+
function getProfilesDir() {
|
|
103
|
+
return join(getProjectContextDir(), "profiles");
|
|
104
|
+
}
|
|
105
|
+
function getProfilePath(name) {
|
|
106
|
+
return join(getProfilesDir(), `${name}.json`);
|
|
107
|
+
}
|
|
108
|
+
function getDefaultProfilePath() {
|
|
109
|
+
return join(getProjectContextDir(), "default-profile");
|
|
110
|
+
}
|
|
111
|
+
async function getProjectContext(profileName) {
|
|
112
|
+
const filePath = profileName ? getProfilePath(profileName) : getProjectContextPath();
|
|
99
113
|
try {
|
|
100
|
-
const content = await fs.readFile(
|
|
114
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
101
115
|
return JSON.parse(content);
|
|
102
116
|
} catch (error) {
|
|
103
117
|
if (error.code === "ENOENT") {
|
|
@@ -106,13 +120,135 @@ async function getProjectContext() {
|
|
|
106
120
|
throw error;
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
|
-
async function writeProjectContext(project) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
async function writeProjectContext(project, profileName) {
|
|
124
|
+
if (profileName) {
|
|
125
|
+
await ensureDir(getProfilesDir());
|
|
126
|
+
const filePath = getProfilePath(profileName);
|
|
127
|
+
await fs.writeFile(filePath, JSON.stringify(project, null, 2), {
|
|
128
|
+
mode: 384
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
const dir = getProjectContextDir();
|
|
132
|
+
await ensureDir(dir);
|
|
133
|
+
const filePath = getProjectContextPath();
|
|
134
|
+
await fs.writeFile(filePath, JSON.stringify(project, null, 2), {
|
|
135
|
+
mode: 384
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function listProfiles() {
|
|
140
|
+
const dir = getProfilesDir();
|
|
141
|
+
let files;
|
|
142
|
+
try {
|
|
143
|
+
files = await fs.readdir(dir);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error.code === "ENOENT") {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
const profiles = [];
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
if (!file.endsWith(".json")) continue;
|
|
153
|
+
const name = file.replace(/\.json$/, "");
|
|
154
|
+
try {
|
|
155
|
+
const content = await fs.readFile(join(dir, file), "utf-8");
|
|
156
|
+
profiles.push({ name, project: JSON.parse(content) });
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return profiles;
|
|
161
|
+
}
|
|
162
|
+
async function getDefaultProfile() {
|
|
163
|
+
try {
|
|
164
|
+
const content = await fs.readFile(getDefaultProfilePath(), "utf-8");
|
|
165
|
+
return content.trim() || null;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code === "ENOENT") {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function setDefaultProfile(name) {
|
|
174
|
+
await ensureDir(getProjectContextDir());
|
|
175
|
+
await fs.writeFile(getDefaultProfilePath(), name, { mode: 384 });
|
|
176
|
+
}
|
|
177
|
+
async function deleteProfile(name) {
|
|
178
|
+
try {
|
|
179
|
+
await fs.unlink(getProfilePath(name));
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error.code !== "ENOENT") {
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const defaultProfile = await getDefaultProfile();
|
|
186
|
+
if (defaultProfile === name) {
|
|
187
|
+
try {
|
|
188
|
+
await fs.unlink(getDefaultProfilePath());
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error.code !== "ENOENT") {
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function resolveProjectContext(options) {
|
|
197
|
+
if (options?.project) {
|
|
198
|
+
const project2 = await getProjectContext(options.project);
|
|
199
|
+
if (project2) {
|
|
200
|
+
return {
|
|
201
|
+
project: project2,
|
|
202
|
+
source: "--project flag",
|
|
203
|
+
profileName: options.project
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Profile "${options.project}" not found. Run \`foir profiles list\` to see available profiles.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const envProject = process.env.FOIR_PROJECT;
|
|
211
|
+
if (envProject) {
|
|
212
|
+
const project2 = await getProjectContext(envProject);
|
|
213
|
+
if (project2) {
|
|
214
|
+
return {
|
|
215
|
+
project: project2,
|
|
216
|
+
source: "FOIR_PROJECT env",
|
|
217
|
+
profileName: envProject
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
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
|
+
const defaultProfile = await getDefaultProfile();
|
|
237
|
+
if (defaultProfile) {
|
|
238
|
+
const project2 = await getProjectContext(defaultProfile);
|
|
239
|
+
if (project2) {
|
|
240
|
+
return {
|
|
241
|
+
project: project2,
|
|
242
|
+
source: "default profile",
|
|
243
|
+
profileName: defaultProfile
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const project = await getProjectContext();
|
|
248
|
+
if (project) {
|
|
249
|
+
return { project, source: "project.json" };
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
116
252
|
}
|
|
117
253
|
|
|
118
254
|
// src/lib/config.ts
|
|
@@ -226,13 +362,13 @@ function withErrorHandler(optsFn, fn) {
|
|
|
226
362
|
// src/commands/login.ts
|
|
227
363
|
async function findAvailablePort(start, end) {
|
|
228
364
|
for (let port = start; port <= end; port++) {
|
|
229
|
-
const available = await new Promise((
|
|
365
|
+
const available = await new Promise((resolve7) => {
|
|
230
366
|
const server = http.createServer();
|
|
231
367
|
server.listen(port, () => {
|
|
232
368
|
server.close();
|
|
233
|
-
|
|
369
|
+
resolve7(true);
|
|
234
370
|
});
|
|
235
|
-
server.on("error", () =>
|
|
371
|
+
server.on("error", () => resolve7(false));
|
|
236
372
|
});
|
|
237
373
|
if (available) return port;
|
|
238
374
|
}
|
|
@@ -270,7 +406,7 @@ async function loginAction(globalOpts) {
|
|
|
270
406
|
const state = crypto.randomBytes(16).toString("hex");
|
|
271
407
|
const port = await findAvailablePort(9876, 9900);
|
|
272
408
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
273
|
-
const authCode = await new Promise((
|
|
409
|
+
const authCode = await new Promise((resolve7, reject) => {
|
|
274
410
|
const server = http.createServer((req, res) => {
|
|
275
411
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
276
412
|
if (url.pathname === "/callback") {
|
|
@@ -303,7 +439,7 @@ async function loginAction(globalOpts) {
|
|
|
303
439
|
`<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>`
|
|
304
440
|
);
|
|
305
441
|
server.close();
|
|
306
|
-
|
|
442
|
+
resolve7(code);
|
|
307
443
|
}
|
|
308
444
|
});
|
|
309
445
|
server.listen(port);
|
|
@@ -469,87 +605,99 @@ async function provisionApiKey(apiUrl, accessToken, projectId, tenantId) {
|
|
|
469
605
|
return { apiKey: created.plainKey, apiKeyId: created.apiKey.id };
|
|
470
606
|
}
|
|
471
607
|
function registerSelectProjectCommand(program2, globalOpts) {
|
|
472
|
-
program2.command("select-project").description("Choose which project to work with").option("--project-id <id>", "Project ID to select directly").action(
|
|
473
|
-
withErrorHandler(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const sessionContext = await fetchSessionContext(
|
|
483
|
-
apiUrl,
|
|
484
|
-
credentials.accessToken
|
|
485
|
-
);
|
|
486
|
-
const { availableTenants: tenants, availableProjects: projects } = sessionContext;
|
|
487
|
-
if (projects.length === 0) {
|
|
488
|
-
console.log("No projects found. Create one in the platform first.");
|
|
489
|
-
throw new Error("No projects available");
|
|
490
|
-
}
|
|
491
|
-
const tenantNameMap = new Map(tenants.map((t) => [t.id, t.name]));
|
|
492
|
-
let selectedProject;
|
|
493
|
-
if (cmdOpts.projectId) {
|
|
494
|
-
const found = projects.find((p) => p.id === cmdOpts.projectId);
|
|
495
|
-
if (!found) {
|
|
496
|
-
console.log(`Project with ID "${cmdOpts.projectId}" not found.`);
|
|
497
|
-
console.log("Available projects:");
|
|
498
|
-
for (const p of projects) {
|
|
499
|
-
console.log(` - ${p.name} (${p.id})`);
|
|
500
|
-
}
|
|
501
|
-
throw new Error("Project not found");
|
|
608
|
+
program2.command("select-project").description("Choose which project to work with").option("--project-id <id>", "Project ID to select directly").option("--save-as <name>", "Save as a named profile").action(
|
|
609
|
+
withErrorHandler(
|
|
610
|
+
globalOpts,
|
|
611
|
+
async (cmdOpts) => {
|
|
612
|
+
const opts = globalOpts();
|
|
613
|
+
const apiUrl = getApiUrl(opts);
|
|
614
|
+
const credentials = await getCredentials();
|
|
615
|
+
if (!credentials) {
|
|
616
|
+
console.log("Not logged in. Run `foir login` first.");
|
|
617
|
+
throw new Error("Not authenticated");
|
|
502
618
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const key = tenantNameMap.get(p.tenantId) ?? "Unknown";
|
|
508
|
-
if (!acc[key]) acc[key] = [];
|
|
509
|
-
acc[key].push(p);
|
|
510
|
-
return acc;
|
|
511
|
-
},
|
|
512
|
-
{}
|
|
619
|
+
console.log("Fetching your projects...\n");
|
|
620
|
+
const sessionContext = await fetchSessionContext(
|
|
621
|
+
apiUrl,
|
|
622
|
+
credentials.accessToken
|
|
513
623
|
);
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
624
|
+
const { availableTenants: tenants, availableProjects: projects } = sessionContext;
|
|
625
|
+
if (projects.length === 0) {
|
|
626
|
+
console.log("No projects found. Create one in the platform first.");
|
|
627
|
+
throw new Error("No projects available");
|
|
628
|
+
}
|
|
629
|
+
const tenantNameMap = new Map(tenants.map((t) => [t.id, t.name]));
|
|
630
|
+
let selectedProject;
|
|
631
|
+
if (cmdOpts.projectId) {
|
|
632
|
+
const found = projects.find((p) => p.id === cmdOpts.projectId);
|
|
633
|
+
if (!found) {
|
|
634
|
+
console.log(`Project with ID "${cmdOpts.projectId}" not found.`);
|
|
635
|
+
console.log("Available projects:");
|
|
636
|
+
for (const p of projects) {
|
|
637
|
+
console.log(` - ${p.name} (${p.id})`);
|
|
638
|
+
}
|
|
639
|
+
throw new Error("Project not found");
|
|
640
|
+
}
|
|
641
|
+
selectedProject = found;
|
|
642
|
+
} else {
|
|
643
|
+
const byTenant = projects.reduce(
|
|
644
|
+
(acc, p) => {
|
|
645
|
+
const key = tenantNameMap.get(p.tenantId) ?? "Unknown";
|
|
646
|
+
if (!acc[key]) acc[key] = [];
|
|
647
|
+
acc[key].push(p);
|
|
648
|
+
return acc;
|
|
649
|
+
},
|
|
650
|
+
{}
|
|
651
|
+
);
|
|
652
|
+
const choices = Object.entries(byTenant).flatMap(
|
|
653
|
+
([tenantName, tenantProjects]) => [
|
|
654
|
+
new inquirer.Separator(`\u2500\u2500 ${tenantName} \u2500\u2500`),
|
|
655
|
+
...tenantProjects.map((p) => ({
|
|
656
|
+
name: ` ${p.name}`,
|
|
657
|
+
value: p.id,
|
|
658
|
+
short: p.name
|
|
659
|
+
}))
|
|
660
|
+
]
|
|
661
|
+
);
|
|
662
|
+
const { projectId } = await inquirer.prompt([
|
|
663
|
+
{
|
|
664
|
+
type: "list",
|
|
665
|
+
name: "projectId",
|
|
666
|
+
message: "Select a project:",
|
|
667
|
+
choices
|
|
668
|
+
}
|
|
669
|
+
]);
|
|
670
|
+
selectedProject = projects.find((p) => p.id === projectId);
|
|
671
|
+
}
|
|
672
|
+
console.log("\nProvisioning API key for CLI access...");
|
|
673
|
+
const { apiKey, apiKeyId } = await provisionApiKey(
|
|
674
|
+
apiUrl,
|
|
675
|
+
credentials.accessToken,
|
|
676
|
+
selectedProject.id,
|
|
677
|
+
selectedProject.tenantId
|
|
523
678
|
);
|
|
524
|
-
|
|
679
|
+
await writeProjectContext(
|
|
525
680
|
{
|
|
526
|
-
|
|
527
|
-
name:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const { apiKey, apiKeyId } = await provisionApiKey(
|
|
536
|
-
apiUrl,
|
|
537
|
-
credentials.accessToken,
|
|
538
|
-
selectedProject.id,
|
|
539
|
-
selectedProject.tenantId
|
|
540
|
-
);
|
|
541
|
-
await writeProjectContext({
|
|
542
|
-
id: selectedProject.id,
|
|
543
|
-
name: selectedProject.name,
|
|
544
|
-
tenantId: selectedProject.tenantId,
|
|
545
|
-
apiKey,
|
|
546
|
-
apiKeyId
|
|
547
|
-
});
|
|
548
|
-
console.log(`
|
|
681
|
+
id: selectedProject.id,
|
|
682
|
+
name: selectedProject.name,
|
|
683
|
+
tenantId: selectedProject.tenantId,
|
|
684
|
+
apiKey,
|
|
685
|
+
apiKeyId
|
|
686
|
+
},
|
|
687
|
+
cmdOpts.saveAs
|
|
688
|
+
);
|
|
689
|
+
console.log(`
|
|
549
690
|
\u2713 Selected project: ${selectedProject.name}`);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
691
|
+
console.log("\u2713 API key provisioned for CLI access");
|
|
692
|
+
if (cmdOpts.saveAs) {
|
|
693
|
+
console.log(
|
|
694
|
+
` Saved as profile "${cmdOpts.saveAs}". Use --project ${cmdOpts.saveAs} or set as default with \`foir profiles default ${cmdOpts.saveAs}\``
|
|
695
|
+
);
|
|
696
|
+
} else {
|
|
697
|
+
console.log(" (stored in .foir/project.json for this repository)");
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
)
|
|
553
701
|
);
|
|
554
702
|
}
|
|
555
703
|
|
|
@@ -653,14 +801,18 @@ function registerWhoamiCommand(program2, globalOpts) {
|
|
|
653
801
|
throw new Error("Not authenticated");
|
|
654
802
|
}
|
|
655
803
|
const expired = isTokenExpired(credentials);
|
|
656
|
-
const
|
|
804
|
+
const resolved = await resolveProjectContext(opts);
|
|
657
805
|
if (opts.json || opts.jsonl) {
|
|
658
806
|
formatOutput(
|
|
659
807
|
{
|
|
660
808
|
authenticated: true,
|
|
661
809
|
tokenValid: !expired,
|
|
662
810
|
user: credentials.user,
|
|
663
|
-
selectedProject:
|
|
811
|
+
selectedProject: resolved ? {
|
|
812
|
+
...resolved.project,
|
|
813
|
+
profile: resolved.profileName ?? null,
|
|
814
|
+
source: resolved.source
|
|
815
|
+
} : null
|
|
664
816
|
},
|
|
665
817
|
opts
|
|
666
818
|
);
|
|
@@ -673,13 +825,18 @@ function registerWhoamiCommand(program2, globalOpts) {
|
|
|
673
825
|
);
|
|
674
826
|
console.log(`User ID: ${credentials.user.id}`);
|
|
675
827
|
console.log(`Token: ${expired ? "\u26A0 Expired" : "\u2713 Valid"}`);
|
|
676
|
-
if (
|
|
828
|
+
if (resolved) {
|
|
677
829
|
console.log("");
|
|
678
830
|
console.log("Selected Project (this repo)");
|
|
679
831
|
console.log("\u2500".repeat(40));
|
|
680
|
-
console.log(`Name: ${
|
|
681
|
-
console.log(`ID: ${
|
|
682
|
-
console.log(`Tenant ID: ${
|
|
832
|
+
console.log(`Name: ${resolved.project.name}`);
|
|
833
|
+
console.log(`ID: ${resolved.project.id}`);
|
|
834
|
+
console.log(`Tenant ID: ${resolved.project.tenantId}`);
|
|
835
|
+
if (resolved.profileName) {
|
|
836
|
+
console.log(
|
|
837
|
+
`Profile: ${resolved.profileName} (from ${resolved.source})`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
683
840
|
} else {
|
|
684
841
|
console.log("");
|
|
685
842
|
console.log("No project selected for this repository.");
|
|
@@ -715,10 +872,10 @@ async function createClient(options) {
|
|
|
715
872
|
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
716
873
|
}
|
|
717
874
|
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
720
|
-
headers["x-tenant-id"] = project.tenantId;
|
|
721
|
-
headers["x-project-id"] = project.id;
|
|
875
|
+
const resolved = await resolveProjectContext(options);
|
|
876
|
+
if (resolved) {
|
|
877
|
+
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
878
|
+
headers["x-project-id"] = resolved.project.id;
|
|
722
879
|
}
|
|
723
880
|
return new GraphQLClient(endpoint, { headers });
|
|
724
881
|
}
|
|
@@ -738,10 +895,10 @@ async function getRestAuth(options) {
|
|
|
738
895
|
throw new Error("Session expired. Run `foir login` to re-authenticate.");
|
|
739
896
|
}
|
|
740
897
|
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
|
741
|
-
const
|
|
742
|
-
if (
|
|
743
|
-
headers["x-tenant-id"] = project.tenantId;
|
|
744
|
-
headers["x-project-id"] = project.id;
|
|
898
|
+
const resolved = await resolveProjectContext(options);
|
|
899
|
+
if (resolved) {
|
|
900
|
+
headers["x-tenant-id"] = resolved.project.tenantId;
|
|
901
|
+
headers["x-project-id"] = resolved.project.id;
|
|
745
902
|
}
|
|
746
903
|
return { apiUrl, headers };
|
|
747
904
|
}
|
|
@@ -784,54 +941,11 @@ function registerMediaCommands(program2, globalOpts) {
|
|
|
784
941
|
}
|
|
785
942
|
|
|
786
943
|
// src/commands/pull.ts
|
|
787
|
-
import { resolve
|
|
944
|
+
import { resolve } from "path";
|
|
788
945
|
import chalk4 from "chalk";
|
|
789
946
|
|
|
790
947
|
// src/config/pull-config.ts
|
|
791
|
-
|
|
792
|
-
import { pathToFileURL } from "url";
|
|
793
|
-
import { existsSync } from "fs";
|
|
794
|
-
var CONFIG_FILE_NAMES = [
|
|
795
|
-
"foir.config.ts",
|
|
796
|
-
"foir.config.js",
|
|
797
|
-
"foir.config.mjs",
|
|
798
|
-
".foirrc.ts",
|
|
799
|
-
".foirrc.js",
|
|
800
|
-
".foirrc.mjs"
|
|
801
|
-
];
|
|
802
|
-
var DEFAULT_TYPES_DIR = "./src/generated/types";
|
|
803
|
-
var DEFAULT_DOCS_DIR = "./src/generated/documents";
|
|
804
|
-
var ALL_DOMAINS = {
|
|
805
|
-
auth: true,
|
|
806
|
-
authProviders: true,
|
|
807
|
-
files: true,
|
|
808
|
-
sync: true,
|
|
809
|
-
notifications: true,
|
|
810
|
-
operations: true,
|
|
811
|
-
schedules: true,
|
|
812
|
-
sharing: true,
|
|
813
|
-
embeddings: true,
|
|
814
|
-
analytics: true
|
|
815
|
-
};
|
|
816
|
-
function findConfigFile(explicitPath) {
|
|
817
|
-
if (explicitPath) {
|
|
818
|
-
const abs = resolve(explicitPath);
|
|
819
|
-
if (existsSync(abs)) return abs;
|
|
820
|
-
throw new Error(`Config file not found: ${explicitPath}`);
|
|
821
|
-
}
|
|
822
|
-
const cwd = process.cwd();
|
|
823
|
-
for (const name of CONFIG_FILE_NAMES) {
|
|
824
|
-
const candidate = resolve(cwd, name);
|
|
825
|
-
if (existsSync(candidate)) return candidate;
|
|
826
|
-
}
|
|
827
|
-
return null;
|
|
828
|
-
}
|
|
829
|
-
async function loadConfigFile(filePath) {
|
|
830
|
-
const url = pathToFileURL(filePath).href;
|
|
831
|
-
const mod = await import(url);
|
|
832
|
-
const config2 = mod.default ?? mod;
|
|
833
|
-
return config2;
|
|
834
|
-
}
|
|
948
|
+
var DEFAULT_OUTPUT_DIR = "./src/generated";
|
|
835
949
|
async function loadPullConfig(flags) {
|
|
836
950
|
let fileConfig = {};
|
|
837
951
|
const configPath = findConfigFile(flags.config);
|
|
@@ -839,46 +953,28 @@ async function loadPullConfig(flags) {
|
|
|
839
953
|
const full = await loadConfigFile(configPath);
|
|
840
954
|
fileConfig = full.pull ?? {};
|
|
841
955
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const output = {
|
|
851
|
-
types,
|
|
852
|
-
documents,
|
|
853
|
-
operations,
|
|
854
|
-
...hooks ? { hooks } : {},
|
|
855
|
-
...loaders ? { loaders } : {},
|
|
856
|
-
...swift ? { swift } : {}
|
|
857
|
-
};
|
|
858
|
-
let domains;
|
|
859
|
-
if (fileConfig.domains === false) {
|
|
860
|
-
domains = {
|
|
861
|
-
auth: false,
|
|
862
|
-
authProviders: false,
|
|
863
|
-
files: false,
|
|
864
|
-
sync: false,
|
|
865
|
-
notifications: false,
|
|
866
|
-
operations: false,
|
|
867
|
-
schedules: false,
|
|
868
|
-
sharing: false,
|
|
869
|
-
embeddings: false,
|
|
870
|
-
analytics: false
|
|
871
|
-
};
|
|
872
|
-
} else if (typeof fileConfig.domains === "object") {
|
|
873
|
-
domains = { ...ALL_DOMAINS, ...fileConfig.domains };
|
|
956
|
+
let outputDir;
|
|
957
|
+
if (flags.out) {
|
|
958
|
+
outputDir = flags.out;
|
|
959
|
+
} else if (typeof fileConfig.output === "string") {
|
|
960
|
+
outputDir = fileConfig.output;
|
|
961
|
+
} else if (typeof fileConfig.output === "object" && fileConfig.output?.types) {
|
|
962
|
+
const legacyTypes = fileConfig.output.types;
|
|
963
|
+
outputDir = legacyTypes.replace(/\/types\/?$/, "") || DEFAULT_OUTPUT_DIR;
|
|
874
964
|
} else {
|
|
875
|
-
|
|
965
|
+
outputDir = DEFAULT_OUTPUT_DIR;
|
|
876
966
|
}
|
|
877
967
|
const only = flags.only ? flags.only.split(",").map((s) => s.trim()) : fileConfig.only ?? [];
|
|
878
968
|
const includeInline = fileConfig.includeInline ?? true;
|
|
879
969
|
const prettier = flags.noPrettier ? false : fileConfig.prettier ?? true;
|
|
880
970
|
const dryRun = flags.dryRun ?? false;
|
|
881
|
-
return {
|
|
971
|
+
return {
|
|
972
|
+
output: { types: outputDir },
|
|
973
|
+
only,
|
|
974
|
+
includeInline,
|
|
975
|
+
prettier,
|
|
976
|
+
dryRun
|
|
977
|
+
};
|
|
882
978
|
}
|
|
883
979
|
|
|
884
980
|
// src/graphql/generated.ts
|
|
@@ -1480,10 +1576,6 @@ function toPascalCase(str) {
|
|
|
1480
1576
|
const camel = toCamelCase(str);
|
|
1481
1577
|
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
1482
1578
|
}
|
|
1483
|
-
function toUpperSnakeCase(str) {
|
|
1484
|
-
if (!str) return "UNKNOWN";
|
|
1485
|
-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
1486
|
-
}
|
|
1487
1579
|
function sanitizeFieldName(key) {
|
|
1488
1580
|
if (!key) return "unknown";
|
|
1489
1581
|
const camel = toCamelCase(key);
|
|
@@ -1545,542 +1637,107 @@ function generateFieldDef(field) {
|
|
|
1545
1637
|
return `{ ${parts.join(", ")} }`;
|
|
1546
1638
|
}
|
|
1547
1639
|
|
|
1548
|
-
// src/codegen/generators/
|
|
1549
|
-
function
|
|
1550
|
-
return
|
|
1551
|
-
* Field Types and Definitions
|
|
1552
|
-
*
|
|
1553
|
-
* Value types, filter types, and field definition types.
|
|
1554
|
-
*
|
|
1555
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
1556
|
-
*/
|
|
1557
|
-
|
|
1558
|
-
// =============================================================================
|
|
1559
|
-
// JSON-SAFE BASE TYPE
|
|
1560
|
-
// =============================================================================
|
|
1561
|
-
|
|
1562
|
-
/** Recursive JSON-serializable value type. Safe for TanStack Router, Remix, RSC, and other serialization boundaries. */
|
|
1563
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
1564
|
-
|
|
1565
|
-
// =============================================================================
|
|
1566
|
-
// VALUE TYPES
|
|
1567
|
-
// =============================================================================
|
|
1568
|
-
|
|
1569
|
-
/** Rich text content (Lexical JSON format) */
|
|
1570
|
-
export type RichtextValue = JsonValue;
|
|
1571
|
-
|
|
1572
|
-
/** Currency value with amount and ISO 4217 code */
|
|
1573
|
-
export interface CurrencyValue {
|
|
1574
|
-
amount: number;
|
|
1575
|
-
currency: string;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
/** Image reference with metadata */
|
|
1579
|
-
export interface ImageValue {
|
|
1580
|
-
id: string;
|
|
1581
|
-
url: string;
|
|
1582
|
-
alt?: string;
|
|
1583
|
-
width?: number;
|
|
1584
|
-
height?: number;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/** Video reference with metadata */
|
|
1588
|
-
export interface VideoValue {
|
|
1589
|
-
id: string;
|
|
1590
|
-
url: string;
|
|
1591
|
-
thumbnail?: string;
|
|
1592
|
-
duration?: number;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
/** File reference with metadata */
|
|
1596
|
-
export interface FileValue {
|
|
1597
|
-
id: string;
|
|
1598
|
-
url: string;
|
|
1599
|
-
name: string;
|
|
1600
|
-
size: number;
|
|
1601
|
-
mimeType: string;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
/** Link value (internal reference or external URL) */
|
|
1605
|
-
export interface LinkValue {
|
|
1606
|
-
type: 'entity' | 'url';
|
|
1607
|
-
entity?: LinkRecordReference;
|
|
1608
|
-
url?: string;
|
|
1609
|
-
target?: '_self' | '_blank';
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
/** Record reference for internal links */
|
|
1613
|
-
export interface LinkRecordReference {
|
|
1614
|
-
modelKey: string;
|
|
1615
|
-
naturalKey: string;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
/** Record reference value (generic TPreview for typed preview data when reference target is known) */
|
|
1619
|
-
export interface ReferenceValue<TPreview = Record<string, JsonValue>> {
|
|
1620
|
-
_type: 'reference';
|
|
1621
|
-
_schema: string;
|
|
1622
|
-
naturalKey: string;
|
|
1623
|
-
_preview?: TPreview;
|
|
1640
|
+
// src/codegen/generators/model-types.ts
|
|
1641
|
+
function isInlineOnlyModel(model) {
|
|
1642
|
+
return model.config.inline && !model.config.records;
|
|
1624
1643
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1644
|
+
function generateModelTypes(model, allModels) {
|
|
1645
|
+
const typeName = toPascalCase(model.key);
|
|
1646
|
+
const configName = toCamelCase(model.key) + "Config";
|
|
1647
|
+
const fields = model.fields ?? [];
|
|
1648
|
+
const fieldTypeImports = getFieldTypeImportsForFields(fields);
|
|
1649
|
+
const inlineSchemaRefs = getInlineSchemaReferences(fields);
|
|
1650
|
+
const referenceModelRefs = getReferenceTypeModelRefs(fields);
|
|
1651
|
+
let code = buildImportStatements(
|
|
1652
|
+
model,
|
|
1653
|
+
fieldTypeImports,
|
|
1654
|
+
inlineSchemaRefs,
|
|
1655
|
+
referenceModelRefs,
|
|
1656
|
+
allModels
|
|
1657
|
+
);
|
|
1658
|
+
if (isInlineOnlyModel(model)) {
|
|
1659
|
+
code += generateDataInterface(model, fields, typeName, allModels);
|
|
1660
|
+
return code;
|
|
1661
|
+
}
|
|
1662
|
+
code += generateConfigObject(model, fields, configName);
|
|
1663
|
+
code += "\n";
|
|
1664
|
+
code += generateDataInterface(model, fields, typeName + "Data", allModels);
|
|
1665
|
+
return code;
|
|
1631
1666
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1667
|
+
function buildImportStatements(model, fieldTypeImports, inlineSchemaRefs, referenceModelRefs, allModels) {
|
|
1668
|
+
const imports = [];
|
|
1669
|
+
if (fieldTypeImports.size > 0) {
|
|
1670
|
+
const types = Array.from(fieldTypeImports).sort().join(", ");
|
|
1671
|
+
imports.push(`import type { ${types} } from '@eide/foir-client';`);
|
|
1672
|
+
}
|
|
1673
|
+
const allModelRefKeys = /* @__PURE__ */ new Set([
|
|
1674
|
+
...inlineSchemaRefs,
|
|
1675
|
+
...referenceModelRefs
|
|
1676
|
+
]);
|
|
1677
|
+
for (const refKey of allModelRefKeys) {
|
|
1678
|
+
if (refKey === model.key) continue;
|
|
1679
|
+
const refModel = allModels.find((m) => m.key === refKey);
|
|
1680
|
+
if (refModel) {
|
|
1681
|
+
const refTypeName = isInlineOnlyModel(refModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
|
|
1682
|
+
imports.push(`import type { ${refTypeName} } from './${refKey}.js';`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return imports.length > 0 ? imports.join("\n") + "\n\n" : "";
|
|
1643
1686
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
containsAny?: T[];
|
|
1699
|
-
containsAll?: T[];
|
|
1700
|
-
isNull?: boolean;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
export interface ReferenceFilter {
|
|
1704
|
-
eq?: string;
|
|
1705
|
-
ne?: string;
|
|
1706
|
-
in?: string[];
|
|
1707
|
-
notIn?: string[];
|
|
1708
|
-
isNull?: boolean;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
export interface FilterInput {
|
|
1712
|
-
field: string;
|
|
1713
|
-
operator: string;
|
|
1714
|
-
value: JsonValue;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
export interface SortInput {
|
|
1718
|
-
field: string;
|
|
1719
|
-
direction: 'ASC' | 'DESC';
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// =============================================================================
|
|
1723
|
-
// RESOLVE TYPES
|
|
1724
|
-
// =============================================================================
|
|
1725
|
-
|
|
1726
|
-
/** Variant context for record resolution */
|
|
1727
|
-
export interface VariantContext {
|
|
1728
|
-
locale?: string;
|
|
1729
|
-
device?: string;
|
|
1730
|
-
region?: string;
|
|
1731
|
-
contexts?: Record<string, JsonValue>;
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
/** Reference resolution options */
|
|
1735
|
-
export interface ReferenceResolutionOptions {
|
|
1736
|
-
maxDepth?: number;
|
|
1737
|
-
resolveMedia?: boolean;
|
|
1738
|
-
resolveReferences?: boolean;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
/** Resolved record metadata */
|
|
1742
|
-
export interface ResolvedRecord {
|
|
1743
|
-
id: string;
|
|
1744
|
-
modelKey: string;
|
|
1745
|
-
naturalKey: string | null;
|
|
1746
|
-
metadata?: Record<string, JsonValue>;
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
/** Resolved variant info */
|
|
1750
|
-
export interface ResolvedVariant {
|
|
1751
|
-
id: string;
|
|
1752
|
-
variantKey: string;
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
/** Resolved field with value */
|
|
1756
|
-
export interface ResolvedField {
|
|
1757
|
-
key: string;
|
|
1758
|
-
type: string;
|
|
1759
|
-
label?: string;
|
|
1760
|
-
required?: boolean;
|
|
1761
|
-
value: JsonValue;
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
/** Resolved content */
|
|
1765
|
-
export interface ResolvedContent {
|
|
1766
|
-
fields: ResolvedField[];
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
/** Resolution context output */
|
|
1770
|
-
export interface ResolutionContext {
|
|
1771
|
-
locale: string;
|
|
1772
|
-
contexts: Record<string, JsonValue>;
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
/** Base resolved record content */
|
|
1776
|
-
export interface ResolvedRecordContentBase {
|
|
1777
|
-
record: ResolvedRecord;
|
|
1778
|
-
variant: ResolvedVariant;
|
|
1779
|
-
content: ResolvedContent;
|
|
1780
|
-
resolvedWith: ResolutionContext;
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
// =============================================================================
|
|
1784
|
-
// FIELD DEFINITION TYPES
|
|
1785
|
-
// =============================================================================
|
|
1786
|
-
|
|
1787
|
-
export interface BaseFieldDef {
|
|
1788
|
-
key: string;
|
|
1789
|
-
label: string;
|
|
1790
|
-
required?: boolean;
|
|
1791
|
-
helpText?: string;
|
|
1792
|
-
defaultValue?: JsonValue;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
export interface TextFieldDef extends BaseFieldDef {
|
|
1796
|
-
type: 'text';
|
|
1797
|
-
maxLength?: number;
|
|
1798
|
-
minLength?: number;
|
|
1799
|
-
pattern?: string;
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
export interface NumberFieldDef extends BaseFieldDef {
|
|
1803
|
-
type: 'number';
|
|
1804
|
-
min?: number;
|
|
1805
|
-
max?: number;
|
|
1806
|
-
step?: number;
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
export interface BooleanFieldDef extends BaseFieldDef {
|
|
1810
|
-
type: 'boolean';
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
export interface DateFieldDef extends BaseFieldDef {
|
|
1814
|
-
type: 'date';
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
export interface RichtextFieldDef extends BaseFieldDef {
|
|
1818
|
-
type: 'richtext';
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
export interface ImageFieldDef extends BaseFieldDef {
|
|
1822
|
-
type: 'image';
|
|
1823
|
-
allowedTypes?: string[];
|
|
1824
|
-
maxSize?: number;
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
export interface VideoFieldDef extends BaseFieldDef {
|
|
1828
|
-
type: 'video';
|
|
1829
|
-
allowedTypes?: string[];
|
|
1830
|
-
maxSize?: number;
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
export interface FileFieldDef extends BaseFieldDef {
|
|
1834
|
-
type: 'file';
|
|
1835
|
-
allowedTypes?: string[];
|
|
1836
|
-
maxSize?: number;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
export interface SelectFieldDef extends BaseFieldDef {
|
|
1840
|
-
type: 'select';
|
|
1841
|
-
options: Array<{ label: string; value: string }>;
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
export interface MultiselectFieldDef extends BaseFieldDef {
|
|
1845
|
-
type: 'multiselect';
|
|
1846
|
-
options: Array<{ label: string; value: string }>;
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
export interface LinkFieldDef extends BaseFieldDef {
|
|
1850
|
-
type: 'link';
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
export interface ReferenceFieldDef extends BaseFieldDef {
|
|
1854
|
-
type: 'reference';
|
|
1855
|
-
referenceTypes?: string[];
|
|
1856
|
-
multiple?: boolean;
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
export interface ListFieldDef extends BaseFieldDef {
|
|
1860
|
-
type: 'list';
|
|
1861
|
-
itemType?: string;
|
|
1862
|
-
minItems?: number;
|
|
1863
|
-
maxItems?: number;
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
export interface JsonFieldDef extends BaseFieldDef {
|
|
1867
|
-
type: 'json';
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
export interface FlexibleFieldDef extends BaseFieldDef {
|
|
1871
|
-
type: 'flexible';
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
/** Field def for inline model types (type is the model's key, e.g. 'seo', 'hero-banner') */
|
|
1875
|
-
export interface InlineModelFieldDef extends BaseFieldDef {
|
|
1876
|
-
type: string;
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
export type FieldDef =
|
|
1880
|
-
| TextFieldDef
|
|
1881
|
-
| NumberFieldDef
|
|
1882
|
-
| BooleanFieldDef
|
|
1883
|
-
| DateFieldDef
|
|
1884
|
-
| RichtextFieldDef
|
|
1885
|
-
| ImageFieldDef
|
|
1886
|
-
| VideoFieldDef
|
|
1887
|
-
| FileFieldDef
|
|
1888
|
-
| SelectFieldDef
|
|
1889
|
-
| MultiselectFieldDef
|
|
1890
|
-
| LinkFieldDef
|
|
1891
|
-
| ReferenceFieldDef
|
|
1892
|
-
| ListFieldDef
|
|
1893
|
-
| JsonFieldDef
|
|
1894
|
-
| FlexibleFieldDef
|
|
1895
|
-
| InlineModelFieldDef;
|
|
1896
|
-
`;
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
// src/codegen/generators/config.ts
|
|
1900
|
-
function generateConfigFile() {
|
|
1901
|
-
return `/**
|
|
1902
|
-
* Model Configuration Type
|
|
1903
|
-
*
|
|
1904
|
-
* Strongly-typed model definitions for the unified data layer.
|
|
1905
|
-
*
|
|
1906
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
1907
|
-
*/
|
|
1908
|
-
|
|
1909
|
-
import type { FieldDef, JsonValue } from './field-types.js';
|
|
1910
|
-
|
|
1911
|
-
/**
|
|
1912
|
-
* Model configuration
|
|
1913
|
-
*
|
|
1914
|
-
* Defines the complete configuration for a model including
|
|
1915
|
-
* its schema, capabilities, and lifecycle hooks.
|
|
1916
|
-
*
|
|
1917
|
-
* @example
|
|
1918
|
-
* export const blogPostConfig = {
|
|
1919
|
-
* key: 'blog-post',
|
|
1920
|
-
* name: 'Blog Post',
|
|
1921
|
-
* records: true,
|
|
1922
|
-
* inline: false,
|
|
1923
|
-
* publicApi: true,
|
|
1924
|
-
* versioning: true,
|
|
1925
|
-
* publishing: true,
|
|
1926
|
-
* variants: false,
|
|
1927
|
-
* customerScoped: false,
|
|
1928
|
-
* fieldDefs: [
|
|
1929
|
-
* { key: 'title', type: 'text', label: 'Title', required: true },
|
|
1930
|
-
* { key: 'content', type: 'richtext', label: 'Content' },
|
|
1931
|
-
* ],
|
|
1932
|
-
* } as const satisfies ModelConfig;
|
|
1933
|
-
*/
|
|
1934
|
-
export interface ModelConfig {
|
|
1935
|
-
/** Unique identifier (kebab-case) */
|
|
1936
|
-
key: string;
|
|
1937
|
-
/** Display name */
|
|
1938
|
-
name: string;
|
|
1939
|
-
/** Description */
|
|
1940
|
-
description?: string;
|
|
1941
|
-
|
|
1942
|
-
// Capability flags (from model config)
|
|
1943
|
-
/** Can create standalone records */
|
|
1944
|
-
records: boolean;
|
|
1945
|
-
/** Available as inline field type in other models */
|
|
1946
|
-
inline: boolean;
|
|
1947
|
-
/** Exposed via public GraphQL API */
|
|
1948
|
-
publicApi: boolean;
|
|
1949
|
-
/** Version history enabled */
|
|
1950
|
-
versioning: boolean;
|
|
1951
|
-
/** Publishing workflow enabled */
|
|
1952
|
-
publishing: boolean;
|
|
1953
|
-
/** Market/device/locale variants enabled */
|
|
1954
|
-
variants: boolean;
|
|
1955
|
-
/** Customer-level record isolation */
|
|
1956
|
-
customerScoped: boolean;
|
|
1957
|
-
|
|
1958
|
-
/** Embedding configuration */
|
|
1959
|
-
embeddings?: {
|
|
1960
|
-
enabled: boolean;
|
|
1961
|
-
fields: Array<{ fieldPath: string; weight?: number }>;
|
|
1962
|
-
};
|
|
1963
|
-
|
|
1964
|
-
/** Lifecycle hooks configuration */
|
|
1965
|
-
hooks?: Record<string, JsonValue>;
|
|
1966
|
-
|
|
1967
|
-
/** Field definitions */
|
|
1968
|
-
fieldDefs: readonly FieldDef[];
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
/**
|
|
1972
|
-
* Helper to create a type-safe model config
|
|
1973
|
-
*/
|
|
1974
|
-
export function defineModel<T extends ModelConfig>(config: T): T {
|
|
1975
|
-
return config;
|
|
1976
|
-
}
|
|
1977
|
-
`;
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
// src/codegen/generators/model-types.ts
|
|
1981
|
-
function isInlineOnlyModel(model) {
|
|
1982
|
-
return model.config.inline && !model.config.records;
|
|
1983
|
-
}
|
|
1984
|
-
function generateModelTypes(model, allModels) {
|
|
1985
|
-
const typeName = toPascalCase(model.key);
|
|
1986
|
-
const configName = toCamelCase(model.key) + "Config";
|
|
1987
|
-
const fields = model.fields ?? [];
|
|
1988
|
-
const fieldTypeImports = getFieldTypeImportsForFields(fields);
|
|
1989
|
-
const inlineSchemaRefs = getInlineSchemaReferences(fields);
|
|
1990
|
-
const referenceModelRefs = getReferenceTypeModelRefs(fields);
|
|
1991
|
-
let code = buildImportStatements(
|
|
1992
|
-
model,
|
|
1993
|
-
fieldTypeImports,
|
|
1994
|
-
inlineSchemaRefs,
|
|
1995
|
-
referenceModelRefs,
|
|
1996
|
-
allModels
|
|
1997
|
-
);
|
|
1998
|
-
if (isInlineOnlyModel(model)) {
|
|
1999
|
-
code += generateDataInterface(model, fields, typeName, allModels);
|
|
2000
|
-
return code;
|
|
2001
|
-
}
|
|
2002
|
-
code += generateConfigObject(model, fields, configName);
|
|
2003
|
-
code += "\n";
|
|
2004
|
-
code += generateDataInterface(model, fields, typeName + "Data", allModels);
|
|
2005
|
-
return code;
|
|
2006
|
-
}
|
|
2007
|
-
function buildImportStatements(model, fieldTypeImports, inlineSchemaRefs, referenceModelRefs, allModels) {
|
|
2008
|
-
const imports = [];
|
|
2009
|
-
if (!isInlineOnlyModel(model)) {
|
|
2010
|
-
imports.push("import type { ModelConfig } from '../config.js';");
|
|
2011
|
-
}
|
|
2012
|
-
if (fieldTypeImports.size > 0) {
|
|
2013
|
-
const types = Array.from(fieldTypeImports).sort().join(", ");
|
|
2014
|
-
imports.push(`import type { ${types} } from '../field-types.js';`);
|
|
2015
|
-
}
|
|
2016
|
-
const allModelRefKeys = /* @__PURE__ */ new Set([
|
|
2017
|
-
...inlineSchemaRefs,
|
|
2018
|
-
...referenceModelRefs
|
|
2019
|
-
]);
|
|
2020
|
-
for (const refKey of allModelRefKeys) {
|
|
2021
|
-
if (refKey === model.key) continue;
|
|
2022
|
-
const refModel = allModels.find((m) => m.key === refKey);
|
|
2023
|
-
if (refModel) {
|
|
2024
|
-
const refTypeName = isInlineOnlyModel(refModel) ? toPascalCase(refKey) : toPascalCase(refKey) + "Data";
|
|
2025
|
-
imports.push(`import type { ${refTypeName} } from './${refKey}.js';`);
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
return imports.length > 0 ? imports.join("\n") + "\n\n" : "";
|
|
2029
|
-
}
|
|
2030
|
-
function generateConfigObject(model, fields, configName) {
|
|
2031
|
-
const lines = [];
|
|
2032
|
-
lines.push("/**");
|
|
2033
|
-
lines.push(` * ${model.name} Configuration`);
|
|
2034
|
-
if (model.description) lines.push(` * ${model.description}`);
|
|
2035
|
-
lines.push(` *`);
|
|
2036
|
-
lines.push(` * @generated from model '${model.key}'`);
|
|
2037
|
-
lines.push(" */");
|
|
2038
|
-
const escapedName = (model.name ?? model.key).replace(/'/g, "\\'");
|
|
2039
|
-
lines.push(`export const ${configName} = {`);
|
|
2040
|
-
lines.push(` key: '${model.key}',`);
|
|
2041
|
-
lines.push(` name: '${escapedName}',`);
|
|
2042
|
-
if (model.description) {
|
|
2043
|
-
lines.push(` description: '${model.description.replace(/'/g, "\\'")}',`);
|
|
2044
|
-
}
|
|
2045
|
-
lines.push("");
|
|
2046
|
-
lines.push(" // Capability flags");
|
|
2047
|
-
lines.push(` records: ${model.config.records},`);
|
|
2048
|
-
lines.push(` inline: ${model.config.inline},`);
|
|
2049
|
-
lines.push(` publicApi: ${model.config.publicApi},`);
|
|
2050
|
-
lines.push(` versioning: ${model.config.versioning},`);
|
|
2051
|
-
lines.push(` publishing: ${model.config.publishing},`);
|
|
2052
|
-
lines.push(` variants: ${model.config.variants},`);
|
|
2053
|
-
lines.push(` customerScoped: ${model.config.customerScoped},`);
|
|
2054
|
-
if (model.config.embeddings?.enabled) {
|
|
2055
|
-
lines.push("");
|
|
2056
|
-
lines.push(" // Embeddings");
|
|
2057
|
-
lines.push(
|
|
2058
|
-
` embeddings: ${JSON.stringify(model.config.embeddings, null, 4).replace(/\n/g, "\n ")},`
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2061
|
-
if (model.hooks && Object.keys(model.hooks).length > 0) {
|
|
2062
|
-
lines.push("");
|
|
2063
|
-
lines.push(" // Lifecycle hooks");
|
|
2064
|
-
lines.push(
|
|
2065
|
-
` hooks: ${JSON.stringify(model.hooks, null, 4).replace(/\n/g, "\n ")},`
|
|
2066
|
-
);
|
|
2067
|
-
} else {
|
|
2068
|
-
lines.push("");
|
|
2069
|
-
lines.push(" hooks: {},");
|
|
2070
|
-
}
|
|
2071
|
-
lines.push("");
|
|
2072
|
-
lines.push(" // Field definitions");
|
|
2073
|
-
if (fields.length === 0) {
|
|
2074
|
-
lines.push(" fieldDefs: [],");
|
|
2075
|
-
} else {
|
|
2076
|
-
lines.push(" fieldDefs: [");
|
|
2077
|
-
for (const field of fields) {
|
|
2078
|
-
lines.push(` ${generateFieldDef(field)},`);
|
|
2079
|
-
}
|
|
2080
|
-
lines.push(" ],");
|
|
2081
|
-
}
|
|
2082
|
-
lines.push("} as const satisfies ModelConfig;");
|
|
2083
|
-
return lines.join("\n") + "\n";
|
|
1687
|
+
function generateConfigObject(model, fields, configName) {
|
|
1688
|
+
const lines = [];
|
|
1689
|
+
lines.push("/**");
|
|
1690
|
+
lines.push(` * ${model.name} Configuration`);
|
|
1691
|
+
if (model.description) lines.push(` * ${model.description}`);
|
|
1692
|
+
lines.push(` *`);
|
|
1693
|
+
lines.push(` * @generated from model '${model.key}'`);
|
|
1694
|
+
lines.push(" */");
|
|
1695
|
+
const escapedName = (model.name ?? model.key).replace(/'/g, "\\'");
|
|
1696
|
+
lines.push(`export const ${configName} = {`);
|
|
1697
|
+
lines.push(` key: '${model.key}',`);
|
|
1698
|
+
lines.push(` name: '${escapedName}',`);
|
|
1699
|
+
if (model.description) {
|
|
1700
|
+
lines.push(` description: '${model.description.replace(/'/g, "\\'")}',`);
|
|
1701
|
+
}
|
|
1702
|
+
lines.push("");
|
|
1703
|
+
lines.push(" // Capability flags");
|
|
1704
|
+
lines.push(` records: ${model.config.records},`);
|
|
1705
|
+
lines.push(` inline: ${model.config.inline},`);
|
|
1706
|
+
lines.push(` publicApi: ${model.config.publicApi},`);
|
|
1707
|
+
lines.push(` versioning: ${model.config.versioning},`);
|
|
1708
|
+
lines.push(` publishing: ${model.config.publishing},`);
|
|
1709
|
+
lines.push(` variants: ${model.config.variants},`);
|
|
1710
|
+
lines.push(` customerScoped: ${model.config.customerScoped},`);
|
|
1711
|
+
if (model.config.embeddings?.enabled) {
|
|
1712
|
+
lines.push("");
|
|
1713
|
+
lines.push(" // Embeddings");
|
|
1714
|
+
lines.push(
|
|
1715
|
+
` embeddings: ${JSON.stringify(model.config.embeddings, null, 4).replace(/\n/g, "\n ")},`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
if (model.hooks && Object.keys(model.hooks).length > 0) {
|
|
1719
|
+
lines.push("");
|
|
1720
|
+
lines.push(" // Lifecycle hooks");
|
|
1721
|
+
lines.push(
|
|
1722
|
+
` hooks: ${JSON.stringify(model.hooks, null, 4).replace(/\n/g, "\n ")},`
|
|
1723
|
+
);
|
|
1724
|
+
} else {
|
|
1725
|
+
lines.push("");
|
|
1726
|
+
lines.push(" hooks: {},");
|
|
1727
|
+
}
|
|
1728
|
+
lines.push("");
|
|
1729
|
+
lines.push(" // Field definitions");
|
|
1730
|
+
if (fields.length === 0) {
|
|
1731
|
+
lines.push(" fieldDefs: [],");
|
|
1732
|
+
} else {
|
|
1733
|
+
lines.push(" fieldDefs: [");
|
|
1734
|
+
for (const field of fields) {
|
|
1735
|
+
lines.push(` ${generateFieldDef(field)},`);
|
|
1736
|
+
}
|
|
1737
|
+
lines.push(" ],");
|
|
1738
|
+
}
|
|
1739
|
+
lines.push("} as const;");
|
|
1740
|
+
return lines.join("\n") + "\n";
|
|
2084
1741
|
}
|
|
2085
1742
|
function generateDataInterface(model, fields, interfaceName, allModels) {
|
|
2086
1743
|
const lines = [];
|
|
@@ -2168,666 +1825,447 @@ function generateModelIndex(models) {
|
|
|
2168
1825
|
`;
|
|
2169
1826
|
code += `export type { ${typeName}Data } from './${model.key}.js';
|
|
2170
1827
|
`;
|
|
1828
|
+
if (model.config.publicApi) {
|
|
1829
|
+
code += `export type { ${typeName}Where, ${typeName}SortField } from './${model.key}.filters.js';
|
|
1830
|
+
`;
|
|
1831
|
+
}
|
|
2171
1832
|
}
|
|
2172
1833
|
}
|
|
2173
1834
|
return code;
|
|
2174
1835
|
}
|
|
2175
1836
|
|
|
2176
|
-
// src/codegen/generators/
|
|
2177
|
-
function
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
naturalKey
|
|
2188
|
-
data
|
|
2189
|
-
metadata
|
|
2190
|
-
publishedVersionNumber
|
|
2191
|
-
publishedAt
|
|
2192
|
-
versionNumber
|
|
2193
|
-
changeDescription
|
|
2194
|
-
createdAt
|
|
2195
|
-
updatedAt
|
|
2196
|
-
}
|
|
1837
|
+
// src/codegen/generators/client-factory.ts
|
|
1838
|
+
function generateClientFactory(models, hasCustomerProfile) {
|
|
1839
|
+
const publicModels = models.filter(
|
|
1840
|
+
(m) => m.config.publicApi && m.config.records
|
|
1841
|
+
);
|
|
1842
|
+
const lines = [];
|
|
1843
|
+
lines.push(`/**
|
|
1844
|
+
* Typed Foir client for this project.
|
|
1845
|
+
*
|
|
1846
|
+
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
1847
|
+
*/
|
|
2197
1848
|
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
1849
|
+
import {
|
|
1850
|
+
createBaseClient,
|
|
1851
|
+
createModelAccessor,
|
|
1852
|
+
createAuthClient,
|
|
1853
|
+
createFilesClient,
|
|
1854
|
+
createNotificationsClient,
|
|
1855
|
+
createSharingClient,
|
|
1856
|
+
createSearchClient,
|
|
1857
|
+
createProfileClient,
|
|
1858
|
+
createOperationsClient,
|
|
1859
|
+
type ClientConfig,
|
|
1860
|
+
type ModelAccessor,
|
|
1861
|
+
type FoirClient,
|
|
1862
|
+
} from '@eide/foir-client';
|
|
1863
|
+
`);
|
|
1864
|
+
for (const model of publicModels) {
|
|
1865
|
+
const typeName = toPascalCase(model.key);
|
|
1866
|
+
const dataType = `${typeName}Data`;
|
|
1867
|
+
const whereType = `${typeName}Where`;
|
|
1868
|
+
const sortType = `${typeName}SortField`;
|
|
1869
|
+
lines.push(
|
|
1870
|
+
`import type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
|
|
1871
|
+
);
|
|
2206
1872
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
...${typeName}Fields
|
|
2212
|
-
resolved(locale: $locale, preview: $preview, fields: $fields) {
|
|
2213
|
-
content
|
|
2214
|
-
record { id modelKey naturalKey }
|
|
2215
|
-
version { id versionNumber }
|
|
2216
|
-
}
|
|
1873
|
+
if (hasCustomerProfile) {
|
|
1874
|
+
lines.push(
|
|
1875
|
+
`import type { CustomerProfileData } from './models/customer-profile.js';`
|
|
1876
|
+
);
|
|
2217
1877
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
$
|
|
2227
|
-
$fields: FieldSelectionInput
|
|
2228
|
-
) {
|
|
2229
|
-
records(
|
|
2230
|
-
modelKey: "${model.key}"
|
|
2231
|
-
limit: $limit
|
|
2232
|
-
offset: $offset
|
|
2233
|
-
filters: $filters
|
|
2234
|
-
sort: $sort
|
|
2235
|
-
) {
|
|
2236
|
-
items {
|
|
2237
|
-
...${typeName}Fields
|
|
2238
|
-
resolved(locale: $locale, preview: $preview, fields: $fields) {
|
|
2239
|
-
content
|
|
2240
|
-
record { id modelKey naturalKey }
|
|
2241
|
-
version { id versionNumber }
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
total
|
|
1878
|
+
lines.push("");
|
|
1879
|
+
lines.push("export interface TypedClient extends FoirClient {");
|
|
1880
|
+
for (const model of publicModels) {
|
|
1881
|
+
const typeName = toPascalCase(model.key);
|
|
1882
|
+
const accessorName = toCamelCase(model.key);
|
|
1883
|
+
const dataType = `${typeName}Data`;
|
|
1884
|
+
const whereType = `${typeName}Where`;
|
|
1885
|
+
const sortType = `${typeName}SortField`;
|
|
1886
|
+
lines.push(` ${accessorName}: ModelAccessor<${dataType}, ${whereType}, ${sortType}>;`);
|
|
2245
1887
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
record {
|
|
2251
|
-
...${typeName}Fields
|
|
2252
|
-
}
|
|
1888
|
+
if (hasCustomerProfile) {
|
|
1889
|
+
lines.push(
|
|
1890
|
+
` profile: ReturnType<typeof createProfileClient<CustomerProfileData>>;`
|
|
1891
|
+
);
|
|
2253
1892
|
}
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
1893
|
+
lines.push("}");
|
|
1894
|
+
lines.push("");
|
|
1895
|
+
lines.push("export function createClient(config: ClientConfig): TypedClient {");
|
|
1896
|
+
lines.push(" const base = createBaseClient(config);");
|
|
1897
|
+
lines.push("");
|
|
1898
|
+
lines.push(" return {");
|
|
1899
|
+
lines.push(" auth: createAuthClient(base),");
|
|
1900
|
+
lines.push(" files: createFilesClient(base),");
|
|
1901
|
+
lines.push(" notifications: createNotificationsClient(base),");
|
|
1902
|
+
lines.push(" sharing: createSharingClient(base),");
|
|
1903
|
+
lines.push(" search: createSearchClient(base),");
|
|
1904
|
+
if (hasCustomerProfile) {
|
|
1905
|
+
lines.push(" profile: createProfileClient<CustomerProfileData>(base),");
|
|
1906
|
+
} else {
|
|
1907
|
+
lines.push(" profile: createProfileClient(base),");
|
|
2262
1908
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
1909
|
+
lines.push(" operations: createOperationsClient(base),");
|
|
1910
|
+
for (const model of publicModels) {
|
|
1911
|
+
const typeName = toPascalCase(model.key);
|
|
1912
|
+
const accessorName = toCamelCase(model.key);
|
|
1913
|
+
const dataType = `${typeName}Data`;
|
|
1914
|
+
const whereType = `${typeName}Where`;
|
|
1915
|
+
const sortType = `${typeName}SortField`;
|
|
1916
|
+
lines.push(
|
|
1917
|
+
` ${accessorName}: createModelAccessor<${dataType}, ${whereType}, ${sortType}>(base, '${model.key}'),`
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
lines.push(` model<TData = unknown>(modelKey: string) {`);
|
|
1921
|
+
lines.push(` return createModelAccessor<TData>(base, modelKey);`);
|
|
1922
|
+
lines.push(` },`);
|
|
1923
|
+
lines.push(" request: base.request,");
|
|
1924
|
+
lines.push(" setCustomerToken: base.setCustomerToken,");
|
|
1925
|
+
lines.push(" setLocale: base.setLocale,");
|
|
1926
|
+
lines.push(" setPreview: base.setPreview,");
|
|
1927
|
+
lines.push(" } as TypedClient;");
|
|
1928
|
+
lines.push("}");
|
|
1929
|
+
lines.push("");
|
|
1930
|
+
lines.push("// Re-export types from @eide/foir-client for convenience");
|
|
1931
|
+
lines.push(`export type {
|
|
1932
|
+
ClientConfig,
|
|
1933
|
+
TypedRecord,
|
|
1934
|
+
TypedList,
|
|
1935
|
+
ModelAccessor,
|
|
1936
|
+
JsonValue,
|
|
1937
|
+
ImageValue,
|
|
1938
|
+
VideoValue,
|
|
1939
|
+
FileValue,
|
|
1940
|
+
LinkValue,
|
|
1941
|
+
ReferenceValue,
|
|
1942
|
+
FlexibleFieldItem,
|
|
1943
|
+
RichtextValue,
|
|
1944
|
+
CurrencyValue,
|
|
1945
|
+
SelectMap,
|
|
1946
|
+
ApplySelect,
|
|
1947
|
+
FoirClient,
|
|
1948
|
+
} from '@eide/foir-client';`);
|
|
1949
|
+
lines.push("");
|
|
1950
|
+
for (const model of publicModels) {
|
|
1951
|
+
const typeName = toPascalCase(model.key);
|
|
1952
|
+
const dataType = `${typeName}Data`;
|
|
1953
|
+
const whereType = `${typeName}Where`;
|
|
1954
|
+
const sortType = `${typeName}SortField`;
|
|
1955
|
+
lines.push(
|
|
1956
|
+
`export type { ${dataType}, ${whereType}, ${sortType} } from './models/${model.key}.js';`
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
if (hasCustomerProfile) {
|
|
1960
|
+
lines.push(
|
|
1961
|
+
`export type { CustomerProfileData } from './models/customer-profile.js';`
|
|
1962
|
+
);
|
|
2268
1963
|
}
|
|
1964
|
+
return lines.join("\n") + "\n";
|
|
2269
1965
|
}
|
|
2270
1966
|
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
expiresAt
|
|
2295
|
-
createdAt
|
|
2296
|
-
createdBy
|
|
2297
|
-
revokedAt
|
|
2298
|
-
}
|
|
2299
|
-
`;
|
|
2300
|
-
}
|
|
2301
|
-
function generateSharingOperations(modelKey, typeName, pluralName) {
|
|
2302
|
-
return `# Sharing operations
|
|
2303
|
-
|
|
2304
|
-
mutation Share${typeName}($recordId: ID!, $sharedWithCustomerId: ID!, $permission: SharePermission!) {
|
|
2305
|
-
shareRecord(recordId: $recordId, sharedWithCustomerId: $sharedWithCustomerId, permission: $permission) {
|
|
2306
|
-
...ShareFields
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
mutation Accept${typeName}Share($shareId: ID!) {
|
|
2311
|
-
acceptShare(shareId: $shareId) {
|
|
2312
|
-
...ShareFields
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
mutation Decline${typeName}Share($shareId: ID!) {
|
|
2317
|
-
declineShare(shareId: $shareId) {
|
|
2318
|
-
...ShareFields
|
|
1967
|
+
// src/codegen/generators/schema-manifest.ts
|
|
1968
|
+
function generateSchemaManifest(models, cpSchema) {
|
|
1969
|
+
const manifest = {
|
|
1970
|
+
$schema: "https://foir.io/schema/v1.json",
|
|
1971
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1972
|
+
models: {}
|
|
1973
|
+
};
|
|
1974
|
+
for (const model of models) {
|
|
1975
|
+
manifest.models[model.key] = {
|
|
1976
|
+
name: model.name,
|
|
1977
|
+
pluralName: model.pluralName,
|
|
1978
|
+
description: model.description,
|
|
1979
|
+
category: model.category,
|
|
1980
|
+
config: model.config,
|
|
1981
|
+
fields: model.fields.map((f) => ({
|
|
1982
|
+
key: f.key,
|
|
1983
|
+
type: f.type,
|
|
1984
|
+
label: f.label,
|
|
1985
|
+
required: f.required,
|
|
1986
|
+
helpText: f.helpText,
|
|
1987
|
+
options: f.options
|
|
1988
|
+
}))
|
|
1989
|
+
};
|
|
2319
1990
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
1991
|
+
if (cpSchema && cpSchema.fields.length > 0) {
|
|
1992
|
+
manifest.customerProfile = {
|
|
1993
|
+
fields: cpSchema.fields.map((f) => ({
|
|
1994
|
+
key: f.key,
|
|
1995
|
+
type: f.type,
|
|
1996
|
+
label: f.label,
|
|
1997
|
+
required: f.required,
|
|
1998
|
+
helpText: f.helpText,
|
|
1999
|
+
options: f.options
|
|
2000
|
+
}))
|
|
2001
|
+
};
|
|
2325
2002
|
}
|
|
2003
|
+
return JSON.stringify(manifest, null, 2) + "\n";
|
|
2326
2004
|
}
|
|
2327
2005
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2006
|
+
// src/codegen/generators/model-filters.ts
|
|
2007
|
+
function isInlineOnlyModel3(model) {
|
|
2008
|
+
return model.config.inline && !model.config.records;
|
|
2332
2009
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2010
|
+
function getFilterType(fieldType) {
|
|
2011
|
+
switch (fieldType) {
|
|
2012
|
+
case "text":
|
|
2013
|
+
case "richtext":
|
|
2014
|
+
case "email":
|
|
2015
|
+
case "phone":
|
|
2016
|
+
case "url":
|
|
2017
|
+
case "select":
|
|
2018
|
+
return "StringFilter";
|
|
2019
|
+
case "number":
|
|
2020
|
+
case "currency":
|
|
2021
|
+
return "NumberFilter";
|
|
2022
|
+
case "boolean":
|
|
2023
|
+
return "BooleanFilter";
|
|
2024
|
+
case "date":
|
|
2025
|
+
return "DateFilter";
|
|
2026
|
+
default:
|
|
2027
|
+
return null;
|
|
2340
2028
|
}
|
|
2341
2029
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
// src/codegen/swift-field-mapping.ts
|
|
2346
|
-
var SWIFT_FIELD_TYPE_MAPPING = {
|
|
2347
|
-
text: {
|
|
2348
|
-
type: "String",
|
|
2349
|
-
alwaysOptional: false,
|
|
2350
|
-
defaultValue: '""',
|
|
2351
|
-
castExpression: "as? String"
|
|
2352
|
-
},
|
|
2353
|
-
richtext: {
|
|
2354
|
-
type: "String",
|
|
2355
|
-
alwaysOptional: true,
|
|
2356
|
-
defaultValue: '""',
|
|
2357
|
-
castExpression: "as? String"
|
|
2358
|
-
},
|
|
2359
|
-
number: {
|
|
2360
|
-
type: "Double",
|
|
2361
|
-
alwaysOptional: true,
|
|
2362
|
-
defaultValue: "0",
|
|
2363
|
-
castExpression: "as? Double"
|
|
2364
|
-
},
|
|
2365
|
-
boolean: {
|
|
2366
|
-
type: "Bool",
|
|
2367
|
-
alwaysOptional: true,
|
|
2368
|
-
defaultValue: "false",
|
|
2369
|
-
castExpression: "as? Bool"
|
|
2370
|
-
},
|
|
2371
|
-
email: {
|
|
2372
|
-
type: "String",
|
|
2373
|
-
alwaysOptional: true,
|
|
2374
|
-
defaultValue: '""',
|
|
2375
|
-
castExpression: "as? String"
|
|
2376
|
-
},
|
|
2377
|
-
phone: {
|
|
2378
|
-
type: "String",
|
|
2379
|
-
alwaysOptional: true,
|
|
2380
|
-
defaultValue: '""',
|
|
2381
|
-
castExpression: "as? String"
|
|
2382
|
-
},
|
|
2383
|
-
url: {
|
|
2384
|
-
type: "String",
|
|
2385
|
-
alwaysOptional: true,
|
|
2386
|
-
defaultValue: '""',
|
|
2387
|
-
castExpression: "as? String"
|
|
2388
|
-
},
|
|
2389
|
-
date: {
|
|
2390
|
-
type: "String",
|
|
2391
|
-
alwaysOptional: true,
|
|
2392
|
-
defaultValue: '""',
|
|
2393
|
-
castExpression: "as? String"
|
|
2394
|
-
},
|
|
2395
|
-
image: {
|
|
2396
|
-
type: "ImageValue",
|
|
2397
|
-
alwaysOptional: true,
|
|
2398
|
-
defaultValue: 'ImageValue(id: "", url: "")',
|
|
2399
|
-
castExpression: "as? [String: Any]",
|
|
2400
|
-
needsSharedType: true
|
|
2401
|
-
},
|
|
2402
|
-
video: {
|
|
2403
|
-
type: "VideoValue",
|
|
2404
|
-
alwaysOptional: true,
|
|
2405
|
-
defaultValue: 'VideoValue(id: "", url: "")',
|
|
2406
|
-
castExpression: "as? [String: Any]",
|
|
2407
|
-
needsSharedType: true
|
|
2408
|
-
},
|
|
2409
|
-
file: {
|
|
2410
|
-
type: "FileValue",
|
|
2411
|
-
alwaysOptional: true,
|
|
2412
|
-
defaultValue: 'FileValue(id: "", url: "", name: "", size: 0, mimeType: "")',
|
|
2413
|
-
// fromSyncData handles fileId→id mapping
|
|
2414
|
-
castExpression: "as? [String: Any]",
|
|
2415
|
-
needsSharedType: true
|
|
2416
|
-
},
|
|
2417
|
-
currency: {
|
|
2418
|
-
type: "CurrencyValue",
|
|
2419
|
-
alwaysOptional: true,
|
|
2420
|
-
defaultValue: 'CurrencyValue(amount: 0, currency: "")',
|
|
2421
|
-
castExpression: "as? [String: Any]",
|
|
2422
|
-
needsSharedType: true
|
|
2423
|
-
},
|
|
2424
|
-
select: {
|
|
2425
|
-
type: "String",
|
|
2426
|
-
alwaysOptional: true,
|
|
2427
|
-
defaultValue: '""',
|
|
2428
|
-
castExpression: "as? String"
|
|
2429
|
-
},
|
|
2430
|
-
multiselect: {
|
|
2431
|
-
type: "[String]",
|
|
2432
|
-
alwaysOptional: true,
|
|
2433
|
-
defaultValue: "[]",
|
|
2434
|
-
castExpression: "as? [String]"
|
|
2435
|
-
},
|
|
2436
|
-
json: {
|
|
2437
|
-
type: "Any",
|
|
2438
|
-
alwaysOptional: true,
|
|
2439
|
-
defaultValue: "nil",
|
|
2440
|
-
castExpression: ""
|
|
2441
|
-
},
|
|
2442
|
-
list: {
|
|
2443
|
-
type: "[Any]",
|
|
2444
|
-
alwaysOptional: true,
|
|
2445
|
-
defaultValue: "[]",
|
|
2446
|
-
castExpression: "as? [Any]"
|
|
2447
|
-
},
|
|
2448
|
-
flexible: {
|
|
2449
|
-
type: "[[String: Any]]",
|
|
2450
|
-
alwaysOptional: true,
|
|
2451
|
-
defaultValue: "[]",
|
|
2452
|
-
castExpression: "as? [[String: Any]]"
|
|
2453
|
-
},
|
|
2454
|
-
reference: {
|
|
2455
|
-
type: "String",
|
|
2456
|
-
alwaysOptional: true,
|
|
2457
|
-
defaultValue: '""',
|
|
2458
|
-
castExpression: "as? String"
|
|
2459
|
-
},
|
|
2460
|
-
link: {
|
|
2461
|
-
type: "LinkValue",
|
|
2462
|
-
alwaysOptional: true,
|
|
2463
|
-
defaultValue: 'LinkValue(type: "")',
|
|
2464
|
-
castExpression: "as? [String: Any]",
|
|
2465
|
-
needsSharedType: true
|
|
2466
|
-
},
|
|
2467
|
-
model: {
|
|
2468
|
-
type: "String",
|
|
2469
|
-
alwaysOptional: true,
|
|
2470
|
-
defaultValue: '""',
|
|
2471
|
-
castExpression: "as? String"
|
|
2472
|
-
}
|
|
2473
|
-
};
|
|
2474
|
-
function getSwiftFieldType(field) {
|
|
2475
|
-
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
2476
|
-
if (!mapping) {
|
|
2477
|
-
return {
|
|
2478
|
-
type: "Any",
|
|
2479
|
-
isOptional: true,
|
|
2480
|
-
mapping: void 0
|
|
2481
|
-
};
|
|
2482
|
-
}
|
|
2483
|
-
const isOptional = mapping.alwaysOptional || !field.required;
|
|
2484
|
-
return { type: mapping.type, isOptional, mapping };
|
|
2030
|
+
function isSortable(fieldType) {
|
|
2031
|
+
return ["text", "number", "date", "boolean", "email", "url", "select"].includes(fieldType);
|
|
2485
2032
|
}
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
function generateSwiftModelFile(model) {
|
|
2033
|
+
function generateModelWhere(model) {
|
|
2034
|
+
if (isInlineOnlyModel3(model)) return "";
|
|
2489
2035
|
const typeName = toPascalCase(model.key);
|
|
2036
|
+
const whereName = `${typeName}Where`;
|
|
2490
2037
|
const fields = model.fields ?? [];
|
|
2491
|
-
const
|
|
2492
|
-
|
|
2493
|
-
lines.push(`// ${typeName}.swift`);
|
|
2494
|
-
lines.push("//");
|
|
2495
|
-
lines.push(`// Generated from model '${model.key}'`);
|
|
2496
|
-
lines.push("//");
|
|
2497
|
-
lines.push("// @generated by foir \u2014 DO NOT EDIT MANUALLY");
|
|
2498
|
-
lines.push("//");
|
|
2499
|
-
lines.push("");
|
|
2500
|
-
lines.push("import Foundation");
|
|
2501
|
-
lines.push("");
|
|
2502
|
-
lines.push(generateFieldsEnum(typeName, fields));
|
|
2503
|
-
lines.push("");
|
|
2504
|
-
lines.push(generateDataStruct(typeName, fields));
|
|
2505
|
-
lines.push("");
|
|
2506
|
-
lines.push(generateSerializationExtension(typeName, fields));
|
|
2507
|
-
lines.push("");
|
|
2508
|
-
lines.push(generateConfigEnum(typeName, model));
|
|
2509
|
-
return lines.join("\n");
|
|
2510
|
-
}
|
|
2511
|
-
function generateFieldsEnum(typeName, fields) {
|
|
2512
|
-
const lines = [];
|
|
2513
|
-
lines.push(`// MARK: - ${typeName} Field Keys`);
|
|
2514
|
-
lines.push("");
|
|
2515
|
-
lines.push(`enum ${typeName}Fields {`);
|
|
2516
|
-
for (const field of fields) {
|
|
2517
|
-
lines.push(` static let ${field.key} = "${field.key}"`);
|
|
2518
|
-
}
|
|
2519
|
-
lines.push("}");
|
|
2520
|
-
return lines.join("\n");
|
|
2521
|
-
}
|
|
2522
|
-
function generateDataStruct(typeName, fields) {
|
|
2523
|
-
const lines = [];
|
|
2524
|
-
lines.push(`// MARK: - ${typeName} Data`);
|
|
2525
|
-
lines.push("");
|
|
2526
|
-
lines.push(`struct ${typeName}Data {`);
|
|
2038
|
+
const filterImports = /* @__PURE__ */ new Set();
|
|
2039
|
+
const filterFields = [];
|
|
2527
2040
|
for (const field of fields) {
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
return lines.join("\n");
|
|
2534
|
-
}
|
|
2535
|
-
function generateSerializationExtension(typeName, fields) {
|
|
2536
|
-
const lines = [];
|
|
2537
|
-
lines.push(`// MARK: - ${typeName} Serialization`);
|
|
2538
|
-
lines.push("");
|
|
2539
|
-
lines.push(`extension ${typeName}Data {`);
|
|
2540
|
-
lines.push(" func toSyncData() -> [String: Any] {");
|
|
2541
|
-
const requiredFields = fields.filter((f) => {
|
|
2542
|
-
const { isOptional } = getSwiftFieldType(f);
|
|
2543
|
-
return !isOptional;
|
|
2544
|
-
});
|
|
2545
|
-
const optionalFields = fields.filter((f) => {
|
|
2546
|
-
const { isOptional } = getSwiftFieldType(f);
|
|
2547
|
-
return isOptional;
|
|
2548
|
-
});
|
|
2549
|
-
if (requiredFields.length > 0) {
|
|
2550
|
-
if (optionalFields.length === 0) {
|
|
2551
|
-
lines.push(` return [`);
|
|
2552
|
-
requiredFields.forEach((f, i) => {
|
|
2553
|
-
const comma = i < requiredFields.length - 1 ? "," : "";
|
|
2554
|
-
lines.push(
|
|
2555
|
-
` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`
|
|
2556
|
-
);
|
|
2557
|
-
});
|
|
2558
|
-
lines.push(" ]");
|
|
2559
|
-
} else {
|
|
2560
|
-
lines.push(` var data: [String: Any] = [`);
|
|
2561
|
-
requiredFields.forEach((f, i) => {
|
|
2562
|
-
const comma = i < requiredFields.length - 1 ? "," : "";
|
|
2563
|
-
lines.push(
|
|
2564
|
-
` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`
|
|
2565
|
-
);
|
|
2566
|
-
});
|
|
2567
|
-
lines.push(" ]");
|
|
2568
|
-
for (const f of optionalFields) {
|
|
2569
|
-
lines.push(
|
|
2570
|
-
` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`
|
|
2571
|
-
);
|
|
2572
|
-
}
|
|
2573
|
-
lines.push(" return data");
|
|
2574
|
-
}
|
|
2575
|
-
} else {
|
|
2576
|
-
lines.push(" var data: [String: Any] = [:]");
|
|
2577
|
-
for (const f of optionalFields) {
|
|
2578
|
-
lines.push(
|
|
2579
|
-
` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`
|
|
2580
|
-
);
|
|
2041
|
+
const filterType = getFilterType(field.type);
|
|
2042
|
+
if (filterType) {
|
|
2043
|
+
filterImports.add(filterType);
|
|
2044
|
+
const fieldName = sanitizeFieldName(field.key);
|
|
2045
|
+
filterFields.push(` ${fieldName}?: ${filterType};`);
|
|
2581
2046
|
}
|
|
2582
|
-
lines.push(" return data");
|
|
2583
2047
|
}
|
|
2584
|
-
|
|
2048
|
+
filterImports.add("DateFilter");
|
|
2049
|
+
filterFields.push(" createdAt?: DateFilter;");
|
|
2050
|
+
filterFields.push(" updatedAt?: DateFilter;");
|
|
2051
|
+
const imports = Array.from(filterImports).sort().join(", ");
|
|
2052
|
+
const lines = [];
|
|
2053
|
+
lines.push(`import type { ${imports} } from '@eide/foir-client';`);
|
|
2585
2054
|
lines.push("");
|
|
2586
|
-
lines.push(
|
|
2587
|
-
|
|
2588
|
-
);
|
|
2589
|
-
lines.push(`
|
|
2590
|
-
|
|
2591
|
-
const comma = i < fields.length - 1 ? "," : "";
|
|
2592
|
-
const { isOptional, mapping } = getSwiftFieldType(field);
|
|
2593
|
-
lines.push(
|
|
2594
|
-
` ${field.key}: ${fromSyncDataExpr(field, typeName, isOptional, mapping)}${comma}`
|
|
2595
|
-
);
|
|
2596
|
-
});
|
|
2597
|
-
lines.push(" )");
|
|
2598
|
-
lines.push(" }");
|
|
2055
|
+
lines.push(`export interface ${whereName} {`);
|
|
2056
|
+
lines.push(...filterFields);
|
|
2057
|
+
lines.push(` AND?: ${whereName}[];`);
|
|
2058
|
+
lines.push(` OR?: ${whereName}[];`);
|
|
2059
|
+
lines.push(` NOT?: ${whereName};`);
|
|
2599
2060
|
lines.push("}");
|
|
2600
|
-
return lines.join("\n");
|
|
2601
|
-
}
|
|
2602
|
-
function toSyncValueExpr(field) {
|
|
2603
|
-
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
2604
|
-
if (mapping?.needsSharedType) {
|
|
2605
|
-
return `${field.key}.toSyncData()`;
|
|
2606
|
-
}
|
|
2607
|
-
return field.key;
|
|
2608
|
-
}
|
|
2609
|
-
function toSyncValueExprForOptional(field) {
|
|
2610
|
-
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
2611
|
-
if (mapping?.needsSharedType) {
|
|
2612
|
-
return `${field.key}.toSyncData()`;
|
|
2613
|
-
}
|
|
2614
|
-
return field.key;
|
|
2061
|
+
return lines.join("\n") + "\n";
|
|
2615
2062
|
}
|
|
2616
|
-
function
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
if (
|
|
2624
|
-
|
|
2063
|
+
function generateModelSortField(model) {
|
|
2064
|
+
if (isInlineOnlyModel3(model)) return "";
|
|
2065
|
+
const typeName = toPascalCase(model.key);
|
|
2066
|
+
const sortName = `${typeName}SortField`;
|
|
2067
|
+
const fields = model.fields ?? [];
|
|
2068
|
+
const sortableFields = [];
|
|
2069
|
+
for (const field of fields) {
|
|
2070
|
+
if (isSortable(field.type)) {
|
|
2071
|
+
sortableFields.push(`'${field.key}'`);
|
|
2625
2072
|
}
|
|
2626
|
-
return `${mapping.type}.fromSyncData(${dictCast} ?? [:])`;
|
|
2627
|
-
}
|
|
2628
|
-
if (field.type === "json") {
|
|
2629
|
-
return isOptional ? accessor : `${accessor}`;
|
|
2630
2073
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
return
|
|
2635
|
-
|
|
2636
|
-
function generateConfigEnum(typeName, model) {
|
|
2637
|
-
const lines = [];
|
|
2638
|
-
lines.push(`// MARK: - ${typeName} Config`);
|
|
2639
|
-
lines.push("");
|
|
2640
|
-
lines.push(`enum ${typeName}Config {`);
|
|
2641
|
-
const escapedName = (model.name ?? model.key).replace(/"/g, '\\"');
|
|
2642
|
-
lines.push(` static let key = "${model.key}"`);
|
|
2643
|
-
lines.push(` static let name = "${escapedName}"`);
|
|
2644
|
-
lines.push(` static let customerScoped = ${model.config.customerScoped}`);
|
|
2645
|
-
lines.push(` static let publicApi = ${model.config.publicApi}`);
|
|
2646
|
-
lines.push(` static let versioning = ${model.config.versioning}`);
|
|
2647
|
-
lines.push(` static let publishing = ${model.config.publishing}`);
|
|
2648
|
-
lines.push(` static let variants = ${model.config.variants}`);
|
|
2649
|
-
lines.push(
|
|
2650
|
-
` static let sharingEnabled = ${model.config.sharing?.enabled ?? false}`
|
|
2651
|
-
);
|
|
2652
|
-
lines.push(
|
|
2653
|
-
` static let sharingRequireAcceptance = ${model.config.sharing?.requireAcceptance ?? true}`
|
|
2654
|
-
);
|
|
2655
|
-
lines.push("}");
|
|
2656
|
-
lines.push("");
|
|
2657
|
-
return lines.join("\n");
|
|
2074
|
+
sortableFields.push(`'createdAt'`);
|
|
2075
|
+
sortableFields.push(`'updatedAt'`);
|
|
2076
|
+
const unique = [...new Set(sortableFields)];
|
|
2077
|
+
return `export type ${sortName} = ${unique.join(" | ")};
|
|
2078
|
+
`;
|
|
2658
2079
|
}
|
|
2659
2080
|
|
|
2660
|
-
// src/codegen/generators/
|
|
2661
|
-
function
|
|
2662
|
-
return
|
|
2663
|
-
// FieldTypes.swift
|
|
2664
|
-
//
|
|
2665
|
-
// Shared value types for platform sync data.
|
|
2666
|
-
//
|
|
2667
|
-
// @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
2668
|
-
//
|
|
2669
|
-
|
|
2670
|
-
import Foundation
|
|
2671
|
-
|
|
2672
|
-
// MARK: - Image
|
|
2673
|
-
|
|
2674
|
-
struct ImageValue {
|
|
2675
|
-
let id: String
|
|
2676
|
-
let url: String
|
|
2677
|
-
var alt: String?
|
|
2678
|
-
var width: Int?
|
|
2679
|
-
var height: Int?
|
|
2680
|
-
var dominantColor: String?
|
|
2681
|
-
var blurhash: String?
|
|
2682
|
-
|
|
2683
|
-
func toSyncData() -> [String: Any] {
|
|
2684
|
-
var data: [String: Any] = ["fileId": id, "source": "internal"]
|
|
2685
|
-
if let alt { data["altText"] = alt }
|
|
2686
|
-
if let width { data["width"] = width }
|
|
2687
|
-
if let height { data["height"] = height }
|
|
2688
|
-
if let dominantColor { data["dominantColor"] = dominantColor }
|
|
2689
|
-
if let blurhash { data["blurhash"] = blurhash }
|
|
2690
|
-
return data
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
static func fromSyncData(_ data: [String: Any]) -> ImageValue {
|
|
2694
|
-
ImageValue(
|
|
2695
|
-
id: data["fileId"] as? String ?? data["id"] as? String ?? "",
|
|
2696
|
-
url: data["url"] as? String ?? "",
|
|
2697
|
-
alt: data["altText"] as? String ?? data["alt"] as? String,
|
|
2698
|
-
width: data["width"] as? Int,
|
|
2699
|
-
height: data["height"] as? Int,
|
|
2700
|
-
dominantColor: data["dominantColor"] as? String,
|
|
2701
|
-
blurhash: data["blurhash"] as? String
|
|
2702
|
-
)
|
|
2703
|
-
}
|
|
2081
|
+
// src/codegen/generators/model-zod.ts
|
|
2082
|
+
function isInlineOnlyModel4(model) {
|
|
2083
|
+
return model.config.inline && !model.config.records;
|
|
2704
2084
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2085
|
+
function getZodExpression(field, allModels) {
|
|
2086
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2087
|
+
let expr;
|
|
2088
|
+
switch (field.type) {
|
|
2089
|
+
case "text":
|
|
2090
|
+
case "email":
|
|
2091
|
+
case "phone":
|
|
2092
|
+
case "url": {
|
|
2093
|
+
imports.add("textField");
|
|
2094
|
+
const opts = [];
|
|
2095
|
+
if (field.options?.maxLength) opts.push(`maxLength: ${field.options.maxLength}`);
|
|
2096
|
+
if (field.options?.minLength) opts.push(`minLength: ${field.options.minLength}`);
|
|
2097
|
+
if (field.options?.pattern) opts.push(`pattern: '${field.options.pattern}'`);
|
|
2098
|
+
expr = opts.length > 0 ? `textField({ ${opts.join(", ")} })` : `textField()`;
|
|
2099
|
+
break;
|
|
2719
2100
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
)
|
|
2101
|
+
case "number": {
|
|
2102
|
+
imports.add("numberField");
|
|
2103
|
+
const opts = [];
|
|
2104
|
+
if (field.options?.min !== void 0) opts.push(`min: ${field.options.min}`);
|
|
2105
|
+
if (field.options?.max !== void 0) opts.push(`max: ${field.options.max}`);
|
|
2106
|
+
expr = opts.length > 0 ? `numberField({ ${opts.join(", ")} })` : `numberField()`;
|
|
2107
|
+
break;
|
|
2728
2108
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2109
|
+
case "boolean":
|
|
2110
|
+
imports.add("booleanField");
|
|
2111
|
+
expr = "booleanField()";
|
|
2112
|
+
break;
|
|
2113
|
+
case "date":
|
|
2114
|
+
imports.add("dateField");
|
|
2115
|
+
expr = "dateField()";
|
|
2116
|
+
break;
|
|
2117
|
+
case "richtext":
|
|
2118
|
+
imports.add("richtextValueSchema");
|
|
2119
|
+
expr = "richtextValueSchema";
|
|
2120
|
+
break;
|
|
2121
|
+
case "image":
|
|
2122
|
+
imports.add("imageValueSchema");
|
|
2123
|
+
expr = "imageValueSchema";
|
|
2124
|
+
break;
|
|
2125
|
+
case "video":
|
|
2126
|
+
imports.add("videoValueSchema");
|
|
2127
|
+
expr = "videoValueSchema";
|
|
2128
|
+
break;
|
|
2129
|
+
case "file":
|
|
2130
|
+
imports.add("fileValueSchema");
|
|
2131
|
+
expr = "fileValueSchema";
|
|
2132
|
+
break;
|
|
2133
|
+
case "currency":
|
|
2134
|
+
imports.add("currencyValueSchema");
|
|
2135
|
+
expr = "currencyValueSchema";
|
|
2136
|
+
break;
|
|
2137
|
+
case "link":
|
|
2138
|
+
imports.add("linkValueSchema");
|
|
2139
|
+
expr = "linkValueSchema";
|
|
2140
|
+
break;
|
|
2141
|
+
case "reference":
|
|
2142
|
+
imports.add("referenceValueSchema");
|
|
2143
|
+
expr = "referenceValueSchema";
|
|
2144
|
+
break;
|
|
2145
|
+
case "select": {
|
|
2146
|
+
if (field.options?.options) {
|
|
2147
|
+
imports.add("selectField");
|
|
2148
|
+
const options = field.options.options;
|
|
2149
|
+
const values = options.map((o) => `'${o.value}'`).join(", ");
|
|
2150
|
+
expr = `selectField([${values}])`;
|
|
2151
|
+
} else {
|
|
2152
|
+
expr = "z.string()";
|
|
2153
|
+
}
|
|
2154
|
+
break;
|
|
2748
2155
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2156
|
+
case "multiselect": {
|
|
2157
|
+
if (field.options?.options) {
|
|
2158
|
+
imports.add("multiselectField");
|
|
2159
|
+
const options = field.options.options;
|
|
2160
|
+
const values = options.map((o) => `'${o.value}'`).join(", ");
|
|
2161
|
+
expr = `multiselectField([${values}])`;
|
|
2162
|
+
} else {
|
|
2163
|
+
expr = "z.array(z.string())";
|
|
2164
|
+
}
|
|
2165
|
+
break;
|
|
2758
2166
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2167
|
+
case "flexible":
|
|
2168
|
+
imports.add("flexibleFieldItemSchema");
|
|
2169
|
+
expr = "z.array(flexibleFieldItemSchema)";
|
|
2170
|
+
break;
|
|
2171
|
+
case "json":
|
|
2172
|
+
imports.add("jsonValueSchema");
|
|
2173
|
+
expr = "jsonValueSchema";
|
|
2174
|
+
break;
|
|
2175
|
+
case "list": {
|
|
2176
|
+
if (field.options?.itemType) {
|
|
2177
|
+
const itemType = field.options.itemType;
|
|
2178
|
+
const refModel = allModels.find((m) => m.key === itemType);
|
|
2179
|
+
if (refModel) {
|
|
2180
|
+
const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
|
|
2181
|
+
expr = `z.array(${refSchemaName})`;
|
|
2182
|
+
} else {
|
|
2183
|
+
imports.add("jsonValueSchema");
|
|
2184
|
+
expr = "z.array(jsonValueSchema)";
|
|
2185
|
+
}
|
|
2186
|
+
} else {
|
|
2187
|
+
imports.add("jsonValueSchema");
|
|
2188
|
+
expr = "z.array(jsonValueSchema)";
|
|
2189
|
+
}
|
|
2190
|
+
break;
|
|
2769
2191
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2192
|
+
default: {
|
|
2193
|
+
const refModel = allModels.find((m) => m.key === field.type);
|
|
2194
|
+
if (refModel) {
|
|
2195
|
+
const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
|
|
2196
|
+
expr = refSchemaName;
|
|
2197
|
+
} else {
|
|
2198
|
+
imports.add("jsonValueSchema");
|
|
2199
|
+
expr = "jsonValueSchema";
|
|
2200
|
+
}
|
|
2776
2201
|
}
|
|
2202
|
+
}
|
|
2203
|
+
if (!field.required) {
|
|
2204
|
+
expr = `${expr}.optional()`;
|
|
2205
|
+
}
|
|
2206
|
+
return { expression: expr, imports };
|
|
2777
2207
|
}
|
|
2208
|
+
function generateModelZodSchema(model, allModels) {
|
|
2209
|
+
const typeName = toPascalCase(model.key);
|
|
2210
|
+
const schemaName = isInlineOnlyModel4(model) ? `${typeName}Schema` : `${typeName}DataSchema`;
|
|
2211
|
+
const fields = model.fields ?? [];
|
|
2212
|
+
if (fields.length === 0) {
|
|
2213
|
+
return `import { z } from '@eide/foir-client/validation';
|
|
2778
2214
|
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2215
|
+
export const ${schemaName} = z.object({});
|
|
2216
|
+
`;
|
|
2217
|
+
}
|
|
2218
|
+
const allImports = /* @__PURE__ */ new Set();
|
|
2219
|
+
const fieldLines = [];
|
|
2220
|
+
const inlineSchemaImports = [];
|
|
2221
|
+
for (const field of fields) {
|
|
2222
|
+
const { expression, imports } = getZodExpression(field, allModels);
|
|
2223
|
+
for (const imp of imports) allImports.add(imp);
|
|
2224
|
+
const fieldName = sanitizeFieldName(field.key);
|
|
2225
|
+
fieldLines.push(` ${fieldName}: ${expression},`);
|
|
2226
|
+
if (!isPrimitiveFieldType(field.type) && field.type !== "list") {
|
|
2227
|
+
const refModel = allModels.find((m) => m.key === field.type);
|
|
2228
|
+
if (refModel && refModel.key !== model.key) {
|
|
2229
|
+
const refSchemaName = `${toPascalCase(field.type)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
|
|
2230
|
+
inlineSchemaImports.push(
|
|
2231
|
+
`import { ${refSchemaName} } from './${field.type}.zod.js';`
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2795
2234
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2235
|
+
if (field.type === "list" && field.options?.itemType) {
|
|
2236
|
+
const itemType = field.options.itemType;
|
|
2237
|
+
const refModel = allModels.find((m) => m.key === itemType);
|
|
2238
|
+
if (refModel && refModel.key !== model.key) {
|
|
2239
|
+
const refSchemaName = `${toPascalCase(itemType)}${isInlineOnlyModel4(refModel) ? "" : "Data"}Schema`;
|
|
2240
|
+
inlineSchemaImports.push(
|
|
2241
|
+
`import { ${refSchemaName} } from './${itemType}.zod.js';`
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2805
2244
|
}
|
|
2806
|
-
}
|
|
2807
|
-
`;
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
// src/codegen/generators/swift-model-keys.ts
|
|
2811
|
-
function generateSwiftModelKeys(models) {
|
|
2245
|
+
}
|
|
2812
2246
|
const lines = [];
|
|
2813
|
-
lines.push(
|
|
2814
|
-
lines.push(
|
|
2815
|
-
lines.push(
|
|
2816
|
-
lines.push(
|
|
2817
|
-
lines.push(
|
|
2818
|
-
lines.push("// @generated by foir \u2014 DO NOT EDIT MANUALLY");
|
|
2819
|
-
lines.push("//");
|
|
2820
|
-
lines.push("");
|
|
2821
|
-
lines.push("import Foundation");
|
|
2247
|
+
lines.push(`/**`);
|
|
2248
|
+
lines.push(` * Zod validation schema for ${model.name}`);
|
|
2249
|
+
lines.push(` *`);
|
|
2250
|
+
lines.push(` * @generated by foir \u2014 DO NOT EDIT MANUALLY`);
|
|
2251
|
+
lines.push(` */`);
|
|
2822
2252
|
lines.push("");
|
|
2823
|
-
lines.push(
|
|
2824
|
-
|
|
2825
|
-
const
|
|
2826
|
-
lines.push(`
|
|
2253
|
+
lines.push(`import { z } from '@eide/foir-client/validation';`);
|
|
2254
|
+
if (allImports.size > 0) {
|
|
2255
|
+
const sorted = Array.from(allImports).sort();
|
|
2256
|
+
lines.push(`import { ${sorted.join(", ")} } from '@eide/foir-client/validation';`);
|
|
2827
2257
|
}
|
|
2828
|
-
|
|
2258
|
+
const uniqueInlineImports = [...new Set(inlineSchemaImports)];
|
|
2259
|
+
for (const imp of uniqueInlineImports) {
|
|
2260
|
+
lines.push(imp);
|
|
2261
|
+
}
|
|
2262
|
+
lines.push("");
|
|
2263
|
+
lines.push(`export const ${schemaName} = z.object({`);
|
|
2264
|
+
lines.push(...fieldLines);
|
|
2265
|
+
lines.push("});");
|
|
2829
2266
|
lines.push("");
|
|
2830
|
-
|
|
2267
|
+
lines.push(`export type ${isInlineOnlyModel4(model) ? typeName : typeName + "Data"}Validated = z.infer<typeof ${schemaName}>;`);
|
|
2268
|
+
return lines.join("\n") + "\n";
|
|
2831
2269
|
}
|
|
2832
2270
|
|
|
2833
2271
|
// src/codegen/generators/customer-profile-types.ts
|
|
@@ -2882,1887 +2320,42 @@ function getFieldTypeImportsForFields2(fields) {
|
|
|
2882
2320
|
return imports;
|
|
2883
2321
|
}
|
|
2884
2322
|
|
|
2885
|
-
// src/codegen/
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
lines.push(generateFieldsEnum2(typeName, fields));
|
|
2903
|
-
lines.push("");
|
|
2904
|
-
lines.push(generateDataStruct2(typeName, fields));
|
|
2905
|
-
lines.push("");
|
|
2906
|
-
lines.push(generateSerializationExtension2(typeName, fields));
|
|
2907
|
-
return lines.join("\n");
|
|
2908
|
-
}
|
|
2909
|
-
function generateFieldsEnum2(typeName, fields) {
|
|
2910
|
-
const lines = [];
|
|
2911
|
-
lines.push(`// MARK: - ${typeName} Field Keys`);
|
|
2912
|
-
lines.push("");
|
|
2913
|
-
lines.push(`enum ${typeName}Fields {`);
|
|
2914
|
-
for (const field of fields) {
|
|
2915
|
-
lines.push(` static let ${field.key} = "${field.key}"`);
|
|
2916
|
-
}
|
|
2917
|
-
lines.push("}");
|
|
2918
|
-
return lines.join("\n");
|
|
2919
|
-
}
|
|
2920
|
-
function generateDataStruct2(typeName, fields) {
|
|
2921
|
-
const lines = [];
|
|
2922
|
-
lines.push(`// MARK: - ${typeName} Data`);
|
|
2923
|
-
lines.push("");
|
|
2924
|
-
lines.push(`struct ${typeName}Data {`);
|
|
2925
|
-
for (const field of fields) {
|
|
2926
|
-
const { type, isOptional } = getSwiftFieldType(field);
|
|
2927
|
-
const optionalSuffix = isOptional ? "?" : "";
|
|
2928
|
-
lines.push(` var ${field.key}: ${type}${optionalSuffix}`);
|
|
2929
|
-
}
|
|
2930
|
-
lines.push("}");
|
|
2931
|
-
return lines.join("\n");
|
|
2932
|
-
}
|
|
2933
|
-
function generateSerializationExtension2(typeName, fields) {
|
|
2934
|
-
const lines = [];
|
|
2935
|
-
lines.push(`// MARK: - ${typeName} Serialization`);
|
|
2936
|
-
lines.push("");
|
|
2937
|
-
lines.push(`extension ${typeName}Data {`);
|
|
2938
|
-
lines.push(" func toSyncData() -> [String: Any] {");
|
|
2939
|
-
const requiredFields = fields.filter((f) => {
|
|
2940
|
-
const { isOptional } = getSwiftFieldType(f);
|
|
2941
|
-
return !isOptional;
|
|
2942
|
-
});
|
|
2943
|
-
const optionalFields = fields.filter((f) => {
|
|
2944
|
-
const { isOptional } = getSwiftFieldType(f);
|
|
2945
|
-
return isOptional;
|
|
2946
|
-
});
|
|
2947
|
-
if (requiredFields.length > 0) {
|
|
2948
|
-
if (optionalFields.length === 0) {
|
|
2949
|
-
lines.push(" return [");
|
|
2950
|
-
requiredFields.forEach((f, i) => {
|
|
2951
|
-
const comma = i < requiredFields.length - 1 ? "," : "";
|
|
2952
|
-
lines.push(
|
|
2953
|
-
` ${typeName}Fields.${f.key}: ${toSyncValueExpr2(f)}${comma}`
|
|
2954
|
-
);
|
|
2955
|
-
});
|
|
2956
|
-
lines.push(" ]");
|
|
2957
|
-
} else {
|
|
2958
|
-
lines.push(" var data: [String: Any] = [");
|
|
2959
|
-
requiredFields.forEach((f, i) => {
|
|
2960
|
-
const comma = i < requiredFields.length - 1 ? "," : "";
|
|
2961
|
-
lines.push(
|
|
2962
|
-
` ${typeName}Fields.${f.key}: ${toSyncValueExpr2(f)}${comma}`
|
|
2963
|
-
);
|
|
2323
|
+
// src/codegen/write-files.ts
|
|
2324
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
2325
|
+
import { dirname as dirname2 } from "path";
|
|
2326
|
+
async function writeGeneratedFile(filePath, content, usePrettier = true) {
|
|
2327
|
+
await mkdir(dirname2(filePath), { recursive: true });
|
|
2328
|
+
let formattedContent = content;
|
|
2329
|
+
const isSwift = filePath.endsWith(".swift");
|
|
2330
|
+
if (usePrettier && !isSwift) {
|
|
2331
|
+
try {
|
|
2332
|
+
const prettier = await import("prettier");
|
|
2333
|
+
const parser = filePath.endsWith(".graphql") ? "graphql" : "typescript";
|
|
2334
|
+
formattedContent = await prettier.format(content, {
|
|
2335
|
+
parser,
|
|
2336
|
+
semi: true,
|
|
2337
|
+
singleQuote: true,
|
|
2338
|
+
trailingComma: "es5",
|
|
2339
|
+
printWidth: 100
|
|
2964
2340
|
});
|
|
2965
|
-
|
|
2966
|
-
for (const f of optionalFields) {
|
|
2967
|
-
lines.push(
|
|
2968
|
-
` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional2(f)} }`
|
|
2969
|
-
);
|
|
2970
|
-
}
|
|
2971
|
-
lines.push(" return data");
|
|
2972
|
-
}
|
|
2973
|
-
} else {
|
|
2974
|
-
lines.push(" var data: [String: Any] = [:]");
|
|
2975
|
-
for (const f of optionalFields) {
|
|
2976
|
-
lines.push(
|
|
2977
|
-
` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional2(f)} }`
|
|
2978
|
-
);
|
|
2341
|
+
} catch {
|
|
2979
2342
|
}
|
|
2980
|
-
lines.push(" return data");
|
|
2981
2343
|
}
|
|
2982
|
-
|
|
2983
|
-
lines.push("");
|
|
2984
|
-
lines.push(
|
|
2985
|
-
" static func fromSyncData(_ data: [String: Any]) -> " + typeName + "Data {"
|
|
2986
|
-
);
|
|
2987
|
-
lines.push(` ${typeName}Data(`);
|
|
2988
|
-
fields.forEach((field, i) => {
|
|
2989
|
-
const comma = i < fields.length - 1 ? "," : "";
|
|
2990
|
-
const { isOptional, mapping } = getSwiftFieldType(field);
|
|
2991
|
-
lines.push(
|
|
2992
|
-
` ${field.key}: ${fromSyncDataExpr2(field, typeName, isOptional, mapping)}${comma}`
|
|
2993
|
-
);
|
|
2994
|
-
});
|
|
2995
|
-
lines.push(" )");
|
|
2996
|
-
lines.push(" }");
|
|
2997
|
-
lines.push("}");
|
|
2998
|
-
lines.push("");
|
|
2999
|
-
return lines.join("\n");
|
|
2344
|
+
await writeFile(filePath, formattedContent, "utf-8");
|
|
3000
2345
|
}
|
|
3001
|
-
function
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
}
|
|
3008
|
-
function toSyncValueExprForOptional2(field) {
|
|
3009
|
-
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
3010
|
-
if (mapping?.needsSharedType) {
|
|
3011
|
-
return `${field.key}.toSyncData()`;
|
|
3012
|
-
}
|
|
3013
|
-
return field.key;
|
|
3014
|
-
}
|
|
3015
|
-
function fromSyncDataExpr2(field, typeName, isOptional, mapping) {
|
|
3016
|
-
const accessor = `data[${typeName}Fields.${field.key}]`;
|
|
3017
|
-
if (!mapping) {
|
|
3018
|
-
return isOptional ? `${accessor}` : `${accessor} ?? nil`;
|
|
3019
|
-
}
|
|
3020
|
-
if (mapping.needsSharedType) {
|
|
3021
|
-
const dictCast = `${accessor} as? [String: Any]`;
|
|
3022
|
-
if (isOptional) {
|
|
3023
|
-
return `(${dictCast}).map { ${mapping.type}.fromSyncData($0) }`;
|
|
3024
|
-
}
|
|
3025
|
-
return `${mapping.type}.fromSyncData(${dictCast} ?? [:])`;
|
|
3026
|
-
}
|
|
3027
|
-
if (field.type === "json") {
|
|
3028
|
-
return isOptional ? accessor : `${accessor}`;
|
|
3029
|
-
}
|
|
3030
|
-
if (isOptional) {
|
|
3031
|
-
return `${accessor} ${mapping.castExpression}`;
|
|
3032
|
-
}
|
|
3033
|
-
return `${accessor} ${mapping.castExpression} ?? ${mapping.defaultValue}`;
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
// src/codegen/generators/customer-profile-documents.ts
|
|
3037
|
-
function generateCustomerProfileDocuments() {
|
|
3038
|
-
return `# Generated GraphQL operations for Customer Profiles
|
|
3039
|
-
# @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
3040
|
-
|
|
3041
|
-
fragment CustomerProfileFields on CustomerProfile {
|
|
3042
|
-
id
|
|
3043
|
-
customerId
|
|
3044
|
-
data
|
|
3045
|
-
createdAt
|
|
3046
|
-
updatedAt
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
query GetMyProfile {
|
|
3050
|
-
myProfile {
|
|
3051
|
-
...CustomerProfileFields
|
|
3052
|
-
resolved
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
|
|
3056
|
-
query GetCustomerProfile($customerId: ID!) {
|
|
3057
|
-
customerProfile(customerId: $customerId) {
|
|
3058
|
-
...CustomerProfileFields
|
|
3059
|
-
resolved
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
mutation SetMyProfile($data: JSON!) {
|
|
3064
|
-
setMyProfile(data: $data) {
|
|
3065
|
-
...CustomerProfileFields
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
|
|
3069
|
-
mutation UpdateCustomerProfile($customerId: ID, $data: JSON!) {
|
|
3070
|
-
updateCustomerProfile(customerId: $customerId, input: { data: $data }) {
|
|
3071
|
-
...CustomerProfileFields
|
|
3072
|
-
}
|
|
3073
|
-
}
|
|
3074
|
-
`;
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3077
|
-
// src/codegen/generators/static-documents.ts
|
|
3078
|
-
var HEADER = "# @generated by foir \u2014 DO NOT EDIT MANUALLY";
|
|
3079
|
-
function authDocument() {
|
|
3080
|
-
return `# Customer authentication operations
|
|
3081
|
-
${HEADER}
|
|
3082
|
-
|
|
3083
|
-
mutation CustomerLogin($email: String!, $password: String!) {
|
|
3084
|
-
customerLogin(email: $email, password: $password) {
|
|
3085
|
-
success
|
|
3086
|
-
accessToken
|
|
3087
|
-
refreshToken
|
|
3088
|
-
user { id email status }
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
mutation CustomerRegister($email: String!, $password: String!) {
|
|
3093
|
-
customerRegister(email: $email, password: $password) {
|
|
3094
|
-
success
|
|
3095
|
-
accessToken
|
|
3096
|
-
refreshToken
|
|
3097
|
-
user { id email status }
|
|
3098
|
-
emailVerificationRequired
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
|
|
3102
|
-
mutation CustomerRequestOTP($email: String!) {
|
|
3103
|
-
customerRequestOTP(email: $email) {
|
|
3104
|
-
success
|
|
3105
|
-
expiresAt
|
|
3106
|
-
message
|
|
3107
|
-
}
|
|
3108
|
-
}
|
|
3109
|
-
|
|
3110
|
-
mutation CustomerLoginOTP($email: String!, $otp: String!) {
|
|
3111
|
-
customerLoginOTP(email: $email, otp: $otp) {
|
|
3112
|
-
success
|
|
3113
|
-
accessToken
|
|
3114
|
-
refreshToken
|
|
3115
|
-
user { id email status }
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
|
|
3119
|
-
mutation CustomerRefreshToken($refreshToken: String!) {
|
|
3120
|
-
customerRefreshToken(refreshToken: $refreshToken) {
|
|
3121
|
-
success
|
|
3122
|
-
accessToken
|
|
3123
|
-
refreshToken
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3127
|
-
mutation CustomerRequestPasswordReset($email: String!) {
|
|
3128
|
-
customerRequestPasswordReset(email: $email) {
|
|
3129
|
-
success
|
|
3130
|
-
message
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
mutation CustomerResetPassword($token: String!, $newPassword: String!) {
|
|
3135
|
-
customerResetPassword(token: $token, newPassword: $newPassword) {
|
|
3136
|
-
success
|
|
3137
|
-
message
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3141
|
-
mutation CustomerUpdatePassword($currentPassword: String!, $newPassword: String!) {
|
|
3142
|
-
customerUpdatePassword(currentPassword: $currentPassword, newPassword: $newPassword) {
|
|
3143
|
-
success
|
|
3144
|
-
message
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
|
|
3148
|
-
mutation CustomerVerifyEmail($token: String!) {
|
|
3149
|
-
customerVerifyEmail(token: $token) {
|
|
3150
|
-
success
|
|
3151
|
-
user { id email }
|
|
3152
|
-
message
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
|
-
mutation CustomerResendVerificationEmail {
|
|
3157
|
-
customerResendVerificationEmail {
|
|
3158
|
-
success
|
|
3159
|
-
message
|
|
3160
|
-
}
|
|
3161
|
-
}
|
|
3162
|
-
|
|
3163
|
-
mutation CustomerLogout {
|
|
3164
|
-
customerLogout {
|
|
3165
|
-
success
|
|
3166
|
-
message
|
|
3167
|
-
}
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
query AuthConfig($tenantId: ID) {
|
|
3171
|
-
authConfig(tenantId: $tenantId) {
|
|
3172
|
-
authMethods
|
|
3173
|
-
passwordPolicy {
|
|
3174
|
-
minLength
|
|
3175
|
-
requireUppercase
|
|
3176
|
-
requireLowercase
|
|
3177
|
-
requireNumbers
|
|
3178
|
-
requireSpecialChars
|
|
3179
|
-
requireSpecial
|
|
3180
|
-
}
|
|
3181
|
-
publicRegistrationEnabled
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
|
|
3185
|
-
query CurrentUser {
|
|
3186
|
-
currentUser {
|
|
3187
|
-
id
|
|
3188
|
-
email
|
|
3189
|
-
emailVerified
|
|
3190
|
-
status
|
|
3191
|
-
userType
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
`;
|
|
3195
|
-
}
|
|
3196
|
-
function authProvidersDocument() {
|
|
3197
|
-
return `# Auth provider operations
|
|
3198
|
-
${HEADER}
|
|
3199
|
-
|
|
3200
|
-
query AuthProviders {
|
|
3201
|
-
authProviders {
|
|
3202
|
-
id
|
|
3203
|
-
key
|
|
3204
|
-
name
|
|
3205
|
-
type
|
|
3206
|
-
enabled
|
|
3207
|
-
isDefault
|
|
3208
|
-
priority
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
query DefaultAuthProvider {
|
|
3213
|
-
defaultAuthProvider {
|
|
3214
|
-
id
|
|
3215
|
-
key
|
|
3216
|
-
name
|
|
3217
|
-
type
|
|
3218
|
-
enabled
|
|
3219
|
-
isDefault
|
|
3220
|
-
priority
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
|
|
3224
|
-
mutation CustomerLoginWithProvider($input: ProviderLoginInput!) {
|
|
3225
|
-
customerLoginWithProvider(input: $input) {
|
|
3226
|
-
method
|
|
3227
|
-
providerId
|
|
3228
|
-
providerKey
|
|
3229
|
-
redirectUrl
|
|
3230
|
-
accessToken
|
|
3231
|
-
refreshToken
|
|
3232
|
-
user { id email userType }
|
|
3233
|
-
otpSent
|
|
3234
|
-
email
|
|
3235
|
-
expiresAt
|
|
3236
|
-
state
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
mutation CustomerProviderCallback($input: ProviderCallbackInput!) {
|
|
3241
|
-
customerProviderCallback(input: $input) {
|
|
3242
|
-
accessToken
|
|
3243
|
-
refreshToken
|
|
3244
|
-
user { id email userType }
|
|
3245
|
-
isNewCustomer
|
|
3246
|
-
providerAccessToken
|
|
3247
|
-
providerAccessTokenExpiresIn
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
mutation CustomerProviderVerifyOTP($input: ProviderOTPVerifyInput!) {
|
|
3252
|
-
customerProviderVerifyOTP(input: $input) {
|
|
3253
|
-
accessToken
|
|
3254
|
-
refreshToken
|
|
3255
|
-
user { id email userType }
|
|
3256
|
-
isNewCustomer
|
|
3257
|
-
providerAccessToken
|
|
3258
|
-
providerAccessTokenExpiresIn
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
`;
|
|
3262
|
-
}
|
|
3263
|
-
function filesDocument() {
|
|
3264
|
-
return `# File management operations
|
|
3265
|
-
${HEADER}
|
|
3266
|
-
|
|
3267
|
-
query GetFile($id: ID!) {
|
|
3268
|
-
file(id: $id) {
|
|
3269
|
-
id
|
|
3270
|
-
filename
|
|
3271
|
-
mimeType
|
|
3272
|
-
size
|
|
3273
|
-
url
|
|
3274
|
-
source
|
|
3275
|
-
status
|
|
3276
|
-
metadata
|
|
3277
|
-
width
|
|
3278
|
-
height
|
|
3279
|
-
blurhash
|
|
3280
|
-
dominantColor
|
|
3281
|
-
duration
|
|
3282
|
-
thumbnailUrl
|
|
3283
|
-
previewUrl
|
|
3284
|
-
altText
|
|
3285
|
-
caption
|
|
3286
|
-
description
|
|
3287
|
-
isImage
|
|
3288
|
-
isVideo
|
|
3289
|
-
createdAt
|
|
3290
|
-
updatedAt
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3293
|
-
|
|
3294
|
-
mutation CreateFileUpload(
|
|
3295
|
-
$filename: String!
|
|
3296
|
-
$mimeType: String!
|
|
3297
|
-
$size: Int!
|
|
3298
|
-
$folder: String
|
|
3299
|
-
$metadata: JSON
|
|
3300
|
-
) {
|
|
3301
|
-
createFileUpload(
|
|
3302
|
-
filename: $filename
|
|
3303
|
-
mimeType: $mimeType
|
|
3304
|
-
size: $size
|
|
3305
|
-
folder: $folder
|
|
3306
|
-
metadata: $metadata
|
|
3307
|
-
) {
|
|
3308
|
-
uploadId
|
|
3309
|
-
uploadUrl
|
|
3310
|
-
expiresAt
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
|
-
mutation ConfirmFileUpload($uploadId: ID!) {
|
|
3315
|
-
confirmFileUpload(uploadId: $uploadId) {
|
|
3316
|
-
id
|
|
3317
|
-
filename
|
|
3318
|
-
mimeType
|
|
3319
|
-
size
|
|
3320
|
-
url
|
|
3321
|
-
source
|
|
3322
|
-
status
|
|
3323
|
-
createdAt
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
|
-
`;
|
|
3327
|
-
}
|
|
3328
|
-
function syncDocument() {
|
|
3329
|
-
return `# Sync engine operations (Layer 1: delta sync protocol)
|
|
3330
|
-
${HEADER}
|
|
3331
|
-
|
|
3332
|
-
query SyncPull($modelKey: String!, $since: String!, $limit: Int) {
|
|
3333
|
-
syncPull(modelKey: $modelKey, since: $since, limit: $limit) {
|
|
3334
|
-
items {
|
|
3335
|
-
id
|
|
3336
|
-
modelKey
|
|
3337
|
-
naturalKey
|
|
3338
|
-
data
|
|
3339
|
-
metadata
|
|
3340
|
-
syncVersion
|
|
3341
|
-
updatedAt
|
|
3342
|
-
deleted
|
|
3343
|
-
}
|
|
3344
|
-
cursor
|
|
3345
|
-
hasMore
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
|
|
3349
|
-
mutation SyncPush($items: [SyncPushItemInput!]!) {
|
|
3350
|
-
syncPush(items: $items) {
|
|
3351
|
-
items {
|
|
3352
|
-
clientId
|
|
3353
|
-
serverId
|
|
3354
|
-
syncVersion
|
|
3355
|
-
status
|
|
3356
|
-
serverData
|
|
3357
|
-
serverSyncVersion
|
|
3358
|
-
error
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
|
|
3363
|
-
subscription RecordChanged($modelKey: String!) {
|
|
3364
|
-
recordChanged(modelKey: $modelKey) {
|
|
3365
|
-
type
|
|
3366
|
-
recordId
|
|
3367
|
-
modelKey
|
|
3368
|
-
naturalKey
|
|
3369
|
-
syncVersion
|
|
3370
|
-
data
|
|
3371
|
-
updatedBy
|
|
3372
|
-
timestamp
|
|
3373
|
-
}
|
|
3374
|
-
}
|
|
3375
|
-
`;
|
|
3376
|
-
}
|
|
3377
|
-
function notificationsDocument() {
|
|
3378
|
-
return `# Customer notification operations
|
|
3379
|
-
${HEADER}
|
|
3380
|
-
|
|
3381
|
-
query CustomerNotifications(
|
|
3382
|
-
$unreadOnly: Boolean
|
|
3383
|
-
$category: String
|
|
3384
|
-
$limit: Int
|
|
3385
|
-
$offset: Int
|
|
3386
|
-
) {
|
|
3387
|
-
customerNotifications(
|
|
3388
|
-
unreadOnly: $unreadOnly
|
|
3389
|
-
category: $category
|
|
3390
|
-
limit: $limit
|
|
3391
|
-
offset: $offset
|
|
3392
|
-
) {
|
|
3393
|
-
items {
|
|
3394
|
-
id
|
|
3395
|
-
type
|
|
3396
|
-
category
|
|
3397
|
-
title
|
|
3398
|
-
message
|
|
3399
|
-
actionUrl
|
|
3400
|
-
imageUrl
|
|
3401
|
-
metadata
|
|
3402
|
-
alertChannels
|
|
3403
|
-
isRead
|
|
3404
|
-
readAt
|
|
3405
|
-
createdAt
|
|
3406
|
-
}
|
|
3407
|
-
total
|
|
3408
|
-
unreadCount
|
|
3409
|
-
hasMore
|
|
3410
|
-
}
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
query CustomerUnreadCount($category: String) {
|
|
3414
|
-
customerUnreadCount(category: $category)
|
|
3415
|
-
}
|
|
3416
|
-
|
|
3417
|
-
query NotificationPreferences {
|
|
3418
|
-
notificationPreferences {
|
|
3419
|
-
id
|
|
3420
|
-
category
|
|
3421
|
-
channel
|
|
3422
|
-
enabled
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
mutation SendNotification($input: SendNotificationInput!) {
|
|
3427
|
-
sendNotification(input: $input) {
|
|
3428
|
-
id
|
|
3429
|
-
type
|
|
3430
|
-
category
|
|
3431
|
-
title
|
|
3432
|
-
message
|
|
3433
|
-
actionUrl
|
|
3434
|
-
imageUrl
|
|
3435
|
-
metadata
|
|
3436
|
-
alertChannels
|
|
3437
|
-
isRead
|
|
3438
|
-
readAt
|
|
3439
|
-
createdAt
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
|
-
mutation SendBulkNotifications($input: SendBulkNotificationsInput!) {
|
|
3444
|
-
sendBulkNotifications(input: $input) {
|
|
3445
|
-
sent
|
|
3446
|
-
failed
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
mutation MarkCustomerNotificationRead($id: ID!) {
|
|
3451
|
-
markCustomerNotificationRead(id: $id) {
|
|
3452
|
-
id
|
|
3453
|
-
isRead
|
|
3454
|
-
readAt
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
|
|
3458
|
-
mutation MarkAllCustomerNotificationsRead($category: String) {
|
|
3459
|
-
markAllCustomerNotificationsRead(category: $category)
|
|
3460
|
-
}
|
|
3461
|
-
|
|
3462
|
-
mutation RegisterDeviceToken($input: RegisterDeviceTokenInput!) {
|
|
3463
|
-
registerDeviceToken(input: $input) {
|
|
3464
|
-
id
|
|
3465
|
-
platform
|
|
3466
|
-
token
|
|
3467
|
-
deviceName
|
|
3468
|
-
isActive
|
|
3469
|
-
createdAt
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
|
|
3473
|
-
mutation UnregisterDeviceToken($token: String!) {
|
|
3474
|
-
unregisterDeviceToken(token: $token)
|
|
3475
|
-
}
|
|
3476
|
-
|
|
3477
|
-
mutation UpdateNotificationPreference($input: UpdateNotificationPreferenceInput!) {
|
|
3478
|
-
updateNotificationPreference(input: $input) {
|
|
3479
|
-
id
|
|
3480
|
-
category
|
|
3481
|
-
channel
|
|
3482
|
-
enabled
|
|
3483
|
-
}
|
|
3484
|
-
}
|
|
3485
|
-
`;
|
|
3486
|
-
}
|
|
3487
|
-
function operationsDocument() {
|
|
3488
|
-
return `# Operation execution operations
|
|
3489
|
-
${HEADER}
|
|
3490
|
-
|
|
3491
|
-
query GetOperationExecution($id: ID!) {
|
|
3492
|
-
operationExecution(id: $id) {
|
|
3493
|
-
id
|
|
3494
|
-
operationKey
|
|
3495
|
-
status
|
|
3496
|
-
result
|
|
3497
|
-
error
|
|
3498
|
-
startedAt
|
|
3499
|
-
completedAt
|
|
3500
|
-
durationMs
|
|
3501
|
-
metadata
|
|
3502
|
-
createdAt
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3506
|
-
query ListOperationExecutions(
|
|
3507
|
-
$operationKey: String
|
|
3508
|
-
$status: OperationExecutionStatus
|
|
3509
|
-
$limit: Int
|
|
3510
|
-
$offset: Int
|
|
3511
|
-
) {
|
|
3512
|
-
operationExecutions(
|
|
3513
|
-
operationKey: $operationKey
|
|
3514
|
-
status: $status
|
|
3515
|
-
limit: $limit
|
|
3516
|
-
offset: $offset
|
|
3517
|
-
) {
|
|
3518
|
-
items {
|
|
3519
|
-
id
|
|
3520
|
-
operationKey
|
|
3521
|
-
status
|
|
3522
|
-
durationMs
|
|
3523
|
-
startedAt
|
|
3524
|
-
completedAt
|
|
3525
|
-
metadata
|
|
3526
|
-
createdAt
|
|
3527
|
-
}
|
|
3528
|
-
total
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
|
|
3532
|
-
mutation ExecuteOperation($input: ExecuteOperationInput!) {
|
|
3533
|
-
executeOperation(input: $input) {
|
|
3534
|
-
success
|
|
3535
|
-
result
|
|
3536
|
-
error {
|
|
3537
|
-
code
|
|
3538
|
-
message
|
|
3539
|
-
}
|
|
3540
|
-
executionId
|
|
3541
|
-
durationMs
|
|
3542
|
-
metadata
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
mutation CancelOperationExecution($id: ID!) {
|
|
3547
|
-
cancelOperationExecution(id: $id) {
|
|
3548
|
-
id
|
|
3549
|
-
status
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
`;
|
|
3553
|
-
}
|
|
3554
|
-
function schedulesDocument() {
|
|
3555
|
-
return `# Schedule management operations
|
|
3556
|
-
${HEADER}
|
|
3557
|
-
|
|
3558
|
-
query GetSchedule($key: String!) {
|
|
3559
|
-
schedule(key: $key) {
|
|
3560
|
-
id
|
|
3561
|
-
key
|
|
3562
|
-
name
|
|
3563
|
-
description
|
|
3564
|
-
cron
|
|
3565
|
-
cronDescription
|
|
3566
|
-
timezone
|
|
3567
|
-
targetType
|
|
3568
|
-
isActive
|
|
3569
|
-
lastRunAt
|
|
3570
|
-
lastRunStatus
|
|
3571
|
-
nextRunAt
|
|
3572
|
-
runCount
|
|
3573
|
-
failureCount
|
|
3574
|
-
createdAt
|
|
3575
|
-
updatedAt
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
|
|
3579
|
-
query ListSchedules(
|
|
3580
|
-
$targetType: ScheduleTargetType
|
|
3581
|
-
$isActive: Boolean
|
|
3582
|
-
$limit: Int
|
|
3583
|
-
$offset: Int
|
|
3584
|
-
) {
|
|
3585
|
-
schedules(
|
|
3586
|
-
targetType: $targetType
|
|
3587
|
-
isActive: $isActive
|
|
3588
|
-
limit: $limit
|
|
3589
|
-
offset: $offset
|
|
3590
|
-
) {
|
|
3591
|
-
items {
|
|
3592
|
-
id
|
|
3593
|
-
key
|
|
3594
|
-
name
|
|
3595
|
-
cron
|
|
3596
|
-
cronDescription
|
|
3597
|
-
timezone
|
|
3598
|
-
isActive
|
|
3599
|
-
lastRunAt
|
|
3600
|
-
lastRunStatus
|
|
3601
|
-
nextRunAt
|
|
3602
|
-
runCount
|
|
3603
|
-
failureCount
|
|
3604
|
-
createdAt
|
|
3605
|
-
}
|
|
3606
|
-
total
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
|
|
3610
|
-
mutation CreateSchedule($input: CreateScheduleInput!) {
|
|
3611
|
-
createSchedule(input: $input) {
|
|
3612
|
-
id
|
|
3613
|
-
key
|
|
3614
|
-
name
|
|
3615
|
-
cron
|
|
3616
|
-
isActive
|
|
3617
|
-
createdAt
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
|
|
3621
|
-
mutation UpdateSchedule($key: String!, $input: UpdateScheduleInput!) {
|
|
3622
|
-
updateSchedule(key: $key, input: $input) {
|
|
3623
|
-
id
|
|
3624
|
-
key
|
|
3625
|
-
name
|
|
3626
|
-
cron
|
|
3627
|
-
isActive
|
|
3628
|
-
updatedAt
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
mutation DeleteSchedule($key: String!) {
|
|
3633
|
-
deleteSchedule(key: $key)
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
mutation PauseSchedule($key: String!) {
|
|
3637
|
-
pauseSchedule(key: $key) {
|
|
3638
|
-
id
|
|
3639
|
-
key
|
|
3640
|
-
isActive
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3643
|
-
|
|
3644
|
-
mutation ResumeSchedule($key: String!) {
|
|
3645
|
-
resumeSchedule(key: $key) {
|
|
3646
|
-
id
|
|
3647
|
-
key
|
|
3648
|
-
isActive
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
|
|
3652
|
-
mutation TriggerSchedule($key: String!) {
|
|
3653
|
-
triggerSchedule(key: $key) {
|
|
3654
|
-
success
|
|
3655
|
-
jobId
|
|
3656
|
-
error
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
`;
|
|
3660
|
-
}
|
|
3661
|
-
function sharingDocument() {
|
|
3662
|
-
return `# Sharing operations
|
|
3663
|
-
${HEADER}
|
|
3664
|
-
|
|
3665
|
-
fragment ShareFields on Share {
|
|
3666
|
-
id
|
|
3667
|
-
resourceType
|
|
3668
|
-
recordId
|
|
3669
|
-
fileId
|
|
3670
|
-
permission
|
|
3671
|
-
status
|
|
3672
|
-
sharedWithCustomerId
|
|
3673
|
-
acceptedAt
|
|
3674
|
-
declinedAt
|
|
3675
|
-
expiresAt
|
|
3676
|
-
createdAt
|
|
3677
|
-
createdBy
|
|
3678
|
-
revokedAt
|
|
3679
|
-
}
|
|
3680
|
-
|
|
3681
|
-
query GetShares($resourceType: ShareResourceType!, $resourceId: ID!, $status: ShareStatus) {
|
|
3682
|
-
shares(resourceType: $resourceType, resourceId: $resourceId, status: $status) {
|
|
3683
|
-
...ShareFields
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
query SharedWithMe(
|
|
3688
|
-
$resourceType: ShareResourceType
|
|
3689
|
-
$modelKey: String
|
|
3690
|
-
$status: ShareStatus
|
|
3691
|
-
$limit: Int
|
|
3692
|
-
$offset: Int
|
|
3693
|
-
) {
|
|
3694
|
-
sharedWithMe(
|
|
3695
|
-
resourceType: $resourceType
|
|
3696
|
-
modelKey: $modelKey
|
|
3697
|
-
status: $status
|
|
3698
|
-
limit: $limit
|
|
3699
|
-
offset: $offset
|
|
3700
|
-
) {
|
|
3701
|
-
...ShareFields
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
|
|
3705
|
-
mutation ShareRecord(
|
|
3706
|
-
$recordId: ID!
|
|
3707
|
-
$sharedWithCustomerId: ID!
|
|
3708
|
-
$permission: SharePermission!
|
|
3709
|
-
$expiresAt: DateTime
|
|
3710
|
-
) {
|
|
3711
|
-
shareRecord(
|
|
3712
|
-
recordId: $recordId
|
|
3713
|
-
sharedWithCustomerId: $sharedWithCustomerId
|
|
3714
|
-
permission: $permission
|
|
3715
|
-
expiresAt: $expiresAt
|
|
3716
|
-
) {
|
|
3717
|
-
...ShareFields
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
|
|
3721
|
-
mutation ShareFile(
|
|
3722
|
-
$fileId: ID!
|
|
3723
|
-
$sharedWithCustomerId: ID!
|
|
3724
|
-
$permission: SharePermission!
|
|
3725
|
-
$expiresAt: DateTime
|
|
3726
|
-
) {
|
|
3727
|
-
shareFile(
|
|
3728
|
-
fileId: $fileId
|
|
3729
|
-
sharedWithCustomerId: $sharedWithCustomerId
|
|
3730
|
-
permission: $permission
|
|
3731
|
-
expiresAt: $expiresAt
|
|
3732
|
-
) {
|
|
3733
|
-
...ShareFields
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
|
|
3737
|
-
mutation AcceptShare($shareId: ID!) {
|
|
3738
|
-
acceptShare(shareId: $shareId) {
|
|
3739
|
-
...ShareFields
|
|
3740
|
-
}
|
|
3741
|
-
}
|
|
3742
|
-
|
|
3743
|
-
mutation DeclineShare($shareId: ID!) {
|
|
3744
|
-
declineShare(shareId: $shareId) {
|
|
3745
|
-
...ShareFields
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
mutation RevokeShare($shareId: ID!) {
|
|
3750
|
-
revokeShare(shareId: $shareId) {
|
|
3751
|
-
...ShareFields
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
|
|
3755
|
-
mutation UpdateSharePermission($shareId: ID!, $permission: SharePermission!) {
|
|
3756
|
-
updateSharePermission(shareId: $shareId, permission: $permission) {
|
|
3757
|
-
...ShareFields
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
`;
|
|
3761
|
-
}
|
|
3762
|
-
function embeddingsDocument() {
|
|
3763
|
-
return `# Vector embedding operations (search, write, delete)
|
|
3764
|
-
${HEADER}
|
|
3765
|
-
|
|
3766
|
-
query SearchEmbeddings($input: SearchEmbeddingsInput!) {
|
|
3767
|
-
searchEmbeddings(input: $input) {
|
|
3768
|
-
recordId
|
|
3769
|
-
modelKey
|
|
3770
|
-
naturalKey
|
|
3771
|
-
key
|
|
3772
|
-
similarity
|
|
3773
|
-
metadata
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
|
-
mutation WriteEmbeddings($input: WriteEmbeddingsInput!) {
|
|
3778
|
-
writeEmbeddings(input: $input) {
|
|
3779
|
-
written
|
|
3780
|
-
errors {
|
|
3781
|
-
recordId
|
|
3782
|
-
key
|
|
3783
|
-
message
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
|
|
3788
|
-
mutation DeleteEmbeddings($input: DeleteEmbeddingsInput!) {
|
|
3789
|
-
deleteEmbeddings(input: $input) {
|
|
3790
|
-
deleted
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
`;
|
|
3794
|
-
}
|
|
3795
|
-
function analyticsDocument() {
|
|
3796
|
-
return `# Analytics & conversion tracking operations
|
|
3797
|
-
${HEADER}
|
|
3798
|
-
|
|
3799
|
-
mutation RecordConversion($input: RecordConversionInput!) {
|
|
3800
|
-
recordConversion(input: $input) {
|
|
3801
|
-
success
|
|
3802
|
-
}
|
|
3803
|
-
}
|
|
3804
|
-
`;
|
|
3805
|
-
}
|
|
3806
|
-
function generateStaticDocuments(domains) {
|
|
3807
|
-
const files = [];
|
|
3808
|
-
if (domains.auth)
|
|
3809
|
-
files.push({ filename: "auth.graphql", content: authDocument() });
|
|
3810
|
-
if (domains.authProviders)
|
|
3811
|
-
files.push({
|
|
3812
|
-
filename: "auth-providers.graphql",
|
|
3813
|
-
content: authProvidersDocument()
|
|
3814
|
-
});
|
|
3815
|
-
if (domains.files)
|
|
3816
|
-
files.push({ filename: "files.graphql", content: filesDocument() });
|
|
3817
|
-
if (domains.sync)
|
|
3818
|
-
files.push({ filename: "sync.graphql", content: syncDocument() });
|
|
3819
|
-
if (domains.notifications)
|
|
3820
|
-
files.push({
|
|
3821
|
-
filename: "notifications.graphql",
|
|
3822
|
-
content: notificationsDocument()
|
|
3823
|
-
});
|
|
3824
|
-
if (domains.operations)
|
|
3825
|
-
files.push({
|
|
3826
|
-
filename: "operations.graphql",
|
|
3827
|
-
content: operationsDocument()
|
|
3828
|
-
});
|
|
3829
|
-
if (domains.schedules)
|
|
3830
|
-
files.push({ filename: "schedules.graphql", content: schedulesDocument() });
|
|
3831
|
-
if (domains.sharing)
|
|
3832
|
-
files.push({ filename: "sharing.graphql", content: sharingDocument() });
|
|
3833
|
-
if (domains.embeddings)
|
|
3834
|
-
files.push({
|
|
3835
|
-
filename: "embeddings.graphql",
|
|
3836
|
-
content: embeddingsDocument()
|
|
3837
|
-
});
|
|
3838
|
-
if (domains.analytics)
|
|
3839
|
-
files.push({ filename: "analytics.graphql", content: analyticsDocument() });
|
|
3840
|
-
return files;
|
|
3841
|
-
}
|
|
3842
|
-
|
|
3843
|
-
// src/codegen/generators/typed-operations-common.ts
|
|
3844
|
-
function generateTypedOperationsCommon(typesRelPath) {
|
|
3845
|
-
return `/**
|
|
3846
|
-
* Shared types for typed GraphQL operations.
|
|
3847
|
-
*
|
|
3848
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
3849
|
-
*/
|
|
3850
|
-
|
|
3851
|
-
import type { JsonValue } from '${typesRelPath}/field-types.js';
|
|
3852
|
-
|
|
3853
|
-
/** A record with strongly-typed data. */
|
|
3854
|
-
export interface BaseRecord<T> {
|
|
3855
|
-
id: string;
|
|
3856
|
-
modelKey: string;
|
|
3857
|
-
naturalKey: string | null;
|
|
3858
|
-
data: T;
|
|
3859
|
-
metadata: Record<string, JsonValue> | null;
|
|
3860
|
-
publishedVersionNumber: number | null;
|
|
3861
|
-
publishedAt: string | null;
|
|
3862
|
-
versionNumber: number | null;
|
|
3863
|
-
changeDescription: string | null;
|
|
3864
|
-
createdAt: string;
|
|
3865
|
-
updatedAt: string;
|
|
3866
|
-
}
|
|
3867
|
-
|
|
3868
|
-
/** Resolved content wrapping strongly-typed data. */
|
|
3869
|
-
export interface ResolvedContent<T> {
|
|
3870
|
-
content: T;
|
|
3871
|
-
record: { id: string; modelKey: string; naturalKey: string | null };
|
|
3872
|
-
version: { id: string; versionNumber: number } | null;
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
/** Paginated list result. */
|
|
3876
|
-
export interface PaginatedResult<T> {
|
|
3877
|
-
items: (BaseRecord<T> & { resolved: ResolvedContent<T> | null })[];
|
|
3878
|
-
total: number;
|
|
3879
|
-
}
|
|
3880
|
-
|
|
3881
|
-
/** Result of a createRecord mutation. */
|
|
3882
|
-
export interface CreateRecordResult<T> {
|
|
3883
|
-
record: BaseRecord<T>;
|
|
3884
|
-
}
|
|
3885
|
-
|
|
3886
|
-
/** Result of an updateRecord mutation. */
|
|
3887
|
-
export interface UpdateRecordResult<T> {
|
|
3888
|
-
record: BaseRecord<T>;
|
|
3889
|
-
matched: boolean;
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
|
-
/** Result of a deleteRecord mutation. */
|
|
3893
|
-
export interface DeleteRecordResult {
|
|
3894
|
-
id: string;
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
/** Share resource type. */
|
|
3898
|
-
export interface ShareResult {
|
|
3899
|
-
id: string;
|
|
3900
|
-
resourceType: string;
|
|
3901
|
-
permission: string;
|
|
3902
|
-
status: string;
|
|
3903
|
-
acceptedAt: string | null;
|
|
3904
|
-
declinedAt: string | null;
|
|
3905
|
-
expiresAt: string | null;
|
|
3906
|
-
createdAt: string;
|
|
3907
|
-
revokedAt: string | null;
|
|
3908
|
-
}
|
|
3909
|
-
|
|
3910
|
-
/** A share that includes the shared record. */
|
|
3911
|
-
export interface ShareWithRecord<T> extends ShareResult {
|
|
3912
|
-
record: BaseRecord<T>;
|
|
3913
|
-
}
|
|
3914
|
-
|
|
3915
|
-
/** Field selection for resolved content \u2014 pick or omit specific fields. */
|
|
3916
|
-
export interface FieldSelection<T = Record<string, unknown>> {
|
|
3917
|
-
/** Include only these field keys (mutually exclusive with omit) */
|
|
3918
|
-
pick?: (keyof T & string)[];
|
|
3919
|
-
/** Exclude these field keys (mutually exclusive with omit) */
|
|
3920
|
-
omit?: (keyof T & string)[];
|
|
3921
|
-
}
|
|
3922
|
-
`;
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
|
-
// src/codegen/generators/typed-operations.ts
|
|
3926
|
-
import path from "path";
|
|
3927
|
-
function generateTypedOperations(model, typesRelPath) {
|
|
3928
|
-
const typeName = toPascalCase(model.key);
|
|
3929
|
-
const upperSnake = toUpperSnakeCase(model.key);
|
|
3930
|
-
const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
|
|
3931
|
-
const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
|
|
3932
|
-
const dataType = `${typeName}Data`;
|
|
3933
|
-
const lines = [];
|
|
3934
|
-
lines.push(`/**
|
|
3935
|
-
* Typed operations for ${model.name ?? model.key}
|
|
3936
|
-
*
|
|
3937
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
3938
|
-
*/
|
|
3939
|
-
|
|
3940
|
-
import type { JsonValue } from '${typesRelPath}/field-types.js';
|
|
3941
|
-
import type { ${dataType} } from '${typesRelPath}/models/${model.key}.js';
|
|
3942
|
-
import type {
|
|
3943
|
-
BaseRecord,
|
|
3944
|
-
ResolvedContent,
|
|
3945
|
-
PaginatedResult,
|
|
3946
|
-
CreateRecordResult,
|
|
3947
|
-
UpdateRecordResult,
|
|
3948
|
-
DeleteRecordResult,
|
|
3949
|
-
FieldSelection,${model.config.sharing?.enabled ? "\n ShareResult,\n ShareWithRecord," : ""}
|
|
3950
|
-
} from './_common.js';
|
|
3951
|
-
`);
|
|
3952
|
-
lines.push(`export type ${typeName}Record = BaseRecord<${dataType}>;`);
|
|
3953
|
-
lines.push("");
|
|
3954
|
-
lines.push(`export const GET_${upperSnake} = \`
|
|
3955
|
-
query Get${typeName}($id: ID!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
|
|
3956
|
-
record(id: $id) {
|
|
3957
|
-
id modelKey naturalKey data metadata
|
|
3958
|
-
publishedVersionNumber publishedAt versionNumber changeDescription
|
|
3959
|
-
createdAt updatedAt
|
|
3960
|
-
resolved(locale: $locale, preview: $preview, fields: $fields) {
|
|
3961
|
-
content
|
|
3962
|
-
record { id modelKey naturalKey }
|
|
3963
|
-
version { id versionNumber }
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
\`;`);
|
|
3968
|
-
lines.push("");
|
|
3969
|
-
lines.push(`export interface Get${typeName}Variables {
|
|
3970
|
-
id: string;
|
|
3971
|
-
locale?: string;
|
|
3972
|
-
preview?: boolean;
|
|
3973
|
-
fields?: FieldSelection<${dataType}>;
|
|
3974
|
-
}`);
|
|
3975
|
-
lines.push("");
|
|
3976
|
-
lines.push(`export interface Get${typeName}Result {
|
|
3977
|
-
record: ${typeName}Record & {
|
|
3978
|
-
resolved: ResolvedContent<${dataType}> | null;
|
|
3979
|
-
} | null;
|
|
3980
|
-
}`);
|
|
3981
|
-
lines.push("");
|
|
3982
|
-
lines.push(`export const GET_${upperSnake}_BY_KEY = \`
|
|
3983
|
-
query Get${typeName}ByKey($naturalKey: String!, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
|
|
3984
|
-
recordByKey(modelKey: "${model.key}", naturalKey: $naturalKey) {
|
|
3985
|
-
id modelKey naturalKey data metadata
|
|
3986
|
-
publishedVersionNumber publishedAt versionNumber changeDescription
|
|
3987
|
-
createdAt updatedAt
|
|
3988
|
-
resolved(locale: $locale, preview: $preview, fields: $fields) {
|
|
3989
|
-
content
|
|
3990
|
-
record { id modelKey naturalKey }
|
|
3991
|
-
version { id versionNumber }
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3994
|
-
}
|
|
3995
|
-
\`;`);
|
|
3996
|
-
lines.push("");
|
|
3997
|
-
lines.push(`export interface Get${typeName}ByKeyVariables {
|
|
3998
|
-
naturalKey: string;
|
|
3999
|
-
locale?: string;
|
|
4000
|
-
preview?: boolean;
|
|
4001
|
-
fields?: FieldSelection<${dataType}>;
|
|
4002
|
-
}`);
|
|
4003
|
-
lines.push("");
|
|
4004
|
-
lines.push(`export interface Get${typeName}ByKeyResult {
|
|
4005
|
-
recordByKey: ${typeName}Record & {
|
|
4006
|
-
resolved: ResolvedContent<${dataType}> | null;
|
|
4007
|
-
} | null;
|
|
4008
|
-
}`);
|
|
4009
|
-
lines.push("");
|
|
4010
|
-
lines.push(`export const LIST_${pluralUpperSnake} = \`
|
|
4011
|
-
query List${pluralName}($limit: Int, $offset: Int, $filters: [FilterInput!], $sort: SortInput, $locale: String, $preview: Boolean, $fields: FieldSelectionInput) {
|
|
4012
|
-
records(modelKey: "${model.key}", limit: $limit, offset: $offset, filters: $filters, sort: $sort) {
|
|
4013
|
-
items {
|
|
4014
|
-
id modelKey naturalKey data metadata
|
|
4015
|
-
publishedVersionNumber publishedAt versionNumber changeDescription
|
|
4016
|
-
createdAt updatedAt
|
|
4017
|
-
resolved(locale: $locale, preview: $preview, fields: $fields) {
|
|
4018
|
-
content
|
|
4019
|
-
record { id modelKey naturalKey }
|
|
4020
|
-
version { id versionNumber }
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
total
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
\`;`);
|
|
4027
|
-
lines.push("");
|
|
4028
|
-
lines.push(`export interface List${pluralName}Variables {
|
|
4029
|
-
limit?: number;
|
|
4030
|
-
offset?: number;
|
|
4031
|
-
filters?: Array<{ field: string; operator: string; value: JsonValue }>;
|
|
4032
|
-
sort?: { field: string; direction: 'ASC' | 'DESC' };
|
|
4033
|
-
locale?: string;
|
|
4034
|
-
preview?: boolean;
|
|
4035
|
-
fields?: FieldSelection<${dataType}>;
|
|
4036
|
-
}`);
|
|
4037
|
-
lines.push("");
|
|
4038
|
-
lines.push(`export interface List${pluralName}Result {
|
|
4039
|
-
records: PaginatedResult<${dataType}>;
|
|
4040
|
-
}`);
|
|
4041
|
-
lines.push("");
|
|
4042
|
-
lines.push(`export const CREATE_${upperSnake} = \`
|
|
4043
|
-
mutation Create${typeName}($input: CreateRecordInput!) {
|
|
4044
|
-
createRecord(input: $input) {
|
|
4045
|
-
record {
|
|
4046
|
-
id modelKey naturalKey data metadata createdAt updatedAt
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
\`;`);
|
|
4051
|
-
lines.push("");
|
|
4052
|
-
lines.push(`export interface Create${typeName}Variables {
|
|
4053
|
-
input: {
|
|
4054
|
-
modelKey: string;
|
|
4055
|
-
naturalKey?: string;
|
|
4056
|
-
data: Partial<${dataType}>;
|
|
4057
|
-
metadata?: Record<string, JsonValue>;
|
|
4058
|
-
};
|
|
4059
|
-
}`);
|
|
4060
|
-
lines.push("");
|
|
4061
|
-
lines.push(`export interface Create${typeName}Result {
|
|
4062
|
-
createRecord: CreateRecordResult<${dataType}>;
|
|
4063
|
-
}`);
|
|
4064
|
-
lines.push("");
|
|
4065
|
-
lines.push(`export const UPDATE_${upperSnake} = \`
|
|
4066
|
-
mutation Update${typeName}($input: UpdateRecordInput!) {
|
|
4067
|
-
updateRecord(input: $input) {
|
|
4068
|
-
record {
|
|
4069
|
-
id modelKey naturalKey data metadata createdAt updatedAt
|
|
4070
|
-
}
|
|
4071
|
-
matched
|
|
4072
|
-
}
|
|
4073
|
-
}
|
|
4074
|
-
\`;`);
|
|
4075
|
-
lines.push("");
|
|
4076
|
-
lines.push(`export interface Update${typeName}Variables {
|
|
4077
|
-
input: {
|
|
4078
|
-
id: string;
|
|
4079
|
-
data?: Partial<${dataType}>;
|
|
4080
|
-
metadata?: Record<string, JsonValue>;
|
|
4081
|
-
changeDescription?: string;
|
|
4082
|
-
};
|
|
4083
|
-
}`);
|
|
4084
|
-
lines.push("");
|
|
4085
|
-
lines.push(`export interface Update${typeName}Result {
|
|
4086
|
-
updateRecord: UpdateRecordResult<${dataType}>;
|
|
4087
|
-
}`);
|
|
4088
|
-
lines.push("");
|
|
4089
|
-
lines.push(`export const DELETE_${upperSnake} = \`
|
|
4090
|
-
mutation Delete${typeName}($id: ID!) {
|
|
4091
|
-
deleteRecord(id: $id) { id }
|
|
4092
|
-
}
|
|
4093
|
-
\`;`);
|
|
4094
|
-
lines.push("");
|
|
4095
|
-
lines.push(`export interface Delete${typeName}Variables {
|
|
4096
|
-
id: string;
|
|
4097
|
-
}`);
|
|
4098
|
-
lines.push("");
|
|
4099
|
-
lines.push(`export interface Delete${typeName}Result {
|
|
4100
|
-
deleteRecord: DeleteRecordResult;
|
|
4101
|
-
}`);
|
|
4102
|
-
lines.push("");
|
|
4103
|
-
lines.push(`export const PUBLISH_${upperSnake}_VERSION = \`
|
|
4104
|
-
mutation Publish${typeName}Version($versionId: ID!) {
|
|
4105
|
-
publishVersion(versionId: $versionId)
|
|
4106
|
-
}
|
|
4107
|
-
\`;`);
|
|
4108
|
-
lines.push("");
|
|
4109
|
-
lines.push(`export const UNPUBLISH_${upperSnake} = \`
|
|
4110
|
-
mutation Unpublish${typeName}($id: ID!) {
|
|
4111
|
-
unpublishRecord(id: $id)
|
|
4112
|
-
}
|
|
4113
|
-
\`;`);
|
|
4114
|
-
lines.push("");
|
|
4115
|
-
if (model.config.sharing?.enabled) {
|
|
4116
|
-
lines.push(`// --- Sharing operations ---`);
|
|
4117
|
-
lines.push("");
|
|
4118
|
-
lines.push(`export const SHARE_${upperSnake} = \`
|
|
4119
|
-
mutation Share${typeName}($recordId: ID!, $sharedWithCustomerId: ID!, $permission: SharePermission!) {
|
|
4120
|
-
shareRecord(recordId: $recordId, sharedWithCustomerId: $sharedWithCustomerId, permission: $permission) {
|
|
4121
|
-
id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
\`;`);
|
|
4125
|
-
lines.push("");
|
|
4126
|
-
lines.push(`export interface Share${typeName}Variables {
|
|
4127
|
-
recordId: string;
|
|
4128
|
-
sharedWithCustomerId: string;
|
|
4129
|
-
permission: 'VIEW' | 'EDIT' | 'ADMIN';
|
|
4130
|
-
}`);
|
|
4131
|
-
lines.push("");
|
|
4132
|
-
lines.push(`export interface Share${typeName}Result {
|
|
4133
|
-
shareRecord: ShareResult;
|
|
4134
|
-
}`);
|
|
4135
|
-
lines.push("");
|
|
4136
|
-
lines.push(`export const ${upperSnake}_SHARES = \`
|
|
4137
|
-
query ${typeName}Shares($resourceId: ID!, $status: ShareStatus) {
|
|
4138
|
-
shares(resourceType: RECORD, resourceId: $resourceId, status: $status) {
|
|
4139
|
-
id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
\`;`);
|
|
4143
|
-
lines.push("");
|
|
4144
|
-
lines.push(`export const ${pluralUpperSnake}_SHARED_WITH_ME = \`
|
|
4145
|
-
query ${pluralName}SharedWithMe($status: ShareStatus) {
|
|
4146
|
-
sharedWithMe(resourceType: RECORD, modelKey: "${model.key}", status: $status) {
|
|
4147
|
-
id resourceType permission status acceptedAt declinedAt expiresAt createdAt revokedAt
|
|
4148
|
-
record {
|
|
4149
|
-
id modelKey naturalKey data metadata
|
|
4150
|
-
publishedVersionNumber publishedAt versionNumber changeDescription
|
|
4151
|
-
createdAt updatedAt
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4154
|
-
}
|
|
4155
|
-
\`;`);
|
|
4156
|
-
lines.push("");
|
|
4157
|
-
lines.push(`export interface ${pluralName}SharedWithMeResult {
|
|
4158
|
-
sharedWithMe: ShareWithRecord<${dataType}>[];
|
|
4159
|
-
}`);
|
|
4160
|
-
lines.push("");
|
|
4161
|
-
}
|
|
4162
|
-
return lines.join("\n");
|
|
4163
|
-
}
|
|
4164
|
-
function computeTypesRelPath(opsDir, typesDir) {
|
|
4165
|
-
const rel = path.relative(opsDir, typesDir).replace(/\\/g, "/");
|
|
4166
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
// src/codegen/generators/typed-operations-index.ts
|
|
4170
|
-
function generateTypedOperationsIndex(models, hasCustomerProfile) {
|
|
4171
|
-
const lines = [];
|
|
4172
|
-
lines.push(`/**
|
|
4173
|
-
* Typed GraphQL operations.
|
|
4174
|
-
*
|
|
4175
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4176
|
-
*/
|
|
4177
|
-
|
|
4178
|
-
export * from './_common.js';
|
|
4179
|
-
`);
|
|
4180
|
-
for (const model of models) {
|
|
4181
|
-
lines.push(`export * from './${model.key}.js';`);
|
|
4182
|
-
}
|
|
4183
|
-
if (hasCustomerProfile) {
|
|
4184
|
-
lines.push(`export * from './customer-profile.js';`);
|
|
4185
|
-
}
|
|
4186
|
-
lines.push("");
|
|
4187
|
-
return lines.join("\n");
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
// src/codegen/generators/customer-profile-operations.ts
|
|
4191
|
-
function generateCustomerProfileOperations(typesRelPath) {
|
|
4192
|
-
return `/**
|
|
4193
|
-
* Typed operations for Customer Profiles
|
|
4194
|
-
*
|
|
4195
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4196
|
-
*/
|
|
4197
|
-
|
|
4198
|
-
import type { CustomerProfileData } from '${typesRelPath}/customer-profile.js';
|
|
4199
|
-
|
|
4200
|
-
export const GET_MY_PROFILE = \`
|
|
4201
|
-
query GetMyProfile {
|
|
4202
|
-
myProfile {
|
|
4203
|
-
id
|
|
4204
|
-
customerId
|
|
4205
|
-
data
|
|
4206
|
-
createdAt
|
|
4207
|
-
updatedAt
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
\`;
|
|
4211
|
-
|
|
4212
|
-
export interface GetMyProfileResult {
|
|
4213
|
-
myProfile: {
|
|
4214
|
-
id: string;
|
|
4215
|
-
customerId: string;
|
|
4216
|
-
data: CustomerProfileData;
|
|
4217
|
-
createdAt: string;
|
|
4218
|
-
updatedAt: string;
|
|
4219
|
-
} | null;
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
export const GET_CUSTOMER_PROFILE = \`
|
|
4223
|
-
query GetCustomerProfile($customerId: ID!) {
|
|
4224
|
-
customerProfile(customerId: $customerId) {
|
|
4225
|
-
id
|
|
4226
|
-
customerId
|
|
4227
|
-
data
|
|
4228
|
-
createdAt
|
|
4229
|
-
updatedAt
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4232
|
-
\`;
|
|
4233
|
-
|
|
4234
|
-
export interface GetCustomerProfileVariables {
|
|
4235
|
-
customerId: string;
|
|
4236
|
-
}
|
|
4237
|
-
|
|
4238
|
-
export interface GetCustomerProfileResult {
|
|
4239
|
-
customerProfile: {
|
|
4240
|
-
id: string;
|
|
4241
|
-
customerId: string;
|
|
4242
|
-
data: CustomerProfileData;
|
|
4243
|
-
createdAt: string;
|
|
4244
|
-
updatedAt: string;
|
|
4245
|
-
} | null;
|
|
4246
|
-
}
|
|
4247
|
-
|
|
4248
|
-
export const SET_MY_PROFILE = \`
|
|
4249
|
-
mutation SetMyProfile($data: JSON!) {
|
|
4250
|
-
setMyProfile(data: $data) {
|
|
4251
|
-
id
|
|
4252
|
-
customerId
|
|
4253
|
-
data
|
|
4254
|
-
createdAt
|
|
4255
|
-
updatedAt
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
\`;
|
|
4259
|
-
|
|
4260
|
-
export interface SetMyProfileVariables {
|
|
4261
|
-
data: Partial<CustomerProfileData>;
|
|
4262
|
-
}
|
|
4263
|
-
|
|
4264
|
-
export interface SetMyProfileResult {
|
|
4265
|
-
setMyProfile: {
|
|
4266
|
-
id: string;
|
|
4267
|
-
customerId: string;
|
|
4268
|
-
data: CustomerProfileData;
|
|
4269
|
-
createdAt: string;
|
|
4270
|
-
updatedAt: string;
|
|
4271
|
-
};
|
|
4272
|
-
}
|
|
4273
|
-
|
|
4274
|
-
export const UPDATE_CUSTOMER_PROFILE = \`
|
|
4275
|
-
mutation UpdateCustomerProfile($customerId: ID, $input: CustomerProfileInput!) {
|
|
4276
|
-
updateCustomerProfile(customerId: $customerId, input: $input) {
|
|
4277
|
-
id
|
|
4278
|
-
customerId
|
|
4279
|
-
data
|
|
4280
|
-
createdAt
|
|
4281
|
-
updatedAt
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
|
-
\`;
|
|
4285
|
-
|
|
4286
|
-
export interface UpdateCustomerProfileVariables {
|
|
4287
|
-
customerId?: string;
|
|
4288
|
-
input: {
|
|
4289
|
-
data: Partial<CustomerProfileData>;
|
|
4290
|
-
};
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
export interface UpdateCustomerProfileResult {
|
|
4294
|
-
updateCustomerProfile: {
|
|
4295
|
-
id: string;
|
|
4296
|
-
customerId: string;
|
|
4297
|
-
data: CustomerProfileData;
|
|
4298
|
-
createdAt: string;
|
|
4299
|
-
updatedAt: string;
|
|
4300
|
-
};
|
|
4301
|
-
}
|
|
4302
|
-
|
|
4303
|
-
export const DELETE_MY_PROFILE = \`
|
|
4304
|
-
mutation DeleteMyProfile {
|
|
4305
|
-
deleteMyProfile
|
|
4306
|
-
}
|
|
4307
|
-
\`;
|
|
4308
|
-
|
|
4309
|
-
export interface DeleteMyProfileResult {
|
|
4310
|
-
deleteMyProfile: boolean;
|
|
4311
|
-
}
|
|
4312
|
-
`;
|
|
4313
|
-
}
|
|
4314
|
-
|
|
4315
|
-
// src/codegen/generators/react-hooks.ts
|
|
4316
|
-
function generateReactHooks(model) {
|
|
4317
|
-
const typeName = toPascalCase(model.key);
|
|
4318
|
-
const upperSnake = toUpperSnakeCase(model.key);
|
|
4319
|
-
const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
|
|
4320
|
-
const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
|
|
4321
|
-
const lines = [];
|
|
4322
|
-
lines.push(`/**
|
|
4323
|
-
* React Apollo hooks for ${model.name ?? model.key}
|
|
4324
|
-
*
|
|
4325
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4326
|
-
*/
|
|
4327
|
-
|
|
4328
|
-
import { useQuery, useMutation } from '@apollo/client';
|
|
4329
|
-
import type { QueryHookOptions, MutationHookOptions } from '@apollo/client';
|
|
4330
|
-
import { gql } from '@apollo/client';
|
|
4331
|
-
import {
|
|
4332
|
-
GET_${upperSnake},
|
|
4333
|
-
GET_${upperSnake}_BY_KEY,
|
|
4334
|
-
LIST_${pluralUpperSnake},
|
|
4335
|
-
CREATE_${upperSnake},
|
|
4336
|
-
UPDATE_${upperSnake},
|
|
4337
|
-
DELETE_${upperSnake},
|
|
4338
|
-
PUBLISH_${upperSnake}_VERSION,
|
|
4339
|
-
UNPUBLISH_${upperSnake},
|
|
4340
|
-
} from '../operations/${model.key}.js';
|
|
4341
|
-
import type {
|
|
4342
|
-
${typeName}Record,
|
|
4343
|
-
Get${typeName}Variables,
|
|
4344
|
-
Get${typeName}Result,
|
|
4345
|
-
Get${typeName}ByKeyVariables,
|
|
4346
|
-
Get${typeName}ByKeyResult,
|
|
4347
|
-
List${pluralName}Variables,
|
|
4348
|
-
List${pluralName}Result,
|
|
4349
|
-
Create${typeName}Variables,
|
|
4350
|
-
Create${typeName}Result,
|
|
4351
|
-
Update${typeName}Variables,
|
|
4352
|
-
Update${typeName}Result,
|
|
4353
|
-
Delete${typeName}Variables,
|
|
4354
|
-
Delete${typeName}Result,
|
|
4355
|
-
} from '../operations/${model.key}.js';
|
|
4356
|
-
`);
|
|
4357
|
-
lines.push(`export type { ${typeName}Record };`);
|
|
4358
|
-
lines.push("");
|
|
4359
|
-
lines.push(`export function useGet${typeName}(
|
|
4360
|
-
id: string | null | undefined,
|
|
4361
|
-
options?: Omit<QueryHookOptions<Get${typeName}Result, Get${typeName}Variables>, 'variables' | 'skip'>
|
|
4362
|
-
) {
|
|
4363
|
-
return useQuery<Get${typeName}Result, Get${typeName}Variables>(
|
|
4364
|
-
gql\`\${GET_${upperSnake}}\`,
|
|
4365
|
-
{ ...options, variables: { id: id! }, skip: !id }
|
|
4366
|
-
);
|
|
4367
|
-
}`);
|
|
4368
|
-
lines.push("");
|
|
4369
|
-
lines.push(`export function useGet${typeName}ByKey(
|
|
4370
|
-
naturalKey: string | null | undefined,
|
|
4371
|
-
options?: Omit<QueryHookOptions<Get${typeName}ByKeyResult, Get${typeName}ByKeyVariables>, 'variables' | 'skip'>
|
|
4372
|
-
) {
|
|
4373
|
-
return useQuery<Get${typeName}ByKeyResult, Get${typeName}ByKeyVariables>(
|
|
4374
|
-
gql\`\${GET_${upperSnake}_BY_KEY}\`,
|
|
4375
|
-
{ ...options, variables: { naturalKey: naturalKey! }, skip: !naturalKey }
|
|
4376
|
-
);
|
|
4377
|
-
}`);
|
|
4378
|
-
lines.push("");
|
|
4379
|
-
lines.push(`export function useList${pluralName}(
|
|
4380
|
-
variables?: List${pluralName}Variables,
|
|
4381
|
-
options?: Omit<QueryHookOptions<List${pluralName}Result, List${pluralName}Variables>, 'variables'>
|
|
4382
|
-
) {
|
|
4383
|
-
return useQuery<List${pluralName}Result, List${pluralName}Variables>(
|
|
4384
|
-
gql\`\${LIST_${pluralUpperSnake}}\`,
|
|
4385
|
-
{ ...options, variables }
|
|
4386
|
-
);
|
|
4387
|
-
}`);
|
|
4388
|
-
lines.push("");
|
|
4389
|
-
lines.push(`export function useCreate${typeName}(
|
|
4390
|
-
options?: MutationHookOptions<Create${typeName}Result, Create${typeName}Variables>
|
|
4391
|
-
) {
|
|
4392
|
-
return useMutation<Create${typeName}Result, Create${typeName}Variables>(
|
|
4393
|
-
gql\`\${CREATE_${upperSnake}}\`,
|
|
4394
|
-
options
|
|
4395
|
-
);
|
|
4396
|
-
}`);
|
|
4397
|
-
lines.push("");
|
|
4398
|
-
lines.push(`export function useUpdate${typeName}(
|
|
4399
|
-
options?: MutationHookOptions<Update${typeName}Result, Update${typeName}Variables>
|
|
4400
|
-
) {
|
|
4401
|
-
return useMutation<Update${typeName}Result, Update${typeName}Variables>(
|
|
4402
|
-
gql\`\${UPDATE_${upperSnake}}\`,
|
|
4403
|
-
options
|
|
4404
|
-
);
|
|
4405
|
-
}`);
|
|
4406
|
-
lines.push("");
|
|
4407
|
-
lines.push(`export function useDelete${typeName}(
|
|
4408
|
-
options?: MutationHookOptions<Delete${typeName}Result, Delete${typeName}Variables>
|
|
4409
|
-
) {
|
|
4410
|
-
return useMutation<Delete${typeName}Result, Delete${typeName}Variables>(
|
|
4411
|
-
gql\`\${DELETE_${upperSnake}}\`,
|
|
4412
|
-
options
|
|
4413
|
-
);
|
|
4414
|
-
}`);
|
|
4415
|
-
lines.push("");
|
|
4416
|
-
lines.push(`export function usePublish${typeName}Version(
|
|
4417
|
-
options?: MutationHookOptions<{ publishVersion: boolean }, { versionId: string }>
|
|
4418
|
-
) {
|
|
4419
|
-
return useMutation<{ publishVersion: boolean }, { versionId: string }>(
|
|
4420
|
-
gql\`\${PUBLISH_${upperSnake}_VERSION}\`,
|
|
4421
|
-
options
|
|
4422
|
-
);
|
|
4423
|
-
}`);
|
|
4424
|
-
lines.push("");
|
|
4425
|
-
lines.push(`export function useUnpublish${typeName}(
|
|
4426
|
-
options?: MutationHookOptions<{ unpublishRecord: boolean }, { id: string }>
|
|
4427
|
-
) {
|
|
4428
|
-
return useMutation<{ unpublishRecord: boolean }, { id: string }>(
|
|
4429
|
-
gql\`\${UNPUBLISH_${upperSnake}}\`,
|
|
4430
|
-
options
|
|
4431
|
-
);
|
|
4432
|
-
}`);
|
|
4433
|
-
lines.push("");
|
|
4434
|
-
return lines.join("\n");
|
|
4435
|
-
}
|
|
4436
|
-
|
|
4437
|
-
// src/codegen/generators/react-hooks-index.ts
|
|
4438
|
-
function generateReactHooksIndex(models, hasCustomerProfile) {
|
|
4439
|
-
const lines = [];
|
|
4440
|
-
lines.push(`/**
|
|
4441
|
-
* React Apollo hooks for all models.
|
|
4442
|
-
*
|
|
4443
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4444
|
-
*/
|
|
4445
|
-
`);
|
|
4446
|
-
for (const model of models) {
|
|
4447
|
-
lines.push(`export * from './${model.key}.js';`);
|
|
4448
|
-
}
|
|
4449
|
-
if (hasCustomerProfile) {
|
|
4450
|
-
lines.push(`export * from './customer-profile.js';`);
|
|
4451
|
-
}
|
|
4452
|
-
lines.push("");
|
|
4453
|
-
return lines.join("\n");
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
|
-
// src/codegen/generators/customer-profile-hooks.ts
|
|
4457
|
-
function generateCustomerProfileHooks() {
|
|
4458
|
-
return `/**
|
|
4459
|
-
* React Apollo hooks for Customer Profiles
|
|
4460
|
-
*
|
|
4461
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4462
|
-
*/
|
|
4463
|
-
|
|
4464
|
-
import { useQuery, useMutation } from '@apollo/client';
|
|
4465
|
-
import type { QueryHookOptions, MutationHookOptions } from '@apollo/client';
|
|
4466
|
-
import { gql } from '@apollo/client';
|
|
4467
|
-
import {
|
|
4468
|
-
GET_MY_PROFILE,
|
|
4469
|
-
GET_CUSTOMER_PROFILE,
|
|
4470
|
-
SET_MY_PROFILE,
|
|
4471
|
-
UPDATE_CUSTOMER_PROFILE,
|
|
4472
|
-
DELETE_MY_PROFILE,
|
|
4473
|
-
} from '../operations/customer-profile.js';
|
|
4474
|
-
import type {
|
|
4475
|
-
GetMyProfileResult,
|
|
4476
|
-
GetCustomerProfileVariables,
|
|
4477
|
-
GetCustomerProfileResult,
|
|
4478
|
-
SetMyProfileVariables,
|
|
4479
|
-
SetMyProfileResult,
|
|
4480
|
-
UpdateCustomerProfileVariables,
|
|
4481
|
-
UpdateCustomerProfileResult,
|
|
4482
|
-
DeleteMyProfileResult,
|
|
4483
|
-
} from '../operations/customer-profile.js';
|
|
4484
|
-
|
|
4485
|
-
export function useMyProfile(
|
|
4486
|
-
options?: QueryHookOptions<GetMyProfileResult>
|
|
4487
|
-
) {
|
|
4488
|
-
return useQuery<GetMyProfileResult>(
|
|
4489
|
-
gql\`\${GET_MY_PROFILE}\`,
|
|
4490
|
-
options
|
|
4491
|
-
);
|
|
4492
|
-
}
|
|
4493
|
-
|
|
4494
|
-
export function useCustomerProfile(
|
|
4495
|
-
customerId: string | null | undefined,
|
|
4496
|
-
options?: Omit<QueryHookOptions<GetCustomerProfileResult, GetCustomerProfileVariables>, 'variables' | 'skip'>
|
|
4497
|
-
) {
|
|
4498
|
-
return useQuery<GetCustomerProfileResult, GetCustomerProfileVariables>(
|
|
4499
|
-
gql\`\${GET_CUSTOMER_PROFILE}\`,
|
|
4500
|
-
{ ...options, variables: { customerId: customerId! }, skip: !customerId }
|
|
4501
|
-
);
|
|
4502
|
-
}
|
|
4503
|
-
|
|
4504
|
-
export function useSetMyProfile(
|
|
4505
|
-
options?: MutationHookOptions<SetMyProfileResult, SetMyProfileVariables>
|
|
4506
|
-
) {
|
|
4507
|
-
return useMutation<SetMyProfileResult, SetMyProfileVariables>(
|
|
4508
|
-
gql\`\${SET_MY_PROFILE}\`,
|
|
4509
|
-
options
|
|
4510
|
-
);
|
|
4511
|
-
}
|
|
4512
|
-
|
|
4513
|
-
export function useUpdateCustomerProfile(
|
|
4514
|
-
options?: MutationHookOptions<UpdateCustomerProfileResult, UpdateCustomerProfileVariables>
|
|
4515
|
-
) {
|
|
4516
|
-
return useMutation<UpdateCustomerProfileResult, UpdateCustomerProfileVariables>(
|
|
4517
|
-
gql\`\${UPDATE_CUSTOMER_PROFILE}\`,
|
|
4518
|
-
options
|
|
4519
|
-
);
|
|
4520
|
-
}
|
|
4521
|
-
|
|
4522
|
-
export function useDeleteMyProfile(
|
|
4523
|
-
options?: MutationHookOptions<DeleteMyProfileResult>
|
|
4524
|
-
) {
|
|
4525
|
-
return useMutation<DeleteMyProfileResult>(
|
|
4526
|
-
gql\`\${DELETE_MY_PROFILE}\`,
|
|
4527
|
-
options
|
|
4528
|
-
);
|
|
4529
|
-
}
|
|
4530
|
-
`;
|
|
4531
|
-
}
|
|
4532
|
-
|
|
4533
|
-
// src/codegen/generators/remix-loaders.ts
|
|
4534
|
-
function generateRemixLoaders(model) {
|
|
4535
|
-
const typeName = toPascalCase(model.key);
|
|
4536
|
-
const upperSnake = toUpperSnakeCase(model.key);
|
|
4537
|
-
const pluralName = model.pluralName ? toPascalCase(model.pluralName.replace(/\s+/g, "")) : `${typeName}s`;
|
|
4538
|
-
const pluralUpperSnake = model.pluralName ? toUpperSnakeCase(model.pluralName.replace(/\s+/g, "")) : `${upperSnake}S`;
|
|
4539
|
-
return `/**
|
|
4540
|
-
* Remix / server-side loader functions for ${model.name ?? model.key}
|
|
4541
|
-
*
|
|
4542
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4543
|
-
*/
|
|
4544
|
-
|
|
4545
|
-
import {
|
|
4546
|
-
GET_${upperSnake},
|
|
4547
|
-
GET_${upperSnake}_BY_KEY,
|
|
4548
|
-
LIST_${pluralUpperSnake},
|
|
4549
|
-
CREATE_${upperSnake},
|
|
4550
|
-
UPDATE_${upperSnake},
|
|
4551
|
-
DELETE_${upperSnake},
|
|
4552
|
-
PUBLISH_${upperSnake}_VERSION,
|
|
4553
|
-
UNPUBLISH_${upperSnake},
|
|
4554
|
-
} from '../operations/${model.key}.js';
|
|
4555
|
-
import type {
|
|
4556
|
-
Get${typeName}Variables,
|
|
4557
|
-
Get${typeName}Result,
|
|
4558
|
-
Get${typeName}ByKeyVariables,
|
|
4559
|
-
Get${typeName}ByKeyResult,
|
|
4560
|
-
List${pluralName}Variables,
|
|
4561
|
-
List${pluralName}Result,
|
|
4562
|
-
Create${typeName}Variables,
|
|
4563
|
-
Create${typeName}Result,
|
|
4564
|
-
Update${typeName}Variables,
|
|
4565
|
-
Update${typeName}Result,
|
|
4566
|
-
Delete${typeName}Variables,
|
|
4567
|
-
Delete${typeName}Result,
|
|
4568
|
-
} from '../operations/${model.key}.js';
|
|
4569
|
-
|
|
4570
|
-
/** A minimal GraphQL client interface (works with graphql-request, urql, or custom). */
|
|
4571
|
-
export interface GraphQLClient {
|
|
4572
|
-
request<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
export async function get${typeName}(
|
|
4576
|
-
client: GraphQLClient,
|
|
4577
|
-
variables: Get${typeName}Variables
|
|
4578
|
-
): Promise<Get${typeName}Result> {
|
|
4579
|
-
return client.request<Get${typeName}Result>(GET_${upperSnake}, variables as Record<string, unknown>);
|
|
4580
|
-
}
|
|
4581
|
-
|
|
4582
|
-
export async function get${typeName}ByKey(
|
|
4583
|
-
client: GraphQLClient,
|
|
4584
|
-
variables: Get${typeName}ByKeyVariables
|
|
4585
|
-
): Promise<Get${typeName}ByKeyResult> {
|
|
4586
|
-
return client.request<Get${typeName}ByKeyResult>(GET_${upperSnake}_BY_KEY, variables as Record<string, unknown>);
|
|
4587
|
-
}
|
|
4588
|
-
|
|
4589
|
-
export async function list${pluralName}(
|
|
4590
|
-
client: GraphQLClient,
|
|
4591
|
-
variables?: List${pluralName}Variables
|
|
4592
|
-
): Promise<List${pluralName}Result> {
|
|
4593
|
-
return client.request<List${pluralName}Result>(LIST_${pluralUpperSnake}, variables as Record<string, unknown>);
|
|
4594
|
-
}
|
|
4595
|
-
|
|
4596
|
-
export async function create${typeName}(
|
|
4597
|
-
client: GraphQLClient,
|
|
4598
|
-
variables: Create${typeName}Variables
|
|
4599
|
-
): Promise<Create${typeName}Result> {
|
|
4600
|
-
return client.request<Create${typeName}Result>(CREATE_${upperSnake}, variables as Record<string, unknown>);
|
|
4601
|
-
}
|
|
4602
|
-
|
|
4603
|
-
export async function update${typeName}(
|
|
4604
|
-
client: GraphQLClient,
|
|
4605
|
-
variables: Update${typeName}Variables
|
|
4606
|
-
): Promise<Update${typeName}Result> {
|
|
4607
|
-
return client.request<Update${typeName}Result>(UPDATE_${upperSnake}, variables as Record<string, unknown>);
|
|
4608
|
-
}
|
|
4609
|
-
|
|
4610
|
-
export async function delete${typeName}(
|
|
4611
|
-
client: GraphQLClient,
|
|
4612
|
-
variables: Delete${typeName}Variables
|
|
4613
|
-
): Promise<Delete${typeName}Result> {
|
|
4614
|
-
return client.request<Delete${typeName}Result>(DELETE_${upperSnake}, variables as Record<string, unknown>);
|
|
4615
|
-
}
|
|
4616
|
-
|
|
4617
|
-
export async function publish${typeName}Version(
|
|
4618
|
-
client: GraphQLClient,
|
|
4619
|
-
versionId: string
|
|
4620
|
-
): Promise<{ publishVersion: boolean }> {
|
|
4621
|
-
return client.request<{ publishVersion: boolean }>(PUBLISH_${upperSnake}_VERSION, { versionId });
|
|
4622
|
-
}
|
|
4623
|
-
|
|
4624
|
-
export async function unpublish${typeName}(
|
|
4625
|
-
client: GraphQLClient,
|
|
4626
|
-
id: string
|
|
4627
|
-
): Promise<{ unpublishRecord: boolean }> {
|
|
4628
|
-
return client.request<{ unpublishRecord: boolean }>(UNPUBLISH_${upperSnake}, { id });
|
|
4629
|
-
}
|
|
4630
|
-
`;
|
|
4631
|
-
}
|
|
4632
|
-
|
|
4633
|
-
// src/codegen/generators/remix-loaders-index.ts
|
|
4634
|
-
function generateRemixLoadersIndex(models, hasCustomerProfile) {
|
|
4635
|
-
const lines = [];
|
|
4636
|
-
lines.push(`/**
|
|
4637
|
-
* Remix / server-side loader functions for all models.
|
|
4638
|
-
*
|
|
4639
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4640
|
-
*/
|
|
4641
|
-
`);
|
|
4642
|
-
for (const model of models) {
|
|
4643
|
-
lines.push(`export * from './${model.key}.js';`);
|
|
4644
|
-
}
|
|
4645
|
-
if (hasCustomerProfile) {
|
|
4646
|
-
lines.push(`export * from './customer-profile.js';`);
|
|
4647
|
-
}
|
|
4648
|
-
lines.push("");
|
|
4649
|
-
return lines.join("\n");
|
|
4650
|
-
}
|
|
4651
|
-
|
|
4652
|
-
// src/codegen/generators/customer-profile-loaders.ts
|
|
4653
|
-
function generateCustomerProfileLoaders() {
|
|
4654
|
-
return `/**
|
|
4655
|
-
* Remix / server-side loader functions for Customer Profiles
|
|
4656
|
-
*
|
|
4657
|
-
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
4658
|
-
*/
|
|
4659
|
-
|
|
4660
|
-
import {
|
|
4661
|
-
GET_MY_PROFILE,
|
|
4662
|
-
GET_CUSTOMER_PROFILE,
|
|
4663
|
-
SET_MY_PROFILE,
|
|
4664
|
-
UPDATE_CUSTOMER_PROFILE,
|
|
4665
|
-
DELETE_MY_PROFILE,
|
|
4666
|
-
} from '../operations/customer-profile.js';
|
|
4667
|
-
import type {
|
|
4668
|
-
GetMyProfileResult,
|
|
4669
|
-
GetCustomerProfileVariables,
|
|
4670
|
-
GetCustomerProfileResult,
|
|
4671
|
-
SetMyProfileVariables,
|
|
4672
|
-
SetMyProfileResult,
|
|
4673
|
-
UpdateCustomerProfileVariables,
|
|
4674
|
-
UpdateCustomerProfileResult,
|
|
4675
|
-
DeleteMyProfileResult,
|
|
4676
|
-
} from '../operations/customer-profile.js';
|
|
4677
|
-
|
|
4678
|
-
/** A minimal GraphQL client interface (works with graphql-request, urql, or custom). */
|
|
4679
|
-
export interface GraphQLClient {
|
|
4680
|
-
request<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
4681
|
-
}
|
|
4682
|
-
|
|
4683
|
-
export async function getMyProfile(
|
|
4684
|
-
client: GraphQLClient
|
|
4685
|
-
): Promise<GetMyProfileResult> {
|
|
4686
|
-
return client.request<GetMyProfileResult>(GET_MY_PROFILE);
|
|
4687
|
-
}
|
|
4688
|
-
|
|
4689
|
-
export async function getCustomerProfile(
|
|
4690
|
-
client: GraphQLClient,
|
|
4691
|
-
variables: GetCustomerProfileVariables
|
|
4692
|
-
): Promise<GetCustomerProfileResult> {
|
|
4693
|
-
return client.request<GetCustomerProfileResult>(GET_CUSTOMER_PROFILE, variables as Record<string, unknown>);
|
|
4694
|
-
}
|
|
4695
|
-
|
|
4696
|
-
export async function setMyProfile(
|
|
4697
|
-
client: GraphQLClient,
|
|
4698
|
-
variables: SetMyProfileVariables
|
|
4699
|
-
): Promise<SetMyProfileResult> {
|
|
4700
|
-
return client.request<SetMyProfileResult>(SET_MY_PROFILE, variables as Record<string, unknown>);
|
|
4701
|
-
}
|
|
4702
|
-
|
|
4703
|
-
export async function updateCustomerProfile(
|
|
4704
|
-
client: GraphQLClient,
|
|
4705
|
-
variables: UpdateCustomerProfileVariables
|
|
4706
|
-
): Promise<UpdateCustomerProfileResult> {
|
|
4707
|
-
return client.request<UpdateCustomerProfileResult>(UPDATE_CUSTOMER_PROFILE, variables as Record<string, unknown>);
|
|
4708
|
-
}
|
|
4709
|
-
|
|
4710
|
-
export async function deleteMyProfile(
|
|
4711
|
-
client: GraphQLClient
|
|
4712
|
-
): Promise<DeleteMyProfileResult> {
|
|
4713
|
-
return client.request<DeleteMyProfileResult>(DELETE_MY_PROFILE);
|
|
4714
|
-
}
|
|
4715
|
-
`;
|
|
4716
|
-
}
|
|
4717
|
-
|
|
4718
|
-
// src/codegen/generators/public-schema-content.ts
|
|
4719
|
-
async function fetchPublicSchema(client) {
|
|
4720
|
-
try {
|
|
4721
|
-
const res = await client.request(
|
|
4722
|
-
`query { _service { sdl } }`
|
|
4723
|
-
);
|
|
4724
|
-
return res._service?.sdl ?? null;
|
|
4725
|
-
} catch {
|
|
4726
|
-
return null;
|
|
4727
|
-
}
|
|
4728
|
-
}
|
|
4729
|
-
|
|
4730
|
-
// src/codegen/write-files.ts
|
|
4731
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
4732
|
-
import { dirname as dirname2 } from "path";
|
|
4733
|
-
async function writeGeneratedFile(filePath, content, usePrettier = true) {
|
|
4734
|
-
await mkdir(dirname2(filePath), { recursive: true });
|
|
4735
|
-
let formattedContent = content;
|
|
4736
|
-
const isSwift = filePath.endsWith(".swift");
|
|
4737
|
-
if (usePrettier && !isSwift) {
|
|
4738
|
-
try {
|
|
4739
|
-
const prettier = await import("prettier");
|
|
4740
|
-
const parser = filePath.endsWith(".graphql") ? "graphql" : "typescript";
|
|
4741
|
-
formattedContent = await prettier.format(content, {
|
|
4742
|
-
parser,
|
|
4743
|
-
semi: true,
|
|
4744
|
-
singleQuote: true,
|
|
4745
|
-
trailingComma: "es5",
|
|
4746
|
-
printWidth: 100
|
|
4747
|
-
});
|
|
4748
|
-
} catch {
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
await writeFile(filePath, formattedContent, "utf-8");
|
|
4752
|
-
}
|
|
4753
|
-
async function writeFiles(files, usePrettier = true) {
|
|
4754
|
-
await Promise.all(
|
|
4755
|
-
files.map(
|
|
4756
|
-
(file) => writeGeneratedFile(file.path, file.content, usePrettier)
|
|
4757
|
-
)
|
|
4758
|
-
);
|
|
2346
|
+
async function writeFiles(files, usePrettier = true) {
|
|
2347
|
+
await Promise.all(
|
|
2348
|
+
files.map(
|
|
2349
|
+
(file) => writeGeneratedFile(file.path, file.content, usePrettier)
|
|
2350
|
+
)
|
|
2351
|
+
);
|
|
4759
2352
|
}
|
|
4760
2353
|
|
|
4761
2354
|
// src/commands/pull.ts
|
|
4762
2355
|
function registerPullCommand(program2, globalOpts) {
|
|
4763
2356
|
program2.command("pull").description(
|
|
4764
|
-
"Generate
|
|
4765
|
-
).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
|
|
2357
|
+
"Generate typed client, model types, and validation schemas from platform models"
|
|
2358
|
+
).option("--config <path>", "Path to config file").option("--only <models>", "Comma-separated model keys to generate").option("--no-prettier", "Skip Prettier formatting").option("--dry-run", "Show what would be generated without writing").option("--out <dir>", "Override output directory").action(
|
|
4766
2359
|
withErrorHandler(
|
|
4767
2360
|
globalOpts,
|
|
4768
2361
|
async (cmdOpts) => {
|
|
@@ -4772,16 +2365,14 @@ function registerPullCommand(program2, globalOpts) {
|
|
|
4772
2365
|
only: cmdOpts.only,
|
|
4773
2366
|
noPrettier: cmdOpts.prettier === false,
|
|
4774
2367
|
dryRun: !!cmdOpts.dryRun,
|
|
4775
|
-
out: cmdOpts.out
|
|
4776
|
-
swift: cmdOpts.swift
|
|
2368
|
+
out: cmdOpts.out
|
|
4777
2369
|
};
|
|
4778
2370
|
const config2 = await loadPullConfig(flags);
|
|
4779
2371
|
const client = await createClient(opts);
|
|
4780
2372
|
console.log(chalk4.dim("Fetching models\u2026"));
|
|
4781
|
-
const [allModels, cpSchema
|
|
2373
|
+
const [allModels, cpSchema] = await Promise.all([
|
|
4782
2374
|
fetchModelsForCodegen(client),
|
|
4783
|
-
fetchCustomerProfileSchema(client)
|
|
4784
|
-
fetchPublicSchema(client)
|
|
2375
|
+
fetchCustomerProfileSchema(client)
|
|
4785
2376
|
]);
|
|
4786
2377
|
if (allModels.length === 0 && !cpSchema) {
|
|
4787
2378
|
console.log(chalk4.yellow("No models found. Nothing to generate."));
|
|
@@ -4794,169 +2385,65 @@ function registerPullCommand(program2, globalOpts) {
|
|
|
4794
2385
|
console.log(
|
|
4795
2386
|
chalk4.dim(
|
|
4796
2387
|
`Found ${allModels.length} model(s), generating for ${models.length}.`
|
|
4797
|
-
)
|
|
4798
|
-
);
|
|
4799
|
-
const cwd = process.cwd();
|
|
4800
|
-
const
|
|
4801
|
-
const
|
|
4802
|
-
const
|
|
4803
|
-
const
|
|
4804
|
-
const
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
const
|
|
4808
|
-
(m) => m.config.publicApi && m.config.records
|
|
4809
|
-
);
|
|
4810
|
-
files.push({
|
|
4811
|
-
path: resolve2(typesDir, "field-types.ts"),
|
|
4812
|
-
content: generateFieldTypesFile()
|
|
4813
|
-
});
|
|
4814
|
-
files.push({
|
|
4815
|
-
path: resolve2(typesDir, "config.ts"),
|
|
4816
|
-
content: generateConfigFile()
|
|
4817
|
-
});
|
|
4818
|
-
for (const model of models) {
|
|
4819
|
-
files.push({
|
|
4820
|
-
path: resolve2(typesDir, "models", `${model.key}.ts`),
|
|
4821
|
-
content: generateModelTypes(model, models)
|
|
4822
|
-
});
|
|
4823
|
-
}
|
|
4824
|
-
files.push({
|
|
4825
|
-
path: resolve2(typesDir, "models", "index.ts"),
|
|
4826
|
-
content: generateModelIndex(models)
|
|
4827
|
-
});
|
|
4828
|
-
if (hasCustomerProfile) {
|
|
4829
|
-
files.push({
|
|
4830
|
-
path: resolve2(typesDir, "customer-profile.ts"),
|
|
4831
|
-
content: generateCustomerProfileTypes(cpSchema)
|
|
4832
|
-
});
|
|
4833
|
-
}
|
|
4834
|
-
files.push({
|
|
4835
|
-
path: resolve2(typesDir, "index.ts"),
|
|
4836
|
-
content: generateMainIndex(hasCustomerProfile)
|
|
4837
|
-
});
|
|
4838
|
-
for (const model of publicModels) {
|
|
4839
|
-
files.push({
|
|
4840
|
-
path: resolve2(docsDir, `${model.key}.graphql`),
|
|
4841
|
-
content: generateModelDocuments(model)
|
|
4842
|
-
});
|
|
4843
|
-
}
|
|
4844
|
-
const hasSharingModels = publicModels.some(
|
|
4845
|
-
(m) => m.config.sharing?.enabled
|
|
4846
|
-
);
|
|
4847
|
-
if (hasSharingModels) {
|
|
4848
|
-
files.push({
|
|
4849
|
-
path: resolve2(docsDir, "_shared.graphql"),
|
|
4850
|
-
content: generateSharedFragments()
|
|
4851
|
-
});
|
|
4852
|
-
}
|
|
4853
|
-
files.push({
|
|
4854
|
-
path: resolve2(docsDir, "customer-profile.graphql"),
|
|
4855
|
-
content: generateCustomerProfileDocuments()
|
|
4856
|
-
});
|
|
4857
|
-
const staticDocs = generateStaticDocuments(config2.domains);
|
|
4858
|
-
for (const doc of staticDocs) {
|
|
4859
|
-
files.push({
|
|
4860
|
-
path: resolve2(docsDir, doc.filename),
|
|
4861
|
-
content: doc.content
|
|
4862
|
-
});
|
|
4863
|
-
}
|
|
4864
|
-
if (publicSchema) {
|
|
4865
|
-
files.push({
|
|
4866
|
-
path: resolve2(docsDir, "public-schema.graphql"),
|
|
4867
|
-
content: publicSchema
|
|
4868
|
-
});
|
|
4869
|
-
}
|
|
4870
|
-
const typesRelPath = computeTypesRelPath(opsDir, typesDir);
|
|
4871
|
-
files.push({
|
|
4872
|
-
path: resolve2(opsDir, "_common.ts"),
|
|
4873
|
-
content: generateTypedOperationsCommon(typesRelPath)
|
|
4874
|
-
});
|
|
4875
|
-
for (const model of publicModels) {
|
|
4876
|
-
files.push({
|
|
4877
|
-
path: resolve2(opsDir, `${model.key}.ts`),
|
|
4878
|
-
content: generateTypedOperations(model, typesRelPath)
|
|
4879
|
-
});
|
|
4880
|
-
}
|
|
4881
|
-
if (hasCustomerProfile) {
|
|
2388
|
+
)
|
|
2389
|
+
);
|
|
2390
|
+
const cwd = process.cwd();
|
|
2391
|
+
const outDir = resolve(cwd, config2.output.types);
|
|
2392
|
+
const modelsDir = resolve(outDir, "models");
|
|
2393
|
+
const files = [];
|
|
2394
|
+
const hasCustomerProfile = !!(cpSchema && cpSchema.fields.length > 0);
|
|
2395
|
+
const publicModels = models.filter(
|
|
2396
|
+
(m) => m.config.publicApi && m.config.records
|
|
2397
|
+
);
|
|
2398
|
+
for (const model of models) {
|
|
4882
2399
|
files.push({
|
|
4883
|
-
path:
|
|
4884
|
-
content:
|
|
2400
|
+
path: resolve(modelsDir, `${model.key}.ts`),
|
|
2401
|
+
content: generateModelTypes(model, models)
|
|
4885
2402
|
});
|
|
4886
|
-
}
|
|
4887
|
-
files.push({
|
|
4888
|
-
path: resolve2(opsDir, "index.ts"),
|
|
4889
|
-
content: generateTypedOperationsIndex(
|
|
4890
|
-
publicModels,
|
|
4891
|
-
hasCustomerProfile
|
|
4892
|
-
)
|
|
4893
|
-
});
|
|
4894
|
-
if (hooksDir) {
|
|
4895
|
-
for (const model of publicModels) {
|
|
4896
|
-
files.push({
|
|
4897
|
-
path: resolve2(hooksDir, `${model.key}.ts`),
|
|
4898
|
-
content: generateReactHooks(model)
|
|
4899
|
-
});
|
|
4900
|
-
}
|
|
4901
|
-
if (hasCustomerProfile) {
|
|
4902
|
-
files.push({
|
|
4903
|
-
path: resolve2(hooksDir, "customer-profile.ts"),
|
|
4904
|
-
content: generateCustomerProfileHooks()
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
2403
|
files.push({
|
|
4908
|
-
path:
|
|
4909
|
-
content:
|
|
4910
|
-
publicModels,
|
|
4911
|
-
hasCustomerProfile
|
|
4912
|
-
)
|
|
2404
|
+
path: resolve(modelsDir, `${model.key}.zod.ts`),
|
|
2405
|
+
content: generateModelZodSchema(model, models)
|
|
4913
2406
|
});
|
|
4914
2407
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
content: generateRemixLoaders(model)
|
|
4920
|
-
});
|
|
4921
|
-
}
|
|
4922
|
-
if (hasCustomerProfile) {
|
|
2408
|
+
for (const model of publicModels) {
|
|
2409
|
+
const whereCode = generateModelWhere(model);
|
|
2410
|
+
const sortCode = generateModelSortField(model);
|
|
2411
|
+
if (whereCode || sortCode) {
|
|
4923
2412
|
files.push({
|
|
4924
|
-
path:
|
|
4925
|
-
content:
|
|
2413
|
+
path: resolve(modelsDir, `${model.key}.filters.ts`),
|
|
2414
|
+
content: `/**
|
|
2415
|
+
* Filter & sort types for ${model.name}
|
|
2416
|
+
*
|
|
2417
|
+
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
2418
|
+
*/
|
|
2419
|
+
|
|
2420
|
+
${whereCode}
|
|
2421
|
+
${sortCode}`
|
|
4926
2422
|
});
|
|
4927
2423
|
}
|
|
4928
|
-
files.push({
|
|
4929
|
-
path: resolve2(loadersDir, "index.ts"),
|
|
4930
|
-
content: generateRemixLoadersIndex(
|
|
4931
|
-
publicModels,
|
|
4932
|
-
hasCustomerProfile
|
|
4933
|
-
)
|
|
4934
|
-
});
|
|
4935
2424
|
}
|
|
4936
|
-
if (
|
|
4937
|
-
const swiftDir = resolve2(cwd, config2.output.swift);
|
|
4938
|
-
files.push({
|
|
4939
|
-
path: resolve2(swiftDir, "FieldTypes.swift"),
|
|
4940
|
-
content: generateSwiftFieldTypesFile()
|
|
4941
|
-
});
|
|
2425
|
+
if (hasCustomerProfile) {
|
|
4942
2426
|
files.push({
|
|
4943
|
-
path:
|
|
4944
|
-
content:
|
|
2427
|
+
path: resolve(modelsDir, "customer-profile.ts"),
|
|
2428
|
+
content: generateCustomerProfileTypes(cpSchema)
|
|
4945
2429
|
});
|
|
4946
|
-
for (const model of models) {
|
|
4947
|
-
const swiftTypeName = toPascalCase(model.key);
|
|
4948
|
-
files.push({
|
|
4949
|
-
path: resolve2(swiftDir, `${swiftTypeName}.swift`),
|
|
4950
|
-
content: generateSwiftModelFile(model)
|
|
4951
|
-
});
|
|
4952
|
-
}
|
|
4953
|
-
if (hasCustomerProfile) {
|
|
4954
|
-
files.push({
|
|
4955
|
-
path: resolve2(swiftDir, "CustomerProfile.swift"),
|
|
4956
|
-
content: generateSwiftCustomerProfileFile(cpSchema)
|
|
4957
|
-
});
|
|
4958
|
-
}
|
|
4959
2430
|
}
|
|
2431
|
+
files.push({
|
|
2432
|
+
path: resolve(modelsDir, "index.ts"),
|
|
2433
|
+
content: generateModelIndex(models)
|
|
2434
|
+
});
|
|
2435
|
+
files.push({
|
|
2436
|
+
path: resolve(outDir, "client.ts"),
|
|
2437
|
+
content: generateClientFactory(publicModels, hasCustomerProfile)
|
|
2438
|
+
});
|
|
2439
|
+
files.push({
|
|
2440
|
+
path: resolve(outDir, "schema.json"),
|
|
2441
|
+
content: generateSchemaManifest(models, cpSchema)
|
|
2442
|
+
});
|
|
2443
|
+
files.push({
|
|
2444
|
+
path: resolve(outDir, "index.ts"),
|
|
2445
|
+
content: generateRootIndex(hasCustomerProfile)
|
|
2446
|
+
});
|
|
4960
2447
|
if (config2.dryRun) {
|
|
4961
2448
|
console.log(
|
|
4962
2449
|
chalk4.bold("\nDry run \u2014 files that would be generated:\n")
|
|
@@ -4970,50 +2457,31 @@ ${chalk4.dim(`${files.length} file(s) total`)}`);
|
|
|
4970
2457
|
return;
|
|
4971
2458
|
}
|
|
4972
2459
|
await writeFiles(files, config2.prettier);
|
|
4973
|
-
const
|
|
4974
|
-
const docCount = publicModels.length + staticDocs.length;
|
|
4975
|
-
const opsCount = publicModels.length + (hasCustomerProfile ? 1 : 0) + 2;
|
|
4976
|
-
const hookCount = hooksDir ? publicModels.length + (hasCustomerProfile ? 1 : 0) + 1 : 0;
|
|
4977
|
-
const loaderCount = loadersDir ? publicModels.length + (hasCustomerProfile ? 1 : 0) + 1 : 0;
|
|
4978
|
-
const swiftCount = config2.output.swift ? models.length + 2 : 0;
|
|
4979
|
-
const cpSuffix = hasCustomerProfile ? ", customer profile" : "";
|
|
2460
|
+
const cpSuffix = hasCustomerProfile ? " + customer profile" : "";
|
|
4980
2461
|
console.log(
|
|
4981
2462
|
chalk4.green(`
|
|
4982
|
-
Generated ${files.length} file(s)`) + chalk4.dim(
|
|
4983
|
-
` (${
|
|
2463
|
+
\u2713 Generated ${files.length} file(s)`) + chalk4.dim(
|
|
2464
|
+
` (${models.length} model(s), ${publicModels.length} typed accessor(s)${cpSuffix})`
|
|
4984
2465
|
)
|
|
4985
2466
|
);
|
|
4986
|
-
console.log(chalk4.dim(`
|
|
4987
|
-
console.log(chalk4.dim(` Documents: ${docsDir}`));
|
|
4988
|
-
console.log(chalk4.dim(` Operations: ${opsDir}`));
|
|
4989
|
-
if (hooksDir) {
|
|
4990
|
-
console.log(chalk4.dim(` Hooks: ${hooksDir}`));
|
|
4991
|
-
}
|
|
4992
|
-
if (loadersDir) {
|
|
4993
|
-
console.log(chalk4.dim(` Loaders: ${loadersDir}`));
|
|
4994
|
-
}
|
|
4995
|
-
if (config2.output.swift) {
|
|
4996
|
-
console.log(
|
|
4997
|
-
chalk4.dim(` Swift: ${resolve2(cwd, config2.output.swift)}`)
|
|
4998
|
-
);
|
|
4999
|
-
}
|
|
2467
|
+
console.log(chalk4.dim(` Output: ${outDir}`));
|
|
5000
2468
|
}
|
|
5001
2469
|
)
|
|
5002
2470
|
);
|
|
5003
2471
|
}
|
|
5004
|
-
function
|
|
2472
|
+
function generateRootIndex(includeCustomerProfile) {
|
|
5005
2473
|
let code = `/**
|
|
5006
|
-
* Generated
|
|
2474
|
+
* Generated Foir client and model types.
|
|
5007
2475
|
*
|
|
5008
2476
|
* @generated by foir \u2014 DO NOT EDIT MANUALLY
|
|
5009
2477
|
*/
|
|
5010
2478
|
|
|
5011
|
-
export
|
|
5012
|
-
export
|
|
2479
|
+
export { createClient } from './client.js';
|
|
2480
|
+
export type { TypedClient } from './client.js';
|
|
5013
2481
|
export * from './models/index.js';
|
|
5014
2482
|
`;
|
|
5015
2483
|
if (includeCustomerProfile) {
|
|
5016
|
-
code += `export
|
|
2484
|
+
code += `export type { CustomerProfileData } from './models/customer-profile.js';
|
|
5017
2485
|
`;
|
|
5018
2486
|
}
|
|
5019
2487
|
return code;
|
|
@@ -5025,11 +2493,11 @@ import inquirer2 from "inquirer";
|
|
|
5025
2493
|
|
|
5026
2494
|
// src/scaffold/scaffold.ts
|
|
5027
2495
|
import * as fs4 from "fs";
|
|
5028
|
-
import * as
|
|
2496
|
+
import * as path2 from "path";
|
|
5029
2497
|
|
|
5030
2498
|
// src/scaffold/package-manager.ts
|
|
5031
2499
|
import * as fs3 from "fs";
|
|
5032
|
-
import * as
|
|
2500
|
+
import * as path from "path";
|
|
5033
2501
|
function detectPackageManager() {
|
|
5034
2502
|
const lockFiles = [
|
|
5035
2503
|
{ file: "pnpm-lock.yaml", manager: "pnpm" },
|
|
@@ -5039,11 +2507,11 @@ function detectPackageManager() {
|
|
|
5039
2507
|
let dir = process.cwd();
|
|
5040
2508
|
while (true) {
|
|
5041
2509
|
for (const { file, manager } of lockFiles) {
|
|
5042
|
-
if (fs3.existsSync(
|
|
2510
|
+
if (fs3.existsSync(path.join(dir, file))) {
|
|
5043
2511
|
return getManagerInfo(manager);
|
|
5044
2512
|
}
|
|
5045
2513
|
}
|
|
5046
|
-
const parentDir =
|
|
2514
|
+
const parentDir = path.dirname(dir);
|
|
5047
2515
|
if (parentDir === dir) {
|
|
5048
2516
|
break;
|
|
5049
2517
|
}
|
|
@@ -5076,7 +2544,7 @@ function getManagerInfo(manager) {
|
|
|
5076
2544
|
|
|
5077
2545
|
// src/scaffold/scaffold.ts
|
|
5078
2546
|
async function scaffold(projectName, extensionType, apiUrl) {
|
|
5079
|
-
const projectDir =
|
|
2547
|
+
const projectDir = path2.resolve(process.cwd(), projectName);
|
|
5080
2548
|
if (fs4.existsSync(projectDir)) {
|
|
5081
2549
|
throw new Error(
|
|
5082
2550
|
`Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`
|
|
@@ -5084,8 +2552,8 @@ async function scaffold(projectName, extensionType, apiUrl) {
|
|
|
5084
2552
|
}
|
|
5085
2553
|
const files = getFiles(projectName, extensionType, apiUrl);
|
|
5086
2554
|
for (const [filePath, content] of Object.entries(files)) {
|
|
5087
|
-
const fullPath =
|
|
5088
|
-
const dir =
|
|
2555
|
+
const fullPath = path2.join(projectDir, filePath);
|
|
2556
|
+
const dir = path2.dirname(fullPath);
|
|
5089
2557
|
if (!fs4.existsSync(dir)) {
|
|
5090
2558
|
fs4.mkdirSync(dir, { recursive: true });
|
|
5091
2559
|
}
|
|
@@ -5653,9 +3121,9 @@ Media (${data.globalSearch.media.length}):`);
|
|
|
5653
3121
|
}
|
|
5654
3122
|
|
|
5655
3123
|
// src/commands/init.ts
|
|
5656
|
-
import { existsSync as
|
|
3124
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
5657
3125
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
5658
|
-
import { resolve as
|
|
3126
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
5659
3127
|
import chalk6 from "chalk";
|
|
5660
3128
|
import inquirer3 from "inquirer";
|
|
5661
3129
|
var FIELD_DEFAULTS = {
|
|
@@ -5746,8 +3214,8 @@ function registerInitCommands(program2, globalOpts) {
|
|
|
5746
3214
|
async (key, opts) => {
|
|
5747
3215
|
const globalFlags = globalOpts();
|
|
5748
3216
|
const template = generateModelTemplate(key);
|
|
5749
|
-
const outDir =
|
|
5750
|
-
if (!
|
|
3217
|
+
const outDir = resolve3(opts.output);
|
|
3218
|
+
if (!existsSync3(outDir)) {
|
|
5751
3219
|
mkdirSync2(outDir, { recursive: true });
|
|
5752
3220
|
}
|
|
5753
3221
|
const ext = opts.ts ? "ts" : "json";
|
|
@@ -5824,8 +3292,8 @@ Edit the file, then run:
|
|
|
5824
3292
|
console.log("No models selected.");
|
|
5825
3293
|
return;
|
|
5826
3294
|
}
|
|
5827
|
-
const outDir =
|
|
5828
|
-
if (!
|
|
3295
|
+
const outDir = resolve3(opts.output);
|
|
3296
|
+
if (!existsSync3(outDir)) {
|
|
5829
3297
|
mkdirSync2(outDir, { recursive: true });
|
|
5830
3298
|
}
|
|
5831
3299
|
const createdFiles = [];
|
|
@@ -5856,11 +3324,247 @@ Edit the files, then run:
|
|
|
5856
3324
|
);
|
|
5857
3325
|
}
|
|
5858
3326
|
|
|
3327
|
+
// src/commands/profiles.ts
|
|
3328
|
+
import chalk7 from "chalk";
|
|
3329
|
+
|
|
3330
|
+
// src/lib/input.ts
|
|
3331
|
+
import inquirer4 from "inquirer";
|
|
3332
|
+
|
|
3333
|
+
// src/lib/config-loader.ts
|
|
3334
|
+
import { readFile } from "fs/promises";
|
|
3335
|
+
import { pathToFileURL } from "url";
|
|
3336
|
+
import { resolve as resolve4 } from "path";
|
|
3337
|
+
async function loadConfig(filePath) {
|
|
3338
|
+
const absPath = resolve4(filePath);
|
|
3339
|
+
if (filePath.endsWith(".ts")) {
|
|
3340
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
3341
|
+
return configModule.default;
|
|
3342
|
+
}
|
|
3343
|
+
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
3344
|
+
const configModule = await import(pathToFileURL(absPath).href);
|
|
3345
|
+
return configModule.default;
|
|
3346
|
+
}
|
|
3347
|
+
if (filePath.endsWith(".json")) {
|
|
3348
|
+
const content = await readFile(absPath, "utf-8");
|
|
3349
|
+
return JSON.parse(content);
|
|
3350
|
+
}
|
|
3351
|
+
throw new Error(
|
|
3352
|
+
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
// src/lib/input.ts
|
|
3357
|
+
async function parseInputData(opts) {
|
|
3358
|
+
if (opts.data) {
|
|
3359
|
+
return JSON.parse(opts.data);
|
|
3360
|
+
}
|
|
3361
|
+
if (opts.file) {
|
|
3362
|
+
return await loadConfig(opts.file);
|
|
3363
|
+
}
|
|
3364
|
+
if (!process.stdin.isTTY) {
|
|
3365
|
+
const chunks = [];
|
|
3366
|
+
for await (const chunk of process.stdin) {
|
|
3367
|
+
chunks.push(chunk);
|
|
3368
|
+
}
|
|
3369
|
+
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3370
|
+
if (stdinContent) {
|
|
3371
|
+
return JSON.parse(stdinContent);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
throw new Error(
|
|
3375
|
+
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
function isUUID(value) {
|
|
3379
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
3380
|
+
value
|
|
3381
|
+
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
3382
|
+
}
|
|
3383
|
+
async function confirmAction(message, opts) {
|
|
3384
|
+
if (opts?.confirm) return true;
|
|
3385
|
+
const { confirmed } = await inquirer4.prompt([
|
|
3386
|
+
{
|
|
3387
|
+
type: "confirm",
|
|
3388
|
+
name: "confirmed",
|
|
3389
|
+
message,
|
|
3390
|
+
default: false
|
|
3391
|
+
}
|
|
3392
|
+
]);
|
|
3393
|
+
return confirmed;
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// src/commands/profiles.ts
|
|
3397
|
+
function registerProfilesCommand(program2, globalOpts) {
|
|
3398
|
+
const profiles = program2.command("profiles").description("Manage named project profiles");
|
|
3399
|
+
profiles.command("list").description("List all saved project profiles").action(
|
|
3400
|
+
withErrorHandler(globalOpts, async () => {
|
|
3401
|
+
const opts = globalOpts();
|
|
3402
|
+
const allProfiles = await listProfiles();
|
|
3403
|
+
const defaultName = await getDefaultProfile();
|
|
3404
|
+
if (allProfiles.length === 0) {
|
|
3405
|
+
if (opts.json || opts.jsonl) {
|
|
3406
|
+
formatOutput([], opts);
|
|
3407
|
+
} else {
|
|
3408
|
+
console.log("No profiles saved.");
|
|
3409
|
+
console.log(
|
|
3410
|
+
"Use `foir select-project --save-as <name>` to create one."
|
|
3411
|
+
);
|
|
3412
|
+
}
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
if (opts.json || opts.jsonl) {
|
|
3416
|
+
formatOutput(
|
|
3417
|
+
allProfiles.map((p) => ({
|
|
3418
|
+
name: p.name,
|
|
3419
|
+
default: p.name === defaultName,
|
|
3420
|
+
projectName: p.project.name,
|
|
3421
|
+
projectId: p.project.id,
|
|
3422
|
+
tenantId: p.project.tenantId
|
|
3423
|
+
})),
|
|
3424
|
+
opts
|
|
3425
|
+
);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
const rows = allProfiles.map((p) => ({
|
|
3429
|
+
name: p.name === defaultName ? `${p.name} (default)` : p.name,
|
|
3430
|
+
project: p.project.name,
|
|
3431
|
+
projectId: p.project.id,
|
|
3432
|
+
tenantId: p.project.tenantId
|
|
3433
|
+
}));
|
|
3434
|
+
formatList(rows, opts, {
|
|
3435
|
+
columns: [
|
|
3436
|
+
{ key: "name", header: "Profile", width: 20 },
|
|
3437
|
+
{ key: "project", header: "Project", width: 30 },
|
|
3438
|
+
{ key: "projectId", header: "Project ID", width: 28 },
|
|
3439
|
+
{ key: "tenantId", header: "Tenant ID", width: 28 }
|
|
3440
|
+
]
|
|
3441
|
+
});
|
|
3442
|
+
})
|
|
3443
|
+
);
|
|
3444
|
+
profiles.command("default [name]").description("Show or set the default profile").action(
|
|
3445
|
+
withErrorHandler(globalOpts, async (name) => {
|
|
3446
|
+
const opts = globalOpts();
|
|
3447
|
+
if (name) {
|
|
3448
|
+
const project = await getProjectContext(name);
|
|
3449
|
+
if (!project) {
|
|
3450
|
+
throw new Error(
|
|
3451
|
+
`Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3454
|
+
await setDefaultProfile(name);
|
|
3455
|
+
if (opts.json || opts.jsonl) {
|
|
3456
|
+
formatOutput({ default: name }, opts);
|
|
3457
|
+
} else {
|
|
3458
|
+
console.log(`Default profile set to "${name}".`);
|
|
3459
|
+
}
|
|
3460
|
+
} else {
|
|
3461
|
+
const current = await getDefaultProfile();
|
|
3462
|
+
if (opts.json || opts.jsonl) {
|
|
3463
|
+
formatOutput({ default: current }, opts);
|
|
3464
|
+
} else if (current) {
|
|
3465
|
+
console.log(`Default profile: ${current}`);
|
|
3466
|
+
} else {
|
|
3467
|
+
console.log("No default profile set.");
|
|
3468
|
+
console.log("Use `foir profiles default <name>` to set one.");
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
})
|
|
3472
|
+
);
|
|
3473
|
+
profiles.command("show [name]").description("Show details of a profile (or active profile if no name)").action(
|
|
3474
|
+
withErrorHandler(globalOpts, async (name) => {
|
|
3475
|
+
const opts = globalOpts();
|
|
3476
|
+
if (name) {
|
|
3477
|
+
const project = await getProjectContext(name);
|
|
3478
|
+
if (!project) {
|
|
3479
|
+
throw new Error(
|
|
3480
|
+
`Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
const defaultName = await getDefaultProfile();
|
|
3484
|
+
if (opts.json || opts.jsonl) {
|
|
3485
|
+
formatOutput(
|
|
3486
|
+
{
|
|
3487
|
+
profileName: name,
|
|
3488
|
+
default: name === defaultName,
|
|
3489
|
+
projectName: project.name,
|
|
3490
|
+
projectId: project.id,
|
|
3491
|
+
tenantId: project.tenantId
|
|
3492
|
+
},
|
|
3493
|
+
opts
|
|
3494
|
+
);
|
|
3495
|
+
} else {
|
|
3496
|
+
console.log(
|
|
3497
|
+
`Profile: ${name}${name === defaultName ? " (default)" : ""}`
|
|
3498
|
+
);
|
|
3499
|
+
console.log("\u2500".repeat(40));
|
|
3500
|
+
console.log(`Name: ${project.name}`);
|
|
3501
|
+
console.log(`ID: ${project.id}`);
|
|
3502
|
+
console.log(`Tenant ID: ${project.tenantId}`);
|
|
3503
|
+
}
|
|
3504
|
+
} else {
|
|
3505
|
+
const resolved = await resolveProjectContext(opts);
|
|
3506
|
+
if (!resolved) {
|
|
3507
|
+
console.log("No active project context.");
|
|
3508
|
+
console.log("Run `foir select-project` to choose a project.");
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
if (opts.json || opts.jsonl) {
|
|
3512
|
+
formatOutput(
|
|
3513
|
+
{
|
|
3514
|
+
profileName: resolved.profileName ?? null,
|
|
3515
|
+
source: resolved.source,
|
|
3516
|
+
...resolved.project
|
|
3517
|
+
},
|
|
3518
|
+
opts
|
|
3519
|
+
);
|
|
3520
|
+
} else {
|
|
3521
|
+
console.log(
|
|
3522
|
+
`Active Project${resolved.profileName ? ` (profile: ${resolved.profileName})` : ""}`
|
|
3523
|
+
);
|
|
3524
|
+
console.log("\u2500".repeat(40));
|
|
3525
|
+
console.log(`Name: ${resolved.project.name}`);
|
|
3526
|
+
console.log(`ID: ${resolved.project.id}`);
|
|
3527
|
+
console.log(`Tenant ID: ${resolved.project.tenantId}`);
|
|
3528
|
+
console.log(`Source: ${resolved.source}`);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
})
|
|
3532
|
+
);
|
|
3533
|
+
profiles.command("delete <name>").description("Delete a named profile").option("--confirm", "Skip confirmation prompt").action(
|
|
3534
|
+
withErrorHandler(
|
|
3535
|
+
globalOpts,
|
|
3536
|
+
async (name, cmdOpts) => {
|
|
3537
|
+
const opts = globalOpts();
|
|
3538
|
+
const project = await getProjectContext(name);
|
|
3539
|
+
if (!project) {
|
|
3540
|
+
throw new Error(
|
|
3541
|
+
`Profile "${name}" not found. Run \`foir profiles list\` to see available profiles.`
|
|
3542
|
+
);
|
|
3543
|
+
}
|
|
3544
|
+
const confirmed = await confirmAction(
|
|
3545
|
+
`Delete profile "${name}" (${project.name})?`,
|
|
3546
|
+
{ confirm: !!cmdOpts.confirm }
|
|
3547
|
+
);
|
|
3548
|
+
if (!confirmed) {
|
|
3549
|
+
console.log("Aborted.");
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
await deleteProfile(name);
|
|
3553
|
+
if (opts.json || opts.jsonl) {
|
|
3554
|
+
formatOutput({ deleted: name }, opts);
|
|
3555
|
+
} else {
|
|
3556
|
+
console.log(chalk7.green(`Deleted profile "${name}".`));
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
)
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
5859
3563
|
// src/commands/register-commands.ts
|
|
5860
3564
|
import { readFileSync, readdirSync } from "fs";
|
|
5861
|
-
import { resolve as
|
|
3565
|
+
import { resolve as resolve5, dirname as dirname5 } from "path";
|
|
5862
3566
|
import { fileURLToPath } from "url";
|
|
5863
|
-
import
|
|
3567
|
+
import chalk8 from "chalk";
|
|
5864
3568
|
|
|
5865
3569
|
// ../command-registry/src/command-map.ts
|
|
5866
3570
|
var COMMANDS = [
|
|
@@ -7457,81 +5161,15 @@ function createSchemaEngine(sdl) {
|
|
|
7457
5161
|
};
|
|
7458
5162
|
}
|
|
7459
5163
|
|
|
7460
|
-
// src/lib/input.ts
|
|
7461
|
-
import inquirer4 from "inquirer";
|
|
7462
|
-
|
|
7463
|
-
// src/lib/config-loader.ts
|
|
7464
|
-
import { readFile } from "fs/promises";
|
|
7465
|
-
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
7466
|
-
import { resolve as resolve5 } from "path";
|
|
7467
|
-
async function loadConfig(filePath) {
|
|
7468
|
-
const absPath = resolve5(filePath);
|
|
7469
|
-
if (filePath.endsWith(".ts")) {
|
|
7470
|
-
const configModule = await import(pathToFileURL2(absPath).href);
|
|
7471
|
-
return configModule.default;
|
|
7472
|
-
}
|
|
7473
|
-
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
|
|
7474
|
-
const configModule = await import(pathToFileURL2(absPath).href);
|
|
7475
|
-
return configModule.default;
|
|
7476
|
-
}
|
|
7477
|
-
if (filePath.endsWith(".json")) {
|
|
7478
|
-
const content = await readFile(absPath, "utf-8");
|
|
7479
|
-
return JSON.parse(content);
|
|
7480
|
-
}
|
|
7481
|
-
throw new Error(
|
|
7482
|
-
`Unsupported file extension for "${filePath}". Supported: .ts, .js, .mjs, .json`
|
|
7483
|
-
);
|
|
7484
|
-
}
|
|
7485
|
-
|
|
7486
|
-
// src/lib/input.ts
|
|
7487
|
-
async function parseInputData(opts) {
|
|
7488
|
-
if (opts.data) {
|
|
7489
|
-
return JSON.parse(opts.data);
|
|
7490
|
-
}
|
|
7491
|
-
if (opts.file) {
|
|
7492
|
-
return await loadConfig(opts.file);
|
|
7493
|
-
}
|
|
7494
|
-
if (!process.stdin.isTTY) {
|
|
7495
|
-
const chunks = [];
|
|
7496
|
-
for await (const chunk of process.stdin) {
|
|
7497
|
-
chunks.push(chunk);
|
|
7498
|
-
}
|
|
7499
|
-
const stdinContent = Buffer.concat(chunks).toString("utf-8").trim();
|
|
7500
|
-
if (stdinContent) {
|
|
7501
|
-
return JSON.parse(stdinContent);
|
|
7502
|
-
}
|
|
7503
|
-
}
|
|
7504
|
-
throw new Error(
|
|
7505
|
-
"No input data provided. Use --data, --file, or pipe via stdin."
|
|
7506
|
-
);
|
|
7507
|
-
}
|
|
7508
|
-
function isUUID(value) {
|
|
7509
|
-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
7510
|
-
value
|
|
7511
|
-
) || /^c[a-z0-9]{24,}$/.test(value);
|
|
7512
|
-
}
|
|
7513
|
-
async function confirmAction(message, opts) {
|
|
7514
|
-
if (opts?.confirm) return true;
|
|
7515
|
-
const { confirmed } = await inquirer4.prompt([
|
|
7516
|
-
{
|
|
7517
|
-
type: "confirm",
|
|
7518
|
-
name: "confirmed",
|
|
7519
|
-
message,
|
|
7520
|
-
default: false
|
|
7521
|
-
}
|
|
7522
|
-
]);
|
|
7523
|
-
return confirmed;
|
|
7524
|
-
}
|
|
7525
|
-
|
|
7526
5164
|
// src/commands/register-commands.ts
|
|
7527
5165
|
var __filename = fileURLToPath(import.meta.url);
|
|
7528
5166
|
var __dirname = dirname5(__filename);
|
|
7529
5167
|
function loadSchemaSDL() {
|
|
7530
|
-
const bundledPath =
|
|
5168
|
+
const bundledPath = resolve5(__dirname, "schema.graphql");
|
|
7531
5169
|
try {
|
|
7532
5170
|
return readFileSync(bundledPath, "utf-8");
|
|
7533
5171
|
} catch {
|
|
7534
|
-
const monorepoPath =
|
|
5172
|
+
const monorepoPath = resolve5(
|
|
7535
5173
|
__dirname,
|
|
7536
5174
|
"../../../graphql-core/schema.graphql"
|
|
7537
5175
|
);
|
|
@@ -7687,11 +5325,11 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7687
5325
|
);
|
|
7688
5326
|
Object.assign(variables, coerced);
|
|
7689
5327
|
if (flags.dir && entry.acceptsInput) {
|
|
7690
|
-
const dirPath =
|
|
5328
|
+
const dirPath = resolve5(String(flags.dir));
|
|
7691
5329
|
const files = readdirSync(dirPath).filter((f) => /\.(json|ts|js|mjs)$/.test(f)).sort();
|
|
7692
5330
|
if (files.length === 0) {
|
|
7693
5331
|
console.error(
|
|
7694
|
-
|
|
5332
|
+
chalk8.yellow(`\u26A0 No .json/.ts/.js files found in ${dirPath}`)
|
|
7695
5333
|
);
|
|
7696
5334
|
return;
|
|
7697
5335
|
}
|
|
@@ -7699,7 +5337,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7699
5337
|
let updated = 0;
|
|
7700
5338
|
let failed = 0;
|
|
7701
5339
|
for (const file of files) {
|
|
7702
|
-
const filePath =
|
|
5340
|
+
const filePath = resolve5(dirPath, file);
|
|
7703
5341
|
const fileData = await parseInputData({ file: filePath });
|
|
7704
5342
|
const argName = entry.inputArgName ?? "input";
|
|
7705
5343
|
const fileVars = { ...variables, [argName]: fileData };
|
|
@@ -7736,19 +5374,19 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7736
5374
|
} catch (updateErr) {
|
|
7737
5375
|
failed++;
|
|
7738
5376
|
const msg2 = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
7739
|
-
console.error(
|
|
5377
|
+
console.error(chalk8.red(`\u2717 ${label}:`), msg2);
|
|
7740
5378
|
continue;
|
|
7741
5379
|
}
|
|
7742
5380
|
}
|
|
7743
5381
|
failed++;
|
|
7744
5382
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7745
|
-
console.error(
|
|
5383
|
+
console.error(chalk8.red(`\u2717 ${label}:`), msg);
|
|
7746
5384
|
}
|
|
7747
5385
|
}
|
|
7748
5386
|
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7749
5387
|
console.log("");
|
|
7750
5388
|
console.log(
|
|
7751
|
-
|
|
5389
|
+
chalk8.bold(
|
|
7752
5390
|
`Done: ${created} created${updated ? `, ${updated} updated` : ""}${failed ? `, ${failed} failed` : ""}`
|
|
7753
5391
|
)
|
|
7754
5392
|
);
|
|
@@ -7767,13 +5405,13 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7767
5405
|
);
|
|
7768
5406
|
const fieldNames = new Set(inputFields.map((f) => f.name));
|
|
7769
5407
|
if (fieldNames.has("projectId") && !inputData.projectId || fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
7770
|
-
const
|
|
7771
|
-
if (
|
|
5408
|
+
const resolved = await resolveProjectContext(opts);
|
|
5409
|
+
if (resolved) {
|
|
7772
5410
|
if (fieldNames.has("projectId") && !inputData.projectId) {
|
|
7773
|
-
inputData.projectId = project.id;
|
|
5411
|
+
inputData.projectId = resolved.project.id;
|
|
7774
5412
|
}
|
|
7775
5413
|
if (fieldNames.has("tenantId") && !inputData.tenantId) {
|
|
7776
|
-
inputData.tenantId = project.tenantId;
|
|
5414
|
+
inputData.tenantId = resolved.project.tenantId;
|
|
7777
5415
|
}
|
|
7778
5416
|
}
|
|
7779
5417
|
}
|
|
@@ -7924,7 +5562,7 @@ function registerDynamicCommands(program2, globalOpts) {
|
|
|
7924
5562
|
}
|
|
7925
5563
|
} else if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
7926
5564
|
console.error(
|
|
7927
|
-
|
|
5565
|
+
chalk8.yellow(
|
|
7928
5566
|
"\u26A0 Could not auto-publish: no version found in response"
|
|
7929
5567
|
)
|
|
7930
5568
|
);
|
|
@@ -7948,24 +5586,26 @@ function autoColumns(items) {
|
|
|
7948
5586
|
// src/cli.ts
|
|
7949
5587
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
7950
5588
|
var __dirname2 = dirname6(__filename2);
|
|
7951
|
-
config({ path:
|
|
5589
|
+
config({ path: resolve6(__dirname2, "../.env.local") });
|
|
7952
5590
|
var require2 = createRequire(import.meta.url);
|
|
7953
5591
|
var { version } = require2("../package.json");
|
|
7954
5592
|
var program = new Command();
|
|
7955
|
-
program.name("foir").description("CLI for Foir platform").version(version).option("--api-url <url>", "API endpoint URL").option("--json", "Output as JSON").option("--jsonl", "Output as JSON Lines").option("--quiet", "Minimal output (IDs only)");
|
|
5593
|
+
program.name("foir").description("CLI for Foir platform").version(version).option("--api-url <url>", "API endpoint URL").option("--json", "Output as JSON").option("--jsonl", "Output as JSON Lines").option("--quiet", "Minimal output (IDs only)").option("--project <name>", "Use a named project profile");
|
|
7956
5594
|
function getGlobalOpts() {
|
|
7957
5595
|
const opts = program.opts();
|
|
7958
5596
|
return {
|
|
7959
5597
|
apiUrl: opts.apiUrl,
|
|
7960
5598
|
json: !!opts.json,
|
|
7961
5599
|
jsonl: !!opts.jsonl,
|
|
7962
|
-
quiet: !!opts.quiet
|
|
5600
|
+
quiet: !!opts.quiet,
|
|
5601
|
+
project: opts.project
|
|
7963
5602
|
};
|
|
7964
5603
|
}
|
|
7965
5604
|
registerLoginCommand(program, getGlobalOpts);
|
|
7966
5605
|
registerLogoutCommand(program, getGlobalOpts);
|
|
7967
5606
|
registerSelectProjectCommand(program, getGlobalOpts);
|
|
7968
5607
|
registerWhoamiCommand(program, getGlobalOpts);
|
|
5608
|
+
registerProfilesCommand(program, getGlobalOpts);
|
|
7969
5609
|
registerMediaCommands(program, getGlobalOpts);
|
|
7970
5610
|
registerSearchCommands(program, getGlobalOpts);
|
|
7971
5611
|
registerPullCommand(program, getGlobalOpts);
|