cloesce 0.0.4-unstable.2 → 0.0.4-unstable.4
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 +99 -7
- package/dist/cli.js +6 -3
- package/dist/common.d.ts +221 -19
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +112 -15
- package/dist/extractor/extract.d.ts +9 -9
- package/dist/extractor/extract.d.ts.map +1 -1
- package/dist/extractor/extract.js +65 -99
- package/dist/generator.wasm +0 -0
- package/dist/orm.wasm +0 -0
- package/dist/router/crud.js +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +3 -3
- package/dist/router/wasm.d.ts +1 -1
- package/dist/router/wasm.d.ts.map +1 -1
- package/dist/router/wasm.js +1 -1
- package/dist/ui/backend.d.ts +333 -9
- package/dist/ui/backend.d.ts.map +1 -1
- package/dist/ui/backend.js +269 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Internal documentation going over design decisions and general thoughts for each
|
|
|
17
17
|
- Create an NPM project and install cloesce
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
npm i cloesce@0.0.4-unstable.
|
|
20
|
+
npm i cloesce@0.0.4-unstable.3
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
2. TypeScript
|
|
@@ -266,7 +266,7 @@ export class Person {
|
|
|
266
266
|
}
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
Data sources are just SQL views and can be invoked in your queries. They are aliased in such a way that its similiar to object properties. The frontend chooses which datasource to use in it's API client. `null` is a valid option, meaning no joins will occur.
|
|
269
|
+
Data sources are just SQL views and can be invoked in your queries. They are aliased in such a way that its similiar to object properties. The frontend chooses which datasource to use in it's API client (all instantiated methods have an implicit DataSource parameter). `null` is a valid option, meaning no joins will occur.
|
|
270
270
|
|
|
271
271
|
```ts
|
|
272
272
|
@D1
|
|
@@ -288,17 +288,107 @@ export class Person {
|
|
|
288
288
|
@GET
|
|
289
289
|
static async get(id: number, @Inject env: WranglerEnv): Promise<Person> {
|
|
290
290
|
let records = await env.db
|
|
291
|
-
.prepare("SELECT * FROM [Person.default] WHERE [
|
|
291
|
+
.prepare("SELECT * FROM [Person.default] WHERE [id] = ?") // Person.default is the SQL view generated from the IncludeTree
|
|
292
292
|
.bind(id)
|
|
293
293
|
.run();
|
|
294
294
|
|
|
295
|
-
let persons = Orm.
|
|
295
|
+
let persons = Orm.mapSql(Person, records.results, Person.default);
|
|
296
296
|
return persons.value[0];
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
-
Note that the `get` code can be simplified using CRUD methods or ORM
|
|
301
|
+
Note that the `get` code can be simplified using CRUD methods or the ORM primitive `get`.
|
|
302
|
+
|
|
303
|
+
#### View Aliasing
|
|
304
|
+
|
|
305
|
+
The generated views will always be aliased so that they can be accessed in an object like notation. For example, given some `Horse` that has a relationship with `Like`:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
@D1
|
|
309
|
+
export class Horse {
|
|
310
|
+
@PrimaryKey
|
|
311
|
+
id: Integer;
|
|
312
|
+
|
|
313
|
+
name: string;
|
|
314
|
+
bio: string | null;
|
|
315
|
+
|
|
316
|
+
@OneToMany("horseId1")
|
|
317
|
+
likes: Like[];
|
|
318
|
+
|
|
319
|
+
@DataSource
|
|
320
|
+
static readonly default: IncludeTree<Horse> = {
|
|
321
|
+
likes: { horse2: {} },
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@D1
|
|
326
|
+
export class Like {
|
|
327
|
+
@PrimaryKey
|
|
328
|
+
id: Integer;
|
|
329
|
+
|
|
330
|
+
@ForeignKey(Horse)
|
|
331
|
+
horseId1: Integer;
|
|
332
|
+
|
|
333
|
+
@ForeignKey(Horse)
|
|
334
|
+
horseId2: Integer;
|
|
335
|
+
|
|
336
|
+
@OneToOne("horseId2")
|
|
337
|
+
horse2: Horse | undefined;
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
If you wanted to find all horses that like one another, a valid SQL query using the `default` data source would look like:
|
|
342
|
+
|
|
343
|
+
```sql
|
|
344
|
+
SELECT * FROM [Horse.default] as H1
|
|
345
|
+
WHERE
|
|
346
|
+
H1.[id] = ?
|
|
347
|
+
AND EXISTS (
|
|
348
|
+
SELECT 1
|
|
349
|
+
FROM [Horse.default] AS H2
|
|
350
|
+
WHERE H2.[id] = H1.[likes.horse2.id]
|
|
351
|
+
AND H2.[likes.horse2.id] = H1.[id]
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The actual generated view for `default` looks like:
|
|
356
|
+
|
|
357
|
+
```sql
|
|
358
|
+
CREATE VIEW IF NOT EXISTS "Horse.default" AS
|
|
359
|
+
SELECT
|
|
360
|
+
"Horse"."id" AS "id",
|
|
361
|
+
"Horse"."name" AS "name",
|
|
362
|
+
"Horse"."bio" AS "bio",
|
|
363
|
+
"Like"."id" AS "likes.id",
|
|
364
|
+
"Like"."horseId1" AS "likes.horseId1",
|
|
365
|
+
"Like"."horseId2" AS "likes.horseId2",
|
|
366
|
+
"Horse1"."id" AS "likes.horse2.id",
|
|
367
|
+
"Horse1"."name" AS "likes.horse2.name",
|
|
368
|
+
"Horse1"."bio" AS "likes.horse2.bio"
|
|
369
|
+
FROM
|
|
370
|
+
"Horse"
|
|
371
|
+
LEFT JOIN
|
|
372
|
+
"Like" ON "Horse"."id" = "Like"."horseId1"
|
|
373
|
+
LEFT JOIN
|
|
374
|
+
"Horse" AS "Horse1" ON "Like"."horseId2" = "Horse1"."id";
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### DataSourceOf<T>
|
|
378
|
+
|
|
379
|
+
If it is important to determine what data source the frontend called the instantiated method with, the type `DataSourceOf<T>` allows explicit data source parameters:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
@D1
|
|
383
|
+
class Foo {
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
@POST
|
|
387
|
+
bar(ds: DataSourceOf<Foo>) {
|
|
388
|
+
// ds = "DataSource1" | "DataSource2" | ... | "none"
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
302
392
|
|
|
303
393
|
### One to Many
|
|
304
394
|
|
|
@@ -404,10 +494,12 @@ class Horse {
|
|
|
404
494
|
|
|
405
495
|
### CRUD Methods
|
|
406
496
|
|
|
407
|
-
Generic GET, POST, PATCH (and in a future version, DEL) boilerplate methods do not need to be copied around. Cloesce supports CRUD generation, a syntactic sugar that adds the methods to the compiler output.
|
|
497
|
+
Generic `GET, POST, PATCH` (and in a future version, DEL) boilerplate methods do not need to be copied around. Cloesce supports CRUD generation, a syntactic sugar that adds the methods to the compiler output.
|
|
498
|
+
|
|
499
|
+
The `SAVE` method is an `upsert`, meaning it both inserts and updates in the same query.
|
|
408
500
|
|
|
409
501
|
```ts
|
|
410
|
-
@CRUD(["
|
|
502
|
+
@CRUD(["SAVE", "GET", "LIST"])
|
|
411
503
|
@D1
|
|
412
504
|
export class CrudHaver {
|
|
413
505
|
@PrimaryKey
|
package/dist/cli.js
CHANGED
|
@@ -189,7 +189,7 @@ async function extract(opts) {
|
|
|
189
189
|
const json = JSON.stringify(ast, null, 4);
|
|
190
190
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
191
191
|
fs.writeFileSync(outPath, json);
|
|
192
|
-
console.log(`CIDL
|
|
192
|
+
console.log(`CIDL extracted to ${outPath}`);
|
|
193
193
|
return { outPath, projectName: cloesceProjectName };
|
|
194
194
|
}
|
|
195
195
|
catch (err) {
|
|
@@ -293,10 +293,13 @@ function formatErr(e) {
|
|
|
293
293
|
const { description, suggestion } = getErrorInfo(e.code);
|
|
294
294
|
const contextLine = e.context ? `Context: ${e.context}\n` : "";
|
|
295
295
|
const snippetLine = e.snippet ? `${e.snippet}\n\n` : "";
|
|
296
|
-
return
|
|
296
|
+
return `
|
|
297
|
+
==== CLOESCE ERROR ====
|
|
297
298
|
Error [${ExtractorErrorCode[e.code]}]: ${description}
|
|
298
299
|
Phase: TypeScript IDL Extraction
|
|
299
|
-
${contextLine}${snippetLine}Suggested fix: ${suggestion}
|
|
300
|
+
${contextLine}${snippetLine}Suggested fix: ${suggestion}
|
|
301
|
+
|
|
302
|
+
`;
|
|
300
303
|
}
|
|
301
304
|
run(cmds, process.argv.slice(2)).catch((err) => {
|
|
302
305
|
console.error(err);
|
package/dist/common.d.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
export type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? {
|
|
2
|
-
[K in keyof T]?: DeepPartial<T[K]>;
|
|
3
|
-
} : T;
|
|
4
|
-
export type Either<L, R> = {
|
|
5
|
-
ok: false;
|
|
6
|
-
value: L;
|
|
7
|
-
} | {
|
|
8
|
-
ok: true;
|
|
9
|
-
value: R;
|
|
10
|
-
};
|
|
11
|
-
export declare function left<L>(value: L): Either<L, never>;
|
|
12
|
-
export declare function right<R>(value: R): Either<never, R>;
|
|
13
1
|
export declare enum ExtractorErrorCode {
|
|
14
2
|
MissingExport = 0,
|
|
15
3
|
AppMissingDefaultExport = 1,
|
|
16
4
|
UnknownType = 2,
|
|
17
5
|
MultipleGenericType = 3,
|
|
18
|
-
|
|
6
|
+
InvalidDataSourceDefinition = 4,
|
|
19
7
|
InvalidPartialType = 5,
|
|
20
8
|
InvalidIncludeTree = 6,
|
|
21
9
|
InvalidAttributeModifier = 7,
|
|
@@ -25,9 +13,10 @@ export declare enum ExtractorErrorCode {
|
|
|
25
13
|
MissingNavigationPropertyReference = 11,
|
|
26
14
|
MissingManyToManyUniqueId = 12,
|
|
27
15
|
MissingPrimaryKey = 13,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
MissingDatabaseBinding = 14,
|
|
17
|
+
MissingWranglerEnv = 15,
|
|
18
|
+
TooManyWranglerEnvs = 16,
|
|
19
|
+
MissingFile = 17
|
|
31
20
|
}
|
|
32
21
|
export declare function getErrorInfo(code: ExtractorErrorCode): {
|
|
33
22
|
description: string;
|
|
@@ -40,6 +29,141 @@ export declare class ExtractorError {
|
|
|
40
29
|
constructor(code: ExtractorErrorCode);
|
|
41
30
|
addContext(fn: (val: string | undefined) => string | undefined): void;
|
|
42
31
|
}
|
|
32
|
+
type DeepPartialInner<T> = T extends (infer U)[] ? DeepPartialInner<U>[] : T extends object ? {
|
|
33
|
+
[K in keyof T]?: DeepPartialInner<T[K]>;
|
|
34
|
+
} : T | (null extends T ? null : never);
|
|
35
|
+
/**
|
|
36
|
+
* Recursively makes all properties of a type optional — including nested objects and arrays.
|
|
37
|
+
*
|
|
38
|
+
* Similar to TypeScript's built-in `Partial<T>`, but applies the transformation deeply across
|
|
39
|
+
* all nested structures. Useful for defining "patch" or "update" objects where only a subset
|
|
40
|
+
* of properties may be provided.
|
|
41
|
+
*
|
|
42
|
+
* **Apart of the Cloesce method grammar**, meaning the type can be apart of method parameters
|
|
43
|
+
* or return types and the generated workers and client API will act accordingly.
|
|
44
|
+
*
|
|
45
|
+
* @template T
|
|
46
|
+
* The target type to make deeply partial.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* - **Objects:** All properties become optional, and their values are recursively wrapped in `DeepPartial`.
|
|
50
|
+
* - **Arrays:** Arrays are preserved, but their elements are recursively made partial.
|
|
51
|
+
* - **Scalars:** Primitive values (string, number, boolean, etc.) remain unchanged.
|
|
52
|
+
* - **Nullable types:** If `null` is assignable to the type, it remains allowed.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* class User {
|
|
57
|
+
* id: string;
|
|
58
|
+
* profile: {
|
|
59
|
+
* name: string;
|
|
60
|
+
* age: number;
|
|
61
|
+
* };
|
|
62
|
+
* tags: string[];
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* // The resulting type:
|
|
66
|
+
* // {
|
|
67
|
+
* // id?: string;
|
|
68
|
+
* // profile?: { name?: string; age?: number };
|
|
69
|
+
* // tags?: (string | undefined)[];
|
|
70
|
+
* // }
|
|
71
|
+
* type PartialUser = DeepPartial<User>;
|
|
72
|
+
*
|
|
73
|
+
* const patch: PartialUser = {
|
|
74
|
+
* profile: { age: 30 } // ok
|
|
75
|
+
* };
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export type DeepPartial<T> = DeepPartialInner<T> & {
|
|
79
|
+
__brand?: "Partial";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A functional result type representing a computation that can either succeed (`ok: true`)
|
|
83
|
+
* or fail (`ok: false`).
|
|
84
|
+
*
|
|
85
|
+
* `Either<L, R>` is used throughout Cloesce to return structured success/error values
|
|
86
|
+
* instead of throwing exceptions.
|
|
87
|
+
* - When `ok` is `true`, `value` contains the success result of type `R`.
|
|
88
|
+
* - When `ok` is `false`, `value` contains the error information of type `L`.
|
|
89
|
+
*
|
|
90
|
+
* This pattern makes control flow predictable and encourages explicit handling
|
|
91
|
+
* of failure cases.
|
|
92
|
+
*
|
|
93
|
+
* Example:
|
|
94
|
+
* ```ts
|
|
95
|
+
* const result: Either<string, number> = compute();
|
|
96
|
+
*
|
|
97
|
+
* if (!result.ok) {
|
|
98
|
+
* console.error("Failed:", result.value);
|
|
99
|
+
* } else {
|
|
100
|
+
* console.log("Success:", result.value);
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export type Either<L, R> = {
|
|
105
|
+
ok: false;
|
|
106
|
+
value: L;
|
|
107
|
+
} | {
|
|
108
|
+
ok: true;
|
|
109
|
+
value: R;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Creates a failed `Either` result.
|
|
113
|
+
*
|
|
114
|
+
* Typically used to represent an error condition or unsuccessful operation.
|
|
115
|
+
*
|
|
116
|
+
* @param value The error or failure value to wrap.
|
|
117
|
+
* @returns An `Either` with `ok: false` and the given value.
|
|
118
|
+
*/
|
|
119
|
+
export declare function left<L>(value: L): Either<L, never>;
|
|
120
|
+
/**
|
|
121
|
+
* Creates a successful `Either` result.
|
|
122
|
+
*
|
|
123
|
+
* Typically used to represent a successful operation while maintaining
|
|
124
|
+
* a consistent `Either`-based return type.
|
|
125
|
+
*
|
|
126
|
+
* @param value The success value to wrap.
|
|
127
|
+
* @returns An `Either` with `ok: true` and the given value.
|
|
128
|
+
*/
|
|
129
|
+
export declare function right<R>(value: R): Either<never, R>;
|
|
130
|
+
/**
|
|
131
|
+
* Represents the result of an HTTP operation in a monadic style.
|
|
132
|
+
*
|
|
133
|
+
* This type provides a uniform way to handle both success and error
|
|
134
|
+
* outcomes of HTTP requests, similar to a `Result` or `Either` monad.
|
|
135
|
+
*
|
|
136
|
+
* It ensures that every HTTP response can be handled in a type-safe,
|
|
137
|
+
* predictable way without throwing exceptions.
|
|
138
|
+
*
|
|
139
|
+
* @template T The type of the successful response data.
|
|
140
|
+
*
|
|
141
|
+
* @property {boolean} ok
|
|
142
|
+
* Indicates whether the HTTP request was successful (`true` for success, `false` for error).
|
|
143
|
+
* This is analogous to `Response.ok` in the Fetch API.
|
|
144
|
+
*
|
|
145
|
+
* @property {number} status
|
|
146
|
+
* The numeric HTTP status code (e.g., 200, 404, 500).
|
|
147
|
+
*
|
|
148
|
+
* @property {T} [data]
|
|
149
|
+
* The parsed response payload, present only when `ok` is `true`.
|
|
150
|
+
*
|
|
151
|
+
* @property {string} [message]
|
|
152
|
+
* An optional human-readable error message or diagnostic information,
|
|
153
|
+
* typically provided when `ok` is `false`.
|
|
154
|
+
*
|
|
155
|
+
* ## Worker APIs
|
|
156
|
+
*
|
|
157
|
+
* HttpResult is a first-class-citizen in the grammar in Cloesce. Methods can return HttpResults
|
|
158
|
+
* which will be serialized on the client api.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* bar(): HttpResult<Integer> {
|
|
163
|
+
* return { ok: false, status: 401, message: "forbidden"}
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
43
167
|
export type HttpResult<T = unknown> = {
|
|
44
168
|
ok: boolean;
|
|
45
169
|
status: number;
|
|
@@ -57,18 +181,93 @@ export type KeysOfType<T, U> = {
|
|
|
57
181
|
[K in keyof T]: T[K] extends U ? (K extends string ? K : never) : never;
|
|
58
182
|
}[keyof T];
|
|
59
183
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
184
|
+
* Represents the core middleware container for a Cloesce application.
|
|
185
|
+
*
|
|
186
|
+
* The `CloesceApp` class provides scoped middleware registration and
|
|
187
|
+
* management across three primary levels of execution:
|
|
188
|
+
*
|
|
189
|
+
* 1. **Global Middleware** — Executed before any routing or model resolution occurs.
|
|
190
|
+
* 2. **Model-Level Middleware** — Executed for requests targeting a specific model type.
|
|
191
|
+
* 3. **Method-Level Middleware** — Executed for requests targeting a specific method on a model.
|
|
192
|
+
*
|
|
193
|
+
* When an instance of `CloesceApp` is exported from `app.cloesce.ts`,
|
|
194
|
+
* it becomes the central container that the Cloesce runtime uses to
|
|
195
|
+
* assemble and apply middleware in the correct execution order.
|
|
196
|
+
*
|
|
197
|
+
* ### Middleware Execution Order
|
|
198
|
+
* Middleware is executed in FIFO order per scope. For example:
|
|
199
|
+
* ```ts
|
|
200
|
+
* app.use(Foo, A);
|
|
201
|
+
* app.use(Foo, B);
|
|
202
|
+
* app.use(Foo, C);
|
|
203
|
+
* // Executed in order: A → B → C
|
|
204
|
+
* ```
|
|
205
|
+
*
|
|
206
|
+
* Each middleware function (`MiddlewareFn`) can optionally short-circuit
|
|
207
|
+
* execution by returning a result, in which case subsequent middleware
|
|
208
|
+
* at the same or lower scope will not run.
|
|
209
|
+
*
|
|
210
|
+
* ### Example Usage
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { app } from "cloesce";
|
|
213
|
+
*
|
|
214
|
+
* // Global authentication middleware
|
|
215
|
+
* app.useGlobal((request, env, di) => {
|
|
216
|
+
* // ... authenticate and inject user
|
|
217
|
+
* });
|
|
218
|
+
*
|
|
219
|
+
* // Model-level authorization
|
|
220
|
+
* app.useModel(User, (user) => user.hasPermissions([UserPermissions.canUseFoo]));
|
|
221
|
+
*
|
|
222
|
+
* // Method-level middleware (e.g., CRUD operation)
|
|
223
|
+
* app.useMethod(Foo, "someMethod", (user) => user.hasPermissions([UserPermissions.canUseFooMethod]));
|
|
224
|
+
* ```
|
|
62
225
|
*/
|
|
63
226
|
export declare class CloesceApp {
|
|
64
227
|
global: MiddlewareFn[];
|
|
65
228
|
model: Map<string, MiddlewareFn[]>;
|
|
66
229
|
method: Map<string, Map<string, MiddlewareFn[]>>;
|
|
230
|
+
/**
|
|
231
|
+
* Registers a new global middleware function.
|
|
232
|
+
*
|
|
233
|
+
* Global middleware runs before all routing and model resolution.
|
|
234
|
+
* It is the ideal place to perform tasks such as:
|
|
235
|
+
* - Authentication (e.g., JWT verification)
|
|
236
|
+
* - Global request logging
|
|
237
|
+
* - Dependency injection of shared context
|
|
238
|
+
*
|
|
239
|
+
* @param m - The middleware function to register.
|
|
240
|
+
*/
|
|
67
241
|
useGlobal(m: MiddlewareFn): void;
|
|
242
|
+
/**
|
|
243
|
+
* Registers middleware for a specific model type.
|
|
244
|
+
*
|
|
245
|
+
* Model-level middleware runs after all global middleware,
|
|
246
|
+
* but before method-specific middleware. This scope allows
|
|
247
|
+
* logic to be applied consistently across all endpoints
|
|
248
|
+
* associated with a given model (e.g., authorization).
|
|
249
|
+
*
|
|
250
|
+
* @typeParam T - The model type.
|
|
251
|
+
* @param ctor - The model constructor (used to derive its name).
|
|
252
|
+
* @param m - The middleware function to register.
|
|
253
|
+
*/
|
|
68
254
|
useModel<T>(ctor: new () => T, m: MiddlewareFn): void;
|
|
255
|
+
/**
|
|
256
|
+
* Registers middleware for a specific method on a model.
|
|
257
|
+
*
|
|
258
|
+
* Method-level middleware is executed after model middleware,
|
|
259
|
+
* and before the method implementation itself. It can be used for:
|
|
260
|
+
* - Fine-grained permission checks
|
|
261
|
+
* - Custom logging or tracing per endpoint
|
|
262
|
+
*
|
|
263
|
+
* @typeParam T - The model type.
|
|
264
|
+
* @param ctor - The model constructor (used to derive its name).
|
|
265
|
+
* @param method - The method name on the model.
|
|
266
|
+
* @param m - The middleware function to register.
|
|
267
|
+
*/
|
|
69
268
|
useMethod<T>(ctor: new () => T, method: KeysOfType<T, (...args: any) => any>, m: MiddlewareFn): void;
|
|
70
269
|
}
|
|
71
|
-
export type CrudKind = "
|
|
270
|
+
export type CrudKind = "SAVE" | "GET" | "LIST";
|
|
72
271
|
export type CidlType = "Void" | "Integer" | "Real" | "Text" | "Blob" | "DateIso" | "Boolean" | {
|
|
73
272
|
DataSource: string;
|
|
74
273
|
} | {
|
|
@@ -133,6 +332,7 @@ export interface Model {
|
|
|
133
332
|
navigation_properties: NavigationProperty[];
|
|
134
333
|
methods: Record<string, ModelMethod>;
|
|
135
334
|
data_sources: Record<string, DataSource>;
|
|
335
|
+
cruds: CrudKind[];
|
|
136
336
|
source_path: string;
|
|
137
337
|
}
|
|
138
338
|
export interface PlainOldObject {
|
|
@@ -151,6 +351,7 @@ export interface DataSource {
|
|
|
151
351
|
export interface WranglerEnv {
|
|
152
352
|
name: string;
|
|
153
353
|
source_path: string;
|
|
354
|
+
db_binding: string;
|
|
154
355
|
}
|
|
155
356
|
export interface CloesceAst {
|
|
156
357
|
version: string;
|
|
@@ -161,4 +362,5 @@ export interface CloesceAst {
|
|
|
161
362
|
poos: Record<string, PlainOldObject>;
|
|
162
363
|
app_source: string | null;
|
|
163
364
|
}
|
|
365
|
+
export {};
|
|
164
366
|
//# sourceMappingURL=common.d.ts.map
|
package/dist/common.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA,oBAAY,kBAAkB;IAC5B,aAAa,IAAA;IACb,uBAAuB,IAAA;IACvB,WAAW,IAAA;IACX,mBAAmB,IAAA;IACnB,2BAA2B,IAAA;IAC3B,kBAAkB,IAAA;IAClB,kBAAkB,IAAA;IAClB,wBAAwB,IAAA;IACxB,wBAAwB,IAAA;IACxB,kCAAkC,IAAA;IAClC,kCAAkC,KAAA;IAClC,kCAAkC,KAAA;IAClC,yBAAyB,KAAA;IACzB,iBAAiB,KAAA;IACjB,sBAAsB,KAAA;IACtB,kBAAkB,KAAA;IAClB,mBAAmB,KAAA;IACnB,WAAW,KAAA;CACZ;AAyFD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB;iBArFpC,MAAM;gBAAc,MAAM;EAuF1C;AAED,qBAAa,cAAc;IAIN,IAAI,EAAE,kBAAkB;IAH3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;gBAEE,IAAI,EAAE,kBAAkB;IAE3C,UAAU,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS;CAG/D;AAED,KAAK,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC5C,gBAAgB,CAAC,CAAC,CAAC,EAAE,GACrB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC3C,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAE5E;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAElD;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAEnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEhD,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,gBAAgB,KACjB,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;AAErC,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK;CACxE,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,UAAU;IACd,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAa;IAC/C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAa;IAEpE;;;;;;;;;;OAUG;IACI,SAAS,CAAC,CAAC,EAAE,YAAY;IAIhC;;;;;;;;;;;OAWG;IACI,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE,YAAY;IAQrD;;;;;;;;;;;;OAYG;IACI,SAAS,CAAC,CAAC,EAChB,IAAI,EAAE,UAAU,CAAC,EACjB,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAC5C,CAAC,EAAE,YAAY;CAalB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/C,MAAM,MAAM,QAAQ,GAChB,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,GACT,SAAS,GACT;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,UAAU,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE7B,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAEpD;AAED,oBAAY,QAAQ;IAClB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,KAAK,UAAU;IACf,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,eAAe,CAAC;IACvB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,QAAQ,CAAC;IACpB,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACnC;IAAE,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACpC;IAAE,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,sBAAsB,CAAC;CAC9B;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,kBAAkB,GACtB,QAAQ,CAIV;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,eAAe,CAAC;IAC7B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,qBAAqB,EAAE,kBAAkB,EAAE,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACzC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;CAChC;AAED,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;IACvB,YAAY,EAAE,WAAW,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
|
package/dist/common.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
export function left(value) {
|
|
2
|
-
return { ok: false, value };
|
|
3
|
-
}
|
|
4
|
-
export function right(value) {
|
|
5
|
-
return { ok: true, value };
|
|
6
|
-
}
|
|
7
1
|
export var ExtractorErrorCode;
|
|
8
2
|
(function (ExtractorErrorCode) {
|
|
9
3
|
ExtractorErrorCode[ExtractorErrorCode["MissingExport"] = 0] = "MissingExport";
|
|
10
4
|
ExtractorErrorCode[ExtractorErrorCode["AppMissingDefaultExport"] = 1] = "AppMissingDefaultExport";
|
|
11
5
|
ExtractorErrorCode[ExtractorErrorCode["UnknownType"] = 2] = "UnknownType";
|
|
12
6
|
ExtractorErrorCode[ExtractorErrorCode["MultipleGenericType"] = 3] = "MultipleGenericType";
|
|
13
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
7
|
+
ExtractorErrorCode[ExtractorErrorCode["InvalidDataSourceDefinition"] = 4] = "InvalidDataSourceDefinition";
|
|
14
8
|
ExtractorErrorCode[ExtractorErrorCode["InvalidPartialType"] = 5] = "InvalidPartialType";
|
|
15
9
|
ExtractorErrorCode[ExtractorErrorCode["InvalidIncludeTree"] = 6] = "InvalidIncludeTree";
|
|
16
10
|
ExtractorErrorCode[ExtractorErrorCode["InvalidAttributeModifier"] = 7] = "InvalidAttributeModifier";
|
|
@@ -20,9 +14,10 @@ export var ExtractorErrorCode;
|
|
|
20
14
|
ExtractorErrorCode[ExtractorErrorCode["MissingNavigationPropertyReference"] = 11] = "MissingNavigationPropertyReference";
|
|
21
15
|
ExtractorErrorCode[ExtractorErrorCode["MissingManyToManyUniqueId"] = 12] = "MissingManyToManyUniqueId";
|
|
22
16
|
ExtractorErrorCode[ExtractorErrorCode["MissingPrimaryKey"] = 13] = "MissingPrimaryKey";
|
|
23
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
24
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
25
|
-
ExtractorErrorCode[ExtractorErrorCode["
|
|
17
|
+
ExtractorErrorCode[ExtractorErrorCode["MissingDatabaseBinding"] = 14] = "MissingDatabaseBinding";
|
|
18
|
+
ExtractorErrorCode[ExtractorErrorCode["MissingWranglerEnv"] = 15] = "MissingWranglerEnv";
|
|
19
|
+
ExtractorErrorCode[ExtractorErrorCode["TooManyWranglerEnvs"] = 16] = "TooManyWranglerEnvs";
|
|
20
|
+
ExtractorErrorCode[ExtractorErrorCode["MissingFile"] = 17] = "MissingFile";
|
|
26
21
|
})(ExtractorErrorCode || (ExtractorErrorCode = {}));
|
|
27
22
|
const errorInfoMap = {
|
|
28
23
|
[ExtractorErrorCode.MissingExport]: {
|
|
@@ -45,9 +40,9 @@ const errorInfoMap = {
|
|
|
45
40
|
description: "Cloesce does not yet support types with multiple generics",
|
|
46
41
|
suggestion: "Simplify your type to use only a single generic parameter, ie Foo<T>",
|
|
47
42
|
},
|
|
48
|
-
[ExtractorErrorCode.
|
|
49
|
-
description: "Data Sources must be
|
|
50
|
-
suggestion: "Declare your data source as `static readonly
|
|
43
|
+
[ExtractorErrorCode.InvalidDataSourceDefinition]: {
|
|
44
|
+
description: "Data Sources must be explicitly typed as a static Include Tree",
|
|
45
|
+
suggestion: "Declare your data source as `static readonly _: IncludeTree<Model>`",
|
|
51
46
|
},
|
|
52
47
|
[ExtractorErrorCode.InvalidIncludeTree]: {
|
|
53
48
|
description: "Invalid Include Tree",
|
|
@@ -81,6 +76,10 @@ const errorInfoMap = {
|
|
|
81
76
|
description: "Missing primary key on a model",
|
|
82
77
|
suggestion: "Add a primary key field to your model (e.g., `id: number`)",
|
|
83
78
|
},
|
|
79
|
+
[ExtractorErrorCode.MissingDatabaseBinding]: {
|
|
80
|
+
description: "Missing a database binding in the WranglerEnv definition",
|
|
81
|
+
suggestion: "Add a `D1Database` to your WranglerEnv",
|
|
82
|
+
},
|
|
84
83
|
[ExtractorErrorCode.MissingWranglerEnv]: {
|
|
85
84
|
description: "Missing a wrangler environment definition in the project",
|
|
86
85
|
suggestion: "Add a @WranglerEnv class in your project.",
|
|
@@ -109,16 +108,101 @@ export class ExtractorError {
|
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* Creates a failed `Either` result.
|
|
112
|
+
*
|
|
113
|
+
* Typically used to represent an error condition or unsuccessful operation.
|
|
114
|
+
*
|
|
115
|
+
* @param value The error or failure value to wrap.
|
|
116
|
+
* @returns An `Either` with `ok: false` and the given value.
|
|
117
|
+
*/
|
|
118
|
+
export function left(value) {
|
|
119
|
+
return { ok: false, value };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Creates a successful `Either` result.
|
|
123
|
+
*
|
|
124
|
+
* Typically used to represent a successful operation while maintaining
|
|
125
|
+
* a consistent `Either`-based return type.
|
|
126
|
+
*
|
|
127
|
+
* @param value The success value to wrap.
|
|
128
|
+
* @returns An `Either` with `ok: true` and the given value.
|
|
129
|
+
*/
|
|
130
|
+
export function right(value) {
|
|
131
|
+
return { ok: true, value };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Represents the core middleware container for a Cloesce application.
|
|
135
|
+
*
|
|
136
|
+
* The `CloesceApp` class provides scoped middleware registration and
|
|
137
|
+
* management across three primary levels of execution:
|
|
138
|
+
*
|
|
139
|
+
* 1. **Global Middleware** — Executed before any routing or model resolution occurs.
|
|
140
|
+
* 2. **Model-Level Middleware** — Executed for requests targeting a specific model type.
|
|
141
|
+
* 3. **Method-Level Middleware** — Executed for requests targeting a specific method on a model.
|
|
142
|
+
*
|
|
143
|
+
* When an instance of `CloesceApp` is exported from `app.cloesce.ts`,
|
|
144
|
+
* it becomes the central container that the Cloesce runtime uses to
|
|
145
|
+
* assemble and apply middleware in the correct execution order.
|
|
146
|
+
*
|
|
147
|
+
* ### Middleware Execution Order
|
|
148
|
+
* Middleware is executed in FIFO order per scope. For example:
|
|
149
|
+
* ```ts
|
|
150
|
+
* app.use(Foo, A);
|
|
151
|
+
* app.use(Foo, B);
|
|
152
|
+
* app.use(Foo, C);
|
|
153
|
+
* // Executed in order: A → B → C
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* Each middleware function (`MiddlewareFn`) can optionally short-circuit
|
|
157
|
+
* execution by returning a result, in which case subsequent middleware
|
|
158
|
+
* at the same or lower scope will not run.
|
|
159
|
+
*
|
|
160
|
+
* ### Example Usage
|
|
161
|
+
* ```ts
|
|
162
|
+
* import { app } from "cloesce";
|
|
163
|
+
*
|
|
164
|
+
* // Global authentication middleware
|
|
165
|
+
* app.useGlobal((request, env, di) => {
|
|
166
|
+
* // ... authenticate and inject user
|
|
167
|
+
* });
|
|
168
|
+
*
|
|
169
|
+
* // Model-level authorization
|
|
170
|
+
* app.useModel(User, (user) => user.hasPermissions([UserPermissions.canUseFoo]));
|
|
171
|
+
*
|
|
172
|
+
* // Method-level middleware (e.g., CRUD operation)
|
|
173
|
+
* app.useMethod(Foo, "someMethod", (user) => user.hasPermissions([UserPermissions.canUseFooMethod]));
|
|
174
|
+
* ```
|
|
114
175
|
*/
|
|
115
176
|
export class CloesceApp {
|
|
116
177
|
global = [];
|
|
117
178
|
model = new Map();
|
|
118
179
|
method = new Map();
|
|
180
|
+
/**
|
|
181
|
+
* Registers a new global middleware function.
|
|
182
|
+
*
|
|
183
|
+
* Global middleware runs before all routing and model resolution.
|
|
184
|
+
* It is the ideal place to perform tasks such as:
|
|
185
|
+
* - Authentication (e.g., JWT verification)
|
|
186
|
+
* - Global request logging
|
|
187
|
+
* - Dependency injection of shared context
|
|
188
|
+
*
|
|
189
|
+
* @param m - The middleware function to register.
|
|
190
|
+
*/
|
|
119
191
|
useGlobal(m) {
|
|
120
192
|
this.global.push(m);
|
|
121
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Registers middleware for a specific model type.
|
|
196
|
+
*
|
|
197
|
+
* Model-level middleware runs after all global middleware,
|
|
198
|
+
* but before method-specific middleware. This scope allows
|
|
199
|
+
* logic to be applied consistently across all endpoints
|
|
200
|
+
* associated with a given model (e.g., authorization).
|
|
201
|
+
*
|
|
202
|
+
* @typeParam T - The model type.
|
|
203
|
+
* @param ctor - The model constructor (used to derive its name).
|
|
204
|
+
* @param m - The middleware function to register.
|
|
205
|
+
*/
|
|
122
206
|
useModel(ctor, m) {
|
|
123
207
|
if (this.model.has(ctor.name)) {
|
|
124
208
|
this.model.get(ctor.name).push(m);
|
|
@@ -127,6 +211,19 @@ export class CloesceApp {
|
|
|
127
211
|
this.model.set(ctor.name, [m]);
|
|
128
212
|
}
|
|
129
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Registers middleware for a specific method on a model.
|
|
216
|
+
*
|
|
217
|
+
* Method-level middleware is executed after model middleware,
|
|
218
|
+
* and before the method implementation itself. It can be used for:
|
|
219
|
+
* - Fine-grained permission checks
|
|
220
|
+
* - Custom logging or tracing per endpoint
|
|
221
|
+
*
|
|
222
|
+
* @typeParam T - The model type.
|
|
223
|
+
* @param ctor - The model constructor (used to derive its name).
|
|
224
|
+
* @param method - The method name on the model.
|
|
225
|
+
* @param m - The middleware function to register.
|
|
226
|
+
*/
|
|
130
227
|
useMethod(ctor, method, m) {
|
|
131
228
|
if (!this.method.has(ctor.name)) {
|
|
132
229
|
this.method.set(ctor.name, new Map());
|