@beignet/cli 0.0.1 → 0.0.3
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/CHANGELOG.md +26 -0
- package/README.md +144 -55
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/create.d.ts +9 -0
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +5 -0
- package/dist/create.js.map +1 -1
- package/dist/db.d.ts +36 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +111 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +243 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +24 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +288 -4
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts +12 -0
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +30 -5
- package/dist/lint.js.map +1 -1
- package/dist/make.d.ts +91 -0
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +602 -72
- package/dist/make.js.map +1 -1
- package/dist/templates.d.ts +33 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +1396 -112
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +31 -0
- package/src/create.ts +11 -0
- package/src/db.ts +166 -0
- package/src/index.ts +320 -0
- package/src/inspect.ts +497 -3
- package/src/lint.ts +43 -9
- package/src/make.ts +917 -80
- package/src/templates.ts +1425 -111
package/src/make.ts
CHANGED
|
@@ -17,6 +17,31 @@ type MakeResourceOptions = {
|
|
|
17
17
|
config?: BeignetConfig;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
export const makeFeatureAddonChoices = [
|
|
21
|
+
"policy",
|
|
22
|
+
"event",
|
|
23
|
+
"events",
|
|
24
|
+
"job",
|
|
25
|
+
"jobs",
|
|
26
|
+
"notification",
|
|
27
|
+
"notifications",
|
|
28
|
+
"upload",
|
|
29
|
+
"uploads",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type MakeFeatureAddon = (typeof makeFeatureAddonChoices)[number];
|
|
33
|
+
|
|
34
|
+
type NormalizedMakeFeatureAddon =
|
|
35
|
+
| "policy"
|
|
36
|
+
| "event"
|
|
37
|
+
| "job"
|
|
38
|
+
| "notification"
|
|
39
|
+
| "upload";
|
|
40
|
+
|
|
41
|
+
type MakeFeatureOptions = MakeResourceOptions & {
|
|
42
|
+
with?: readonly MakeFeatureAddon[];
|
|
43
|
+
};
|
|
44
|
+
|
|
20
45
|
type MakeContractOptions = {
|
|
21
46
|
name: string;
|
|
22
47
|
cwd?: string;
|
|
@@ -81,6 +106,30 @@ type MakeJobOptions = {
|
|
|
81
106
|
config?: BeignetConfig;
|
|
82
107
|
};
|
|
83
108
|
|
|
109
|
+
type MakeFactoryOptions = {
|
|
110
|
+
name: string;
|
|
111
|
+
cwd?: string;
|
|
112
|
+
force?: boolean;
|
|
113
|
+
dryRun?: boolean;
|
|
114
|
+
config?: BeignetConfig;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type MakeSeedOptions = {
|
|
118
|
+
name: string;
|
|
119
|
+
cwd?: string;
|
|
120
|
+
force?: boolean;
|
|
121
|
+
dryRun?: boolean;
|
|
122
|
+
config?: BeignetConfig;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type MakeNotificationOptions = {
|
|
126
|
+
name: string;
|
|
127
|
+
cwd?: string;
|
|
128
|
+
force?: boolean;
|
|
129
|
+
dryRun?: boolean;
|
|
130
|
+
config?: BeignetConfig;
|
|
131
|
+
};
|
|
132
|
+
|
|
84
133
|
type MakeListenerOptions = {
|
|
85
134
|
name: string;
|
|
86
135
|
event: string;
|
|
@@ -101,6 +150,14 @@ type MakeScheduleOptions = {
|
|
|
101
150
|
route?: boolean;
|
|
102
151
|
};
|
|
103
152
|
|
|
153
|
+
type MakeUploadOptions = {
|
|
154
|
+
name: string;
|
|
155
|
+
cwd?: string;
|
|
156
|
+
force?: boolean;
|
|
157
|
+
dryRun?: boolean;
|
|
158
|
+
config?: BeignetConfig;
|
|
159
|
+
};
|
|
160
|
+
|
|
104
161
|
type MakeResult = {
|
|
105
162
|
name: string;
|
|
106
163
|
targetDir: string;
|
|
@@ -111,17 +168,70 @@ type MakeResult = {
|
|
|
111
168
|
skippedFiles: string[];
|
|
112
169
|
};
|
|
113
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Result returned by `beignet make resource`.
|
|
173
|
+
*/
|
|
114
174
|
export type MakeResourceResult = MakeResult;
|
|
175
|
+
/**
|
|
176
|
+
* Result returned by `beignet make feature`.
|
|
177
|
+
*/
|
|
178
|
+
export type MakeFeatureResult = MakeResult;
|
|
179
|
+
/**
|
|
180
|
+
* Result returned by `beignet make contract`.
|
|
181
|
+
*/
|
|
115
182
|
export type MakeContractResult = MakeResult;
|
|
183
|
+
/**
|
|
184
|
+
* Result returned by `beignet make use-case`.
|
|
185
|
+
*/
|
|
116
186
|
export type MakeUseCaseResult = MakeResult;
|
|
187
|
+
/**
|
|
188
|
+
* Result returned by `beignet make test`.
|
|
189
|
+
*/
|
|
117
190
|
export type MakeTestResult = MakeResult;
|
|
191
|
+
/**
|
|
192
|
+
* Result returned by `beignet make port`.
|
|
193
|
+
*/
|
|
118
194
|
export type MakePortResult = MakeResult;
|
|
195
|
+
/**
|
|
196
|
+
* Result returned by `beignet make adapter`.
|
|
197
|
+
*/
|
|
119
198
|
export type MakeAdapterResult = MakeResult;
|
|
199
|
+
/**
|
|
200
|
+
* Result returned by `beignet make policy`.
|
|
201
|
+
*/
|
|
120
202
|
export type MakePolicyResult = MakeResult;
|
|
203
|
+
/**
|
|
204
|
+
* Result returned by `beignet make event`.
|
|
205
|
+
*/
|
|
121
206
|
export type MakeEventResult = MakeResult;
|
|
207
|
+
/**
|
|
208
|
+
* Result returned by `beignet make job`.
|
|
209
|
+
*/
|
|
122
210
|
export type MakeJobResult = MakeResult;
|
|
211
|
+
/**
|
|
212
|
+
* Result returned by `beignet make factory`.
|
|
213
|
+
*/
|
|
214
|
+
export type MakeFactoryResult = MakeResult;
|
|
215
|
+
/**
|
|
216
|
+
* Result returned by `beignet make seed`.
|
|
217
|
+
*/
|
|
218
|
+
export type MakeSeedResult = MakeResult;
|
|
219
|
+
/**
|
|
220
|
+
* Result returned by `beignet make notification`.
|
|
221
|
+
*/
|
|
222
|
+
export type MakeNotificationResult = MakeResult;
|
|
223
|
+
/**
|
|
224
|
+
* Result returned by `beignet make listener`.
|
|
225
|
+
*/
|
|
123
226
|
export type MakeListenerResult = MakeResult;
|
|
227
|
+
/**
|
|
228
|
+
* Result returned by `beignet make schedule`.
|
|
229
|
+
*/
|
|
124
230
|
export type MakeScheduleResult = MakeResult;
|
|
231
|
+
/**
|
|
232
|
+
* Result returned by `beignet make upload`.
|
|
233
|
+
*/
|
|
234
|
+
export type MakeUploadResult = MakeResult;
|
|
125
235
|
|
|
126
236
|
type ResourceNames = {
|
|
127
237
|
input: string;
|
|
@@ -190,6 +300,23 @@ type JobNames = FeatureArtifactNames & {
|
|
|
190
300
|
payloadTypeName: string;
|
|
191
301
|
};
|
|
192
302
|
|
|
303
|
+
type FactoryNames = FeatureArtifactNames & {
|
|
304
|
+
factoryName: string;
|
|
305
|
+
factoryExportName: string;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
type SeedNames = FeatureArtifactNames & {
|
|
309
|
+
seedName: string;
|
|
310
|
+
seedExportName: string;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
type NotificationNames = FeatureArtifactNames & {
|
|
314
|
+
notificationName: string;
|
|
315
|
+
notificationExportName: string;
|
|
316
|
+
payloadSchemaName: string;
|
|
317
|
+
payloadTypeName: string;
|
|
318
|
+
};
|
|
319
|
+
|
|
193
320
|
type ListenerNames = FeatureArtifactNames & {
|
|
194
321
|
listenerName: string;
|
|
195
322
|
listenerExportName: string;
|
|
@@ -205,6 +332,13 @@ type ScheduleNames = FeatureArtifactNames & {
|
|
|
205
332
|
timezone?: string;
|
|
206
333
|
};
|
|
207
334
|
|
|
335
|
+
type UploadNames = FeatureArtifactNames & {
|
|
336
|
+
uploadName: string;
|
|
337
|
+
uploadExportName: string;
|
|
338
|
+
metadataSchemaName: string;
|
|
339
|
+
metadataTypeName: string;
|
|
340
|
+
};
|
|
341
|
+
|
|
208
342
|
export async function makeResource(
|
|
209
343
|
options: MakeResourceOptions,
|
|
210
344
|
): Promise<MakeResourceResult> {
|
|
@@ -266,6 +400,119 @@ export async function makeResource(
|
|
|
266
400
|
};
|
|
267
401
|
}
|
|
268
402
|
|
|
403
|
+
export async function makeFeature(
|
|
404
|
+
options: MakeFeatureOptions,
|
|
405
|
+
): Promise<MakeFeatureResult> {
|
|
406
|
+
const addons = normalizeMakeFeatureAddons(options.with ?? []);
|
|
407
|
+
if (addons.length === 0) {
|
|
408
|
+
return makeFeatureResource(options);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (options.dryRun) {
|
|
412
|
+
return makeFeatureSlice(options, addons);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
await makeFeatureSlice({ ...options, dryRun: true }, addons);
|
|
416
|
+
return makeFeatureSlice(options, addons);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function makeFeatureResource(
|
|
420
|
+
options: MakeResourceOptions,
|
|
421
|
+
): Promise<MakeResourceResult> {
|
|
422
|
+
try {
|
|
423
|
+
return await makeResource(options);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error instanceof Error && error.message.includes("make resource")) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
error.message.replaceAll("make resource", "make feature"),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function makeFeatureSlice(
|
|
435
|
+
options: MakeFeatureOptions,
|
|
436
|
+
addons: readonly NormalizedMakeFeatureAddon[],
|
|
437
|
+
): Promise<MakeFeatureResult> {
|
|
438
|
+
let result = await makeFeatureResource(options);
|
|
439
|
+
|
|
440
|
+
for (const addon of addons) {
|
|
441
|
+
result = mergeMakeResults(
|
|
442
|
+
result,
|
|
443
|
+
await makeFeatureAddon(addon, result.name, options),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function makeFeatureAddon(
|
|
451
|
+
addon: NormalizedMakeFeatureAddon,
|
|
452
|
+
feature: string,
|
|
453
|
+
options: MakeFeatureOptions,
|
|
454
|
+
): Promise<MakeResult> {
|
|
455
|
+
const shared = {
|
|
456
|
+
cwd: options.cwd,
|
|
457
|
+
force: options.force,
|
|
458
|
+
dryRun: options.dryRun,
|
|
459
|
+
config: options.config,
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
if (addon === "policy") {
|
|
463
|
+
return makePolicy({ ...shared, name: feature });
|
|
464
|
+
}
|
|
465
|
+
if (addon === "event") {
|
|
466
|
+
return makeEvent({ ...shared, name: `${feature}/created` });
|
|
467
|
+
}
|
|
468
|
+
if (addon === "job") {
|
|
469
|
+
return makeJob({ ...shared, name: `${feature}/process` });
|
|
470
|
+
}
|
|
471
|
+
if (addon === "notification") {
|
|
472
|
+
return makeNotification({ ...shared, name: `${feature}/created` });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return makeUpload({ ...shared, name: `${feature}/attachment` });
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function normalizeMakeFeatureAddons(
|
|
479
|
+
addons: readonly MakeFeatureAddon[],
|
|
480
|
+
): NormalizedMakeFeatureAddon[] {
|
|
481
|
+
const normalized = new Set<NormalizedMakeFeatureAddon>();
|
|
482
|
+
|
|
483
|
+
for (const addon of addons) {
|
|
484
|
+
if (addon === "events") normalized.add("event");
|
|
485
|
+
else if (addon === "jobs") normalized.add("job");
|
|
486
|
+
else if (addon === "notifications") normalized.add("notification");
|
|
487
|
+
else if (addon === "uploads") normalized.add("upload");
|
|
488
|
+
else normalized.add(addon);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return featureAddonOrder.filter((addon) => normalized.has(addon));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const featureAddonOrder: readonly NormalizedMakeFeatureAddon[] = [
|
|
495
|
+
"policy",
|
|
496
|
+
"event",
|
|
497
|
+
"job",
|
|
498
|
+
"notification",
|
|
499
|
+
"upload",
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
function mergeMakeResults(left: MakeResult, right: MakeResult): MakeResult {
|
|
503
|
+
return {
|
|
504
|
+
...left,
|
|
505
|
+
files: uniqueStrings([...left.files, ...right.files]),
|
|
506
|
+
createdFiles: uniqueStrings([...left.createdFiles, ...right.createdFiles]),
|
|
507
|
+
updatedFiles: uniqueStrings([...left.updatedFiles, ...right.updatedFiles]),
|
|
508
|
+
skippedFiles: uniqueStrings([...left.skippedFiles, ...right.skippedFiles]),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function uniqueStrings(values: readonly string[]): string[] {
|
|
513
|
+
return [...new Set(values)];
|
|
514
|
+
}
|
|
515
|
+
|
|
269
516
|
export async function makeContract(
|
|
270
517
|
options: MakeContractOptions,
|
|
271
518
|
): Promise<MakeContractResult> {
|
|
@@ -371,7 +618,11 @@ export async function makeTest(
|
|
|
371
618
|
if (result === "skipped") skippedFiles.push(file.path);
|
|
372
619
|
}
|
|
373
620
|
|
|
374
|
-
if (
|
|
621
|
+
if (
|
|
622
|
+
await updatePackageJson(targetDir, {
|
|
623
|
+
dryRun: Boolean(options.dryRun),
|
|
624
|
+
})
|
|
625
|
+
) {
|
|
375
626
|
updatedFiles.push("package.json");
|
|
376
627
|
}
|
|
377
628
|
|
|
@@ -578,6 +829,81 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
|
|
|
578
829
|
});
|
|
579
830
|
}
|
|
580
831
|
|
|
832
|
+
export async function makeFactory(
|
|
833
|
+
options: MakeFactoryOptions,
|
|
834
|
+
): Promise<MakeFactoryResult> {
|
|
835
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
836
|
+
const names = factoryNames(options.name);
|
|
837
|
+
const config = options.config
|
|
838
|
+
? resolveConfig(options.config)
|
|
839
|
+
: await loadBeignetConfig(targetDir);
|
|
840
|
+
|
|
841
|
+
await assertPersistenceArtifactApp(targetDir, names, config, "factory");
|
|
842
|
+
|
|
843
|
+
return makeFeatureArtifact({
|
|
844
|
+
targetDir,
|
|
845
|
+
config,
|
|
846
|
+
force: Boolean(options.force),
|
|
847
|
+
dryRun: Boolean(options.dryRun),
|
|
848
|
+
name: `${names.feature.kebab}/${names.artifact.kebab}`,
|
|
849
|
+
files: factoryFiles(names, config),
|
|
850
|
+
index: featureArtifactIndexFile("factory", names, config),
|
|
851
|
+
dependencies: {
|
|
852
|
+
"@beignet/core": beignetDependencyVersion,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export async function makeSeed(
|
|
858
|
+
options: MakeSeedOptions,
|
|
859
|
+
): Promise<MakeSeedResult> {
|
|
860
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
861
|
+
const names = seedNames(options.name);
|
|
862
|
+
const config = options.config
|
|
863
|
+
? resolveConfig(options.config)
|
|
864
|
+
: await loadBeignetConfig(targetDir);
|
|
865
|
+
|
|
866
|
+
await assertPersistenceArtifactApp(targetDir, names, config, "seed");
|
|
867
|
+
|
|
868
|
+
return makeFeatureArtifact({
|
|
869
|
+
targetDir,
|
|
870
|
+
config,
|
|
871
|
+
force: Boolean(options.force),
|
|
872
|
+
dryRun: Boolean(options.dryRun),
|
|
873
|
+
name: `${names.feature.kebab}/${names.artifact.kebab}`,
|
|
874
|
+
files: seedFiles(names, config),
|
|
875
|
+
index: featureArtifactIndexFile("seed", names, config),
|
|
876
|
+
dependencies: {
|
|
877
|
+
"@beignet/core": beignetDependencyVersion,
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
export async function makeNotification(
|
|
883
|
+
options: MakeNotificationOptions,
|
|
884
|
+
): Promise<MakeNotificationResult> {
|
|
885
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
886
|
+
const names = notificationNames(options.name);
|
|
887
|
+
const config = options.config
|
|
888
|
+
? resolveConfig(options.config)
|
|
889
|
+
: await loadBeignetConfig(targetDir);
|
|
890
|
+
|
|
891
|
+
await assertFeatureArtifactApp(targetDir, config, "notification");
|
|
892
|
+
|
|
893
|
+
return makeFeatureArtifact({
|
|
894
|
+
targetDir,
|
|
895
|
+
config,
|
|
896
|
+
force: Boolean(options.force),
|
|
897
|
+
dryRun: Boolean(options.dryRun),
|
|
898
|
+
name: `${names.feature.kebab}/${names.artifact.kebab}`,
|
|
899
|
+
files: notificationFiles(names, config),
|
|
900
|
+
index: featureArtifactIndexFile("notification", names, config),
|
|
901
|
+
dependencies: {
|
|
902
|
+
"@beignet/core": beignetDependencyVersion,
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
581
907
|
export async function makeListener(
|
|
582
908
|
options: MakeListenerOptions,
|
|
583
909
|
): Promise<MakeListenerResult> {
|
|
@@ -652,6 +978,31 @@ export async function makeSchedule(
|
|
|
652
978
|
});
|
|
653
979
|
}
|
|
654
980
|
|
|
981
|
+
export async function makeUpload(
|
|
982
|
+
options: MakeUploadOptions,
|
|
983
|
+
): Promise<MakeUploadResult> {
|
|
984
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
985
|
+
const names = uploadNames(options.name);
|
|
986
|
+
const config = options.config
|
|
987
|
+
? resolveConfig(options.config)
|
|
988
|
+
: await loadBeignetConfig(targetDir);
|
|
989
|
+
|
|
990
|
+
await assertFeatureArtifactApp(targetDir, config, "upload");
|
|
991
|
+
|
|
992
|
+
return makeFeatureArtifact({
|
|
993
|
+
targetDir,
|
|
994
|
+
config,
|
|
995
|
+
force: Boolean(options.force),
|
|
996
|
+
dryRun: Boolean(options.dryRun),
|
|
997
|
+
name: `${names.feature.kebab}/${names.artifact.kebab}`,
|
|
998
|
+
files: uploadFiles(names, config),
|
|
999
|
+
index: featureArtifactIndexFile("upload", names, config),
|
|
1000
|
+
dependencies: {
|
|
1001
|
+
"@beignet/core": beignetDependencyVersion,
|
|
1002
|
+
},
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
655
1006
|
async function assertStandardApp(
|
|
656
1007
|
targetDir: string,
|
|
657
1008
|
config: ResolvedBeignetConfig,
|
|
@@ -777,7 +1128,13 @@ async function assertPolicyApp(
|
|
|
777
1128
|
async function assertFeatureArtifactApp(
|
|
778
1129
|
targetDir: string,
|
|
779
1130
|
config: ResolvedBeignetConfig,
|
|
780
|
-
artifact:
|
|
1131
|
+
artifact:
|
|
1132
|
+
| "event"
|
|
1133
|
+
| "job"
|
|
1134
|
+
| "notification"
|
|
1135
|
+
| "listener"
|
|
1136
|
+
| "schedule"
|
|
1137
|
+
| "upload",
|
|
781
1138
|
options: { requireServer?: boolean } = {},
|
|
782
1139
|
): Promise<void> {
|
|
783
1140
|
const requiredFiles = [
|
|
@@ -796,6 +1153,29 @@ async function assertFeatureArtifactApp(
|
|
|
796
1153
|
}
|
|
797
1154
|
}
|
|
798
1155
|
|
|
1156
|
+
async function assertPersistenceArtifactApp(
|
|
1157
|
+
targetDir: string,
|
|
1158
|
+
names: FactoryNames | SeedNames,
|
|
1159
|
+
config: ResolvedBeignetConfig,
|
|
1160
|
+
artifact: "factory" | "seed",
|
|
1161
|
+
): Promise<void> {
|
|
1162
|
+
const requiredFiles = [
|
|
1163
|
+
config.paths.appContext,
|
|
1164
|
+
config.paths.ports,
|
|
1165
|
+
resourcePortFilePath(featureResourceNames(names), config),
|
|
1166
|
+
];
|
|
1167
|
+
|
|
1168
|
+
for (const file of requiredFiles) {
|
|
1169
|
+
try {
|
|
1170
|
+
await stat(path.join(targetDir, file));
|
|
1171
|
+
} catch {
|
|
1172
|
+
throw new Error(
|
|
1173
|
+
`beignet make ${artifact} expects a feature with a repository port. Missing ${file}. Run beignet make feature ${names.feature.kebab} first, or add the repository port before generating ${artifact}s.`,
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
799
1179
|
type WriteGeneratedFileOptions = {
|
|
800
1180
|
force: boolean;
|
|
801
1181
|
dryRun: boolean;
|
|
@@ -996,11 +1376,21 @@ async function updateResourceWiring(
|
|
|
996
1376
|
? (maybeOptions as { dryRun: boolean })
|
|
997
1377
|
: persistenceOrOptions;
|
|
998
1378
|
const updated = new Set<string>();
|
|
999
|
-
if (
|
|
1379
|
+
if (
|
|
1380
|
+
await updatePackageJson(targetDir, {
|
|
1381
|
+
...options,
|
|
1382
|
+
webTestingDependency: true,
|
|
1383
|
+
})
|
|
1384
|
+
) {
|
|
1385
|
+
updated.add("package.json");
|
|
1386
|
+
}
|
|
1000
1387
|
if (await updatePortsIndex(targetDir, names, config, options)) {
|
|
1001
1388
|
updated.add(config.paths.ports);
|
|
1002
1389
|
}
|
|
1003
|
-
if (
|
|
1390
|
+
if (
|
|
1391
|
+
persistence === "memory" &&
|
|
1392
|
+
(await updateInfrastructurePorts(targetDir, names, config, options))
|
|
1393
|
+
) {
|
|
1004
1394
|
updated.add(config.paths.infrastructurePorts);
|
|
1005
1395
|
}
|
|
1006
1396
|
if (
|
|
@@ -1037,17 +1427,27 @@ async function updateResourceWiring(
|
|
|
1037
1427
|
|
|
1038
1428
|
async function updatePackageJson(
|
|
1039
1429
|
targetDir: string,
|
|
1040
|
-
options: { dryRun: boolean },
|
|
1430
|
+
options: { dryRun: boolean; webTestingDependency?: boolean },
|
|
1041
1431
|
): Promise<boolean> {
|
|
1042
1432
|
const filePath = path.join(targetDir, "package.json");
|
|
1043
1433
|
const original = await readFile(filePath, "utf8");
|
|
1044
1434
|
const packageJson = JSON.parse(original) as {
|
|
1045
1435
|
scripts?: Record<string, string>;
|
|
1436
|
+
dependencies?: Record<string, string>;
|
|
1437
|
+
devDependencies?: Record<string, string>;
|
|
1046
1438
|
};
|
|
1047
1439
|
packageJson.scripts = packageJson.scripts ?? {};
|
|
1048
|
-
if (packageJson.scripts.test) return false;
|
|
1049
1440
|
|
|
1050
|
-
packageJson.scripts.test
|
|
1441
|
+
packageJson.scripts.test ??= "bun test";
|
|
1442
|
+
|
|
1443
|
+
if (options.webTestingDependency) {
|
|
1444
|
+
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
1445
|
+
packageJson.devDependencies["@beignet/web"] ??=
|
|
1446
|
+
packageJson.dependencies?.["@beignet/core"] ??
|
|
1447
|
+
packageJson.dependencies?.["@beignet/next"] ??
|
|
1448
|
+
"*";
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1051
1451
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
1052
1452
|
if (!options.dryRun) await writeFile(filePath, next);
|
|
1053
1453
|
return next !== original;
|
|
@@ -2193,6 +2593,10 @@ function resourceNames(input: string): ResourceNames {
|
|
|
2193
2593
|
return names;
|
|
2194
2594
|
}
|
|
2195
2595
|
|
|
2596
|
+
function featureResourceNames(names: FeatureArtifactNames): ResourceNames {
|
|
2597
|
+
return resourceNames(names.feature.kebab);
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2196
2600
|
function useCaseNames(input: string): UseCaseNames {
|
|
2197
2601
|
const parts = input
|
|
2198
2602
|
.trim()
|
|
@@ -2333,6 +2737,49 @@ function jobNames(input: string): JobNames {
|
|
|
2333
2737
|
};
|
|
2334
2738
|
}
|
|
2335
2739
|
|
|
2740
|
+
function factoryNames(input: string): FactoryNames {
|
|
2741
|
+
const names = featureArtifactNames(input, "Factory");
|
|
2742
|
+
const factoryExportName = `${names.artifact.camel}Factory`;
|
|
2743
|
+
|
|
2744
|
+
assertGeneratedIdentifiers(input, "Factory name", [factoryExportName]);
|
|
2745
|
+
|
|
2746
|
+
return {
|
|
2747
|
+
...names,
|
|
2748
|
+
factoryName: names.stableName,
|
|
2749
|
+
factoryExportName,
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function seedNames(input: string): SeedNames {
|
|
2754
|
+
const names = featureArtifactNames(input, "Seed");
|
|
2755
|
+
const seedExportName = `${names.artifact.camel}Seed`;
|
|
2756
|
+
|
|
2757
|
+
assertGeneratedIdentifiers(input, "Seed name", [seedExportName]);
|
|
2758
|
+
|
|
2759
|
+
return {
|
|
2760
|
+
...names,
|
|
2761
|
+
seedName: names.stableName,
|
|
2762
|
+
seedExportName,
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
function notificationNames(input: string): NotificationNames {
|
|
2767
|
+
const names = featureArtifactNames(input, "Notification");
|
|
2768
|
+
const notificationExportName = `${names.featureSingularPascal}${names.artifact.pascal}Notification`;
|
|
2769
|
+
|
|
2770
|
+
assertGeneratedIdentifiers(input, "Notification name", [
|
|
2771
|
+
notificationExportName,
|
|
2772
|
+
]);
|
|
2773
|
+
|
|
2774
|
+
return {
|
|
2775
|
+
...names,
|
|
2776
|
+
notificationName: names.stableName,
|
|
2777
|
+
notificationExportName,
|
|
2778
|
+
payloadSchemaName: `${notificationExportName}PayloadSchema`,
|
|
2779
|
+
payloadTypeName: `${notificationExportName}Payload`,
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2336
2783
|
function listenerNames(input: string, eventInput: string): ListenerNames {
|
|
2337
2784
|
const names = featureArtifactNames(input, "Listener");
|
|
2338
2785
|
const listenerExportName = names.artifact.camel;
|
|
@@ -2367,6 +2814,21 @@ function scheduleNames(
|
|
|
2367
2814
|
};
|
|
2368
2815
|
}
|
|
2369
2816
|
|
|
2817
|
+
function uploadNames(input: string): UploadNames {
|
|
2818
|
+
const names = featureArtifactNames(input, "Upload");
|
|
2819
|
+
const uploadExportName = `${names.artifact.pascal}Upload`;
|
|
2820
|
+
|
|
2821
|
+
assertGeneratedIdentifiers(input, "Upload name", [uploadExportName]);
|
|
2822
|
+
|
|
2823
|
+
return {
|
|
2824
|
+
...names,
|
|
2825
|
+
uploadName: names.stableName,
|
|
2826
|
+
uploadExportName,
|
|
2827
|
+
metadataSchemaName: `${uploadExportName}MetadataSchema`,
|
|
2828
|
+
metadataTypeName: `${uploadExportName}Metadata`,
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2370
2832
|
function normalizeEventReference(input: string): string {
|
|
2371
2833
|
const trimmed = input.trim();
|
|
2372
2834
|
if (trimmed.includes("/")) return trimmed;
|
|
@@ -2689,6 +3151,42 @@ function jobFiles(
|
|
|
2689
3151
|
];
|
|
2690
3152
|
}
|
|
2691
3153
|
|
|
3154
|
+
function notificationFiles(
|
|
3155
|
+
names: NotificationNames,
|
|
3156
|
+
config: ResolvedBeignetConfig,
|
|
3157
|
+
): GeneratedFile[] {
|
|
3158
|
+
return [
|
|
3159
|
+
{
|
|
3160
|
+
path: notificationFilePath(names, config),
|
|
3161
|
+
content: notificationFile(names, config),
|
|
3162
|
+
},
|
|
3163
|
+
];
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
function factoryFiles(
|
|
3167
|
+
names: FactoryNames,
|
|
3168
|
+
config: ResolvedBeignetConfig,
|
|
3169
|
+
): GeneratedFile[] {
|
|
3170
|
+
return [
|
|
3171
|
+
{
|
|
3172
|
+
path: factoryFilePath(names, config),
|
|
3173
|
+
content: factoryFile(names, config),
|
|
3174
|
+
},
|
|
3175
|
+
];
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
function seedFiles(
|
|
3179
|
+
names: SeedNames,
|
|
3180
|
+
config: ResolvedBeignetConfig,
|
|
3181
|
+
): GeneratedFile[] {
|
|
3182
|
+
return [
|
|
3183
|
+
{
|
|
3184
|
+
path: seedFilePath(names, config),
|
|
3185
|
+
content: seedFile(names, config),
|
|
3186
|
+
},
|
|
3187
|
+
];
|
|
3188
|
+
}
|
|
3189
|
+
|
|
2692
3190
|
function listenerFiles(
|
|
2693
3191
|
names: ListenerNames,
|
|
2694
3192
|
config: ResolvedBeignetConfig,
|
|
@@ -2723,6 +3221,18 @@ function scheduleFiles(
|
|
|
2723
3221
|
return files;
|
|
2724
3222
|
}
|
|
2725
3223
|
|
|
3224
|
+
function uploadFiles(
|
|
3225
|
+
names: UploadNames,
|
|
3226
|
+
config: ResolvedBeignetConfig,
|
|
3227
|
+
): GeneratedFile[] {
|
|
3228
|
+
return [
|
|
3229
|
+
{
|
|
3230
|
+
path: uploadFilePath(names, config),
|
|
3231
|
+
content: uploadFile(names, config),
|
|
3232
|
+
},
|
|
3233
|
+
];
|
|
3234
|
+
}
|
|
3235
|
+
|
|
2726
3236
|
function portFilePath(names: PortNames, config: ResolvedBeignetConfig): string {
|
|
2727
3237
|
return path.join(path.dirname(config.paths.ports), `${names.kebab}.ts`);
|
|
2728
3238
|
}
|
|
@@ -2917,7 +3427,15 @@ function policyFilePath(
|
|
|
2917
3427
|
|
|
2918
3428
|
function featureArtifactDir(
|
|
2919
3429
|
names: FeatureArtifactNames,
|
|
2920
|
-
kind:
|
|
3430
|
+
kind:
|
|
3431
|
+
| "events"
|
|
3432
|
+
| "factories"
|
|
3433
|
+
| "jobs"
|
|
3434
|
+
| "notifications"
|
|
3435
|
+
| "listeners"
|
|
3436
|
+
| "seeds"
|
|
3437
|
+
| "schedules"
|
|
3438
|
+
| "uploads",
|
|
2921
3439
|
config: ResolvedBeignetConfig,
|
|
2922
3440
|
): string {
|
|
2923
3441
|
const featureDir = path.join(config.paths.features, names.feature.kebab);
|
|
@@ -2925,6 +3443,9 @@ function featureArtifactDir(
|
|
|
2925
3443
|
if (kind === "events") {
|
|
2926
3444
|
return path.join(featureDir, "domain", "events");
|
|
2927
3445
|
}
|
|
3446
|
+
if (kind === "factories") {
|
|
3447
|
+
return path.join(featureDir, "tests", "factories");
|
|
3448
|
+
}
|
|
2928
3449
|
|
|
2929
3450
|
return path.join(featureDir, kind);
|
|
2930
3451
|
}
|
|
@@ -2946,6 +3467,33 @@ function jobFilePath(names: JobNames, config: ResolvedBeignetConfig): string {
|
|
|
2946
3467
|
);
|
|
2947
3468
|
}
|
|
2948
3469
|
|
|
3470
|
+
function factoryFilePath(
|
|
3471
|
+
names: FactoryNames,
|
|
3472
|
+
config: ResolvedBeignetConfig,
|
|
3473
|
+
): string {
|
|
3474
|
+
return path.join(
|
|
3475
|
+
featureArtifactDir(names, "factories", config),
|
|
3476
|
+
`${names.artifact.kebab}.ts`,
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
function seedFilePath(names: SeedNames, config: ResolvedBeignetConfig): string {
|
|
3481
|
+
return path.join(
|
|
3482
|
+
featureArtifactDir(names, "seeds", config),
|
|
3483
|
+
`${names.artifact.kebab}.ts`,
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
function notificationFilePath(
|
|
3488
|
+
names: NotificationNames,
|
|
3489
|
+
config: ResolvedBeignetConfig,
|
|
3490
|
+
): string {
|
|
3491
|
+
return path.join(
|
|
3492
|
+
featureArtifactDir(names, "notifications", config),
|
|
3493
|
+
`${names.artifact.kebab}.ts`,
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3496
|
+
|
|
2949
3497
|
function listenerFilePath(
|
|
2950
3498
|
names: ListenerNames,
|
|
2951
3499
|
config: ResolvedBeignetConfig,
|
|
@@ -2966,6 +3514,16 @@ function scheduleFilePath(
|
|
|
2966
3514
|
);
|
|
2967
3515
|
}
|
|
2968
3516
|
|
|
3517
|
+
function uploadFilePath(
|
|
3518
|
+
names: UploadNames,
|
|
3519
|
+
config: ResolvedBeignetConfig,
|
|
3520
|
+
): string {
|
|
3521
|
+
return path.join(
|
|
3522
|
+
featureArtifactDir(names, "uploads", config),
|
|
3523
|
+
`${names.artifact.kebab}.ts`,
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
2969
3527
|
function scheduleRouteFilePath(
|
|
2970
3528
|
names: ScheduleNames,
|
|
2971
3529
|
config: ResolvedBeignetConfig,
|
|
@@ -3029,14 +3587,40 @@ async function updateUseCaseIndex(
|
|
|
3029
3587
|
|
|
3030
3588
|
type FeatureArtifactIndexFile = {
|
|
3031
3589
|
path: string;
|
|
3590
|
+
kind:
|
|
3591
|
+
| "event"
|
|
3592
|
+
| "job"
|
|
3593
|
+
| "factory"
|
|
3594
|
+
| "seed"
|
|
3595
|
+
| "notification"
|
|
3596
|
+
| "listener"
|
|
3597
|
+
| "schedule"
|
|
3598
|
+
| "upload";
|
|
3032
3599
|
importName: string;
|
|
3033
3600
|
artifactPath: string;
|
|
3034
3601
|
registryName: string;
|
|
3602
|
+
registryEntryName: string;
|
|
3035
3603
|
};
|
|
3036
3604
|
|
|
3037
3605
|
function featureArtifactIndexFile(
|
|
3038
|
-
kind:
|
|
3039
|
-
|
|
3606
|
+
kind:
|
|
3607
|
+
| "event"
|
|
3608
|
+
| "job"
|
|
3609
|
+
| "factory"
|
|
3610
|
+
| "seed"
|
|
3611
|
+
| "notification"
|
|
3612
|
+
| "listener"
|
|
3613
|
+
| "schedule"
|
|
3614
|
+
| "upload",
|
|
3615
|
+
names:
|
|
3616
|
+
| EventNames
|
|
3617
|
+
| JobNames
|
|
3618
|
+
| FactoryNames
|
|
3619
|
+
| SeedNames
|
|
3620
|
+
| NotificationNames
|
|
3621
|
+
| ListenerNames
|
|
3622
|
+
| ScheduleNames
|
|
3623
|
+
| UploadNames,
|
|
3040
3624
|
config: ResolvedBeignetConfig,
|
|
3041
3625
|
): FeatureArtifactIndexFile {
|
|
3042
3626
|
const artifactPath =
|
|
@@ -3044,31 +3628,57 @@ function featureArtifactIndexFile(
|
|
|
3044
3628
|
? eventFilePath(names as EventNames, config)
|
|
3045
3629
|
: kind === "job"
|
|
3046
3630
|
? jobFilePath(names as JobNames, config)
|
|
3047
|
-
: kind === "
|
|
3048
|
-
?
|
|
3049
|
-
:
|
|
3631
|
+
: kind === "factory"
|
|
3632
|
+
? factoryFilePath(names as FactoryNames, config)
|
|
3633
|
+
: kind === "seed"
|
|
3634
|
+
? seedFilePath(names as SeedNames, config)
|
|
3635
|
+
: kind === "notification"
|
|
3636
|
+
? notificationFilePath(names as NotificationNames, config)
|
|
3637
|
+
: kind === "listener"
|
|
3638
|
+
? listenerFilePath(names as ListenerNames, config)
|
|
3639
|
+
: kind === "schedule"
|
|
3640
|
+
? scheduleFilePath(names as ScheduleNames, config)
|
|
3641
|
+
: uploadFilePath(names as UploadNames, config);
|
|
3050
3642
|
const registrySuffix =
|
|
3051
3643
|
kind === "event"
|
|
3052
3644
|
? "Events"
|
|
3053
3645
|
: kind === "job"
|
|
3054
3646
|
? "Jobs"
|
|
3055
|
-
: kind === "
|
|
3056
|
-
? "
|
|
3057
|
-
: "
|
|
3647
|
+
: kind === "factory"
|
|
3648
|
+
? "Factories"
|
|
3649
|
+
: kind === "seed"
|
|
3650
|
+
? "Seeds"
|
|
3651
|
+
: kind === "notification"
|
|
3652
|
+
? "Notifications"
|
|
3653
|
+
: kind === "listener"
|
|
3654
|
+
? "Listeners"
|
|
3655
|
+
: kind === "schedule"
|
|
3656
|
+
? "Schedules"
|
|
3657
|
+
: "Uploads";
|
|
3058
3658
|
const importName =
|
|
3059
3659
|
kind === "event"
|
|
3060
3660
|
? (names as EventNames).eventExportName
|
|
3061
3661
|
: kind === "job"
|
|
3062
3662
|
? (names as JobNames).jobExportName
|
|
3063
|
-
: kind === "
|
|
3064
|
-
? (names as
|
|
3065
|
-
:
|
|
3663
|
+
: kind === "factory"
|
|
3664
|
+
? (names as FactoryNames).factoryExportName
|
|
3665
|
+
: kind === "seed"
|
|
3666
|
+
? (names as SeedNames).seedExportName
|
|
3667
|
+
: kind === "notification"
|
|
3668
|
+
? (names as NotificationNames).notificationExportName
|
|
3669
|
+
: kind === "listener"
|
|
3670
|
+
? (names as ListenerNames).listenerExportName
|
|
3671
|
+
: kind === "schedule"
|
|
3672
|
+
? (names as ScheduleNames).scheduleExportName
|
|
3673
|
+
: (names as UploadNames).uploadExportName;
|
|
3066
3674
|
|
|
3067
3675
|
return {
|
|
3068
3676
|
path: path.join(path.dirname(artifactPath), "index.ts"),
|
|
3677
|
+
kind,
|
|
3069
3678
|
importName,
|
|
3070
3679
|
artifactPath,
|
|
3071
3680
|
registryName: `${names.featureSingularCamel}${registrySuffix}`,
|
|
3681
|
+
registryEntryName: names.artifact.camel,
|
|
3072
3682
|
};
|
|
3073
3683
|
}
|
|
3074
3684
|
|
|
@@ -3081,10 +3691,22 @@ async function updateFeatureArtifactIndex(
|
|
|
3081
3691
|
const existing = await readOptionalFile(destination);
|
|
3082
3692
|
const specifier = relativeModule(file.path, file.artifactPath);
|
|
3083
3693
|
const importLine = `import { ${file.importName} } from "${specifier}";`;
|
|
3694
|
+
const defineUploadsImport = `import { defineUploads } from "@beignet/core/uploads";`;
|
|
3084
3695
|
const exportLine = `export { ${file.importName} } from "${specifier}";`;
|
|
3085
3696
|
|
|
3086
3697
|
if (existing === undefined) {
|
|
3087
|
-
const content =
|
|
3698
|
+
const content =
|
|
3699
|
+
file.kind === "upload"
|
|
3700
|
+
? `${defineUploadsImport}
|
|
3701
|
+
${importLine}
|
|
3702
|
+
|
|
3703
|
+
${exportLine}
|
|
3704
|
+
|
|
3705
|
+
export const ${file.registryName} = defineUploads({
|
|
3706
|
+
\t${file.registryEntryName}: ${file.importName},
|
|
3707
|
+
});
|
|
3708
|
+
`
|
|
3709
|
+
: `${importLine}
|
|
3088
3710
|
|
|
3089
3711
|
${exportLine}
|
|
3090
3712
|
|
|
@@ -3114,16 +3736,49 @@ export const ${file.registryName} = [${file.importName}] as const;
|
|
|
3114
3736
|
: `${next.trimEnd()}\n${exportLine}\n`;
|
|
3115
3737
|
}
|
|
3116
3738
|
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3739
|
+
if (file.kind === "upload") {
|
|
3740
|
+
const registry = defineUploadsInitializerInfo(next, file.registryName);
|
|
3741
|
+
const arrayRegistry = arrayInitializerInfo(next, file.registryName);
|
|
3742
|
+
if (registry) {
|
|
3743
|
+
if (!next.includes(defineUploadsImport)) {
|
|
3744
|
+
next = insertAfterImports(next, defineUploadsImport);
|
|
3745
|
+
}
|
|
3746
|
+
if (!registry.text.includes(`${file.registryEntryName}:`)) {
|
|
3747
|
+
next = `${next.slice(0, registry.end)}\t${file.registryEntryName}: ${file.importName},\n${next.slice(
|
|
3748
|
+
registry.end,
|
|
3749
|
+
)}`;
|
|
3750
|
+
}
|
|
3751
|
+
} else if (arrayRegistry) {
|
|
3752
|
+
if (
|
|
3753
|
+
!identifiersFromArrayExpression(arrayRegistry.text).has(file.importName)
|
|
3754
|
+
) {
|
|
3755
|
+
const nextArray = appendToArrayExpression(arrayRegistry.text, [
|
|
3756
|
+
file.importName,
|
|
3757
|
+
]);
|
|
3758
|
+
next = `${next.slice(0, arrayRegistry.start)}${nextArray}${next.slice(
|
|
3759
|
+
arrayRegistry.end,
|
|
3760
|
+
)}`;
|
|
3761
|
+
}
|
|
3762
|
+
} else {
|
|
3763
|
+
if (!next.includes(defineUploadsImport)) {
|
|
3764
|
+
next = insertAfterImports(next, defineUploadsImport);
|
|
3765
|
+
}
|
|
3766
|
+
next = `${next.trimEnd()}\n\nexport const ${file.registryName} = defineUploads({\n\t${file.registryEntryName}: ${file.importName},\n});\n`;
|
|
3767
|
+
}
|
|
3768
|
+
} else {
|
|
3769
|
+
const registry = arrayInitializerInfo(next, file.registryName);
|
|
3770
|
+
if (!registry) {
|
|
3771
|
+
next = `${next.trimEnd()}\n\nexport const ${file.registryName} = [${file.importName}] as const;\n`;
|
|
3772
|
+
} else if (
|
|
3773
|
+
!identifiersFromArrayExpression(registry.text).has(file.importName)
|
|
3774
|
+
) {
|
|
3775
|
+
const nextArray = appendToArrayExpression(registry.text, [
|
|
3776
|
+
file.importName,
|
|
3777
|
+
]);
|
|
3778
|
+
next = `${next.slice(0, registry.start)}${nextArray}${next.slice(
|
|
3779
|
+
registry.end,
|
|
3780
|
+
)}`;
|
|
3781
|
+
}
|
|
3127
3782
|
}
|
|
3128
3783
|
|
|
3129
3784
|
if (next === existing) return "skipped";
|
|
@@ -3151,6 +3806,28 @@ function arrayInitializerInfo(
|
|
|
3151
3806
|
};
|
|
3152
3807
|
}
|
|
3153
3808
|
|
|
3809
|
+
function defineUploadsInitializerInfo(
|
|
3810
|
+
source: string,
|
|
3811
|
+
constName: string,
|
|
3812
|
+
): { text: string; start: number; end: number } | undefined {
|
|
3813
|
+
const match = new RegExp(
|
|
3814
|
+
`\\bconst\\s+${constName}\\s*=\\s*defineUploads\\s*\\(`,
|
|
3815
|
+
).exec(source);
|
|
3816
|
+
if (!match) return undefined;
|
|
3817
|
+
|
|
3818
|
+
const openBrace = source.indexOf("{", match.index);
|
|
3819
|
+
if (openBrace === -1) return undefined;
|
|
3820
|
+
|
|
3821
|
+
const closeBrace = matchingDelimiterIndex(source, openBrace, "{", "}");
|
|
3822
|
+
if (closeBrace === -1) return undefined;
|
|
3823
|
+
|
|
3824
|
+
return {
|
|
3825
|
+
text: source.slice(openBrace, closeBrace + 1),
|
|
3826
|
+
start: openBrace,
|
|
3827
|
+
end: closeBrace,
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3154
3831
|
function schemasFile(names: ResourceNames): string {
|
|
3155
3832
|
return `import { z } from "zod";
|
|
3156
3833
|
|
|
@@ -3166,10 +3843,14 @@ export const List${names.pluralPascal}InputSchema = z.object({
|
|
|
3166
3843
|
});
|
|
3167
3844
|
|
|
3168
3845
|
export const List${names.pluralPascal}OutputSchema = z.object({
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3846
|
+
items: z.array(${names.singularPascal}Schema),
|
|
3847
|
+
page: z.object({
|
|
3848
|
+
kind: z.literal("offset"),
|
|
3849
|
+
limit: z.number().int().min(1),
|
|
3850
|
+
offset: z.number().int().min(0),
|
|
3851
|
+
total: z.number().int().min(0),
|
|
3852
|
+
hasMore: z.boolean(),
|
|
3853
|
+
}),
|
|
3173
3854
|
});
|
|
3174
3855
|
|
|
3175
3856
|
export const Create${names.singularPascal}InputSchema = z.object({
|
|
@@ -3195,7 +3876,8 @@ function listUseCaseFile(
|
|
|
3195
3876
|
`list-${names.pluralKebab}.ts`,
|
|
3196
3877
|
);
|
|
3197
3878
|
|
|
3198
|
-
return `import {
|
|
3879
|
+
return `import { normalizeOffsetPage } from "@beignet/core/pagination";
|
|
3880
|
+
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
3199
3881
|
import {
|
|
3200
3882
|
List${names.pluralPascal}InputSchema,
|
|
3201
3883
|
List${names.pluralPascal}OutputSchema,
|
|
@@ -3206,13 +3888,12 @@ export const list${names.pluralPascal}UseCase = useCase
|
|
|
3206
3888
|
.input(List${names.pluralPascal}InputSchema)
|
|
3207
3889
|
.output(List${names.pluralPascal}OutputSchema)
|
|
3208
3890
|
.run(async ({ ctx, input }) => {
|
|
3209
|
-
const
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
};
|
|
3891
|
+
const page = normalizeOffsetPage(input, {
|
|
3892
|
+
defaultLimit: 20,
|
|
3893
|
+
maxLimit: 100,
|
|
3894
|
+
});
|
|
3895
|
+
|
|
3896
|
+
return ctx.ports.${names.pluralCamel}.list(page);
|
|
3216
3897
|
});
|
|
3217
3898
|
`;
|
|
3218
3899
|
}
|
|
@@ -3260,18 +3941,19 @@ function repositoryPortFile(
|
|
|
3260
3941
|
config: ResolvedBeignetConfig,
|
|
3261
3942
|
): string {
|
|
3262
3943
|
return `import type {
|
|
3944
|
+
OffsetPage,
|
|
3945
|
+
OffsetPageInfo,
|
|
3946
|
+
PageResult,
|
|
3947
|
+
} from "@beignet/core/pagination";
|
|
3948
|
+
import type {
|
|
3263
3949
|
Create${names.singularPascal}Input,
|
|
3264
|
-
List${names.pluralPascal}Input,
|
|
3265
3950
|
${names.singularPascal},
|
|
3266
3951
|
} from "${aliasModule(path.join(resourceUseCaseDir(names, config), "schemas.ts"))}";
|
|
3267
3952
|
|
|
3268
|
-
export type List${names.pluralPascal}Result = {
|
|
3269
|
-
${names.pluralCamel}: ${names.singularPascal}[];
|
|
3270
|
-
total: number;
|
|
3271
|
-
};
|
|
3953
|
+
export type List${names.pluralPascal}Result = PageResult<${names.singularPascal}, OffsetPageInfo>;
|
|
3272
3954
|
|
|
3273
3955
|
export interface ${names.singularPascal}Repository {
|
|
3274
|
-
list(
|
|
3956
|
+
list(page: OffsetPage): Promise<List${names.pluralPascal}Result>;
|
|
3275
3957
|
create(input: Create${names.singularPascal}Input): Promise<${names.singularPascal}>;
|
|
3276
3958
|
}
|
|
3277
3959
|
`;
|
|
@@ -3283,10 +3965,10 @@ function inMemoryRepositoryFile(
|
|
|
3283
3965
|
): string {
|
|
3284
3966
|
const repositoryPortPath = resourcePortFilePath(names, config);
|
|
3285
3967
|
|
|
3286
|
-
return `import
|
|
3968
|
+
return `import { offsetPageResult } from "@beignet/core/pagination";
|
|
3969
|
+
import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
|
|
3287
3970
|
import type {
|
|
3288
3971
|
Create${names.singularPascal}Input,
|
|
3289
|
-
List${names.pluralPascal}Input,
|
|
3290
3972
|
${names.singularPascal},
|
|
3291
3973
|
} from "${aliasModule(path.join(resourceUseCaseDir(names, config), "schemas.ts"))}";
|
|
3292
3974
|
|
|
@@ -3296,18 +3978,16 @@ export function createInMemory${names.singularPascal}Repository(
|
|
|
3296
3978
|
const ${names.pluralCamel} = new Map(seed.map((${names.singularCamel}) => [${names.singularCamel}.id, ${names.singularCamel}]));
|
|
3297
3979
|
|
|
3298
3980
|
return {
|
|
3299
|
-
async list(
|
|
3981
|
+
async list(page) {
|
|
3300
3982
|
const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values()).sort(
|
|
3301
3983
|
(left, right) => right.createdAt.localeCompare(left.createdAt),
|
|
3302
3984
|
);
|
|
3303
3985
|
|
|
3304
|
-
return
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
total: all${names.pluralPascal}.length,
|
|
3310
|
-
};
|
|
3986
|
+
return offsetPageResult(
|
|
3987
|
+
all${names.pluralPascal}.slice(page.offset, page.offset + page.limit),
|
|
3988
|
+
page,
|
|
3989
|
+
all${names.pluralPascal}.length,
|
|
3990
|
+
);
|
|
3311
3991
|
},
|
|
3312
3992
|
async create(input: Create${names.singularPascal}Input) {
|
|
3313
3993
|
const ${names.singularCamel}: ${names.singularPascal} = {
|
|
@@ -3341,12 +4021,12 @@ function drizzleRepositoryFile(
|
|
|
3341
4021
|
const repositoryPortPath = resourcePortFilePath(names, config);
|
|
3342
4022
|
const schemaPath = drizzleSchemaIndexPath(config);
|
|
3343
4023
|
|
|
3344
|
-
return `import
|
|
4024
|
+
return `import { offsetPageResult } from "@beignet/core/pagination";
|
|
4025
|
+
import type { DrizzleTursoDatabase } from "@beignet/provider-drizzle-turso";
|
|
3345
4026
|
import { count, desc } from "drizzle-orm";
|
|
3346
4027
|
import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
|
|
3347
4028
|
import type {
|
|
3348
4029
|
Create${names.singularPascal}Input,
|
|
3349
|
-
List${names.pluralPascal}Input,
|
|
3350
4030
|
${names.singularPascal},
|
|
3351
4031
|
} from "${aliasModule(path.join(resourceUseCaseDir(names, config), "schemas.ts"))}";
|
|
3352
4032
|
import * as schema from "${aliasModule(schemaPath)}";
|
|
@@ -3365,21 +4045,18 @@ export function createDrizzle${names.singularPascal}Repository(
|
|
|
3365
4045
|
db: DrizzleTursoDatabase<typeof schema>,
|
|
3366
4046
|
): ${names.singularPascal}Repository {
|
|
3367
4047
|
return {
|
|
3368
|
-
async list(
|
|
4048
|
+
async list(page) {
|
|
3369
4049
|
const rows = await db
|
|
3370
4050
|
.select()
|
|
3371
4051
|
.from(schema.${names.pluralCamel})
|
|
3372
4052
|
.orderBy(desc(schema.${names.pluralCamel}.createdAt))
|
|
3373
|
-
.limit(
|
|
3374
|
-
.offset(
|
|
4053
|
+
.limit(page.limit)
|
|
4054
|
+
.offset(page.offset);
|
|
3375
4055
|
const [{ total }] = await db
|
|
3376
4056
|
.select({ total: count() })
|
|
3377
4057
|
.from(schema.${names.pluralCamel});
|
|
3378
4058
|
|
|
3379
|
-
return {
|
|
3380
|
-
${names.pluralCamel}: rows.map(to${names.singularPascal}),
|
|
3381
|
-
total,
|
|
3382
|
-
};
|
|
4059
|
+
return offsetPageResult(rows.map(to${names.singularPascal}), page, total);
|
|
3383
4060
|
},
|
|
3384
4061
|
async create(input: Create${names.singularPascal}Input) {
|
|
3385
4062
|
const [row] = await db
|
|
@@ -3472,10 +4149,14 @@ export const list${names.pluralPascal} = ${names.pluralCamel}
|
|
|
3472
4149
|
)
|
|
3473
4150
|
.responses({
|
|
3474
4151
|
200: z.object({
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
4152
|
+
items: z.array(${names.singularCamel}Schema),
|
|
4153
|
+
page: z.object({
|
|
4154
|
+
kind: z.literal("offset"),
|
|
4155
|
+
limit: z.number().int().min(1),
|
|
4156
|
+
offset: z.number().int().min(0),
|
|
4157
|
+
total: z.number().int().min(0),
|
|
4158
|
+
hasMore: z.boolean(),
|
|
4159
|
+
}),
|
|
3479
4160
|
}),
|
|
3480
4161
|
});
|
|
3481
4162
|
|
|
@@ -3532,7 +4213,7 @@ describe("${names.exportName}", () => {
|
|
|
3532
4213
|
...appPorts,
|
|
3533
4214
|
uow: createNoopUnitOfWork(
|
|
3534
4215
|
() => appPorts as unknown as AppContext["ports"],
|
|
3535
|
-
) as AppContext["ports"]["uow"],
|
|
4216
|
+
) as unknown as AppContext["ports"]["uow"],
|
|
3536
4217
|
devtools: createInMemoryDevtools(),
|
|
3537
4218
|
storage: createMemoryStorage(),
|
|
3538
4219
|
} as AppContext["ports"];
|
|
@@ -3626,7 +4307,7 @@ export const ${names.eventExportName} = defineEvent("${names.eventName}", {
|
|
|
3626
4307
|
}
|
|
3627
4308
|
|
|
3628
4309
|
function jobFile(names: JobNames, config: ResolvedBeignetConfig): string {
|
|
3629
|
-
return `import { createJobHandlers } from "@beignet/core/jobs";
|
|
4310
|
+
return `import { createJobHandlers, retry } from "@beignet/core/jobs";
|
|
3630
4311
|
import { z } from "zod";
|
|
3631
4312
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
3632
4313
|
|
|
@@ -3640,9 +4321,11 @@ export type ${names.payloadTypeName} = z.infer<typeof ${names.payloadSchemaName}
|
|
|
3640
4321
|
|
|
3641
4322
|
export const ${names.jobExportName} = jobs.defineJob("${names.jobName}", {
|
|
3642
4323
|
\tpayload: ${names.payloadSchemaName},
|
|
3643
|
-
\tretry: {
|
|
4324
|
+
\tretry: retry.exponential({
|
|
3644
4325
|
\t\tattempts: 3,
|
|
3645
|
-
\t
|
|
4326
|
+
\t\tinitialDelay: "1s",
|
|
4327
|
+
\t\tmaxDelay: "1m",
|
|
4328
|
+
\t}),
|
|
3646
4329
|
\tasync handle({ payload, ctx }) {
|
|
3647
4330
|
\t\tctx.ports.logger.info("Job handled", {
|
|
3648
4331
|
\t\t\tjobName: "${names.jobName}",
|
|
@@ -3653,6 +4336,78 @@ export const ${names.jobExportName} = jobs.defineJob("${names.jobName}", {
|
|
|
3653
4336
|
`;
|
|
3654
4337
|
}
|
|
3655
4338
|
|
|
4339
|
+
function factoryFile(
|
|
4340
|
+
names: FactoryNames,
|
|
4341
|
+
config: ResolvedBeignetConfig,
|
|
4342
|
+
): string {
|
|
4343
|
+
const resource = featureResourceNames(names);
|
|
4344
|
+
|
|
4345
|
+
return `import { defineFactory } from "@beignet/core/testing";
|
|
4346
|
+
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
4347
|
+
|
|
4348
|
+
export const ${names.factoryExportName} = defineFactory("${names.factoryName}", {
|
|
4349
|
+
\tdefaults: ({ sequence }) => ({
|
|
4350
|
+
\t\tname: "${resource.singularPascal} " + sequence,
|
|
4351
|
+
\t}),
|
|
4352
|
+
\tpersist: (ctx: AppContext, input) => ctx.ports.${resource.pluralCamel}.create(input),
|
|
4353
|
+
});
|
|
4354
|
+
`;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
function seedFile(names: SeedNames, config: ResolvedBeignetConfig): string {
|
|
4358
|
+
const resource = featureResourceNames(names);
|
|
4359
|
+
|
|
4360
|
+
return `import { defineSeed } from "@beignet/core/testing";
|
|
4361
|
+
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
4362
|
+
|
|
4363
|
+
export const ${names.seedExportName} = defineSeed("${names.seedName}", {
|
|
4364
|
+
\tdescription: "Creates starter ${names.feature.kebab} data.",
|
|
4365
|
+
\trun: async (ctx: AppContext) => {
|
|
4366
|
+
\t\tawait ctx.ports.${resource.pluralCamel}.create({
|
|
4367
|
+
\t\t\tname: "Demo ${resource.singularPascal}",
|
|
4368
|
+
\t\t});
|
|
4369
|
+
\t},
|
|
4370
|
+
});
|
|
4371
|
+
`;
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
function notificationFile(
|
|
4375
|
+
names: NotificationNames,
|
|
4376
|
+
config: ResolvedBeignetConfig,
|
|
4377
|
+
): string {
|
|
4378
|
+
return `import {
|
|
4379
|
+
\tcreateNotificationHandlers,
|
|
4380
|
+
\tdefineMailNotificationChannel,
|
|
4381
|
+
} from "@beignet/core/notifications";
|
|
4382
|
+
import { z } from "zod";
|
|
4383
|
+
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
4384
|
+
|
|
4385
|
+
const notifications = createNotificationHandlers<AppContext>();
|
|
4386
|
+
|
|
4387
|
+
export const ${names.payloadSchemaName} = z.object({
|
|
4388
|
+
\tid: z.string().uuid(),
|
|
4389
|
+
\tsubject: z.string(),
|
|
4390
|
+
\trecipientEmail: z.string().email(),
|
|
4391
|
+
});
|
|
4392
|
+
|
|
4393
|
+
export type ${names.payloadTypeName} = z.infer<typeof ${names.payloadSchemaName}>;
|
|
4394
|
+
|
|
4395
|
+
export const ${names.notificationExportName} = notifications.defineNotification(
|
|
4396
|
+
\t"${names.notificationName}",
|
|
4397
|
+
\t{
|
|
4398
|
+
\t\tpayload: ${names.payloadSchemaName},
|
|
4399
|
+
\t\tchannels: {
|
|
4400
|
+
\t\t\temail: defineMailNotificationChannel(({ payload }) => ({
|
|
4401
|
+
\t\t\t\tto: payload.recipientEmail,
|
|
4402
|
+
\t\t\t\tsubject: payload.subject,
|
|
4403
|
+
\t\t\t\ttext: \`${names.notificationName} notification for \${payload.id}.\`,
|
|
4404
|
+
\t\t\t})),
|
|
4405
|
+
\t\t},
|
|
4406
|
+
\t},
|
|
4407
|
+
);
|
|
4408
|
+
`;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
3656
4411
|
function listenerFile(
|
|
3657
4412
|
names: ListenerNames,
|
|
3658
4413
|
config: ResolvedBeignetConfig,
|
|
@@ -3716,6 +4471,61 @@ ${names.timezone ? `\t\ttimezone: "${names.timezone}",\n` : ""}\t\tpayload: ${na
|
|
|
3716
4471
|
`;
|
|
3717
4472
|
}
|
|
3718
4473
|
|
|
4474
|
+
function uploadFile(names: UploadNames, config: ResolvedBeignetConfig): string {
|
|
4475
|
+
return `import { defineUpload } from "@beignet/core/uploads";
|
|
4476
|
+
import { z } from "zod";
|
|
4477
|
+
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
4478
|
+
|
|
4479
|
+
export const ${names.metadataSchemaName} = z.object({
|
|
4480
|
+
\tresourceId: z.string().min(1),
|
|
4481
|
+
});
|
|
4482
|
+
|
|
4483
|
+
export type ${names.metadataTypeName} = z.infer<typeof ${names.metadataSchemaName}>;
|
|
4484
|
+
|
|
4485
|
+
export const ${names.uploadExportName} = defineUpload<
|
|
4486
|
+
\t"${names.uploadName}",
|
|
4487
|
+
\ttypeof ${names.metadataSchemaName},
|
|
4488
|
+
\tAppContext,
|
|
4489
|
+
\t{ objectKeys: string[] }
|
|
4490
|
+
>("${names.uploadName}", {
|
|
4491
|
+
\tmetadata: ${names.metadataSchemaName},
|
|
4492
|
+
\tfile: {
|
|
4493
|
+
\t\tcontentTypes: ["application/pdf", "text/plain"],
|
|
4494
|
+
\t\tmaxSizeBytes: 5 * 1024 * 1024,
|
|
4495
|
+
\t\tmaxFiles: 1,
|
|
4496
|
+
\t\tvisibility: "private",
|
|
4497
|
+
\t\tcacheControl: "private, max-age=0",
|
|
4498
|
+
\t},
|
|
4499
|
+
\tauthorize({ ctx }) {
|
|
4500
|
+
\t\treturn ctx.actor.type === "user";
|
|
4501
|
+
\t},
|
|
4502
|
+
\tkey({ ctx, metadata, uploadId, file }) {
|
|
4503
|
+
\t\tconst tenantId = ctx.tenant?.id ?? "default";
|
|
4504
|
+
\t\tconst extension = file.name.includes(".") ? file.name.split(".").pop() : undefined;
|
|
4505
|
+
\t\tconst suffix = extension ? \`.\${extension}\` : "";
|
|
4506
|
+
\t\treturn "${names.feature.kebab}/" + tenantId + "/" + metadata.resourceId + "/" + uploadId + suffix;
|
|
4507
|
+
\t},
|
|
4508
|
+
\tstorageMetadata({ ctx, metadata }) {
|
|
4509
|
+
\t\treturn {
|
|
4510
|
+
\t\t\ttenantId: ctx.tenant?.id ?? "default",
|
|
4511
|
+
\t\t\tresourceId: metadata.resourceId,
|
|
4512
|
+
\t\t};
|
|
4513
|
+
\t},
|
|
4514
|
+
\tasync onComplete({ ctx, metadata, files }) {
|
|
4515
|
+
\t\tctx.ports.logger.info("Upload completed", {
|
|
4516
|
+
\t\t\tuploadName: "${names.uploadName}",
|
|
4517
|
+
\t\t\tresourceId: metadata.resourceId,
|
|
4518
|
+
\t\t\tobjectKeys: files.map((file) => file.key),
|
|
4519
|
+
\t\t});
|
|
4520
|
+
|
|
4521
|
+
\t\treturn {
|
|
4522
|
+
\t\t\tobjectKeys: files.map((file) => file.key),
|
|
4523
|
+
\t\t};
|
|
4524
|
+
\t},
|
|
4525
|
+
});
|
|
4526
|
+
`;
|
|
4527
|
+
}
|
|
4528
|
+
|
|
3719
4529
|
function scheduleRouteFile(
|
|
3720
4530
|
names: ScheduleNames,
|
|
3721
4531
|
config: ResolvedBeignetConfig,
|
|
@@ -3878,11 +4688,15 @@ function testFile(names: ResourceNames, config: ResolvedBeignetConfig): string {
|
|
|
3878
4688
|
|
|
3879
4689
|
return `import { describe, expect, it } from "bun:test";
|
|
3880
4690
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
4691
|
+
import { defineRoutes } from "@beignet/web";
|
|
4692
|
+
import { createTestApp } from "@beignet/web/testing";
|
|
3881
4693
|
import { createInMemoryDevtools } from "@beignet/devtools";
|
|
3882
4694
|
import { createAnonymousActor, createMemoryStorage, createNoopUnitOfWork } from "@beignet/core/ports";
|
|
3883
4695
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
3884
4696
|
import { appPorts } from "${aliasModule(config.paths.infrastructurePorts)}";
|
|
4697
|
+
import { create${names.singularPascal}, list${names.pluralPascal} } from "${aliasModule(resourceContractFilePath(names, config))}";
|
|
3885
4698
|
import { createInMemory${names.singularPascal}Repository } from "${aliasModule(repositoryPath)}";
|
|
4699
|
+
import { ${names.singularCamel}Routes } from "${aliasModule(path.join(resourceFeatureDir(names, config), "routes.ts"))}";
|
|
3886
4700
|
import {
|
|
3887
4701
|
create${names.singularPascal}UseCase,
|
|
3888
4702
|
list${names.pluralPascal}UseCase,
|
|
@@ -3897,18 +4711,19 @@ describe("${names.pluralCamel} resource", () => {
|
|
|
3897
4711
|
uow: createNoopUnitOfWork(() => ({
|
|
3898
4712
|
...(appPorts as unknown as AppContext["ports"]),
|
|
3899
4713
|
${names.pluralCamel},
|
|
3900
|
-
})) as AppContext["ports"]["uow"],
|
|
4714
|
+
})) as unknown as AppContext["ports"]["uow"],
|
|
3901
4715
|
devtools: createInMemoryDevtools(),
|
|
3902
4716
|
storage: createMemoryStorage(),
|
|
3903
4717
|
} as AppContext["ports"];
|
|
3904
4718
|
const actor = createAnonymousActor();
|
|
3905
|
-
const
|
|
4719
|
+
const createTestContext = () => ({
|
|
3906
4720
|
requestId: "test-request",
|
|
3907
4721
|
actor,
|
|
3908
4722
|
auth: null,
|
|
3909
4723
|
gate: testPorts.gate.bind({ actor, auth: null }),
|
|
3910
4724
|
ports: testPorts,
|
|
3911
|
-
})
|
|
4725
|
+
});
|
|
4726
|
+
const tester = createUseCaseTester<AppContext>(createTestContext);
|
|
3912
4727
|
|
|
3913
4728
|
const ctx = await tester.ctx();
|
|
3914
4729
|
const created = await tester.run(
|
|
@@ -3923,8 +4738,30 @@ describe("${names.pluralCamel} resource", () => {
|
|
|
3923
4738
|
);
|
|
3924
4739
|
|
|
3925
4740
|
expect(created.name).toBe("First ${names.singularPascal}");
|
|
3926
|
-
expect(result.total).toBe(1);
|
|
3927
|
-
expect(result
|
|
4741
|
+
expect(result.page.total).toBe(1);
|
|
4742
|
+
expect(result.items).toEqual([created]);
|
|
4743
|
+
|
|
4744
|
+
const app = await createTestApp<AppContext, AppContext["ports"]>({
|
|
4745
|
+
ports: testPorts,
|
|
4746
|
+
routes: defineRoutes<AppContext>([${names.singularCamel}Routes]),
|
|
4747
|
+
createContext: async () => createTestContext(),
|
|
4748
|
+
mapUnhandledError: () => ({
|
|
4749
|
+
status: 500,
|
|
4750
|
+
body: { code: "INTERNAL_SERVER_ERROR", message: "Internal server error" },
|
|
4751
|
+
}),
|
|
4752
|
+
});
|
|
4753
|
+
|
|
4754
|
+
const createdViaRoute = await app.request(create${names.singularPascal}, {
|
|
4755
|
+
body: { name: "Second ${names.singularPascal}" },
|
|
4756
|
+
});
|
|
4757
|
+
const listedViaRoute = await app.request(list${names.pluralPascal}, {
|
|
4758
|
+
query: { limit: 20, offset: 0 },
|
|
4759
|
+
});
|
|
4760
|
+
await app.stop();
|
|
4761
|
+
|
|
4762
|
+
expect(createdViaRoute.name).toBe("Second ${names.singularPascal}");
|
|
4763
|
+
expect(listedViaRoute.page.total).toBe(2);
|
|
4764
|
+
expect(listedViaRoute.items).toContainEqual(createdViaRoute);
|
|
3928
4765
|
});
|
|
3929
4766
|
});
|
|
3930
4767
|
`;
|