@fougere/adapter-rest 0.11.0-alpha.0 → 0.12.1-alpha.0
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteDefinition.d.ts","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,OAAO,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;CAItB;AAED,kFAAkF;AAClF,KAAK,YAAY,GAAG,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG;IAClE,iFAAiF;IACjF,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,mFAAmF;IACnF,IAAI,CAAC,EAAE,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;CACxE,CAAC;AASF,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,yFAAyF;IACzF,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CACtG;AAED,UAAU,OAAO;IACf,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;IAClF,kDAAkD;IAClD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,CAAC;CACzF;AA6DD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"RouteDefinition.d.ts","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,OAAO,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;CAItB;AAED,kFAAkF;AAClF,KAAK,YAAY,GAAG,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG;IAClE,iFAAiF;IACjF,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,mFAAmF;IACnF,IAAI,CAAC,EAAE,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;CACxE,CAAC;AASF,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,yFAAyF;IACzF,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CACtG;AAED,UAAU,OAAO;IACf,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;IAClF,kDAAkD;IAClD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,CAAC;CACzF;AA6DD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,qBAA0B,GAAG,eAAe,EAAE,CAGnG"}
|
|
@@ -50,83 +50,75 @@ function derivePath(entityName, opName) {
|
|
|
50
50
|
// Membership is the axes-derived `Visibility.input` projection from @fougere/schema.
|
|
51
51
|
// ─── Public API ─────────────────────────────────
|
|
52
52
|
/** Generate REST route definitions from a fougere App. */
|
|
53
|
-
export function generateRoutes(app, options) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// comes first because it names the operation; a host that overrides is deciding
|
|
93
|
-
// for someone else's, which is what `overrides:` is for and why it wins.
|
|
94
|
-
const stated = frond.operationsOverrides?.[opName]?.rest;
|
|
95
|
-
const override = { ...stated, ...entityOverrides[opName] };
|
|
96
|
-
const method = override.method ?? deriveMethod(opName, meta?.kind);
|
|
97
|
-
const path = prefix + (override.path ?? derivePath(entity.name, opName));
|
|
98
|
-
// Input/output fields: use meta if available, fallback to entity fields for CRUD.
|
|
99
|
-
// Both pass through the client-surface projections (write-only out, read-only in).
|
|
100
|
-
let inputFields;
|
|
101
|
-
let outputFields = Visibility.of(fields).output;
|
|
102
|
-
if (meta?.input) {
|
|
103
|
-
inputFields = meta.input.getFields();
|
|
104
|
-
}
|
|
105
|
-
else if (opName === 'create' || opName === 'update') {
|
|
106
|
-
inputFields = Visibility.of(fields).input;
|
|
107
|
-
}
|
|
108
|
-
if (meta?.output)
|
|
109
|
-
outputFields = Visibility.of(meta.output.getFields()).output;
|
|
110
|
-
// Handler/method overrides are already executed by the facade from the same
|
|
111
|
-
// EffectiveOperation local and RPC use. The adapter never resolves DI itself.
|
|
112
|
-
const op = facade[opName];
|
|
113
|
-
if (typeof op !== 'function') {
|
|
114
|
-
throw new Error(`REST EffectiveOperation table exposes '${opName}' but its facade does not.`);
|
|
115
|
-
}
|
|
116
|
-
routes.push({
|
|
117
|
-
method,
|
|
118
|
-
path,
|
|
119
|
-
operationName: opName,
|
|
120
|
-
entityName: entity.name,
|
|
121
|
-
handler: (invocation) => op(invocation),
|
|
122
|
-
inputFields,
|
|
123
|
-
outputFields,
|
|
124
|
-
successStatus: override.status,
|
|
125
|
-
...(meta?.description && { description: meta.description }),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
53
|
+
export function generateRoutes(app, options = {}) {
|
|
54
|
+
return app.fronds.flatMap((frond) => frond.entities.flatMap((entity) => routesOf(app, frond, entity, options)));
|
|
55
|
+
}
|
|
56
|
+
/** What one entity answers on REST — nothing, when this surface does not serve it. */
|
|
57
|
+
function routesOf(app, frond, entity, options) {
|
|
58
|
+
const surface = options.surface;
|
|
59
|
+
// Membership is core's answer, not ours — one rule, read here (see App.facadeFor).
|
|
60
|
+
const facade = app.facadeFor(entity.name, surface);
|
|
61
|
+
if (!facade)
|
|
62
|
+
return [];
|
|
63
|
+
if (options.filter && !options.filter(entity, frond.name))
|
|
64
|
+
return [];
|
|
65
|
+
if (!surface && entity.exposed === false)
|
|
66
|
+
return [];
|
|
67
|
+
const effectiveOperations = app.operationsFor(entity.name, surface);
|
|
68
|
+
if (!effectiveOperations) {
|
|
69
|
+
throw new Error(`REST cannot project '${entity.name}' without its EffectiveOperation table.`);
|
|
70
|
+
}
|
|
71
|
+
const handler = (surface
|
|
72
|
+
? frond.handlers.find((one) => one.address === entity.name && one.surface === surface)
|
|
73
|
+
: undefined) ?? frond.handlers.find((one) => !one.surface && one.address === entity.name);
|
|
74
|
+
// The handler's output schema if declared, otherwise the entity. A frond whose class never
|
|
75
|
+
// crossed the wire arrives as one too: boot rebuilds the card before here.
|
|
76
|
+
const outputSchema = handler?.outputOverride ?? handler?.ctor?.__output ?? entity.entityClass;
|
|
77
|
+
// The resolved table defines the public operation set. This also works for remote proxy
|
|
78
|
+
// facades, which intentionally cannot enumerate their keys before discovery.
|
|
79
|
+
return [...effectiveOperations.keys()].map((opName) => routeFor({
|
|
80
|
+
entity,
|
|
81
|
+
frond,
|
|
82
|
+
facade,
|
|
83
|
+
fields: outputSchema.getFields(),
|
|
84
|
+
opName,
|
|
85
|
+
meta: effectiveOperations.get(opName),
|
|
86
|
+
options,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
function routeFor({ entity, frond, facade, fields, opName, meta, options }) {
|
|
90
|
+
if (!meta) {
|
|
91
|
+
throw new Error(`REST facade '${entity.name}' exposes '${opName}' but its EffectiveOperation table does not.`);
|
|
129
92
|
}
|
|
130
|
-
|
|
93
|
+
// What the FROND said, then what this call said, then the convention. The frond comes first
|
|
94
|
+
// because it names the operation; a host that overrides is deciding for someone else's,
|
|
95
|
+
// which is what `overrides:` is for and why it wins.
|
|
96
|
+
const stated = frond.operationsOverrides?.[opName]?.rest;
|
|
97
|
+
const override = { ...stated, ...(options.overrides?.[entity.name] ?? {})[opName] };
|
|
98
|
+
// Handler and method overrides are already executed by the facade, from the same
|
|
99
|
+
// EffectiveOperation local and RPC use. The adapter never resolves DI itself.
|
|
100
|
+
const op = facade[opName];
|
|
101
|
+
if (typeof op !== 'function') {
|
|
102
|
+
throw new Error(`REST EffectiveOperation table exposes '${opName}' but its facade does not.`);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
method: override.method ?? deriveMethod(opName, meta.kind),
|
|
106
|
+
path: (options.prefix ?? '') + (override.path ?? derivePath(entity.name, opName)),
|
|
107
|
+
operationName: opName,
|
|
108
|
+
entityName: entity.name,
|
|
109
|
+
handler: (invocation) => op(invocation),
|
|
110
|
+
...inputAndOutput(meta, fields, opName),
|
|
111
|
+
successStatus: override.status,
|
|
112
|
+
...(meta.description && { description: meta.description }),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Both pass through the client-surface projections — write-only out, read-only in. */
|
|
116
|
+
function inputAndOutput(meta, fields, opName) {
|
|
117
|
+
const inputFields = meta.input?.getFields()
|
|
118
|
+
?? (opName === 'create' || opName === 'update' ? Visibility.of(fields).input : undefined);
|
|
119
|
+
return {
|
|
120
|
+
inputFields,
|
|
121
|
+
outputFields: meta.output ? Visibility.of(meta.output.getFields()).output : Visibility.of(fields).output,
|
|
122
|
+
};
|
|
131
123
|
}
|
|
132
124
|
//# sourceMappingURL=RouteDefinition.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteDefinition.js","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmE7C,mDAAmD;AAEnD,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,CAAC;AAChG,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACvB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK;QAC3B,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;AACjB,CAAC;AAED,kFAAkF;AAClF,SAAS,YAAY,CACnB,MAAc,EACd,YAAmC;IAEnC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,yCAAyC;cACrE,uFAAuF,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/C,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2DAA2D;AAC3D,SAAS,UAAU,CAAC,UAAkB,EAAE,MAAc;IACpD,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;IAEzC,gBAAgB;IAChB,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,GAAG,IAAI,MAAM,CAAC;IAChD,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,GAAG,IAAI,MAAM,CAAC;IAC9C,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,GAAG,IAAI,MAAM,CAAC;IAE9C,0CAA0C;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAE1E,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;AAClE,CAAC;AAED,mDAAmD;AAEnD,iFAAiF;AACjF,+EAA+E;AAC/E,qFAAqF;AAErF,mDAAmD;AAEnD,0DAA0D;AAC1D,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"RouteDefinition.js","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/adapter-rest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1-alpha.0",
|
|
4
4
|
"description": "Entity → REST routes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"src"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@fougere/core": "0.
|
|
33
|
-
"@fougere/schema": "0.
|
|
34
|
-
"@fougere/http": "0.
|
|
32
|
+
"@fougere/core": "0.12.1-alpha.0",
|
|
33
|
+
"@fougere/schema": "0.12.1-alpha.0",
|
|
34
|
+
"@fougere/http": "0.12.1-alpha.0"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -124,95 +124,106 @@ function derivePath(entityName: string, opName: string): string {
|
|
|
124
124
|
// ─── Public API ─────────────────────────────────
|
|
125
125
|
|
|
126
126
|
/** Generate REST route definitions from a fougere App. */
|
|
127
|
-
export function generateRoutes(app: AppLike, options
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!effectiveOperations) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`REST cannot project '${entity.name}' without its EffectiveOperation table.`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
// The resolved table defines the public operation set. This also works for remote
|
|
155
|
-
// proxy facades, which intentionally cannot enumerate their keys before discovery.
|
|
156
|
-
const opNames = [...effectiveOperations.keys()];
|
|
157
|
-
const entityOverrides = overrides[entity.name] ?? {};
|
|
158
|
-
// Use handler's output schema if declared, otherwise entity. A frond whose class
|
|
159
|
-
// never crossed the wire arrives as one too: boot rebuilds the card before here.
|
|
160
|
-
const outputSchema: SchemaView = handler?.outputOverride
|
|
161
|
-
?? handler?.ctor?.__output
|
|
162
|
-
?? entity.entityClass;
|
|
163
|
-
const fields = outputSchema.getFields();
|
|
164
|
-
|
|
165
|
-
for (const opName of opNames) {
|
|
166
|
-
const meta = effectiveOperations.get(opName);
|
|
167
|
-
if (!meta) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`REST facade '${entity.name}' exposes '${opName}' but its EffectiveOperation table does not.`,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
// What the FROND said, then what this call said, then the convention. The frond
|
|
173
|
-
// comes first because it names the operation; a host that overrides is deciding
|
|
174
|
-
// for someone else's, which is what `overrides:` is for and why it wins.
|
|
175
|
-
const stated = frond.operationsOverrides?.[opName]?.rest as
|
|
176
|
-
{ method?: HttpMethod; path?: string; status?: number } | undefined;
|
|
177
|
-
const override = { ...stated, ...entityOverrides[opName] } as
|
|
178
|
-
{ method?: HttpMethod; path?: string; status?: number };
|
|
179
|
-
const method = override.method ?? deriveMethod(opName, meta?.kind);
|
|
180
|
-
const path = prefix + (override.path ?? derivePath(entity.name, opName));
|
|
181
|
-
|
|
182
|
-
// Input/output fields: use meta if available, fallback to entity fields for CRUD.
|
|
183
|
-
// Both pass through the client-surface projections (write-only out, read-only in).
|
|
184
|
-
let inputFields: Fields | undefined;
|
|
185
|
-
let outputFields: Fields | undefined = Visibility.of(fields).output;
|
|
186
|
-
if (meta?.input) {
|
|
187
|
-
inputFields = meta.input.getFields();
|
|
188
|
-
} else if (opName === 'create' || opName === 'update') {
|
|
189
|
-
inputFields = Visibility.of(fields).input;
|
|
190
|
-
}
|
|
191
|
-
if (meta?.output) outputFields = Visibility.of(meta.output.getFields()).output;
|
|
192
|
-
|
|
193
|
-
// Handler/method overrides are already executed by the facade from the same
|
|
194
|
-
// EffectiveOperation local and RPC use. The adapter never resolves DI itself.
|
|
195
|
-
const op = facade[opName];
|
|
196
|
-
if (typeof op !== 'function') {
|
|
197
|
-
throw new Error(
|
|
198
|
-
`REST EffectiveOperation table exposes '${opName}' but its facade does not.`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
routes.push({
|
|
203
|
-
method,
|
|
204
|
-
path,
|
|
205
|
-
operationName: opName,
|
|
206
|
-
entityName: entity.name,
|
|
207
|
-
handler: (invocation) => op(invocation),
|
|
208
|
-
inputFields,
|
|
209
|
-
outputFields,
|
|
210
|
-
successStatus: override.status,
|
|
211
|
-
...(meta?.description && { description: meta.description }),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
127
|
+
export function generateRoutes(app: AppLike, options: GenerateRoutesOptions = {}): RouteDefinition[] {
|
|
128
|
+
return app.fronds.flatMap((frond) =>
|
|
129
|
+
frond.entities.flatMap((entity) => routesOf(app, frond, entity, options)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** What one entity answers on REST — nothing, when this surface does not serve it. */
|
|
133
|
+
function routesOf(
|
|
134
|
+
app: AppLike,
|
|
135
|
+
frond: FrondLike,
|
|
136
|
+
entity: EntityEntry,
|
|
137
|
+
options: GenerateRoutesOptions,
|
|
138
|
+
): RouteDefinition[] {
|
|
139
|
+
const surface = options.surface;
|
|
140
|
+
// Membership is core's answer, not ours — one rule, read here (see App.facadeFor).
|
|
141
|
+
const facade = app.facadeFor(entity.name, surface) as HandlerFacade | undefined;
|
|
142
|
+
if (!facade) return [];
|
|
143
|
+
if (options.filter && !options.filter(entity, frond.name)) return [];
|
|
144
|
+
if (!surface && entity.exposed === false) return [];
|
|
145
|
+
|
|
146
|
+
const effectiveOperations = app.operationsFor(entity.name, surface);
|
|
147
|
+
if (!effectiveOperations) {
|
|
148
|
+
throw new Error(`REST cannot project '${entity.name}' without its EffectiveOperation table.`);
|
|
215
149
|
}
|
|
216
150
|
|
|
217
|
-
|
|
151
|
+
const handler = (surface
|
|
152
|
+
? frond.handlers.find((one) => one.address === entity.name && one.surface === surface)
|
|
153
|
+
: undefined) ?? frond.handlers.find((one) => !one.surface && one.address === entity.name);
|
|
154
|
+
// The handler's output schema if declared, otherwise the entity. A frond whose class never
|
|
155
|
+
// crossed the wire arrives as one too: boot rebuilds the card before here.
|
|
156
|
+
const outputSchema: SchemaView = handler?.outputOverride ?? handler?.ctor?.__output ?? entity.entityClass;
|
|
157
|
+
|
|
158
|
+
// The resolved table defines the public operation set. This also works for remote proxy
|
|
159
|
+
// facades, which intentionally cannot enumerate their keys before discovery.
|
|
160
|
+
return [...effectiveOperations.keys()].map((opName) => routeFor({
|
|
161
|
+
entity,
|
|
162
|
+
frond,
|
|
163
|
+
facade,
|
|
164
|
+
fields: outputSchema.getFields(),
|
|
165
|
+
opName,
|
|
166
|
+
meta: effectiveOperations.get(opName),
|
|
167
|
+
options,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** One operation of one entity, and everything that decides how it is addressed. */
|
|
172
|
+
interface Projecting {
|
|
173
|
+
entity: EntityEntry;
|
|
174
|
+
frond: FrondLike;
|
|
175
|
+
facade: HandlerFacade;
|
|
176
|
+
fields: Fields;
|
|
177
|
+
opName: string;
|
|
178
|
+
meta: OperationMeta | undefined;
|
|
179
|
+
options: GenerateRoutesOptions;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function routeFor({ entity, frond, facade, fields, opName, meta, options }: Projecting): RouteDefinition {
|
|
183
|
+
if (!meta) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`REST facade '${entity.name}' exposes '${opName}' but its EffectiveOperation table does not.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// What the FROND said, then what this call said, then the convention. The frond comes first
|
|
190
|
+
// because it names the operation; a host that overrides is deciding for someone else's,
|
|
191
|
+
// which is what `overrides:` is for and why it wins.
|
|
192
|
+
const stated = frond.operationsOverrides?.[opName]?.rest as
|
|
193
|
+
{ method?: HttpMethod; path?: string; status?: number } | undefined;
|
|
194
|
+
const override = { ...stated, ...(options.overrides?.[entity.name] ?? {})[opName] } as
|
|
195
|
+
{ method?: HttpMethod; path?: string; status?: number };
|
|
196
|
+
|
|
197
|
+
// Handler and method overrides are already executed by the facade, from the same
|
|
198
|
+
// EffectiveOperation local and RPC use. The adapter never resolves DI itself.
|
|
199
|
+
const op = facade[opName];
|
|
200
|
+
if (typeof op !== 'function') {
|
|
201
|
+
throw new Error(`REST EffectiveOperation table exposes '${opName}' but its facade does not.`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
method: override.method ?? deriveMethod(opName, meta.kind),
|
|
206
|
+
path: (options.prefix ?? '') + (override.path ?? derivePath(entity.name, opName)),
|
|
207
|
+
operationName: opName,
|
|
208
|
+
entityName: entity.name,
|
|
209
|
+
handler: (invocation) => op(invocation),
|
|
210
|
+
...inputAndOutput(meta, fields, opName),
|
|
211
|
+
successStatus: override.status,
|
|
212
|
+
...(meta.description && { description: meta.description }),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Both pass through the client-surface projections — write-only out, read-only in. */
|
|
217
|
+
function inputAndOutput(
|
|
218
|
+
meta: OperationMeta,
|
|
219
|
+
fields: Fields,
|
|
220
|
+
opName: string,
|
|
221
|
+
): { inputFields: Fields | undefined; outputFields: Fields | undefined } {
|
|
222
|
+
const inputFields = meta.input?.getFields()
|
|
223
|
+
?? (opName === 'create' || opName === 'update' ? Visibility.of(fields).input : undefined);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
inputFields,
|
|
227
|
+
outputFields: meta.output ? Visibility.of(meta.output.getFields()).output : Visibility.of(fields).output,
|
|
228
|
+
};
|
|
218
229
|
}
|