@kozou/api 0.0.1 → 1.0.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/LICENSE +202 -0
- package/README.md +200 -1
- package/dist/auth.d.ts +75 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +128 -0
- package/dist/auth.js.map +1 -0
- package/dist/embed.d.ts +46 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +181 -0
- package/dist/embed.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +39 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +267 -0
- package/dist/handler.js.map +1 -0
- package/dist/ident.d.ts +7 -0
- package/dist/ident.d.ts.map +1 -0
- package/dist/ident.js +12 -0
- package/dist/ident.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi.d.ts +9 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +300 -0
- package/dist/openapi.js.map +1 -0
- package/dist/query-builder.d.ts +83 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/query-builder.js +592 -0
- package/dist/query-builder.js.map +1 -0
- package/dist/schema-lookup.d.ts +30 -0
- package/dist/schema-lookup.d.ts.map +1 -0
- package/dist/schema-lookup.js +61 -0
- package/dist/schema-lookup.js.map +1 -0
- package/dist/startApiServer.d.ts +53 -0
- package/dist/startApiServer.d.ts.map +1 -0
- package/dist/startApiServer.js +210 -0
- package/dist/startApiServer.js.map +1 -0
- package/package.json +44 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,202 @@
|
|
|
1
1
|
# @kozou/api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Stable (Kozou v1.0).** The REST wire format, query grammar, and OpenAPI
|
|
4
|
+
> extensions documented below are a stable contract: they will not change
|
|
5
|
+
> incompatibly without a major release. The one part still evolving is the
|
|
6
|
+
> composite-foreign-key relation shape — see [Stability](#stability).
|
|
7
|
+
|
|
8
|
+
Kozou's own REST layer. Given a `SchemaContext` (from `@kozou/introspect`
|
|
9
|
+
+ `@kozou/core`) and a PostgreSQL connection, it serves the tables and
|
|
10
|
+
views of your database as a REST API, driven entirely by your DDL and
|
|
11
|
+
`COMMENT` metadata — no hand-written route code.
|
|
12
|
+
|
|
13
|
+
This is the in-house data layer that Kozou v1.0 makes the default backend.
|
|
14
|
+
The Admin UI talks to it through the same `DataAdapter` seam (`@kozou/core`)
|
|
15
|
+
it already uses, so swapping the data layer is not a breaking change for UI
|
|
16
|
+
code.
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### Service
|
|
21
|
+
|
|
22
|
+
- `GET /` — service info (name, version) and the list of available resources.
|
|
23
|
+
- `GET /openapi.json` — an OpenAPI 3.1 document for the whole API
|
|
24
|
+
(see [OpenAPI](#openapi)).
|
|
25
|
+
|
|
26
|
+
### Reading
|
|
27
|
+
|
|
28
|
+
- `GET /<resource>` — list rows of a table or view. Returns
|
|
29
|
+
`{ rows, total, page, pageSize }`. Supports:
|
|
30
|
+
- **pagination** — `page` (1-based) and `pageSize` (`LIMIT` / `OFFSET`, capped);
|
|
31
|
+
- **sort** — `sort=field.asc,other.desc`;
|
|
32
|
+
- **filter** — `<column>=<op>.<value>` (see [Filtering](#filtering));
|
|
33
|
+
- **free-text search** — `search=<text>` (`ILIKE` across text columns);
|
|
34
|
+
- **embedding** — `embed=<relation-chain>` (see [Embedding](#embedding)).
|
|
35
|
+
- `GET /<resource>/<id>` — fetch a single table row by primary key. For a
|
|
36
|
+
composite primary key, `<id>` is the key columns in declaration order,
|
|
37
|
+
comma-joined in one path segment (`/order_lines/42,3`); `embed` is
|
|
38
|
+
supported here too. Returns the row, or `404`.
|
|
39
|
+
- `GET /<resource>?as=options&label=<col>&fields=<a,b>&q=<text>&limit=<n>`
|
|
40
|
+
— lightweight relation-select lookup. Returns `{ options: [{ id, label }] }`.
|
|
41
|
+
|
|
42
|
+
### Writing
|
|
43
|
+
|
|
44
|
+
- `POST /<resource>` — create a row from a JSON body; returns `201` + the
|
|
45
|
+
created row. An empty body inserts a row of column defaults.
|
|
46
|
+
- `PATCH /<resource>/<id>` — update the supplied columns; returns the row,
|
|
47
|
+
or `404`.
|
|
48
|
+
- `DELETE /<resource>/<id>` — delete by primary key; returns the deleted
|
|
49
|
+
row, or `404`.
|
|
50
|
+
|
|
51
|
+
Writes are rejected on views (`405`) and on unknown columns (`400`).
|
|
52
|
+
|
|
53
|
+
### Filtering
|
|
54
|
+
|
|
55
|
+
List filters use a `<column>=<op>.<value>` grammar:
|
|
56
|
+
|
|
57
|
+
| operator | meaning |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `eq` / `neq` | equal / not equal |
|
|
60
|
+
| `gt` / `gte` / `lt` / `lte` | range comparisons |
|
|
61
|
+
| `like` / `ilike` | pattern match (`*` is the wildcard) |
|
|
62
|
+
| `in` | membership — `in.(a,b,c)` |
|
|
63
|
+
| `is` | `is.null` / `is.notnull` / `is.true` / `is.false` |
|
|
64
|
+
|
|
65
|
+
A bare value (`status=paid`) is shorthand for `eq` and stays backward
|
|
66
|
+
compatible. Repeating a column ANDs its filters (e.g. a `gte` + `lt` range).
|
|
67
|
+
A value that itself looks like an operator can be forced to equality with an
|
|
68
|
+
explicit `eq.` prefix (`name=eq.gt.5`). Operators are a fixed allowlist, the
|
|
69
|
+
column is checked against the schema, and every value is passed as a bound
|
|
70
|
+
parameter — nothing is interpolated into SQL text. Values for the common
|
|
71
|
+
scalar families (integer, numeric / float, boolean) are validated up front
|
|
72
|
+
and rejected with a `400`; values for other types are enforced by
|
|
73
|
+
PostgreSQL.
|
|
74
|
+
|
|
75
|
+
### Embedding
|
|
76
|
+
|
|
77
|
+
`embed=<relation-chain>` inlines related rows as nested JSON, on both list
|
|
78
|
+
and item reads:
|
|
79
|
+
|
|
80
|
+
- forward (to-one) and reverse (to-many) relations, mixed in one request;
|
|
81
|
+
- dot-separated chains up to 5 deep (`embed=order.customer.region`);
|
|
82
|
+
- comma-separated for several relations (`embed=customer,lines`);
|
|
83
|
+
- up to 25 distinct relations per request, and up to 100 child rows inlined
|
|
84
|
+
per parent for a to-many relation.
|
|
85
|
+
|
|
86
|
+
To-many embeds are rendered as a JSON array ordered by the child's primary
|
|
87
|
+
key. Many-to-many is expressed by naming the junction explicitly
|
|
88
|
+
(`embed=link.far`); there is no automatic flattening of junction tables.
|
|
89
|
+
|
|
90
|
+
### OpenAPI
|
|
91
|
+
|
|
92
|
+
`GET /openapi.json` returns an OpenAPI 3.1 document generated from the
|
|
93
|
+
schema. Database `COMMENT`s drive it: table / view / column descriptions
|
|
94
|
+
become schema `description`s, CHECK / ENUM members become `enum`, and
|
|
95
|
+
Kozou's `@`-annotations become vendor extensions:
|
|
96
|
+
|
|
97
|
+
- `@ai:` notes → `x-kozou-ai`
|
|
98
|
+
- the resolved widget → `x-kozou-widget`
|
|
99
|
+
- `@policy:` advisories → `x-kozou-policy`
|
|
100
|
+
- embeddable relations → `x-kozou-embeds` (with `cardinality`)
|
|
101
|
+
|
|
102
|
+
The document models the schema, the `x-kozou-*` metadata, and the list /
|
|
103
|
+
item / CRUD surface. A few runtime behaviors are enforced by the server but
|
|
104
|
+
not yet fully modeled in the generated document: create accepts an empty body
|
|
105
|
+
(column defaults) and `PATCH` accepts a partial column subset, the
|
|
106
|
+
`as=options` relation-select mode shares the collection path, and ambiguous
|
|
107
|
+
embeds are rejected at request time. These are being refined; the wire
|
|
108
|
+
behavior itself is stable.
|
|
109
|
+
|
|
110
|
+
## Stability
|
|
111
|
+
|
|
112
|
+
Stable as of Kozou v1.0 (covered by semantic versioning — no incompatible
|
|
113
|
+
change without a major release):
|
|
114
|
+
|
|
115
|
+
- the REST envelopes — the list `{ rows, total, page, pageSize }`, the item
|
|
116
|
+
shape, and the create / update / delete return shapes;
|
|
117
|
+
- the query grammar — pagination, `sort`, the `<column>=<op>.<value>` filter
|
|
118
|
+
grammar, `as=options`, and `embed`;
|
|
119
|
+
- the `GET /openapi.json` document — OpenAPI 3.1 plus the `x-kozou-ai` /
|
|
120
|
+
`x-kozou-widget` / `x-kozou-policy` / `x-kozou-embeds` extensions;
|
|
121
|
+
- the auth boundary — JWT claims mapped to `SET LOCAL ROLE` (see below).
|
|
122
|
+
|
|
123
|
+
Still evolving (not yet covered by the stability guarantee):
|
|
124
|
+
|
|
125
|
+
- the **composite-foreign-key relation shape**. A composite FK is surfaced
|
|
126
|
+
as a `BuildIssue` rather than silently dropped, but embedding and
|
|
127
|
+
relation-select over a composite FK are a fast-follow, so the relation
|
|
128
|
+
shape they will take is not frozen yet.
|
|
129
|
+
- the **`@kozou/codegen`** output (a separate package, still experimental).
|
|
130
|
+
|
|
131
|
+
## Scope: default coverage vs PostgREST opt-out
|
|
132
|
+
|
|
133
|
+
`@kozou/api` covers the relational REST surface most schemas need; Kozou
|
|
134
|
+
v1.0 makes it the default backend. Deployments that need a feature in the
|
|
135
|
+
"opt-out" column can stay on (or switch back to) the PostgREST adapter
|
|
136
|
+
(`adapter: postgrest`) — see the migration notes in the `kozou` package. The
|
|
137
|
+
intent is no silent gap: the in-house backend by default, PostgREST as a
|
|
138
|
+
deliberate opt-out.
|
|
139
|
+
|
|
140
|
+
| Capability | `@kozou/api` (v1.0 default) | Notes |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| Table CRUD | ✅ (incl. composite primary keys) | item routes (get/update/delete by id) need a primary key — single or composite; a primary-key-less table gets list + create only |
|
|
143
|
+
| Views | ✅ read-only / list | no item-by-id, writes are `405`, no embedding from a view |
|
|
144
|
+
| Embed 1:1 / many-to-1 / 1-to-many | ✅ `embed=` (mixed direction, multi-hop) | forward to-one + reverse to-many |
|
|
145
|
+
| Many-to-many | △ name the junction and chain (`embed=link.far`) | no automatic junction flattening |
|
|
146
|
+
| Relation-select | ✅ `as=options&label=&q=` | composite PK / FK support is a fast-follow |
|
|
147
|
+
| Filter operators | ✅ `eq` / `neq` / `gt` / `gte` / `lt` / `lte` / `like` / `ilike` / `in` / `is` | |
|
|
148
|
+
| Sort / pagination | ✅ | `sort`, `page` / `pageSize` |
|
|
149
|
+
| COMMENT-native OpenAPI 3.1 (`x-kozou-*`) | ✅ | **Kozou's differentiator** — PostgREST treats COMMENTs as opaque text |
|
|
150
|
+
| JWT + RLS (`SET LOCAL ROLE`) | ✅ | |
|
|
151
|
+
| RPC (Postgres functions) | ❌ opt-out | |
|
|
152
|
+
| Full-text search (fts) | ❌ opt-out | approximate with `ilike` |
|
|
153
|
+
| Vertical select (column projection) | ❌ opt-out | always returns all columns |
|
|
154
|
+
| Writable views (`INSTEAD OF`) | ❌ opt-out | views are read-only |
|
|
155
|
+
| Upsert / bulk insert | ❌ opt-out | |
|
|
156
|
+
| Automatic M:N flattening | ❌ opt-out | name the junction instead |
|
|
157
|
+
|
|
158
|
+
## Security boundary
|
|
159
|
+
|
|
160
|
+
By default the API ships with **no authentication** and binds to
|
|
161
|
+
`127.0.0.1` (like the MCP HTTP server); it prints a
|
|
162
|
+
loud warning when bound to a non-loopback host. Run the unauthenticated
|
|
163
|
+
server only inside a trusted boundary (local dev, a docker-compose network).
|
|
164
|
+
|
|
165
|
+
**Opt-in JWT + row-level security.** Pass an `auth` config (and a `pool`)
|
|
166
|
+
to `startApiServer` to require a signed JWT on every request. Kozou verifies
|
|
167
|
+
the token (an HS256 shared secret, an RS256 public key, or a provider's
|
|
168
|
+
remote JWKS endpoint — exactly one), then runs each request
|
|
169
|
+
inside a transaction on a dedicated connection under
|
|
170
|
+
`SET LOCAL ROLE <role-from-claim>`, with the claims published via
|
|
171
|
+
`set_config('request.jwt.claims', …, true)` — so **your own Postgres RLS
|
|
172
|
+
policies** decide what each request can read and write. Kozou authenticates
|
|
173
|
+
and switches role; it does not generate policies. A missing or invalid token
|
|
174
|
+
gets `401`; a token whose role is not permitted gets `403`.
|
|
175
|
+
|
|
176
|
+
Set `auth.anonRole` to allow **anonymous access**: a request that carries no
|
|
177
|
+
`Authorization` header runs under that role (with empty claims) so your RLS
|
|
178
|
+
policies decide what an anonymous caller sees, instead of a `401`. Only a
|
|
179
|
+
fully absent header is anonymous — a present but invalid/expired token is
|
|
180
|
+
still `401`, never silently downgraded. The login role must be `GRANT`ed
|
|
181
|
+
membership in the anonymous role.
|
|
182
|
+
|
|
183
|
+
Set `auth.jwt.jwksUri` to verify against a provider's **remote JWKS endpoint**
|
|
184
|
+
(Auth0, Clerk, Supabase, …) instead of a static key: the verification key is
|
|
185
|
+
selected by the token's `kid`, fetched once, cached, and refreshed when the
|
|
186
|
+
provider rotates keys.
|
|
187
|
+
|
|
188
|
+
For a trusted same-host caller that has no end user to obtain a token from
|
|
189
|
+
(the bundled Admin UI under `kozou dev`), `signServiceToken` mints an HS256
|
|
190
|
+
token claiming a given role, signed with the same secret the server verifies.
|
|
191
|
+
|
|
192
|
+
## Safety
|
|
193
|
+
|
|
194
|
+
- Table / view / column identifiers are validated against the introspected
|
|
195
|
+
`SchemaContext` (an allowlist) before any query is built, and are quoted
|
|
196
|
+
defensively.
|
|
197
|
+
- All user-supplied values are passed as parameterized query arguments;
|
|
198
|
+
values are never interpolated into SQL text.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
Apache 2.0
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type JWTPayload } from 'jose';
|
|
2
|
+
export type JwtAlgorithm = 'HS256' | 'RS256';
|
|
3
|
+
export type AuthConfig = {
|
|
4
|
+
jwt: {
|
|
5
|
+
/** Shared secret for HS256. Provide exactly one of secret / publicKey / jwksUri. */
|
|
6
|
+
secret?: string;
|
|
7
|
+
/** Verification key for RS256: a PEM (SPKI) string or a JWK JSON string. */
|
|
8
|
+
publicKey?: string;
|
|
9
|
+
/** URL of the provider's JWKS endpoint (Auth0 / Clerk / Supabase, …). The
|
|
10
|
+
* key is selected by the token's `kid`, fetched once, cached, and
|
|
11
|
+
* refreshed on rotation. Provide exactly one of secret / publicKey / jwksUri. */
|
|
12
|
+
jwksUri?: string;
|
|
13
|
+
/** Accepted algorithms. Defaults to ['HS256'] or ['RS256'] by key type. */
|
|
14
|
+
algorithms?: JwtAlgorithm[];
|
|
15
|
+
/** Expected `iss`. When set, a mismatch is rejected. */
|
|
16
|
+
issuer?: string;
|
|
17
|
+
/** Expected `aud`. When set, a mismatch is rejected. */
|
|
18
|
+
audience?: string | string[];
|
|
19
|
+
};
|
|
20
|
+
/** Claim that names the database role to assume. Default: 'role'. */
|
|
21
|
+
roleClaim?: string;
|
|
22
|
+
/** Allowlist of assumable roles. When set, any other role is forbidden. */
|
|
23
|
+
allowedRoles?: string[];
|
|
24
|
+
/** Role used when the token carries no role claim. */
|
|
25
|
+
defaultRole?: string;
|
|
26
|
+
/** Role assumed when a request carries NO Authorization header at all, so
|
|
27
|
+
* the database's RLS policies decide what an anonymous caller may see.
|
|
28
|
+
* Unset (default): a request with no token is rejected with 401. A present
|
|
29
|
+
* but invalid/expired token is always 401 — only a fully absent header is
|
|
30
|
+
* treated as anonymous (it is not subject to `allowedRoles`). */
|
|
31
|
+
anonRole?: string;
|
|
32
|
+
/** Runtime setting the claims are published under. Default 'request.jwt.claims'. */
|
|
33
|
+
claimsGuc?: string;
|
|
34
|
+
};
|
|
35
|
+
export type AuthContext = {
|
|
36
|
+
role: string;
|
|
37
|
+
claims: JWTPayload;
|
|
38
|
+
};
|
|
39
|
+
export type Authenticator = {
|
|
40
|
+
roleClaim: string;
|
|
41
|
+
claimsGuc: string;
|
|
42
|
+
/** Verify a raw `Authorization` header value and resolve the role.
|
|
43
|
+
* Throws a 401 KozouApiError for any token problem, 403 for a role one. */
|
|
44
|
+
authenticate(authorizationHeader: string | undefined): Promise<AuthContext>;
|
|
45
|
+
};
|
|
46
|
+
/** Validate config (throws a plain Error at startup on misconfiguration),
|
|
47
|
+
* import the key once, and return a verifier closure. */
|
|
48
|
+
export declare function createAuthenticator(config: AuthConfig): Authenticator;
|
|
49
|
+
export type ServiceTokenOptions = {
|
|
50
|
+
/** HS256 shared secret used to sign. Must match the verifier's secret. */
|
|
51
|
+
secret: string;
|
|
52
|
+
/** Claim that names the database role. Default: 'role'. */
|
|
53
|
+
roleClaim?: string;
|
|
54
|
+
/** Role to assume. When omitted, no role claim is set and the verifier
|
|
55
|
+
* falls back to its configured defaultRole. */
|
|
56
|
+
role?: string;
|
|
57
|
+
/** `iss` to set; required when the verifier expects a matching issuer. */
|
|
58
|
+
issuer?: string;
|
|
59
|
+
/** `aud` to set; required when the verifier expects a matching audience. */
|
|
60
|
+
audience?: string | string[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Mint an HS256 service token for a trusted same-host caller — the bundled
|
|
64
|
+
* Admin UI under `kozou dev`, which has no end user to obtain a token from.
|
|
65
|
+
* The token is signed with the same HS256 secret the API verifies against
|
|
66
|
+
* and carries the role claim the authenticator reads, so the caller runs
|
|
67
|
+
* under that role with the schema author's own RLS policies applied.
|
|
68
|
+
*
|
|
69
|
+
* No `exp` is set: the token lives only for the lifetime of the dev process
|
|
70
|
+
* that mints it and is passed in-process to the UI, never persisted. Minting
|
|
71
|
+
* needs the shared secret, so it is HS256-only — an RS256 deployment must
|
|
72
|
+
* supply a token from its identity provider instead.
|
|
73
|
+
*/
|
|
74
|
+
export declare function signServiceToken(opts: ServiceTokenOptions): Promise<string>;
|
|
75
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,OAAO,EAOL,KAAK,UAAU,EAGhB,MAAM,MAAM,CAAC;AAGd,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE;QACH,oFAAoF;QACpF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,4EAA4E;QAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;;0FAEkF;QAClF,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,2EAA2E;QAC3E,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;QAC5B,wDAAwD;QACxD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,wDAAwD;QACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KAC9B,CAAC;IACF,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;sEAIkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;gFAC4E;IAC5E,YAAY,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7E,CAAC;AAEF;0DAC0D;AAC1D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CA0DrE;AAsCD,MAAM,MAAM,mBAAmB,GAAG;IAChC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;oDACgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAajF"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// JWT verification for the read/write path. Pure over (Authorization header,
|
|
2
|
+
// config): verifies the token, resolves the database role to assume, and
|
|
3
|
+
// returns the claims. The caller (startApiServer) then opens a transaction,
|
|
4
|
+
// runs `SET LOCAL ROLE <role>`, and exposes the claims to PostgreSQL so the
|
|
5
|
+
// schema author's own row-level-security policies decide what each request
|
|
6
|
+
// can see — kozou authenticates and switches role; it never writes policies.
|
|
7
|
+
//
|
|
8
|
+
// No node:http and no pg here, so this unit-tests with a signed token alone.
|
|
9
|
+
import { importSPKI, importJWK, jwtVerify, createRemoteJWKSet, SignJWT, errors as joseErrors, } from 'jose';
|
|
10
|
+
import { unauthorized, forbidden } from './errors.js';
|
|
11
|
+
/** Validate config (throws a plain Error at startup on misconfiguration),
|
|
12
|
+
* import the key once, and return a verifier closure. */
|
|
13
|
+
export function createAuthenticator(config) {
|
|
14
|
+
const { jwt } = config;
|
|
15
|
+
const hasSecret = typeof jwt.secret === 'string' && jwt.secret.length > 0;
|
|
16
|
+
const hasPublicKey = typeof jwt.publicKey === 'string' && jwt.publicKey.length > 0;
|
|
17
|
+
const hasJwksUri = typeof jwt.jwksUri === 'string' && jwt.jwksUri.length > 0;
|
|
18
|
+
if ([hasSecret, hasPublicKey, hasJwksUri].filter(Boolean).length !== 1) {
|
|
19
|
+
throw new Error('@kozou/api auth: configure exactly one of jwt.secret (HS256), ' +
|
|
20
|
+
'jwt.publicKey (RS256), or jwt.jwksUri (remote JWKS).');
|
|
21
|
+
}
|
|
22
|
+
const algorithms = jwt.algorithms ?? (hasSecret ? ['HS256'] : ['RS256']);
|
|
23
|
+
const roleClaim = config.roleClaim ?? 'role';
|
|
24
|
+
const claimsGuc = config.claimsGuc ?? 'request.jwt.claims';
|
|
25
|
+
const verifyOptions = { algorithms };
|
|
26
|
+
if (jwt.issuer !== undefined)
|
|
27
|
+
verifyOptions.issuer = jwt.issuer;
|
|
28
|
+
if (jwt.audience !== undefined)
|
|
29
|
+
verifyOptions.audience = jwt.audience;
|
|
30
|
+
// Resolve the key once into a getKey function jose calls per token. A remote
|
|
31
|
+
// JWKS resolves the key by `kid` (fetched + cached); a secret / static public
|
|
32
|
+
// key is imported once and wrapped so the verify call is uniform.
|
|
33
|
+
const getKey = hasJwksUri
|
|
34
|
+
? Promise.resolve(createRemoteJWKSet(new URL(jwt.jwksUri)))
|
|
35
|
+
: (hasSecret
|
|
36
|
+
? Promise.resolve(new TextEncoder().encode(jwt.secret))
|
|
37
|
+
: importPublicKey(jwt.publicKey, algorithms[0] ?? 'RS256')).then((key) => () => Promise.resolve(key));
|
|
38
|
+
return {
|
|
39
|
+
roleClaim,
|
|
40
|
+
claimsGuc,
|
|
41
|
+
async authenticate(header) {
|
|
42
|
+
const token = extractBearer(header);
|
|
43
|
+
if (token === undefined) {
|
|
44
|
+
// Only a fully absent header is anonymous. A present-but-malformed
|
|
45
|
+
// header ("Basic …", "Bearer "} is a failed auth attempt, never
|
|
46
|
+
// silently downgraded to the anonymous role.
|
|
47
|
+
if (header === undefined && config.anonRole !== undefined) {
|
|
48
|
+
return { role: config.anonRole, claims: {} };
|
|
49
|
+
}
|
|
50
|
+
throw unauthorized('Missing or malformed Authorization header.');
|
|
51
|
+
}
|
|
52
|
+
let payload;
|
|
53
|
+
try {
|
|
54
|
+
({ payload } = await jwtVerify(token, await getKey, verifyOptions));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Any verification failure (signature, expiry, nbf, iss, aud, alg)
|
|
58
|
+
// is a 401 with a generic message — never leak which check failed.
|
|
59
|
+
if (err instanceof joseErrors.JOSEError) {
|
|
60
|
+
throw unauthorized('Invalid or expired token.');
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
return { role: resolveRole(payload, roleClaim, config), claims: payload };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function extractBearer(header) {
|
|
69
|
+
// Linear parse (no regex) to avoid backtracking on adversarial input:
|
|
70
|
+
// split on the first space into "<scheme> <token>".
|
|
71
|
+
if (header === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const trimmed = header.trim();
|
|
74
|
+
const space = trimmed.indexOf(' ');
|
|
75
|
+
if (space === -1)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (trimmed.slice(0, space).toLowerCase() !== 'bearer')
|
|
78
|
+
return undefined;
|
|
79
|
+
const token = trimmed.slice(space + 1).trim();
|
|
80
|
+
return token.length > 0 ? token : undefined;
|
|
81
|
+
}
|
|
82
|
+
async function importPublicKey(publicKey, algorithm) {
|
|
83
|
+
const trimmed = publicKey.trim();
|
|
84
|
+
if (trimmed.startsWith('-----BEGIN')) {
|
|
85
|
+
return importSPKI(trimmed, algorithm);
|
|
86
|
+
}
|
|
87
|
+
return importJWK(JSON.parse(trimmed), algorithm);
|
|
88
|
+
}
|
|
89
|
+
function resolveRole(payload, roleClaim, config) {
|
|
90
|
+
const claimed = payload[roleClaim];
|
|
91
|
+
const role = typeof claimed === 'string' && claimed.length > 0 ? claimed : config.defaultRole;
|
|
92
|
+
if (role === undefined || role.length === 0) {
|
|
93
|
+
throw forbidden('Token does not specify a role and no default role is configured.');
|
|
94
|
+
}
|
|
95
|
+
if (config.allowedRoles !== undefined && !config.allowedRoles.includes(role)) {
|
|
96
|
+
throw forbidden(`Role "${role}" is not permitted.`);
|
|
97
|
+
}
|
|
98
|
+
return role;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mint an HS256 service token for a trusted same-host caller — the bundled
|
|
102
|
+
* Admin UI under `kozou dev`, which has no end user to obtain a token from.
|
|
103
|
+
* The token is signed with the same HS256 secret the API verifies against
|
|
104
|
+
* and carries the role claim the authenticator reads, so the caller runs
|
|
105
|
+
* under that role with the schema author's own RLS policies applied.
|
|
106
|
+
*
|
|
107
|
+
* No `exp` is set: the token lives only for the lifetime of the dev process
|
|
108
|
+
* that mints it and is passed in-process to the UI, never persisted. Minting
|
|
109
|
+
* needs the shared secret, so it is HS256-only — an RS256 deployment must
|
|
110
|
+
* supply a token from its identity provider instead.
|
|
111
|
+
*/
|
|
112
|
+
export async function signServiceToken(opts) {
|
|
113
|
+
if (typeof opts.secret !== 'string' || opts.secret.length === 0) {
|
|
114
|
+
throw new Error('@kozou/api signServiceToken: a non-empty HS256 secret is required.');
|
|
115
|
+
}
|
|
116
|
+
const roleClaim = opts.roleClaim ?? 'role';
|
|
117
|
+
const payload = {};
|
|
118
|
+
if (typeof opts.role === 'string' && opts.role.length > 0) {
|
|
119
|
+
payload[roleClaim] = opts.role;
|
|
120
|
+
}
|
|
121
|
+
let jwt = new SignJWT(payload).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();
|
|
122
|
+
if (opts.issuer !== undefined)
|
|
123
|
+
jwt = jwt.setIssuer(opts.issuer);
|
|
124
|
+
if (opts.audience !== undefined)
|
|
125
|
+
jwt = jwt.setAudience(opts.audience);
|
|
126
|
+
return jwt.sign(new TextEncoder().encode(opts.secret));
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|