fastify-auto-sqlapi 0.2.0 → 0.2.2
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/AGENTS.md +50 -0
- package/AGENTS_BACKEND.md +312 -9
- package/AGENTS_FRONTEND.md +1 -1
- package/BREAKING_CHANGES.md +132 -0
- package/CHANGELOG.md +170 -0
- package/README.md +255 -877
- package/dist/bin/generate-schema.js +49 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/agent/manifest.d.ts +2 -0
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +4 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +15 -5
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +53 -14
- package/dist/lib/cli/schema-codegen.d.ts +2 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +17 -4
- package/dist/lib/cli/tables-codegen.d.ts +4 -0
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +125 -27
- package/dist/lib/db.d.ts +6 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +8 -2
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +6 -5
- package/dist/lib/engine/read-hooks.d.ts +19 -0
- package/dist/lib/engine/read-hooks.d.ts.map +1 -0
- package/dist/lib/engine/read-hooks.js +20 -0
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +10 -6
- package/dist/lib/engine/rest/insert.js +1 -1
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +15 -11
- package/dist/lib/engine/search/join-families.d.ts +11 -2
- package/dist/lib/engine/search/join-families.d.ts.map +1 -1
- package/dist/lib/engine/search/join-families.js +11 -13
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +14 -7
- package/dist/lib/engine/validate.d.ts +3 -3
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +7 -3
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +29 -30
- package/dist/lib/errors.d.ts +24 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +59 -0
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +2 -1
- package/dist/lib/schema/get.d.ts +14 -0
- package/dist/lib/schema/get.d.ts.map +1 -0
- package/dist/lib/schema/get.js +18 -0
- package/dist/lib/schema/helpers.d.ts +21 -3
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +37 -5
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +7 -3
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +3 -11
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +7 -3
- package/dist/lib/sql-api.d.ts +9 -9
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +2 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +169 -0
- package/dist/lib/tenant.d.ts +43 -8
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +169 -54
- package/dist/lib/write-access.d.ts +36 -0
- package/dist/lib/write-access.d.ts.map +1 -0
- package/dist/lib/write-access.js +58 -0
- package/dist/routes/auto/get.routes.d.ts.map +1 -1
- package/dist/routes/auto/get.routes.js +3 -2
- package/dist/routes/auto/plugin.d.ts.map +1 -1
- package/dist/routes/auto/plugin.js +8 -2
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +13 -4
- package/dist/routes/setup-swagger.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.d.ts +1 -1
- package/dist/routes/sql-api-decorator.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.js +4 -4
- package/dist/types/cli.d.ts +27 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/dml.d.ts +6 -4
- package/dist/types/dml.d.ts.map +1 -1
- package/dist/types/plugin.d.ts +22 -3
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/request.d.ts +24 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +1 -0
- package/dist/types/schema.d.ts +15 -0
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/search.d.ts +3 -0
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/table.d.ts +84 -11
- package/dist/types/table.d.ts.map +1 -1
- package/dist/types/tenant.d.ts +26 -2
- package/dist/types/tenant.d.ts.map +1 -1
- package/dist/types/validation.d.ts +3 -3
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/docs/GLOSSARY.md +3 -1
- package/docs/adr/0004-updates-always-open.md +2 -1
- package/docs/adr/0006-raw-db-errors.md +2 -1
- package/docs/adr/0010-joins-do-not-run-route-guards.md +4 -1
- package/docs/adr/0013-sanitized-db-errors.md +114 -0
- package/docs/adr/0014-what-follows-a-write-join.md +84 -0
- package/docs/adr/0015-non-writable-columns.md +86 -0
- package/docs/adr/0016-a-view-is-a-table-config.md +92 -0
- package/docs/adr/0017-the-schema-is-the-contract.md +85 -0
- package/docs/adr/0018-one-seam-to-the-http-framework.md +68 -0
- package/docs/adr/README.md +6 -0
- package/package.json +1 -1
- package/dist/lib/setup-swagger.d.ts.map +0 -1
- package/dist/lib/sql-api-decorator.d.ts.map +0 -1
- /package/dist/{lib → routes}/setup-swagger.d.ts +0 -0
- /package/dist/{lib → routes}/setup-swagger.js +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ensureSqlApiDecorator } from '
|
|
1
|
+
import { ensureSqlApiDecorator } from '../sql-api-decorator.js';
|
|
2
|
+
import { sanitizeRouteError } from '../../lib/errors.js';
|
|
2
3
|
import { isCompositePrimary } from '../../types.js';
|
|
3
4
|
function mergeOnRequests(options, tableConf) {
|
|
4
5
|
return [...(options.onRequests || []), ...(tableConf.onRequests || [])];
|
|
@@ -75,9 +76,17 @@ export async function registerForAllTables(fastify, options, spec) {
|
|
|
75
76
|
schema: buildRouteSchema(tableName, tableConf, spec, schemas),
|
|
76
77
|
onRequest: mergeOnRequests(options, tableConf),
|
|
77
78
|
handler: async (request, reply) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
reply.
|
|
79
|
+
try {
|
|
80
|
+
const result = await spec.handle(fastify, tableName, tableConf, request, reply);
|
|
81
|
+
if (!reply.sent)
|
|
82
|
+
reply.status(spec.successStatus).send(result);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const safe = sanitizeRouteError(err, request.id, options.exposeDebugInfo);
|
|
86
|
+
if (safe !== err)
|
|
87
|
+
request.log.error({ err }, 'sqlapi: unhandled error');
|
|
88
|
+
throw safe;
|
|
89
|
+
}
|
|
81
90
|
},
|
|
82
91
|
});
|
|
83
92
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-swagger.d.ts","sourceRoot":"","sources":["../../src/routes/setup-swagger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAkB,MAAM,aAAa,CAAC;AAEvE,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-api-decorator.d.ts","sourceRoot":"","sources":["../../src/routes/sql-api-decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,IAAI,CA2BN"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createQueryClient } from '
|
|
2
|
-
import { pgQueryable } from '
|
|
3
|
-
import { mysqlQueryable } from '
|
|
4
|
-
import { createSqlApi } from '
|
|
1
|
+
import { createQueryClient } from '../lib/db.js';
|
|
2
|
+
import { pgQueryable } from '../lib/adapters/pg-adapter.js';
|
|
3
|
+
import { mysqlQueryable } from '../lib/adapters/mysql-adapter.js';
|
|
4
|
+
import { createSqlApi } from '../lib/sql-api.js';
|
|
5
5
|
/**
|
|
6
6
|
* Decorates `fastify.sqlApi` (lazy getter) on the given instance unless the
|
|
7
7
|
* decorator is already present in scope. The main plugin calls this at the
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -19,8 +19,30 @@ export interface ColumnInfo {
|
|
|
19
19
|
is_nullable: string;
|
|
20
20
|
/** True when the column is part of the table's PRIMARY KEY (from information_schema). */
|
|
21
21
|
is_primary?: boolean;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* True for columns the database fills in by itself without a visible `column_default`:
|
|
24
|
+
* MySQL `AUTO_INCREMENT` and PostgreSQL `GENERATED ... AS IDENTITY`. Both behave like a
|
|
25
|
+
* default — omit the column and the value appears — so the generated field is Optional and
|
|
26
|
+
* the CLI lists it under `excludeFromCreation`.
|
|
27
|
+
*/
|
|
23
28
|
is_auto_increment?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* True for a column computed from an expression: `GENERATED ALWAYS AS (<expr>)`, STORED on
|
|
31
|
+
* PostgreSQL, VIRTUAL or STORED on MySQL. Unlike an identity column this one is never
|
|
32
|
+
* writable at all — naming it in an INSERT or an UPDATE is an error on both engines — so it
|
|
33
|
+
* is read-only for the whole API, not just at creation time.
|
|
34
|
+
*/
|
|
35
|
+
is_generated?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* True when the relation is a view rather than a base table — `information_schema.tables`
|
|
38
|
+
* on both engines, plus `pg_class.relkind = 'm'` for PostgreSQL materialized views, which
|
|
39
|
+
* `information_schema` does not describe at all.
|
|
40
|
+
*
|
|
41
|
+
* A view is still a table config: what changes is what may be assumed about it. It carries
|
|
42
|
+
* no PRIMARY KEY constraint, so `is_primary` is false on every column, and only a view
|
|
43
|
+
* simple enough for the engine to make updatable accepts a write.
|
|
44
|
+
*/
|
|
45
|
+
is_view?: boolean;
|
|
24
46
|
}
|
|
25
47
|
export interface TableMap {
|
|
26
48
|
[schemaName: string]: {
|
|
@@ -29,6 +51,10 @@ export interface TableMap {
|
|
|
29
51
|
colMap: Record<string, string>;
|
|
30
52
|
/** PRIMARY KEY fields (camelCase), in table column order. Empty when unknown. */
|
|
31
53
|
primary: string[];
|
|
54
|
+
/** Database-computed fields (camelCase), in table column order. Never writable. */
|
|
55
|
+
generated: string[];
|
|
56
|
+
/** True when this relation is a view (or a materialized view), not a base table. */
|
|
57
|
+
isView: boolean;
|
|
32
58
|
};
|
|
33
59
|
}
|
|
34
60
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/types/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/types/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/types/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,UAAU,EAAE,MAAM,GAAG;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,iFAAiF;QACjF,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,mFAAmF;QACnF,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,oFAAoF;QACpF,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;CACH"}
|
package/dist/types/dml.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiRequest } from './request.js';
|
|
2
2
|
import type { QueryClient } from '../lib/db.js';
|
|
3
3
|
import type { ITable, DbTables } from './table.js';
|
|
4
4
|
import type { TenantContext } from './tenant.js';
|
|
@@ -9,7 +9,7 @@ interface DmlBaseParams {
|
|
|
9
9
|
}
|
|
10
10
|
interface DmlWriteBaseParams extends DmlBaseParams {
|
|
11
11
|
dbTables: DbTables;
|
|
12
|
-
request:
|
|
12
|
+
request: ApiRequest;
|
|
13
13
|
}
|
|
14
14
|
export interface InsertParams extends DmlWriteBaseParams {
|
|
15
15
|
record: Record<string, unknown>;
|
|
@@ -31,13 +31,15 @@ export interface UpdateResult {
|
|
|
31
31
|
}
|
|
32
32
|
export interface GetParams extends DmlBaseParams {
|
|
33
33
|
id: string | number;
|
|
34
|
+
/** Handed to the `afterRead` hook; absent for a programmatic caller that did not pass one. */
|
|
35
|
+
request?: ApiRequest;
|
|
34
36
|
}
|
|
35
37
|
export interface GetResult {
|
|
36
38
|
main: Record<string, unknown>;
|
|
37
39
|
}
|
|
38
40
|
export interface DeleteParams extends DmlBaseParams {
|
|
39
41
|
id: string | number;
|
|
40
|
-
request?:
|
|
42
|
+
request?: ApiRequest;
|
|
41
43
|
}
|
|
42
44
|
export interface DeleteResult {
|
|
43
45
|
main: Record<string, unknown>;
|
|
@@ -57,7 +59,7 @@ export interface BulkUpsertResult {
|
|
|
57
59
|
}
|
|
58
60
|
export interface BulkDeleteParams extends DmlBaseParams {
|
|
59
61
|
ids: (string | number)[];
|
|
60
|
-
request?:
|
|
62
|
+
request?: ApiRequest;
|
|
61
63
|
}
|
|
62
64
|
export interface BulkDeleteResult {
|
|
63
65
|
main: Record<string, unknown>;
|
package/dist/types/dml.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dml.d.ts","sourceRoot":"","sources":["../../src/types/dml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"dml.d.ts","sourceRoot":"","sources":["../../src/types/dml.ts"],"names":[],"mappings":"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"}
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiRequest, ApiReply } from './request.js';
|
|
2
2
|
import type { DialectName } from '../lib/dialect.js';
|
|
3
3
|
import type { DbTables } from './table.js';
|
|
4
4
|
import type { TenantId } from './tenant.js';
|
|
@@ -10,11 +10,11 @@ export interface SwaggerOptions {
|
|
|
10
10
|
}
|
|
11
11
|
export interface SqlApiPluginOptions {
|
|
12
12
|
DbTables: DbTables;
|
|
13
|
-
onRequests?: ((request:
|
|
13
|
+
onRequests?: ((request: ApiRequest, reply: ApiReply) => Promise<void | ApiReply>)[];
|
|
14
14
|
prefix?: string;
|
|
15
15
|
swagger?: boolean | SwaggerOptions;
|
|
16
16
|
dialect?: DialectName;
|
|
17
|
-
getTenantId?: (request:
|
|
17
|
+
getTenantId?: (request: ApiRequest) => TenantId | TenantId[] | null | undefined | Promise<TenantId | TenantId[] | null | undefined>;
|
|
18
18
|
debug?: boolean;
|
|
19
19
|
/**
|
|
20
20
|
* Hard cap on the search page size (the `itemsPerPage` query param), and the row LIMIT applied
|
|
@@ -35,6 +35,25 @@ export interface SqlApiPluginOptions {
|
|
|
35
35
|
* routes run behind the same global `onRequests` hooks as the data routes.
|
|
36
36
|
*/
|
|
37
37
|
agentManifest?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Attach a `debugInfo` payload — the driver message, its `code`/`constraint`/`detail`,
|
|
40
|
+
* the stack — to the `500` an error the plugin did not raise itself produces (a
|
|
41
|
+
* constraint violation, a hook throwing without a `statusCode`).
|
|
42
|
+
*
|
|
43
|
+
* **Development only**: it hands whoever triggers an error a description of the schema,
|
|
44
|
+
* and the auto routes are open by default (ADR 0002). Off unless set. It is additive —
|
|
45
|
+
* `statusCode`, `error`, `message` and `requestId` keep the shape they have in production,
|
|
46
|
+
* so client-side error handling does not fork between environments.
|
|
47
|
+
*
|
|
48
|
+
* Without it the response carries no database detail, and the original error is still
|
|
49
|
+
* reachable: logged through `request.log.error` under the same `reqId` the body reports as
|
|
50
|
+
* `requestId`, and attached as the `cause` of the error a consumer `setErrorHandler`
|
|
51
|
+
* receives — which is where policy such as unique-violation → `409` belongs (ADR 0006).
|
|
52
|
+
*
|
|
53
|
+
* Nothing derives this: not `debug` (whoever registers the plugin decides that one, and
|
|
54
|
+
* may wire it to an environment variable), not `NODE_ENV`. See ADR 0013.
|
|
55
|
+
*/
|
|
56
|
+
exposeDebugInfo?: boolean;
|
|
38
57
|
}
|
|
39
58
|
/** Default row cap for search (page size and no-paginator LIMIT). Override via `maxItemsPerPage`. */
|
|
40
59
|
export declare const DEFAULT_MAX_ITEMS_PER_PAGE = 1000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/types/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/types/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACnC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,GAC3E,OAAO,CAAC,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qGAAqG;AACrG,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,OAAO,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* The request the plugin carries from a route down to consumer code.
|
|
4
|
+
*
|
|
5
|
+
* Every hook, `validate` and `getTenantId` receive it; **nothing inside `src/lib/` ever reads a
|
|
6
|
+
* property off it**. It is an opaque token that the HTTP layer produces and consumer code
|
|
7
|
+
* consumes, and the engines only forward — which is why the engines, the schema builders and
|
|
8
|
+
* the write pipeline hold no dependency on a web framework at all, only on this name.
|
|
9
|
+
*
|
|
10
|
+
* That is the point of the alias: it is the single seam where the plugin's core meets Fastify.
|
|
11
|
+
* A second HTTP adapter — or a core published apart from one — changes this file and nothing
|
|
12
|
+
* else, and `.dependency-cruiser.mjs` keeps it that way by refusing a `fastify` import anywhere
|
|
13
|
+
* under `src/lib/` or `src/types/` but here.
|
|
14
|
+
*
|
|
15
|
+
* It is a plain alias rather than a generic parameter on `ITable`: that type appears bare in
|
|
16
|
+
* over a hundred places, and threading `<Req>` through all of them — plus `DbTables`, plus
|
|
17
|
+
* every `*Params` — would change the public type surface today for a benefit that only
|
|
18
|
+
* materialises once the core ships as its own package. See
|
|
19
|
+
* [ADR 0018](../../docs/adr/0018-one-seam-to-the-http-framework.md).
|
|
20
|
+
*/
|
|
21
|
+
export type ApiRequest = FastifyRequest;
|
|
22
|
+
/** The reply, which only the HTTP layer's own options need to name. */
|
|
23
|
+
export type ApiReply = FastifyReply;
|
|
24
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/types/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAExC,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/schema.d.ts
CHANGED
|
@@ -8,5 +8,20 @@ export interface SchemaDefinition<T = Record<string, TSchema>> {
|
|
|
8
8
|
partialValidation: TObject;
|
|
9
9
|
/** PRIMARY KEY fields (camelCase) as introspected from the DB. Set by generated schemas. */
|
|
10
10
|
primaryKey?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Fields (camelCase) the database computes from an expression — `GENERATED ALWAYS AS (...)`.
|
|
13
|
+
* Set by generated schemas. They are readable like any other column but every write path
|
|
14
|
+
* drops them: the engine rejects a value for them, so offering the field could only ever
|
|
15
|
+
* turn a client mistake into a driver error.
|
|
16
|
+
*/
|
|
17
|
+
generatedFields?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* True when the underlying relation is a view. Set by generated schemas.
|
|
20
|
+
*
|
|
21
|
+
* A view is a table config like any other — this only records that the database had no
|
|
22
|
+
* PRIMARY KEY to report, so an absent `primaryKey` means "not declared" rather than
|
|
23
|
+
* "this data has no key".
|
|
24
|
+
*/
|
|
25
|
+
isView?: boolean;
|
|
11
26
|
}
|
|
12
27
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,EAAE,CAAC,CAAC;IACV,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,EAAE,CAAC,CAAC;IACV,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
|
package/dist/types/search.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ApiRequest } from './request.js';
|
|
1
2
|
import type { QueryClient } from '../lib/db.js';
|
|
2
3
|
import type { ITable, FilterRecord } from './table.js';
|
|
3
4
|
import type { TenantContext } from './tenant.js';
|
|
@@ -35,6 +36,8 @@ export interface SearchParams {
|
|
|
35
36
|
* sets it from `maxItemsPerPage`.
|
|
36
37
|
*/
|
|
37
38
|
maxRows?: number;
|
|
39
|
+
/** Handed to the `afterRead` hook; absent for a programmatic caller that did not pass one. */
|
|
40
|
+
request?: ApiRequest;
|
|
38
41
|
}
|
|
39
42
|
export interface PaginationResult {
|
|
40
43
|
total: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,UAAU,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B"}
|
package/dist/types/table.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Expression, ConditionBuilder, ConditionValueOrUndefined, DialectName as CbDialect } from 'node-condition-builder';
|
|
2
2
|
import type { TSchema, Static } from '@sinclair/typebox';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ApiRequest, ApiReply } from './request.js';
|
|
4
4
|
import type { QueryClient } from '../lib/db.js';
|
|
5
5
|
import type { SchemaDefinition } from './schema.js';
|
|
6
6
|
import type { ComputedFieldFn } from './computed.js';
|
|
@@ -23,6 +23,14 @@ export type ExtendedConditionFn = (condition: ConditionBuilder, filters: FilterR
|
|
|
23
23
|
* resolve against the OUTER query.
|
|
24
24
|
*/
|
|
25
25
|
export type TableFilterFn = (filters: FilterRecord, dialect?: CbDialect, qualifier?: string) => ConditionBuilder;
|
|
26
|
+
/** Which read path produced the rows an `afterRead` hook is handed. */
|
|
27
|
+
export type ReadSource = 'get' | 'search' | 'joinMultiple' | 'joinLeft';
|
|
28
|
+
/** Where the rows an `afterRead` hook receives came from. */
|
|
29
|
+
export interface AfterReadContext {
|
|
30
|
+
source: ReadSource;
|
|
31
|
+
/** The relation's alias, on the two join sources; absent on `get` and `search`. */
|
|
32
|
+
alias?: string;
|
|
33
|
+
}
|
|
26
34
|
export interface ITable<F extends Record<string, TSchema> = Record<string, TSchema>> {
|
|
27
35
|
primary: (string & keyof F) | (string & keyof F)[];
|
|
28
36
|
Schema: SchemaDefinition<F>;
|
|
@@ -41,16 +49,37 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
|
|
|
41
49
|
allowedReadJoins?: JoinDefinition[];
|
|
42
50
|
allowedWriteJoins?: JoinDefinition[];
|
|
43
51
|
upsertMap?: Map<SchemaDefinition, string[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Narrow a field's generated type where the DB could only describe it loosely: a column
|
|
54
|
+
* Postgres calls `text` is an email, a UUID, a URL; an `integer` has a range. The generated
|
|
55
|
+
* schema is as precise as `information_schema` allows, and this is where the rest goes.
|
|
56
|
+
*
|
|
57
|
+
* **Write bodies only.** An override is a rule about what this API accepts from now on, and
|
|
58
|
+
* the rows already in the table were written before it existed: a column narrowed to
|
|
59
|
+
* `format: 'email'` today holds whatever was accepted last year. Narrowing the response
|
|
60
|
+
* would publish a promise about data nobody can retroactively make true, so responses and
|
|
61
|
+
* the `filters` map stay on the generated type.
|
|
62
|
+
*
|
|
63
|
+
* The override is taken **verbatim**, because on the request side the schema is the rule: no
|
|
64
|
+
* `Type.Optional` means the field is mandatory and no `Nullable` means an explicit `null` is
|
|
65
|
+
* rejected, whatever the column allows. That is how a column the database had to leave
|
|
66
|
+
* nullable, because it was added to a populated table, is made mandatory going forward.
|
|
67
|
+
*
|
|
68
|
+
* It is not a re-typing mechanism. Nothing stops an override from turning a `number` into a
|
|
69
|
+
* `string`, but the value crossing the wire is still whatever the driver read from the
|
|
70
|
+
* column, so the declaration would simply be false. Transforming a value between storage and
|
|
71
|
+
* API is what `afterRead` (out) and `beforeInsert`/`beforeUpdate` (in) are for.
|
|
72
|
+
*/
|
|
44
73
|
schemaOverrides?: Partial<Record<string & keyof F, TSchema>>;
|
|
45
74
|
validate?: ValidatorFn<F>;
|
|
46
75
|
validateBulk?: BulkValidatorFn<F>;
|
|
47
|
-
beforeInsert?: (db: QueryClient, req:
|
|
76
|
+
beforeInsert?: (db: QueryClient, req: ApiRequest, record: {
|
|
48
77
|
[K in keyof F]?: Static<F[K]> | Expression | null;
|
|
49
78
|
}) => Promise<void>;
|
|
50
|
-
beforeUpdate?: (db: QueryClient, req:
|
|
79
|
+
beforeUpdate?: (db: QueryClient, req: ApiRequest, fields: {
|
|
51
80
|
[K in keyof F]?: Static<F[K]> | Expression | null;
|
|
52
81
|
}, secondaryFieldsFetcher?: unknown) => void | Promise<void>;
|
|
53
|
-
afterInsert?: (db: QueryClient, req:
|
|
82
|
+
afterInsert?: (db: QueryClient, req: ApiRequest, record: {
|
|
54
83
|
[K in keyof F]?: Static<F[K]>;
|
|
55
84
|
}, secondaryRecords?: unknown) => Promise<void>;
|
|
56
85
|
/**
|
|
@@ -59,9 +88,34 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
|
|
|
59
88
|
* adapter supports transactions). Receives the camelCase input record (including the
|
|
60
89
|
* PK) plus the secondaries/deletions results, mirroring `afterInsert`.
|
|
61
90
|
*/
|
|
62
|
-
afterUpdate?: (db: QueryClient, req:
|
|
91
|
+
afterUpdate?: (db: QueryClient, req: ApiRequest, record: {
|
|
63
92
|
[K in keyof F]?: Static<F[K]> | Expression | null;
|
|
64
93
|
}, secondaryRecords?: unknown, deletionRecords?: unknown) => void | Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Runs on the rows a read returned, once per result set, before they become the response.
|
|
96
|
+
* The read-side counterpart of `beforeInsert`/`beforeUpdate`: the place to turn a stored
|
|
97
|
+
* representation back into the API one (decrypting a column, unpacking a blob).
|
|
98
|
+
*
|
|
99
|
+
* `rows` are camelCase records and are mutated **in place** — the return value is ignored.
|
|
100
|
+
* Rows cannot be added or removed: the pagination `COUNT` has already run, so a hook that
|
|
101
|
+
* dropped rows would report a `total` that does not match what it returned. Narrowing what
|
|
102
|
+
* a caller sees is `filters`, `tenantScope` and `readExclude`; hiding a field is
|
|
103
|
+
* `readExclude`.
|
|
104
|
+
*
|
|
105
|
+
* Declared on the table that OWNS the column, and it follows a join: the hook runs whenever
|
|
106
|
+
* that table's rows surface, including through a `joinMultiple` or `joinLeft` declared on
|
|
107
|
+
* another table (`ctx.source` and `ctx.alias` say which). It does not run on `joinGroup`,
|
|
108
|
+
* whose rows are aggregates rather than table rows.
|
|
109
|
+
*
|
|
110
|
+
* Not called when the read returned no rows. Whatever the hook writes is serialized against
|
|
111
|
+
* the response schema, so a transform that changes a field's JSON type needs a matching
|
|
112
|
+
* `schemaOverrides` entry — without it `fast-json-stringify` coerces the value silently.
|
|
113
|
+
*
|
|
114
|
+
* `req` is the request that triggered the read. Same caveat as `beforeDelete`:
|
|
115
|
+
* present through the auto-generated HTTP routes, `undefined` when a programmatic caller
|
|
116
|
+
* invokes `sqlApi.search()`/`sqlApi.get()` without passing one.
|
|
117
|
+
*/
|
|
118
|
+
afterRead?: (db: QueryClient, req: ApiRequest | undefined, rows: Record<string, unknown>[], ctx: AfterReadContext) => void | Promise<void>;
|
|
65
119
|
/**
|
|
66
120
|
* Runs before a single record is deleted (DELETE /rest/:id). Throw to abort the
|
|
67
121
|
* deletion (the thrown error's `statusCode`/`message` are surfaced to the client).
|
|
@@ -70,17 +124,17 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
|
|
|
70
124
|
* For tenant-scoped tables the hook only runs once ownership has been verified, so it
|
|
71
125
|
* never fires for rows the caller cannot access.
|
|
72
126
|
*
|
|
73
|
-
* `req` is the
|
|
127
|
+
* `req` is the request that triggered the operation. It is always present when
|
|
74
128
|
* the delete comes through the auto-generated HTTP route. It is `undefined` only if you
|
|
75
129
|
* call `sqlApi.delete(table, id)` programmatically without passing a request — in that
|
|
76
130
|
* case any hook that reads request context (e.g. `req.user`) must guard for it.
|
|
77
131
|
*/
|
|
78
|
-
beforeDelete?: (db: QueryClient, req:
|
|
132
|
+
beforeDelete?: (db: QueryClient, req: ApiRequest, id: string | number) => void | Promise<void>;
|
|
79
133
|
/**
|
|
80
134
|
* Runs after a single record has been deleted (DELETE /rest/:id). Not called when the
|
|
81
135
|
* record was not found (404). Same `req` caveat as `beforeDelete`.
|
|
82
136
|
*/
|
|
83
|
-
afterDelete?: (db: QueryClient, req:
|
|
137
|
+
afterDelete?: (db: QueryClient, req: ApiRequest, id: string | number) => void | Promise<void>;
|
|
84
138
|
/**
|
|
85
139
|
* Bulk counterpart of `beforeDelete`, invoked ONCE with all ids before a bulk delete
|
|
86
140
|
* (POST /bulk/:table/delete). Called once — not per id — to preserve the single-query
|
|
@@ -90,13 +144,13 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
|
|
|
90
144
|
* Same `req` caveat as `beforeDelete`: present via the HTTP route, `undefined` only when
|
|
91
145
|
* `sqlApi.bulkDelete(table, ids)` is called programmatically without a request.
|
|
92
146
|
*/
|
|
93
|
-
beforeBulkDelete?: (db: QueryClient, req:
|
|
147
|
+
beforeBulkDelete?: (db: QueryClient, req: ApiRequest, ids: (string | number)[]) => void | Promise<void>;
|
|
94
148
|
/**
|
|
95
149
|
* Bulk counterpart of `afterDelete`, invoked ONCE after a bulk delete with the ids that
|
|
96
150
|
* were ACTUALLY deleted (which may be a subset of the requested ids). Not called when
|
|
97
151
|
* nothing was deleted. Same `req` caveat as `beforeBulkDelete`.
|
|
98
152
|
*/
|
|
99
|
-
afterBulkDelete?: (db: QueryClient, req:
|
|
153
|
+
afterBulkDelete?: (db: QueryClient, req: ApiRequest, deletedIds: (string | number)[]) => void | Promise<void>;
|
|
100
154
|
/**
|
|
101
155
|
* Whitelist of auto-generated HTTP routes for this table. When omitted, ALL operations
|
|
102
156
|
* are exposed (search, get, insert, update, delete, bulkUpsert, bulkDelete) — the
|
|
@@ -120,10 +174,29 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
|
|
|
120
174
|
* password hash). Primary-key fields cannot be excluded.
|
|
121
175
|
*/
|
|
122
176
|
readExclude?: (string & keyof F)[];
|
|
177
|
+
/**
|
|
178
|
+
* Fields that no write may carry: removed from the insert, update and bulk-upsert bodies
|
|
179
|
+
* (so sending one is a 400, not a driver error) and dropped again in the engines, which
|
|
180
|
+
* `sqlApi.*` reaches without the HTTP schemas. Reads are untouched — the field stays
|
|
181
|
+
* projected, filterable and orderable.
|
|
182
|
+
*
|
|
183
|
+
* It is the write-side counterpart of `readExclude`, and it exists for columns the database
|
|
184
|
+
* refuses to be told about: a `GENERATED ALWAYS AS (<expr>)` column is already excluded
|
|
185
|
+
* automatically through the Schema's `generatedFields`, and this key covers the rest — a
|
|
186
|
+
* column a trigger owns, one a migration is about to drop.
|
|
187
|
+
*
|
|
188
|
+
* Not a per-caller permission: it is static and applies to everyone, admins included.
|
|
189
|
+
* Field-level rules that depend on who is asking belong in `beforeUpdate` (silent strip) or
|
|
190
|
+
* `validate` (loud 400). Not `excludeFromCreation` either — that one whitelists client input
|
|
191
|
+
* on insert only, and deliberately lets a hook assign the field afterwards.
|
|
192
|
+
*
|
|
193
|
+
* The primary key cannot be excluded: the update body identifies the row with it.
|
|
194
|
+
*/
|
|
195
|
+
writeExclude?: (string & keyof F)[];
|
|
123
196
|
defaultOrder?: string;
|
|
124
197
|
excludeFromCreation?: (string & keyof F)[];
|
|
125
198
|
distinctResults?: boolean;
|
|
126
|
-
onRequests?: ((request:
|
|
199
|
+
onRequests?: ((request: ApiRequest, reply: ApiReply) => Promise<void | ApiReply>)[];
|
|
127
200
|
tenantScope?: TenantScope;
|
|
128
201
|
}
|
|
129
202
|
export type DbTables = Record<string, ITable<any>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/types/table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,WAAW,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAChI,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/types/table.ts"],"names":[],"mappings":"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"}
|
package/dist/types/tenant.d.ts
CHANGED
|
@@ -11,9 +11,33 @@ export interface TenantScopeIndirect {
|
|
|
11
11
|
foreignField: string;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* A row owned by several parties, visible to any of them: a message (`sender_id` /
|
|
16
|
+
* `recipient_id`), a transfer (`from_account_id` / `to_account_id`), a shift swap. Reads test
|
|
17
|
+
* every listed column with an OR, so a row matches when *any* of them holds a tenant id.
|
|
18
|
+
*
|
|
19
|
+
* Writes cannot auto-inject a tenant the way a single-column scope does — the plugin has no way
|
|
20
|
+
* to know which party the caller is meant to be — so the payload must anchor the row itself:
|
|
21
|
+
* see `assertTenantAnchor`. Columns are DB column names, like `TenantScopeDirect.column`.
|
|
22
|
+
*
|
|
23
|
+
* Not combinable with `column`/`through`: an entry reached through a parent FK would need its
|
|
24
|
+
* own join per entry, and is rejected at `defineTable` time rather than half-supported.
|
|
25
|
+
*/
|
|
26
|
+
export interface TenantScopeAnyOf {
|
|
27
|
+
anyOf: string[];
|
|
28
|
+
}
|
|
29
|
+
export type TenantScope = TenantScopeDirect | TenantScopeIndirect | TenantScopeAnyOf;
|
|
30
|
+
/**
|
|
31
|
+
* Who the caller is, for this request — plus how the table currently being addressed is scoped.
|
|
32
|
+
*
|
|
33
|
+
* `ids` is request-level: it is resolved once from `getTenantId` and stays valid for every table
|
|
34
|
+
* the request touches. `scope` is table-level and therefore **optional**: the addressed table may
|
|
35
|
+
* declare no scope of its own while a table it reaches through a relation does. Absent `scope`
|
|
36
|
+
* means "no filtering on this table", never "no tenant" — `tenantForTable` re-targets the same
|
|
37
|
+
* ids onto a related table's own scope.
|
|
38
|
+
*/
|
|
15
39
|
export interface TenantContext {
|
|
16
40
|
ids: TenantId[];
|
|
17
|
-
scope
|
|
41
|
+
scope?: TenantScope;
|
|
18
42
|
}
|
|
19
43
|
//# sourceMappingURL=tenant.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/types/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,MAAM,EAAE,gBAAgB,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/types/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,MAAM,EAAE,gBAAgB,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSchema, Static } from '@sinclair/typebox';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ApiRequest } from './request.js';
|
|
3
3
|
import type { QueryClient } from '../lib/db.js';
|
|
4
4
|
/**
|
|
5
5
|
* Validation error tuple: [field, code] or [field, code, message].
|
|
@@ -8,7 +8,7 @@ import type { QueryClient } from '../lib/db.js';
|
|
|
8
8
|
* - message: human-readable description (defaults to code if omitted)
|
|
9
9
|
*/
|
|
10
10
|
export type ValidationError = [field: string, code: string] | [field: string, code: string, message: string];
|
|
11
|
-
export type ValidatorFn<F extends Record<string, TSchema> = Record<string, TSchema>> = (db: QueryClient, req:
|
|
11
|
+
export type ValidatorFn<F extends Record<string, TSchema> = Record<string, TSchema>> = (db: QueryClient, req: ApiRequest, main: {
|
|
12
12
|
[K in keyof F]?: Static<F[K]>;
|
|
13
13
|
}, secondaries?: Record<string, Record<string, unknown>[]>) => Promise<ValidationError[]> | ValidationError[];
|
|
14
14
|
export interface BulkValidatorItem<F extends Record<string, TSchema> = Record<string, TSchema>> {
|
|
@@ -17,5 +17,5 @@ export interface BulkValidatorItem<F extends Record<string, TSchema> = Record<st
|
|
|
17
17
|
};
|
|
18
18
|
secondaries?: Record<string, Record<string, unknown>[]>;
|
|
19
19
|
}
|
|
20
|
-
export type BulkValidatorFn<F extends Record<string, TSchema> = Record<string, TSchema>> = (db: QueryClient, req:
|
|
20
|
+
export type BulkValidatorFn<F extends Record<string, TSchema> = Record<string, TSchema>> = (db: QueryClient, req: ApiRequest, items: BulkValidatorItem<F>[]) => Promise<ValidationError[]> | ValidationError[];
|
|
21
21
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/types/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/types/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GACvB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAC7B,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAEnD,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACrF,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,UAAU,EACf,IAAI,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,EACvC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KACpD,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC;AAEpD,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5F,IAAI,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAAE,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;CACzD;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACzF,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,KAC1B,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
|
package/dist/types.js
CHANGED
package/docs/GLOSSARY.md
CHANGED
|
@@ -19,7 +19,9 @@ existing concept makes search fail and the concept get built twice.
|
|
|
19
19
|
| Extra filter | Non-column search filter declared per table. (not: custom filter) |
|
|
20
20
|
| Extended condition | Consumer-supplied SQL fragment merged into a search WHERE. |
|
|
21
21
|
| Dotted notation | `orderBy`/condition key counted in parts: 1-part `field`, 2-part `alias.field`, 3-part `alias.fn.field`. (not: parti) |
|
|
22
|
-
| Tenant scope | Row-level isolation: `direct` (own column)
|
|
22
|
+
| Tenant scope | Row-level isolation: `direct` (own column), `indirect` (via FK) or `anyOf`. |
|
|
23
|
+
| `anyOf` scope | Tenant scope over several owner columns: the row is visible to any of them. |
|
|
24
|
+
| Anchor | The owner column an `anyOf` write names to tie a new row to the caller. |
|
|
23
25
|
| Engine | Dialect-agnostic function performing one operation. (not: service, handler) |
|
|
24
26
|
| Route | Fastify plugin exposing an engine over HTTP. (not: controller) |
|
|
25
27
|
| Dialect | Per-DB SQL differences (`postgres` \| `mysql`). |
|