@fougere/adapter-rest 0.11.0-alpha.0 → 0.12.0-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,CAAC,EAAE,qBAAqB,GAAG,eAAe,EAAE,CA2F/F"}
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
- const prefix = options?.prefix ?? '';
55
- const overrides = options?.overrides ?? {};
56
- const routes = [];
57
- for (const frond of app.fronds) {
58
- const handlerMap = new Map(frond.handlers.filter((h) => !h.surface).map((h) => [h.address, h]));
59
- const surfaceName = options?.surface;
60
- for (const entity of frond.entities) {
61
- // Membership is core's answer, not ours — one rule, read here (see App.facadeFor).
62
- const facade = app.facadeFor(entity.name, surfaceName);
63
- if (!facade)
64
- continue;
65
- if (options?.filter && !options.filter(entity, frond.name))
66
- continue;
67
- if (!surfaceName && entity.exposed === false)
68
- continue;
69
- const handler = (surfaceName
70
- ? frond.handlers.find((h) => h.address === entity.name && h.surface === surfaceName)
71
- : undefined) ?? handlerMap.get(entity.name);
72
- const effectiveOperations = app.operationsFor(entity.name, surfaceName);
73
- if (!effectiveOperations) {
74
- throw new Error(`REST cannot project '${entity.name}' without its EffectiveOperation table.`);
75
- }
76
- // The resolved table defines the public operation set. This also works for remote
77
- // proxy facades, which intentionally cannot enumerate their keys before discovery.
78
- const opNames = [...effectiveOperations.keys()];
79
- const entityOverrides = overrides[entity.name] ?? {};
80
- // Use handler's output schema if declared, otherwise entity. A frond whose class
81
- // never crossed the wire arrives as one too: boot rebuilds the card before here.
82
- const outputSchema = handler?.outputOverride
83
- ?? handler?.ctor?.__output
84
- ?? entity.entityClass;
85
- const fields = outputSchema.getFields();
86
- for (const opName of opNames) {
87
- const meta = effectiveOperations.get(opName);
88
- if (!meta) {
89
- throw new Error(`REST facade '${entity.name}' exposes '${opName}' but its EffectiveOperation table does not.`);
90
- }
91
- // What the FROND said, then what this call said, then the convention. The frond
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
- return routes;
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,OAA+B;IAC1E,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEhG,MAAM,WAAW,GAAG,OAAO,EAAE,OAAO,CAAC;QAErC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpC,mFAAmF;YACnF,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAA8B,CAAC;YACpF,IAAI,CAAC,MAAM;gBAAE,SAAS;YAEtB,IAAI,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACrE,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;gBAAE,SAAS;YAEvD,MAAM,OAAO,GAAG,CAAC,WAAW;gBAC1B,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,WAAW,CAAC;gBACpF,CAAC,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9C,MAAM,mBAAmB,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YACxE,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,CAAC,IAAI,yCAAyC,CAC7E,CAAC;YACJ,CAAC;YACD,kFAAkF;YAClF,mFAAmF;YACnF,MAAM,OAAO,GAAG,CAAC,GAAG,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACrD,iFAAiF;YACjF,iFAAiF;YACjF,MAAM,YAAY,GAAe,OAAO,EAAE,cAAc;mBACnD,OAAO,EAAE,IAAI,EAAE,QAAQ;mBACvB,MAAM,CAAC,WAAW,CAAC;YACxB,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE,CAAC;YAExC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CACb,gBAAgB,MAAM,CAAC,IAAI,cAAc,MAAM,8CAA8C,CAC9F,CAAC;gBACJ,CAAC;gBACD,gFAAgF;gBAChF,gFAAgF;gBAChF,yEAAyE;gBACzE,MAAM,MAAM,GAAG,KAAK,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,EAAE,IACiB,CAAC;gBACtE,MAAM,QAAQ,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,EACC,CAAC;gBAC1D,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBACnE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBAEzE,kFAAkF;gBAClF,mFAAmF;gBACnF,IAAI,WAA+B,CAAC;gBACpC,IAAI,YAAY,GAAuB,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;gBACpE,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;oBAChB,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;gBACvC,CAAC;qBAAM,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACtD,WAAW,GAAG,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;gBAC5C,CAAC;gBACD,IAAI,IAAI,EAAE,MAAM;oBAAE,YAAY,GAAG,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;gBAE/E,4EAA4E;gBAC5E,8EAA8E;gBAC9E,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC1B,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;oBAC7B,MAAM,IAAI,KAAK,CACb,0CAA0C,MAAM,4BAA4B,CAC7E,CAAC;gBACJ,CAAC;gBAED,MAAM,CAAC,IAAI,CAAC;oBACV,MAAM;oBACN,IAAI;oBACJ,aAAa,EAAE,MAAM;oBACrB,UAAU,EAAE,MAAM,CAAC,IAAI;oBACvB,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC;oBACvC,WAAW;oBACX,YAAY;oBACZ,aAAa,EAAE,QAAQ,CAAC,MAAM;oBAC9B,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;iBAC5D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
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.11.0-alpha.0",
3
+ "version": "0.12.0-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.11.0-alpha.0",
33
- "@fougere/schema": "0.11.0-alpha.0",
34
- "@fougere/http": "0.11.0-alpha.0"
32
+ "@fougere/core": "0.12.0-alpha.0",
33
+ "@fougere/schema": "0.12.0-alpha.0",
34
+ "@fougere/http": "0.12.0-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?: GenerateRoutesOptions): RouteDefinition[] {
128
- const prefix = options?.prefix ?? '';
129
- const overrides = options?.overrides ?? {};
130
- const routes: RouteDefinition[] = [];
131
-
132
- for (const frond of app.fronds) {
133
- const handlerMap = new Map(frond.handlers.filter((h) => !h.surface).map((h) => [h.address, h]));
134
-
135
- const surfaceName = options?.surface;
136
-
137
- for (const entity of frond.entities) {
138
- // Membership is core's answer, not ours — one rule, read here (see App.facadeFor).
139
- const facade = app.facadeFor(entity.name, surfaceName) as HandlerFacade | undefined;
140
- if (!facade) continue;
141
-
142
- if (options?.filter && !options.filter(entity, frond.name)) continue;
143
- if (!surfaceName && entity.exposed === false) continue;
144
-
145
- const handler = (surfaceName
146
- ? frond.handlers.find((h) => h.address === entity.name && h.surface === surfaceName)
147
- : undefined) ?? handlerMap.get(entity.name);
148
- const effectiveOperations = app.operationsFor(entity.name, surfaceName);
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
- return routes;
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
  }