@spfn/core 0.3.0-beta.17 → 0.3.0-beta.19
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 +20 -0
- package/dist/codegen/index.js +68 -10
- package/dist/codegen/index.js.map +1 -1
- package/dist/contract/index.d.ts +37 -15
- package/dist/contract/index.js +147 -23
- package/dist/contract/index.js.map +1 -1
- package/dist/event/index.d.ts +4 -3
- package/dist/event/index.js +32 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +3 -2
- package/dist/event/sse/index.d.ts +5 -4
- package/dist/event/ws/client.d.ts +3 -2
- package/dist/event/ws/index.d.ts +4 -3
- package/dist/nextjs/index.d.ts +17 -4
- package/dist/nextjs/index.js +85 -48
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +1 -1
- package/dist/ops/index.d.ts +1 -1
- package/dist/{token-manager-BT5EnUAR.d.ts → router-CysBmgv3.d.ts} +81 -127
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.js +26 -2
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-Ckjt2-qq.d.ts +122 -0
- package/dist/{types-ClQVomgV.d.ts → types-B4DuGCyM.d.ts} +42 -3
- package/dist/{types-ZQODsBft.d.ts → types-B8zAxFsE.d.ts} +2 -1
- package/dist/{types-QoyjaJWw.d.ts → types-CxhPH4s_.d.ts} +2 -2
- package/dist/{types-2AbaW4Ie.d.ts → types-W4u_S0lF.d.ts} +4 -3
- package/package.json +1 -1
- package/src/codegen/README.md +11 -2
- package/src/contract/README.md +113 -5
- package/src/event/README.md +48 -0
- package/src/nextjs/README.md +39 -7
package/dist/codegen/index.d.ts
CHANGED
|
@@ -379,6 +379,7 @@ declare class RouteMapGeneratorError extends Error {
|
|
|
379
379
|
* defineGenerator({
|
|
380
380
|
* name: '@spfn/core:contract',
|
|
381
381
|
* routerPath: './src/server/router.ts',
|
|
382
|
+
* eventRouterPath: './src/server/events.ts', // optional: contracted SSE events
|
|
382
383
|
* outputDir: './contracts',
|
|
383
384
|
* })
|
|
384
385
|
* ]
|
|
@@ -401,6 +402,18 @@ interface ContractGeneratorConfig {
|
|
|
401
402
|
* @default 'appRouter', falling back to the default export
|
|
402
403
|
*/
|
|
403
404
|
routerExport?: string;
|
|
405
|
+
/**
|
|
406
|
+
* Path to the file exporting the `defineEventRouter()` result (relative to
|
|
407
|
+
* project root). When set, its contracted events are written into the
|
|
408
|
+
* document's `events` section; unset, the document has none.
|
|
409
|
+
* @example './src/server/events.ts'
|
|
410
|
+
*/
|
|
411
|
+
eventRouterPath?: string;
|
|
412
|
+
/**
|
|
413
|
+
* Named export holding the event router.
|
|
414
|
+
* @default 'eventRouter', falling back to the default export
|
|
415
|
+
*/
|
|
416
|
+
eventRouterExport?: string;
|
|
404
417
|
/**
|
|
405
418
|
* Directory holding current.json, released/ and usage/ (relative to project root)
|
|
406
419
|
* @default './contracts'
|
|
@@ -466,7 +479,14 @@ interface RegistrationScan {
|
|
|
466
479
|
exportName: string;
|
|
467
480
|
/** What is being generated, named in the message: `route map`, `contract`. */
|
|
468
481
|
subject: string;
|
|
482
|
+
/**
|
|
483
|
+
* The call the router is built with. `defineEventRouter` scans an event
|
|
484
|
+
* router file, whose event set the contract publishes the same way.
|
|
485
|
+
* @default 'defineRouter'
|
|
486
|
+
*/
|
|
487
|
+
factory?: RouterFactory;
|
|
469
488
|
}
|
|
489
|
+
type RouterFactory = 'defineRouter' | 'defineEventRouter';
|
|
470
490
|
/**
|
|
471
491
|
* Refuse a router whose route set depends on a condition.
|
|
472
492
|
*/
|
package/dist/codegen/index.js
CHANGED
|
@@ -348,6 +348,18 @@ var ConditionalRegistrationError = class extends Error {
|
|
|
348
348
|
this.name = "ConditionalRegistrationError";
|
|
349
349
|
}
|
|
350
350
|
};
|
|
351
|
+
var REFUSAL_WORDING = {
|
|
352
|
+
defineRouter: {
|
|
353
|
+
noun: "routes",
|
|
354
|
+
consequence: "a route production registers is left out, and the client that addresses it by name gets a 404",
|
|
355
|
+
remedy: "Register the route unconditionally and gate its behaviour inside the handler instead."
|
|
356
|
+
},
|
|
357
|
+
defineEventRouter: {
|
|
358
|
+
noun: "events",
|
|
359
|
+
consequence: "an event production streams is left out, and the client that subscribes to it by name gets a 400",
|
|
360
|
+
remedy: "Register the event unconditionally and decide who receives it with the stream's authorize hook instead."
|
|
361
|
+
}
|
|
362
|
+
};
|
|
351
363
|
function assertUnconditionalRegistration(scan) {
|
|
352
364
|
const compiler2 = typescript();
|
|
353
365
|
const file = compiler2.createSourceFile(
|
|
@@ -358,11 +370,14 @@ function assertUnconditionalRegistration(scan) {
|
|
|
358
370
|
false
|
|
359
371
|
);
|
|
360
372
|
const declarations = topLevelInitialisers(compiler2, file);
|
|
361
|
-
const routes = routesLiteral(compiler2, declarations.get(scan.exportName), declarations, /* @__PURE__ */ new Set());
|
|
373
|
+
const routes = routesLiteral(compiler2, declarations.get(scan.exportName), declarations, /* @__PURE__ */ new Set(), factoryOf(scan));
|
|
362
374
|
if (routes) {
|
|
363
375
|
assertLiteral(compiler2, file, routes, declarations, /* @__PURE__ */ new Set(), scan);
|
|
364
376
|
}
|
|
365
377
|
}
|
|
378
|
+
function factoryOf(scan) {
|
|
379
|
+
return scan.factory ?? "defineRouter";
|
|
380
|
+
}
|
|
366
381
|
function topLevelInitialisers(compiler2, file) {
|
|
367
382
|
const found = /* @__PURE__ */ new Map();
|
|
368
383
|
for (const statement of file.statements) {
|
|
@@ -404,7 +419,7 @@ function unwrap(compiler2, expression) {
|
|
|
404
419
|
}
|
|
405
420
|
return expression;
|
|
406
421
|
}
|
|
407
|
-
function routesLiteral(compiler2, expression, declarations, seen) {
|
|
422
|
+
function routesLiteral(compiler2, expression, declarations, seen, factory) {
|
|
408
423
|
const node = unwrap(compiler2, expression);
|
|
409
424
|
if (!node) {
|
|
410
425
|
return void 0;
|
|
@@ -414,17 +429,17 @@ function routesLiteral(compiler2, expression, declarations, seen) {
|
|
|
414
429
|
return void 0;
|
|
415
430
|
}
|
|
416
431
|
seen.add(node.text);
|
|
417
|
-
return routesLiteral(compiler2, declarations.get(node.text), declarations, seen);
|
|
432
|
+
return routesLiteral(compiler2, declarations.get(node.text), declarations, seen, factory);
|
|
418
433
|
}
|
|
419
434
|
if (!compiler2.isCallExpression(node)) {
|
|
420
435
|
return void 0;
|
|
421
436
|
}
|
|
422
437
|
const callee = unwrap(compiler2, node.expression);
|
|
423
|
-
if (callee && compiler2.isIdentifier(callee) && callee.text ===
|
|
438
|
+
if (callee && compiler2.isIdentifier(callee) && callee.text === factory) {
|
|
424
439
|
const routes = unwrap(compiler2, node.arguments[0]);
|
|
425
440
|
return routes && compiler2.isObjectLiteralExpression(routes) ? routes : void 0;
|
|
426
441
|
}
|
|
427
|
-
return callee && compiler2.isPropertyAccessExpression(callee) ? routesLiteral(compiler2, callee.expression, declarations, seen) : void 0;
|
|
442
|
+
return callee && compiler2.isPropertyAccessExpression(callee) ? routesLiteral(compiler2, callee.expression, declarations, seen, factory) : void 0;
|
|
428
443
|
}
|
|
429
444
|
function assertLiteral(compiler2, file, literal, declarations, seen, scan) {
|
|
430
445
|
for (const property of literal.properties) {
|
|
@@ -432,7 +447,7 @@ function assertLiteral(compiler2, file, literal, declarations, seen, scan) {
|
|
|
432
447
|
assertUnconditionalSpread(compiler2, file, property.expression, scan);
|
|
433
448
|
continue;
|
|
434
449
|
}
|
|
435
|
-
const nested = routesLiteral(compiler2, mountedRouter(compiler2, property), declarations, seen);
|
|
450
|
+
const nested = routesLiteral(compiler2, mountedRouter(compiler2, property), declarations, seen, factoryOf(scan));
|
|
436
451
|
if (nested) {
|
|
437
452
|
assertLiteral(compiler2, file, nested, declarations, seen, scan);
|
|
438
453
|
}
|
|
@@ -449,11 +464,12 @@ function assertUnconditionalSpread(compiler2, file, expression, scan) {
|
|
|
449
464
|
if (!condition) {
|
|
450
465
|
return;
|
|
451
466
|
}
|
|
467
|
+
const wording = REFUSAL_WORDING[factoryOf(scan)];
|
|
452
468
|
throw new ConditionalRegistrationError(
|
|
453
|
-
`${scan.routerPath} registers
|
|
469
|
+
`${scan.routerPath} registers ${wording.noun} conditionally: "...${spreadText(file, expression)}" (${condition}).
|
|
454
470
|
|
|
455
|
-
The ${scan.subject} is generated from the router as it loads, so a
|
|
456
|
-
|
|
471
|
+
The ${scan.subject} is generated from the router as it loads, so a set of ${wording.noun} that depends on a flag or an environment describes whichever way the generator happened to run: ${wording.consequence}.
|
|
472
|
+
` + wording.remedy
|
|
457
473
|
);
|
|
458
474
|
}
|
|
459
475
|
function conditionKind(compiler2, expression) {
|
|
@@ -985,10 +1001,51 @@ function loadRouter2(cwd, absoluteRouterPath, routerExport) {
|
|
|
985
1001
|
`No router found in ${relative(cwd, absoluteRouterPath)}. Looked for: ${candidates.join(", ")}. Set "routerExport" to the export holding the defineRouter() result.`
|
|
986
1002
|
);
|
|
987
1003
|
}
|
|
1004
|
+
function isEventRouter(value) {
|
|
1005
|
+
return value !== null && typeof value === "object" && "events" in value && "eventNames" in value;
|
|
1006
|
+
}
|
|
1007
|
+
function loadEventRouter(cwd, absolutePath, eventRouterExport) {
|
|
1008
|
+
const module = loadRouterModule({
|
|
1009
|
+
cwd,
|
|
1010
|
+
absoluteRouterPath: absolutePath,
|
|
1011
|
+
subject: "contract",
|
|
1012
|
+
fail: (message) => new ContractGeneratorError(message)
|
|
1013
|
+
});
|
|
1014
|
+
const candidates = eventRouterExport ? [eventRouterExport] : ["eventRouter", "default"];
|
|
1015
|
+
const exportName = candidates.find((candidate) => isEventRouter(module[candidate]));
|
|
1016
|
+
if (exportName) {
|
|
1017
|
+
return { eventRouter: module[exportName], exportName };
|
|
1018
|
+
}
|
|
1019
|
+
throw new ContractGeneratorError(
|
|
1020
|
+
`No event router found in ${relative(cwd, absolutePath)}. Looked for: ${candidates.join(", ")}. Set "eventRouterExport" to the export holding the defineEventRouter() result.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
function readEventRouter(cwd, eventRouterPath, eventRouterExport) {
|
|
1024
|
+
if (!eventRouterPath) {
|
|
1025
|
+
return void 0;
|
|
1026
|
+
}
|
|
1027
|
+
const absolutePath = join(cwd, eventRouterPath);
|
|
1028
|
+
if (!existsSync(absolutePath)) {
|
|
1029
|
+
throw new ContractGeneratorError(
|
|
1030
|
+
`Event router file not found: ${eventRouterPath}. The contract generator is configured with "eventRouterPath" but has nothing to read there.`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
const { eventRouter, exportName } = loadEventRouter(cwd, absolutePath, eventRouterExport);
|
|
1034
|
+
assertUnconditionalRegistration({
|
|
1035
|
+
routerPath: eventRouterPath,
|
|
1036
|
+
source: readFileSync(absolutePath, "utf-8"),
|
|
1037
|
+
exportName,
|
|
1038
|
+
subject: "contract",
|
|
1039
|
+
factory: "defineEventRouter"
|
|
1040
|
+
});
|
|
1041
|
+
return eventRouter;
|
|
1042
|
+
}
|
|
988
1043
|
function createContractGenerator(config) {
|
|
989
1044
|
const {
|
|
990
1045
|
routerPath,
|
|
991
1046
|
routerExport,
|
|
1047
|
+
eventRouterPath,
|
|
1048
|
+
eventRouterExport,
|
|
992
1049
|
outputDir = "./contracts",
|
|
993
1050
|
additionalRouteDirs = []
|
|
994
1051
|
} = config;
|
|
@@ -1007,6 +1064,7 @@ Usage:
|
|
|
1007
1064
|
name: "@spfn/core:contract",
|
|
1008
1065
|
watchPatterns: [
|
|
1009
1066
|
routerPath,
|
|
1067
|
+
...eventRouterPath ? [eventRouterPath] : [],
|
|
1010
1068
|
"src/server/routes/**/*.ts",
|
|
1011
1069
|
...additionalRouteDirs.map((dir) => `${dir}/**/*.ts`)
|
|
1012
1070
|
],
|
|
@@ -1028,7 +1086,7 @@ Usage:
|
|
|
1028
1086
|
exportName,
|
|
1029
1087
|
subject: "contract"
|
|
1030
1088
|
});
|
|
1031
|
-
const document = collectContractDocument(router);
|
|
1089
|
+
const document = collectContractDocument(router, readEventRouter(cwd, eventRouterPath, eventRouterExport));
|
|
1032
1090
|
const changed = writeCurrentDocument(contractsDir, document);
|
|
1033
1091
|
genLogger2.info(
|
|
1034
1092
|
`${changed ? "Wrote" : "Verified"} ${relative(cwd, join(contractsDir, "current.json"))} (${document.operations.length} contracted operation(s))`
|