@fougere/adapter-rest 0.17.4-alpha.0 → 0.19.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.
- package/README.md +1 -1
- package/dist/routes/OperationMeta.d.ts +3 -9
- package/dist/routes/OperationMeta.d.ts.map +1 -1
- package/dist/routes/RouteDefinition.d.ts +2 -8
- package/dist/routes/RouteDefinition.d.ts.map +1 -1
- package/dist/routes/RouteDefinition.js +18 -23
- package/dist/routes/RouteDefinition.js.map +1 -1
- package/package.json +4 -4
- package/src/routes/OperationMeta.ts +4 -9
- package/src/routes/RouteDefinition.ts +22 -22
package/README.md
CHANGED
|
@@ -11,4 +11,4 @@ pnpm add @fougere/adapter-rest
|
|
|
11
11
|
|
|
12
12
|
Part of [Fougere](https://github.com/chok/fougere) — one schema, a gradient from
|
|
13
13
|
monolith to distributed, the same user code.
|
|
14
|
-
Reference documentation: [the site](https://fougere.dev/)
|
|
14
|
+
Reference documentation: [the site](https://fougere.dev/).
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
output?: SchemaView;
|
|
5
|
-
/** Canonical kind from core's EffectiveOperation. */
|
|
6
|
-
kind: 'query' | 'command';
|
|
7
|
-
/** The operation in words — see `RouteDefinition.description`. */
|
|
8
|
-
description?: string;
|
|
9
|
-
}
|
|
1
|
+
import type { EffectiveOperation } from '@fougere/core';
|
|
2
|
+
/** What a route reads of an effective operation — its `binding` is what the path is derived from. */
|
|
3
|
+
export type OperationMeta = Pick<EffectiveOperation, 'input' | 'output' | 'kind' | 'description'> & Partial<Pick<EffectiveOperation, 'binding'>>;
|
|
10
4
|
//# sourceMappingURL=OperationMeta.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OperationMeta.d.ts","sourceRoot":"","sources":["../../src/routes/OperationMeta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"OperationMeta.d.ts","sourceRoot":"","sources":["../../src/routes/OperationMeta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,qGAAqG;AACrG,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAC,GAC7F,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Fields } from '@fougere/schema';
|
|
2
2
|
import type { HttpMethod } from '@fougere/http';
|
|
3
|
-
import type { HandlerEntry as CoreHandlerEntry } from '@fougere/core';
|
|
3
|
+
import type { HandlerEntry as CoreHandlerEntry, OperationOverride } from '@fougere/core';
|
|
4
4
|
import type { OperationMeta } from './OperationMeta.js';
|
|
5
5
|
import type { GenerateRoutesOptions } from './GenerateRoutesOptions.js';
|
|
6
6
|
export interface RouteDefinition {
|
|
@@ -32,13 +32,7 @@ interface FrondLike {
|
|
|
32
32
|
presenters: PresenterEntry[];
|
|
33
33
|
surfaces?: Record<string, string[]>;
|
|
34
34
|
/** What `frond.config.ts` said per op — `rest:` is read here, `graphql:` next facade. */
|
|
35
|
-
operationsOverrides?: Record<string,
|
|
36
|
-
rest?: {
|
|
37
|
-
method?: string;
|
|
38
|
-
path?: string;
|
|
39
|
-
status?: number;
|
|
40
|
-
};
|
|
41
|
-
}>;
|
|
35
|
+
operationsOverrides?: Record<string, Pick<OperationOverride, 'rest'>>;
|
|
42
36
|
}
|
|
43
37
|
interface AppLike {
|
|
44
38
|
fronds: FrondLike[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteDefinition.d.ts","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"RouteDefinition.d.ts","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEzF,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,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,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,4DAA4D;AAC5D,KAAK,YAAY,GAAG,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC;AAS9E,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,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,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;CACvE;AAED,UAAU,OAAO;IACf,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;IACnF,kDAAkD;IAClD,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,CAAC;CAC1F;AA6DD,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,qBAA0B,GAAG,eAAe,EAAE,CAInG"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Visibility } from '@fougere/schema';
|
|
2
2
|
// ─── Naming conventions ─────────────────────────
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
3
|
+
/** The five a Crud handler brings answer at the collection or the row, never under their own name. */
|
|
4
|
+
const CRUD = new Set(['list', 'findById', 'create', 'update', 'delete']);
|
|
6
5
|
function pluralize(name) {
|
|
7
6
|
return name.endsWith('y')
|
|
8
7
|
? name.slice(0, -1) + 'ies'
|
|
@@ -24,25 +23,21 @@ function deriveMethod(opName, resolvedKind) {
|
|
|
24
23
|
return 'DELETE';
|
|
25
24
|
return 'POST';
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const withId = hasById(opName);
|
|
43
|
-
const cleanName = opName.replace('ById', '');
|
|
44
|
-
const segment = cleanName.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
|
|
45
|
-
return withId ? `${base}/:id/${segment}` : `${base}/${segment}`;
|
|
26
|
+
/**
|
|
27
|
+
* Read off the signature: what designates a row comes before the op, the other required values
|
|
28
|
+
* after it, and an optional one is left to the query string.
|
|
29
|
+
* `publish(id: Post['id'])` → `/posts/:id/publish`, `move(id: Post['id'], position: number)` →
|
|
30
|
+
* `/posts/:id/move/:position`, `byTag(tag: string)` → `/posts/by-tag/:tag`.
|
|
31
|
+
*/
|
|
32
|
+
function derivePath(address, opName, binding = []) {
|
|
33
|
+
const required = binding.flatMap(({ source, optional }) => source.kind === 'param' && !optional ? [source] : []);
|
|
34
|
+
const designating = required.filter((param) => param.identifies);
|
|
35
|
+
const following = required.filter((param) => !param.identifies);
|
|
36
|
+
const named = CRUD.has(opName) ? [] : [opName.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase())];
|
|
37
|
+
return ['', pluralize(address), ...designating.map(segmentOf), ...named, ...following.map(segmentOf)].join('/');
|
|
38
|
+
}
|
|
39
|
+
function segmentOf({ name }) {
|
|
40
|
+
return `:${name}`;
|
|
46
41
|
}
|
|
47
42
|
// ─── Route generation helpers ───────────────────
|
|
48
43
|
// Update routes carry the SAME fields: input omissibility is a projection of the
|
|
@@ -100,7 +95,7 @@ function routeFor({ address, frond, facade, opName, meta, options }) {
|
|
|
100
95
|
}
|
|
101
96
|
return {
|
|
102
97
|
method: override.method ?? deriveMethod(opName, meta.kind),
|
|
103
|
-
path: (options.prefix ?? '') + (override.path ?? derivePath(address, opName)),
|
|
98
|
+
path: (options.prefix ?? '') + (override.path ?? derivePath(address, opName, meta.binding)),
|
|
104
99
|
operationName: opName,
|
|
105
100
|
address,
|
|
106
101
|
handler: (invocation) => op(invocation),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteDefinition.js","sourceRoot":"","sources":["../../src/routes/RouteDefinition.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,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.
|
|
3
|
+
"version": "0.19.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.
|
|
33
|
-
"@fougere/schema": "0.
|
|
34
|
-
"@fougere/http": "0.
|
|
32
|
+
"@fougere/core": "0.19.0-alpha.0",
|
|
33
|
+
"@fougere/schema": "0.19.0-alpha.0",
|
|
34
|
+
"@fougere/http": "0.19.0-alpha.0"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EffectiveOperation } from '@fougere/core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** Canonical kind from core's EffectiveOperation. */
|
|
7
|
-
kind: 'query' | 'command';
|
|
8
|
-
/** The operation in words — see `RouteDefinition.description`. */
|
|
9
|
-
description?: string;
|
|
10
|
-
}
|
|
3
|
+
/** What a route reads of an effective operation — its `binding` is what the path is derived from. */
|
|
4
|
+
export type OperationMeta = Pick<EffectiveOperation, 'input' | 'output' | 'kind' | 'description'>
|
|
5
|
+
& Partial<Pick<EffectiveOperation, 'binding'>>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Fields } from '@fougere/schema';
|
|
2
2
|
import { Visibility } from '@fougere/schema';
|
|
3
3
|
import type { HttpMethod } from '@fougere/http';
|
|
4
|
-
import type { HandlerEntry as CoreHandlerEntry } from '@fougere/core';
|
|
4
|
+
import type { HandlerEntry as CoreHandlerEntry, OperationOverride } from '@fougere/core';
|
|
5
|
+
import type { BindingPlan } from '@fougere/core/descriptor';
|
|
5
6
|
import type { OperationMeta } from './OperationMeta.js';
|
|
6
7
|
import type { GenerateRoutesOptions } from './GenerateRoutesOptions.js';
|
|
7
8
|
|
|
@@ -47,7 +48,7 @@ interface FrondLike {
|
|
|
47
48
|
presenters: PresenterEntry[];
|
|
48
49
|
surfaces?: Record<string, string[]>;
|
|
49
50
|
/** What `frond.config.ts` said per op — `rest:` is read here, `graphql:` next facade. */
|
|
50
|
-
operationsOverrides?: Record<string,
|
|
51
|
+
operationsOverrides?: Record<string, Pick<OperationOverride, 'rest'>>;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
interface AppLike {
|
|
@@ -62,9 +63,8 @@ type HandlerFacade = Record<string, Function>;
|
|
|
62
63
|
|
|
63
64
|
// ─── Naming conventions ─────────────────────────
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
66
|
+
/** The five a Crud handler brings answer at the collection or the row, never under their own name. */
|
|
67
|
+
const CRUD = new Set(['list', 'findById', 'create', 'update', 'delete']);
|
|
68
68
|
|
|
69
69
|
function pluralize(name: string): string {
|
|
70
70
|
return name.endsWith('y')
|
|
@@ -91,23 +91,23 @@ function deriveMethod(
|
|
|
91
91
|
return 'POST';
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const segment = cleanName.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
|
|
94
|
+
/**
|
|
95
|
+
* Read off the signature: what designates a row comes before the op, the other required values
|
|
96
|
+
* after it, and an optional one is left to the query string.
|
|
97
|
+
* `publish(id: Post['id'])` → `/posts/:id/publish`, `move(id: Post['id'], position: number)` →
|
|
98
|
+
* `/posts/:id/move/:position`, `byTag(tag: string)` → `/posts/by-tag/:tag`.
|
|
99
|
+
*/
|
|
100
|
+
function derivePath(address: string, opName: string, binding: BindingPlan = []): string {
|
|
101
|
+
const required = binding.flatMap(({ source, optional }) => source.kind === 'param' && !optional ? [source] : []);
|
|
102
|
+
const designating = required.filter((param) => param.identifies);
|
|
103
|
+
const following = required.filter((param) => !param.identifies);
|
|
104
|
+
const named = CRUD.has(opName) ? [] : [opName.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase())];
|
|
105
|
+
|
|
106
|
+
return ['', pluralize(address), ...designating.map(segmentOf), ...named, ...following.map(segmentOf)].join('/');
|
|
107
|
+
}
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
function segmentOf({ name }: { name: string }): string {
|
|
110
|
+
return `:${name}`;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// ─── Route generation helpers ───────────────────
|
|
@@ -194,7 +194,7 @@ function routeFor({ address, frond, facade, opName, meta, options }: Projecting)
|
|
|
194
194
|
|
|
195
195
|
return {
|
|
196
196
|
method: override.method ?? deriveMethod(opName, meta.kind),
|
|
197
|
-
path: (options.prefix ?? '') + (override.path ?? derivePath(address, opName)),
|
|
197
|
+
path: (options.prefix ?? '') + (override.path ?? derivePath(address, opName, meta.binding)),
|
|
198
198
|
operationName: opName,
|
|
199
199
|
address,
|
|
200
200
|
handler: (invocation) => op(invocation),
|