@spfn/core 0.3.0-beta.28 → 0.3.0-beta.29
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/codegen/index.d.ts +65 -2
- package/dist/codegen/index.js +160 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/{error-TsUIjp_x.d.ts → error-CIZlYJlL.d.ts} +1 -1
- package/dist/ops/index.d.ts +3 -3
- package/dist/ops/index.js +15 -5
- package/dist/ops/index.js.map +1 -1
- package/dist/route/index.d.ts +2 -2
- package/dist/route/index.js +15 -5
- package/dist/route/index.js.map +1 -1
- package/dist/{router-4YoIy5fN.d.ts → router-C57y-vz1.d.ts} +12 -0
- package/dist/server/index.js +15 -5
- package/dist/server/index.js.map +1 -1
- package/dist/state/index.js +8 -3
- package/dist/state/index.js.map +1 -1
- package/dist/surface/index.d.ts +3 -3
- package/dist/surface/index.js +15 -5
- package/dist/surface/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/README.md +89 -2
- package/src/state/README.md +67 -0
package/dist/codegen/index.d.ts
CHANGED
|
@@ -429,6 +429,64 @@ declare class ContractGeneratorError extends Error {
|
|
|
429
429
|
constructor(message: string);
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* States Generator
|
|
434
|
+
*
|
|
435
|
+
* Generates a file naming the states the app declared — their ids and their
|
|
436
|
+
* manifest — as values a browser root or a server component can execute.
|
|
437
|
+
*
|
|
438
|
+
* `defineStateRouter` declares the states, and the browser reads them through
|
|
439
|
+
* `createStateClient<typeof states>`: a type import, erased at build. So nothing
|
|
440
|
+
* that runs in the browser says which states the app declared, and importing
|
|
441
|
+
* the router to find out is not an option — each state's `read()` closes over
|
|
442
|
+
* repositories and the database. A root that compares the declared ids with the
|
|
443
|
+
* `manifest` of `GET /state` can report a deploy skew between the Next.js server
|
|
444
|
+
* and the API before rendering, instead of a screen meeting `state.<id>` as
|
|
445
|
+
* `undefined`.
|
|
446
|
+
*
|
|
447
|
+
* The router is loaded and walked, as the route map's is. A state router is told
|
|
448
|
+
* apart by the `_stateManifest` marker `defineStateRouter` sets on the router it
|
|
449
|
+
* derives — the object the snapshot route answers as `manifest` — so the ids
|
|
450
|
+
* written here and the manifest the server serves come from one value.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* // .spfnrc.ts
|
|
455
|
+
* import { defineConfig, defineGenerator } from '@spfn/core/codegen';
|
|
456
|
+
*
|
|
457
|
+
* export default defineConfig({
|
|
458
|
+
* generators: [
|
|
459
|
+
* defineGenerator({
|
|
460
|
+
* name: '@spfn/core:states',
|
|
461
|
+
* routerPath: './src/server/router.ts',
|
|
462
|
+
* outputPath: './src/generated/states.ts',
|
|
463
|
+
* })
|
|
464
|
+
* ]
|
|
465
|
+
* });
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
interface StatesGeneratorConfig {
|
|
470
|
+
/**
|
|
471
|
+
* Generator name (required for package-based loading)
|
|
472
|
+
*/
|
|
473
|
+
name: '@spfn/core:states';
|
|
474
|
+
/**
|
|
475
|
+
* Path to the router file (relative to project root) — the one the route map reads
|
|
476
|
+
* @example './src/server/router.ts'
|
|
477
|
+
*/
|
|
478
|
+
routerPath: string;
|
|
479
|
+
/**
|
|
480
|
+
* Output path for the generated states file (relative to project root)
|
|
481
|
+
* @default './src/generated/states.ts'
|
|
482
|
+
*/
|
|
483
|
+
outputPath?: string;
|
|
484
|
+
}
|
|
485
|
+
/** Thrown when the router cannot produce the app's declared states. */
|
|
486
|
+
declare class StatesGeneratorError extends Error {
|
|
487
|
+
constructor(message: string);
|
|
488
|
+
}
|
|
489
|
+
|
|
432
490
|
/**
|
|
433
491
|
* Router registration guard
|
|
434
492
|
*
|
|
@@ -499,7 +557,7 @@ declare function assertUnconditionalRegistration(scan: RegistrationScan): void;
|
|
|
499
557
|
* ```typescript
|
|
500
558
|
* // .spfnrc.ts
|
|
501
559
|
* import { defineConfig, defineGenerator } from '@spfn/core/codegen';
|
|
502
|
-
* import type { RouteMapGeneratorConfig, ContractGeneratorConfig } from '@spfn/core/codegen';
|
|
560
|
+
* import type { RouteMapGeneratorConfig, ContractGeneratorConfig, StatesGeneratorConfig } from '@spfn/core/codegen';
|
|
503
561
|
*
|
|
504
562
|
* export default defineConfig({
|
|
505
563
|
* generators: [
|
|
@@ -513,6 +571,11 @@ declare function assertUnconditionalRegistration(scan: RegistrationScan): void;
|
|
|
513
571
|
* routerPath: './src/server/router.ts',
|
|
514
572
|
* outputDir: './contracts',
|
|
515
573
|
* }),
|
|
574
|
+
* defineGenerator<StatesGeneratorConfig>({
|
|
575
|
+
* name: '@spfn/core:states',
|
|
576
|
+
* routerPath: './src/server/router.ts',
|
|
577
|
+
* outputPath: './src/generated/states.ts',
|
|
578
|
+
* }),
|
|
516
579
|
* ]
|
|
517
580
|
* });
|
|
518
581
|
* ```
|
|
@@ -525,4 +588,4 @@ declare function assertUnconditionalRegistration(scan: RegistrationScan): void;
|
|
|
525
588
|
*/
|
|
526
589
|
declare const generators: Record<string, unknown>;
|
|
527
590
|
|
|
528
|
-
export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, ConditionalRegistrationError, type ContractGeneratorConfig, ContractGeneratorError, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type RegistrationScan, type ResourceRoutes, type RouteContractMapping, type RouteMapGeneratorConfig, RouteMapGeneratorError, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
|
|
591
|
+
export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, ConditionalRegistrationError, type ContractGeneratorConfig, ContractGeneratorError, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type RegistrationScan, type ResourceRoutes, type RouteContractMapping, type RouteMapGeneratorConfig, RouteMapGeneratorError, type StatesGeneratorConfig, StatesGeneratorError, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
|
package/dist/codegen/index.js
CHANGED
|
@@ -497,6 +497,21 @@ function conditionKind(compiler2, expression) {
|
|
|
497
497
|
function spreadText(file, expression) {
|
|
498
498
|
return expression.getText(file).replace(/\s+/g, " ");
|
|
499
499
|
}
|
|
500
|
+
|
|
501
|
+
// src/codegen/generators/emit.ts
|
|
502
|
+
function toObjectKey(name) {
|
|
503
|
+
if (name === "__proto__") {
|
|
504
|
+
return `[${JSON.stringify(name)}]`;
|
|
505
|
+
}
|
|
506
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
507
|
+
}
|
|
508
|
+
var SINGLE_QUOTABLE = /^[\x20-\x26\x28-\x5B\x5D-\x7E]*$/;
|
|
509
|
+
function toStringLiteral(value) {
|
|
510
|
+
if (SINGLE_QUOTABLE.test(value)) {
|
|
511
|
+
return `'${value}'`;
|
|
512
|
+
}
|
|
513
|
+
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
514
|
+
}
|
|
500
515
|
var loadLogger = logger.child("@spfn/core:codegen");
|
|
501
516
|
function pinNodeEnv() {
|
|
502
517
|
if (process.env.NODE_ENV) {
|
|
@@ -833,19 +848,6 @@ function collectRouteMap(router) {
|
|
|
833
848
|
assertNoPackageCollision(collection);
|
|
834
849
|
return collection;
|
|
835
850
|
}
|
|
836
|
-
function toObjectKey(name) {
|
|
837
|
-
if (name === "__proto__") {
|
|
838
|
-
return `[${JSON.stringify(name)}]`;
|
|
839
|
-
}
|
|
840
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
841
|
-
}
|
|
842
|
-
var SINGLE_QUOTABLE = /^[\x20-\x26\x28-\x5B\x5D-\x7E]*$/;
|
|
843
|
-
function toStringLiteral(value) {
|
|
844
|
-
if (SINGLE_QUOTABLE.test(value)) {
|
|
845
|
-
return `'${value}'`;
|
|
846
|
-
}
|
|
847
|
-
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
848
|
-
}
|
|
849
851
|
function describeMount(mount) {
|
|
850
852
|
if (mount.count === 0) {
|
|
851
853
|
return `${mount.label} \u2014 no routes${mount.skipped ? ` (${mount.skipped})` : ""}`;
|
|
@@ -1114,13 +1116,156 @@ A released client cannot be fixed by redeploying the server. Keep the promise, o
|
|
|
1114
1116
|
}
|
|
1115
1117
|
};
|
|
1116
1118
|
}
|
|
1119
|
+
var genLogger3 = logger.child("@spfn/core:states-generator");
|
|
1120
|
+
var StatesGeneratorError = class extends Error {
|
|
1121
|
+
constructor(message) {
|
|
1122
|
+
super(message);
|
|
1123
|
+
this.name = "StatesGeneratorError";
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var ROUTER_EXPORTS2 = ["appRouter", "default", "router"];
|
|
1127
|
+
function loadRouter3(cwd, absoluteRouterPath) {
|
|
1128
|
+
const module = loadRouterModule({
|
|
1129
|
+
cwd,
|
|
1130
|
+
absoluteRouterPath,
|
|
1131
|
+
subject: "state manifest",
|
|
1132
|
+
fail: (message) => new StatesGeneratorError(message)
|
|
1133
|
+
});
|
|
1134
|
+
const resolved = resolveRouterExport(module, ROUTER_EXPORTS2);
|
|
1135
|
+
if (resolved) {
|
|
1136
|
+
return resolved;
|
|
1137
|
+
}
|
|
1138
|
+
throw new StatesGeneratorError(
|
|
1139
|
+
`No router found in ${relative(cwd, absoluteRouterPath)}. Looked for: ${ROUTER_EXPORTS2.join(", ")}. Export the defineRouter() result under one of those names.`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
function collectStates(router, trail, visited, into) {
|
|
1143
|
+
if (visited.has(router)) {
|
|
1144
|
+
return 0;
|
|
1145
|
+
}
|
|
1146
|
+
visited.add(router);
|
|
1147
|
+
let stateRouters = addStates(router, trail.join("."), into) ? 1 : 0;
|
|
1148
|
+
for (const [name, entry] of Object.entries(router.routes)) {
|
|
1149
|
+
if (isRouter(entry)) {
|
|
1150
|
+
stateRouters += collectStates(entry, [...trail, name], visited, into);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
for (const [index, nested] of (router._packageRouters ?? []).entries()) {
|
|
1154
|
+
stateRouters += collectStates(nested, [...trail, `packages[${index}]`], visited, into);
|
|
1155
|
+
}
|
|
1156
|
+
return stateRouters;
|
|
1157
|
+
}
|
|
1158
|
+
function addStates(router, trail, into) {
|
|
1159
|
+
if (!Object.hasOwn(router, "_stateManifest")) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
for (const [id, entry] of Object.entries(router._stateManifest ?? {})) {
|
|
1163
|
+
const existing = into.get(id);
|
|
1164
|
+
if (existing) {
|
|
1165
|
+
throw new StatesGeneratorError(
|
|
1166
|
+
`Two state routers both declare a state "${id}" (${existing.trail} and ${trail}). The browser addresses a state by its id alone and the generated manifest holds one entry per id, so one of the two would be read through the other's routes. Rename one of the states, or mount one of the two routers only.`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
into.set(id, { entry, trail });
|
|
1170
|
+
}
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
1173
|
+
function collectDeclaredStates(router, routerPath) {
|
|
1174
|
+
const states = /* @__PURE__ */ new Map();
|
|
1175
|
+
if (collectStates(router, ["router"], /* @__PURE__ */ new Set(), states) === 0) {
|
|
1176
|
+
throw new StatesGeneratorError(
|
|
1177
|
+
`No state router is mounted by ${routerPath}. The @spfn/core:states generator is configured, but nothing it reads mounts a defineStateRouter(...).router, so the file it would write could not say which states the app declared, and every id the server serves would read as a skew. Mount the state router \u2014 \`appRouter.packages([states.router])\` \u2014 or remove the generator from .spfnrc.ts.`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
return states;
|
|
1181
|
+
}
|
|
1182
|
+
function entryLine2(id, entry) {
|
|
1183
|
+
return ` ${toObjectKey(id)}: { key: [${entry.key.map(toStringLiteral).join(", ")}], write: ${entry.write} },`;
|
|
1184
|
+
}
|
|
1185
|
+
function generateStatesContent(states, nodeEnv) {
|
|
1186
|
+
const ids = [...states.keys()];
|
|
1187
|
+
return [
|
|
1188
|
+
"/**",
|
|
1189
|
+
" * State Manifest (Auto-generated)",
|
|
1190
|
+
" *",
|
|
1191
|
+
" * DO NOT EDIT - This file is generated by @spfn/core:states generator",
|
|
1192
|
+
" *",
|
|
1193
|
+
` * Generated with NODE_ENV=${nodeEnv}. A state router mounted only under one environment is`,
|
|
1194
|
+
" * named here only when the generator ran under it, so this line changing is that",
|
|
1195
|
+
" * difference rather than an edit.",
|
|
1196
|
+
" *",
|
|
1197
|
+
" * Compare `stateManifest` with the `manifest` of `GET <state path>` (or of the snapshot a",
|
|
1198
|
+
" * server component read): an id here that the server's manifest lacks, or an entry that",
|
|
1199
|
+
" * differs, is a deploy skew between this build and the API.",
|
|
1200
|
+
" */",
|
|
1201
|
+
"",
|
|
1202
|
+
`export const stateIds = [${ids.map(toStringLiteral).join(", ")}] as const;`,
|
|
1203
|
+
"",
|
|
1204
|
+
"export type StateId = (typeof stateIds)[number];",
|
|
1205
|
+
"",
|
|
1206
|
+
"export const stateManifest: Readonly<Record<StateId, { readonly key: readonly string[]; readonly write: boolean }>> = {",
|
|
1207
|
+
...ids.map((id) => entryLine2(id, states.get(id).entry)),
|
|
1208
|
+
"};",
|
|
1209
|
+
""
|
|
1210
|
+
].join("\n");
|
|
1211
|
+
}
|
|
1212
|
+
function createStatesGenerator(config) {
|
|
1213
|
+
const { routerPath, outputPath = "./src/generated/states.ts" } = config;
|
|
1214
|
+
if (!routerPath) {
|
|
1215
|
+
throw new Error(
|
|
1216
|
+
`[@spfn/core:states] Missing required "routerPath" option.
|
|
1217
|
+
|
|
1218
|
+
Usage:
|
|
1219
|
+
defineGenerator<StatesGeneratorConfig>({
|
|
1220
|
+
name: '@spfn/core:states',
|
|
1221
|
+
routerPath: './src/server/router.ts',
|
|
1222
|
+
})`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
name: "@spfn/core:states",
|
|
1227
|
+
watchPatterns: [
|
|
1228
|
+
routerPath,
|
|
1229
|
+
"src/server/states.ts",
|
|
1230
|
+
"src/server/states/**/*.ts",
|
|
1231
|
+
"src/server/routes/**/*.ts"
|
|
1232
|
+
],
|
|
1233
|
+
runOn: ["watch", "manual", "build"],
|
|
1234
|
+
async generate(options) {
|
|
1235
|
+
const { cwd, debug } = options;
|
|
1236
|
+
const absoluteRouterPath = join(cwd, routerPath);
|
|
1237
|
+
const absoluteOutputPath = join(cwd, outputPath);
|
|
1238
|
+
if (!existsSync(absoluteRouterPath)) {
|
|
1239
|
+
genLogger3.warn(`Router file not found: ${absoluteRouterPath}`);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
pinNodeEnv();
|
|
1243
|
+
const { router, exportName } = loadRouter3(cwd, absoluteRouterPath);
|
|
1244
|
+
assertUnconditionalRegistration({
|
|
1245
|
+
routerPath,
|
|
1246
|
+
source: readFileSync(absoluteRouterPath, "utf-8"),
|
|
1247
|
+
exportName,
|
|
1248
|
+
subject: "state manifest"
|
|
1249
|
+
});
|
|
1250
|
+
const states = collectDeclaredStates(router, routerPath);
|
|
1251
|
+
if (debug) {
|
|
1252
|
+
genLogger3.info(`Found ${states.size} states`, { ids: [...states.keys()] });
|
|
1253
|
+
}
|
|
1254
|
+
mkdirSync(dirname(absoluteOutputPath), { recursive: true });
|
|
1255
|
+
const nodeEnv = process.env.NODE_ENV ?? "unset";
|
|
1256
|
+
writeFileSync(absoluteOutputPath, generateStatesContent(states, nodeEnv), "utf-8");
|
|
1257
|
+
genLogger3.info(`Generated states: ${relative(cwd, absoluteOutputPath)} (${states.size} states)`);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1117
1261
|
|
|
1118
1262
|
// src/codegen/generators/index.ts
|
|
1119
1263
|
var generators = {
|
|
1120
1264
|
"route-map": createRouteMapGenerator,
|
|
1121
|
-
contract: createContractGenerator
|
|
1265
|
+
contract: createContractGenerator,
|
|
1266
|
+
states: createStatesGenerator
|
|
1122
1267
|
};
|
|
1123
1268
|
|
|
1124
|
-
export { CodegenOrchestrator, ConditionalRegistrationError, ContractGeneratorError, RouteMapGeneratorError, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
|
|
1269
|
+
export { CodegenOrchestrator, ConditionalRegistrationError, ContractGeneratorError, RouteMapGeneratorError, StatesGeneratorError, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
|
|
1125
1270
|
//# sourceMappingURL=index.js.map
|
|
1126
1271
|
//# sourceMappingURL=index.js.map
|