okengine 0.15.2 → 0.16.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/package.json +1 -1
- package/site/content/docs/elements/flow.mdx +6 -0
- package/site/content/docs/elements/gate.mdx +55 -0
- package/site/content/docs/elements/store.mdx +13 -15
- package/site/content/docs/elements/vault.mdx +3 -0
- package/site/content/docs/reference/client.mdx +3 -0
- package/site/content/docs/reference/fx.mdx +29 -18
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +254 -0
- package/src/auth/api-keys.ts +273 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/config.ts +5 -5
- package/src/auth/index.ts +25 -1
- package/src/auth/schema.ts +8 -0
- package/src/cli/attach-host-console.test.ts +37 -0
- package/src/cli/attach-host-console.ts +6 -0
- package/src/compiler/effects-infer.ts +17 -2
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +1 -0
- package/src/console/server/dev-identities.test.ts +127 -0
- package/src/console/server/dev-identities.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +21 -4
- package/src/console/server/flows.ts +113 -58
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +8 -21
- package/src/console/server/index.ts +1 -0
- package/src/console/server/live.test.ts +26 -1
- package/src/console/server/live.ts +4 -2
- package/src/console/server/operator-db.ts +2 -0
- package/src/console/server/state.ts +29 -55
- package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-C0X1fbWF.js → agent-disclosure-BP0Y0Sux.js} +1 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CS_O8L-r.js → highlighted-json-Awq7gYdu.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
- package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
- package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-BLbZhzyF.js → use-vault-list-uk4WVboC.js} +1 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
- package/src/console/ui-next/dist/index.html +11 -9
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +167 -0
- package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
- package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
- package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
- package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
- package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
- package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
- package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
- package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
- package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
- package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
- package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
- package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
- package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
- package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
- package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
- package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
- package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
- package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
- package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
- package/src/console/ui-next/src/lib/document-meta.ts +2 -1
- package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
- package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
- package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
- package/src/console/ui-next/src/lib/shortcut.ts +3 -0
- package/src/console/ui-next/src/router.tsx +10 -1
- package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
- package/src/console/ui-next/ui-next-seed-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/elements/gate/declare.ts +2 -0
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/runtime.ts +14 -0
- package/src/elements/store/schema-decl.ts +99 -41
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +54 -32
- package/src/elements/store.ts +3 -0
- package/src/index.ts +1 -0
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app.ts +14 -2
- package/src/kernel/auth-resolve.ts +10 -5
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +198 -0
- package/src/kernel/fx.test.ts +17 -1
- package/src/kernel/fx.ts +36 -18
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline.ts +4 -2
- package/src/manifest/types.ts +9 -3
- package/src/release/build-lib.ts +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-DEDCl_j3.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-3ptDzXbz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-BvYrygyO.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +0 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +0 -66
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +0 -2
- package/src/console/ui-next/dist/assets/link-COeyggt-.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +0 -4
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +0 -54
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +0 -2
|
@@ -38,8 +38,14 @@ export interface ColumnReference {
|
|
|
38
38
|
* `getSQL` is a structural bridge so drizzle-orm operators (`eq`, `isNull`, …)
|
|
39
39
|
* accept abstract columns in TypeScript. OKE’s SQL compiler never calls it —
|
|
40
40
|
* it reads {@link SchemaColumnDecl.sqlName} / metadata directly.
|
|
41
|
+
*
|
|
42
|
+
* `TData` / `TNotNull` are type-level only (`$infer` is a phantom). They let
|
|
43
|
+
* {@link InferColumnJs} / table `$inferSelect` resolve real row shapes.
|
|
41
44
|
*/
|
|
42
|
-
export interface SchemaColumnDecl
|
|
45
|
+
export interface SchemaColumnDecl<
|
|
46
|
+
TData = unknown,
|
|
47
|
+
TNotNull extends boolean = boolean,
|
|
48
|
+
> extends ColumnDef {
|
|
43
49
|
/** JS object key (e.g. `createdAt`). */
|
|
44
50
|
readonly key: string;
|
|
45
51
|
/** Database column name (e.g. `created_at`). */
|
|
@@ -47,7 +53,12 @@ export interface SchemaColumnDecl extends ColumnDef {
|
|
|
47
53
|
/** Declared SQL type. */
|
|
48
54
|
readonly sqlType: FieldSqlType;
|
|
49
55
|
readonly primaryKey: boolean;
|
|
50
|
-
readonly notNull:
|
|
56
|
+
readonly notNull: TNotNull;
|
|
57
|
+
/**
|
|
58
|
+
* Phantom JS value type — not present at runtime. Distinguishes `string`
|
|
59
|
+
* vs `number` so structural typing does not collapse column data types.
|
|
60
|
+
*/
|
|
61
|
+
readonly $infer?: TData;
|
|
51
62
|
readonly unique: boolean;
|
|
52
63
|
/** Literal `.default(v)` when set. */
|
|
53
64
|
readonly defaultValue?: string | number | boolean | null;
|
|
@@ -120,33 +131,41 @@ export interface SchemaTableDecl extends TableHandle {
|
|
|
120
131
|
readonly policies?: readonly SchemaPolicyDecl[];
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Fluent field builder before key finalization.
|
|
136
|
+
*
|
|
137
|
+
* `TData` is the JS value type (`string` / `number`). `TNotNull` is `true`
|
|
138
|
+
* after `.notNull()` or `.primaryKey()` — matching runtime finalize.
|
|
139
|
+
*/
|
|
140
|
+
export interface FieldBuilder<TData = unknown, TNotNull extends boolean = false> {
|
|
141
|
+
primaryKey(): FieldBuilder<TData, true>;
|
|
142
|
+
notNull(): FieldBuilder<TData, true>;
|
|
143
|
+
unique(): FieldBuilder<TData, TNotNull>;
|
|
144
|
+
default(value: string | number | boolean | null): FieldBuilder<TData, TNotNull>;
|
|
145
|
+
defaultFn(fn: () => unknown): FieldBuilder<TData, TNotNull>;
|
|
146
|
+
pii(): FieldBuilder<TData, TNotNull>;
|
|
147
|
+
sensitive(): FieldBuilder<TData, TNotNull>;
|
|
148
|
+
retain(duration: string): FieldBuilder<TData, TNotNull>;
|
|
133
149
|
/** Override snake_case SQL name. */
|
|
134
|
-
as(sqlName: string): FieldBuilder
|
|
150
|
+
as(sqlName: string): FieldBuilder<TData, TNotNull>;
|
|
135
151
|
/** Optional human description for Console / docs (falls back to the JS key). */
|
|
136
|
-
describe(description: string): FieldBuilder
|
|
152
|
+
describe(description: string): FieldBuilder<TData, TNotNull>;
|
|
137
153
|
/**
|
|
138
154
|
* Declare a foreign key to another column (dialect-agnostic).
|
|
139
155
|
*
|
|
140
156
|
* @param ref - Lazy target column (`() => links.code`)
|
|
141
157
|
* @param actions - Optional ON DELETE / ON UPDATE
|
|
142
158
|
*/
|
|
143
|
-
references(
|
|
159
|
+
references(
|
|
160
|
+
ref: () => SchemaColumnDecl,
|
|
161
|
+
actions?: ReferenceActions,
|
|
162
|
+
): FieldBuilder<TData, TNotNull>;
|
|
144
163
|
/**
|
|
145
164
|
* Bind the JS key and produce a {@link SchemaColumnDecl}.
|
|
146
165
|
*
|
|
147
166
|
* @param key - Object key in the table column map
|
|
148
167
|
*/
|
|
149
|
-
finalize(key: string): SchemaColumnDecl
|
|
168
|
+
finalize(key: string): SchemaColumnDecl<TData, TNotNull>;
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
interface FieldState {
|
|
@@ -186,31 +205,39 @@ function mergeClassification(
|
|
|
186
205
|
return { ...(current ?? {}), ...patch };
|
|
187
206
|
}
|
|
188
207
|
|
|
189
|
-
function createBuilder
|
|
190
|
-
|
|
208
|
+
function createBuilder<TData, TNotNull extends boolean>(
|
|
209
|
+
state: FieldState,
|
|
210
|
+
): FieldBuilder<TData, TNotNull> {
|
|
211
|
+
const next = <N extends boolean>(patch: Partial<FieldState>): FieldBuilder<TData, N> =>
|
|
212
|
+
createBuilder<TData, N>({ ...state, ...patch });
|
|
191
213
|
|
|
192
214
|
return {
|
|
193
|
-
primaryKey: () => next({ primaryKey: true, notNull: true }),
|
|
194
|
-
notNull: () => next({ notNull: true }),
|
|
195
|
-
unique: () => next({ unique: true }),
|
|
196
|
-
default: (value) => next({ defaultValue: value }),
|
|
215
|
+
primaryKey: () => next<true>({ primaryKey: true, notNull: true }),
|
|
216
|
+
notNull: () => next<true>({ notNull: true }),
|
|
217
|
+
unique: () => next<TNotNull>({ unique: true }),
|
|
218
|
+
default: (value) => next<TNotNull>({ defaultValue: value }),
|
|
197
219
|
defaultFn: (fn) =>
|
|
198
|
-
next({
|
|
220
|
+
next<TNotNull>({
|
|
199
221
|
defaultFn: fn,
|
|
200
222
|
defaultFnKind: defaultFnKindOf(fn),
|
|
201
223
|
}),
|
|
202
|
-
pii: () =>
|
|
224
|
+
pii: () =>
|
|
225
|
+
next<TNotNull>({ classification: mergeClassification(state.classification, { pii: true }) }),
|
|
203
226
|
sensitive: () =>
|
|
204
|
-
next({
|
|
227
|
+
next<TNotNull>({
|
|
228
|
+
classification: mergeClassification(state.classification, { sensitive: true }),
|
|
229
|
+
}),
|
|
205
230
|
retain: (duration) =>
|
|
206
|
-
next({
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
next<TNotNull>({
|
|
232
|
+
classification: mergeClassification(state.classification, { retain: duration }),
|
|
233
|
+
}),
|
|
234
|
+
as: (sqlName) => next<TNotNull>({ sqlName }),
|
|
235
|
+
describe: (description) => next<TNotNull>({ description }),
|
|
209
236
|
references: (ref, actions) =>
|
|
210
|
-
next({
|
|
237
|
+
next<TNotNull>({
|
|
211
238
|
references: actions ? { ref, actions } : { ref },
|
|
212
239
|
}),
|
|
213
|
-
finalize(key: string): SchemaColumnDecl {
|
|
240
|
+
finalize(key: string): SchemaColumnDecl<TData, TNotNull> {
|
|
214
241
|
const sqlName = state.sqlName ?? camelToSnake(key);
|
|
215
242
|
return {
|
|
216
243
|
key,
|
|
@@ -218,7 +245,7 @@ function createBuilder(state: FieldState): FieldBuilder {
|
|
|
218
245
|
sqlName,
|
|
219
246
|
sqlType: state.sqlType,
|
|
220
247
|
primaryKey: state.primaryKey,
|
|
221
|
-
notNull: state.notNull || state.primaryKey,
|
|
248
|
+
notNull: (state.notNull || state.primaryKey) as TNotNull,
|
|
222
249
|
unique: state.unique,
|
|
223
250
|
...(state.defaultValue !== undefined ? { defaultValue: state.defaultValue } : {}),
|
|
224
251
|
...(state.defaultFn ? { defaultFn: state.defaultFn } : {}),
|
|
@@ -240,15 +267,15 @@ function createBuilder(state: FieldState): FieldBuilder {
|
|
|
240
267
|
* Field builders — `field.text()` / `field.integer()`.
|
|
241
268
|
*/
|
|
242
269
|
export const field = {
|
|
243
|
-
text: (): FieldBuilder =>
|
|
244
|
-
createBuilder({
|
|
270
|
+
text: (): FieldBuilder<string, false> =>
|
|
271
|
+
createBuilder<string, false>({
|
|
245
272
|
sqlType: "text",
|
|
246
273
|
primaryKey: false,
|
|
247
274
|
notNull: false,
|
|
248
275
|
unique: false,
|
|
249
276
|
}),
|
|
250
|
-
integer: (): FieldBuilder =>
|
|
251
|
-
createBuilder({
|
|
277
|
+
integer: (): FieldBuilder<number, false> =>
|
|
278
|
+
createBuilder<number, false>({
|
|
252
279
|
sqlType: "integer",
|
|
253
280
|
primaryKey: false,
|
|
254
281
|
notNull: false,
|
|
@@ -256,8 +283,19 @@ export const field = {
|
|
|
256
283
|
}),
|
|
257
284
|
} as const;
|
|
258
285
|
|
|
259
|
-
/**
|
|
260
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Column map input for {@link schemaTable}.
|
|
288
|
+
*
|
|
289
|
+
* Explicit `string` / `number` × nullability variants so `field.text()`
|
|
290
|
+
* (`FieldBuilder<string, false>`) remains assignable — methodful builders
|
|
291
|
+
* are invariant in their type parameters.
|
|
292
|
+
*/
|
|
293
|
+
export type SchemaColumnInput =
|
|
294
|
+
| FieldBuilder<string, false>
|
|
295
|
+
| FieldBuilder<string, true>
|
|
296
|
+
| FieldBuilder<number, false>
|
|
297
|
+
| FieldBuilder<number, true>
|
|
298
|
+
| SchemaColumnDecl;
|
|
261
299
|
|
|
262
300
|
/**
|
|
263
301
|
* Whether a value is a finalized {@link SchemaColumnDecl}.
|
|
@@ -301,7 +339,7 @@ export function finalizeColumnMap(
|
|
|
301
339
|
): Record<string, SchemaColumnDecl> {
|
|
302
340
|
const out: Record<string, SchemaColumnDecl> = {};
|
|
303
341
|
for (const [key, value] of Object.entries(columns)) {
|
|
304
|
-
out[key] = isFieldBuilder(value) ? value.finalize(key) : value;
|
|
342
|
+
out[key] = isFieldBuilder(value) ? value.finalize(key) : (value as SchemaColumnDecl);
|
|
305
343
|
}
|
|
306
344
|
return out;
|
|
307
345
|
}
|
|
@@ -333,14 +371,34 @@ export function schemaTableSqlName(table: SchemaTableDecl): string | undefined {
|
|
|
333
371
|
return undefined;
|
|
334
372
|
}
|
|
335
373
|
|
|
374
|
+
/**
|
|
375
|
+
* JS value type for a finalized column (`string` / `number`, plus `| null`
|
|
376
|
+
* when the column is nullable).
|
|
377
|
+
*/
|
|
378
|
+
export type InferColumnJs<C> =
|
|
379
|
+
C extends SchemaColumnDecl<infer D, infer N> ? (N extends true ? D : D | null) : unknown;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Finalize a {@link schemaTable} column input into a typed decl.
|
|
383
|
+
*/
|
|
384
|
+
export type FinalizeColumn<T> =
|
|
385
|
+
T extends FieldBuilder<infer D, infer N>
|
|
386
|
+
? SchemaColumnDecl<D, N>
|
|
387
|
+
: T extends SchemaColumnDecl<infer D, infer N>
|
|
388
|
+
? SchemaColumnDecl<D, N>
|
|
389
|
+
: SchemaColumnDecl;
|
|
390
|
+
|
|
336
391
|
/**
|
|
337
392
|
* Table decl with columns as own properties (`links.code`) for FK / relations.
|
|
393
|
+
*
|
|
394
|
+
* `$inferSelect` is type-level only (Drizzle's own alias) — not set at runtime.
|
|
338
395
|
*/
|
|
339
396
|
export type SchemaTableWithColumns<
|
|
340
397
|
C extends Record<string, SchemaColumnDecl> = Record<string, SchemaColumnDecl>,
|
|
341
398
|
> = Omit<SchemaTableDecl, "columns"> & {
|
|
342
399
|
readonly columns: Readonly<C>;
|
|
343
|
-
|
|
400
|
+
readonly $inferSelect: { [K in keyof C]: InferColumnJs<C[K]> };
|
|
401
|
+
} & Omit<C, "kind" | "tableName" | "$inferSelect">;
|
|
344
402
|
|
|
345
403
|
/**
|
|
346
404
|
* Declare an abstract schema table (ORM-agnostic).
|
|
@@ -356,7 +414,7 @@ export function schemaTable<C extends Record<string, SchemaColumnInput>>(
|
|
|
356
414
|
name: string,
|
|
357
415
|
columns: C,
|
|
358
416
|
extras: readonly SchemaTableExtra[] = [],
|
|
359
|
-
): SchemaTableWithColumns<{ [K in keyof C]:
|
|
417
|
+
): SchemaTableWithColumns<{ [K in keyof C]: FinalizeColumn<C[K]> }> {
|
|
360
418
|
const finalized = finalizeColumnMap(columns);
|
|
361
419
|
const stamped: Record<string, SchemaColumnDecl> = {};
|
|
362
420
|
for (const [key, col] of Object.entries(finalized)) {
|
|
@@ -377,7 +435,7 @@ export function schemaTable<C extends Record<string, SchemaColumnInput>>(
|
|
|
377
435
|
// the table discriminant / SQL name).
|
|
378
436
|
Object.defineProperty(table, "kind", { value: "schema-table", enumerable: false });
|
|
379
437
|
Object.defineProperty(table, "tableName", { value: name, enumerable: false });
|
|
380
|
-
return table as SchemaTableWithColumns<{ [K in keyof C]:
|
|
438
|
+
return table as SchemaTableWithColumns<{ [K in keyof C]: FinalizeColumn<C[K]> }>;
|
|
381
439
|
}
|
|
382
440
|
|
|
383
441
|
/**
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level proof: `select().from(store.schema.table())` resolves to the
|
|
3
|
+
* declared column JS shape — not `SqlRow`.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import type { SqlRow } from "../../drivers/types.ts";
|
|
8
|
+
import { field, schemaTable } from "./schema-decl.ts";
|
|
9
|
+
import type { InferSelectRow, SqlStoreHandle } from "./sql-session.ts";
|
|
10
|
+
import { defineTable } from "./table.ts";
|
|
11
|
+
|
|
12
|
+
/** Compile-time equality. */
|
|
13
|
+
type Eq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
14
|
+
type Assert<T extends true> = T;
|
|
15
|
+
|
|
16
|
+
const items = schemaTable("items", {
|
|
17
|
+
id: field.text().primaryKey(),
|
|
18
|
+
title: field.text().notNull(),
|
|
19
|
+
note: field.text(),
|
|
20
|
+
count: field.integer().notNull(),
|
|
21
|
+
score: field.integer(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type ExpectedRow = {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
note: string | null;
|
|
28
|
+
count: number;
|
|
29
|
+
score: number | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function selectAll(db: SqlStoreHandle) {
|
|
33
|
+
return db.select().from(items);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function selectWhere(db: SqlStoreHandle) {
|
|
37
|
+
return db.select().from(items).where({ id: "x" });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function selectOrder(db: SqlStoreHandle) {
|
|
41
|
+
return db.select().from(items).orderBy(items.title);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function selectLimit(db: SqlStoreHandle) {
|
|
45
|
+
return db.select().from(items).limit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function selectProj(db: SqlStoreHandle) {
|
|
49
|
+
return db.select({ title: items.title }).from(items);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bare = defineTable("bare", { id: { name: "id" } });
|
|
53
|
+
|
|
54
|
+
async function selectBare(db: SqlStoreHandle) {
|
|
55
|
+
return db.select().from(bare);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type _Infer = Assert<Eq<InferSelectRow<typeof items>, ExpectedRow>>;
|
|
59
|
+
type _All = Assert<Eq<Awaited<ReturnType<typeof selectAll>>, ExpectedRow[]>>;
|
|
60
|
+
type _Where = Assert<Eq<Awaited<ReturnType<typeof selectWhere>>, ExpectedRow[]>>;
|
|
61
|
+
type _Order = Assert<Eq<Awaited<ReturnType<typeof selectOrder>>, ExpectedRow[]>>;
|
|
62
|
+
type _Limit = Assert<Eq<Awaited<ReturnType<typeof selectLimit>>, ExpectedRow[]>>;
|
|
63
|
+
type _Proj = Assert<Eq<Awaited<ReturnType<typeof selectProj>>, SqlRow[]>>;
|
|
64
|
+
type _Bare = Assert<Eq<Awaited<ReturnType<typeof selectBare>>, SqlRow[]>>;
|
|
65
|
+
|
|
66
|
+
describe("select().from() row inference", () => {
|
|
67
|
+
test("type-level row shape matches declared columns", () => {
|
|
68
|
+
const _keep: _Infer & _All & _Where & _Order & _Limit & _Proj & _Bare = true;
|
|
69
|
+
expect(_keep).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -34,6 +34,15 @@ import {
|
|
|
34
34
|
|
|
35
35
|
export type { WhereMap } from "./sql-condition.ts";
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Inferred select-row shape for a table handle.
|
|
39
|
+
*
|
|
40
|
+
* Uses `$inferSelect` when present (OKE `store.schema.table()` and Drizzle
|
|
41
|
+
* `pgTable()` / `sqliteTable()`). Bare {@link TableHandle} / `unknown` stay
|
|
42
|
+
* {@link SqlRow}.
|
|
43
|
+
*/
|
|
44
|
+
export type InferSelectRow<T> = T extends { readonly $inferSelect: infer R } ? R : SqlRow;
|
|
45
|
+
|
|
37
46
|
/** Serialize stamp frames on a shared connection so concurrent identities cannot interleave. */
|
|
38
47
|
const rlsStampTails = new WeakMap<SqlConnection, Promise<unknown>>();
|
|
39
48
|
|
|
@@ -97,44 +106,44 @@ export interface SqlSessionOptions {
|
|
|
97
106
|
* Continuations after `.orderBy(...)` — awaitable, optional `.limit` /
|
|
98
107
|
* `.offset`.
|
|
99
108
|
*/
|
|
100
|
-
export interface SelectOrderBuilder extends PromiseLike<
|
|
109
|
+
export interface SelectOrderBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
101
110
|
/**
|
|
102
111
|
* Cap the number of returned rows.
|
|
103
112
|
*
|
|
104
113
|
* @param n - Max rows
|
|
105
114
|
*/
|
|
106
|
-
limit(n: number): Promise<
|
|
115
|
+
limit(n: number): Promise<TRow[]>;
|
|
107
116
|
/**
|
|
108
117
|
* Skip the first `n` rows (`LIMIT … OFFSET …`).
|
|
109
118
|
*
|
|
110
119
|
* @param n - Rows to skip
|
|
111
120
|
*/
|
|
112
|
-
offset(n: number): Promise<
|
|
121
|
+
offset(n: number): Promise<TRow[]>;
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
/**
|
|
116
125
|
* Continuations after `.where(...)` on a select — awaitable, optional
|
|
117
126
|
* `.orderBy(...)` / `.limit(...)` / `.offset(...)`.
|
|
118
127
|
*/
|
|
119
|
-
export interface SelectWhereBuilder extends PromiseLike<
|
|
128
|
+
export interface SelectWhereBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
120
129
|
/**
|
|
121
130
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
122
131
|
*
|
|
123
132
|
* @param orders - Order terms
|
|
124
133
|
*/
|
|
125
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
134
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
126
135
|
/**
|
|
127
136
|
* Cap the number of returned rows.
|
|
128
137
|
*
|
|
129
138
|
* @param n - Max rows
|
|
130
139
|
*/
|
|
131
|
-
limit(n: number): Promise<
|
|
140
|
+
limit(n: number): Promise<TRow[]>;
|
|
132
141
|
/**
|
|
133
142
|
* Skip the first `n` rows.
|
|
134
143
|
*
|
|
135
144
|
* @param n - Rows to skip
|
|
136
145
|
*/
|
|
137
|
-
offset(n: number): Promise<
|
|
146
|
+
offset(n: number): Promise<TRow[]>;
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
/**
|
|
@@ -142,42 +151,49 @@ export interface SelectWhereBuilder extends PromiseLike<SqlRow[]> {
|
|
|
142
151
|
* `.where(...)` / `.orderBy(...)` / `.limit(...)` / `.offset(...)` in any
|
|
143
152
|
* order.
|
|
144
153
|
*/
|
|
145
|
-
export interface SelectFromBuilder extends PromiseLike<
|
|
154
|
+
export interface SelectFromBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
146
155
|
/**
|
|
147
156
|
* Filter with a plain equality map or a Drizzle SQL condition
|
|
148
157
|
* (`eq`, `and`, `or`, `lt`, `like`, …).
|
|
149
158
|
*
|
|
150
159
|
* @param where - Condition
|
|
151
160
|
*/
|
|
152
|
-
where(where: unknown): SelectWhereBuilder
|
|
161
|
+
where(where: unknown): SelectWhereBuilder<TRow>;
|
|
153
162
|
/**
|
|
154
163
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
155
164
|
*
|
|
156
165
|
* @param orders - Order terms
|
|
157
166
|
*/
|
|
158
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
167
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
159
168
|
/**
|
|
160
169
|
* Cap the number of returned rows.
|
|
161
170
|
*
|
|
162
171
|
* @param n - Max rows
|
|
163
172
|
*/
|
|
164
|
-
limit(n: number): Promise<
|
|
173
|
+
limit(n: number): Promise<TRow[]>;
|
|
165
174
|
/**
|
|
166
175
|
* Skip the first `n` rows.
|
|
167
176
|
*
|
|
168
177
|
* @param n - Rows to skip
|
|
169
178
|
*/
|
|
170
|
-
offset(n: number): Promise<
|
|
179
|
+
offset(n: number): Promise<TRow[]>;
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Fluent select builder.
|
|
184
|
+
*
|
|
185
|
+
* `TLocked` is `undefined` for `select()` (row type inferred from `.from`)
|
|
186
|
+
* and {@link SqlRow} for `select({ alias: col })` projections.
|
|
187
|
+
*/
|
|
188
|
+
export interface SelectBuilder<TLocked extends SqlRow | undefined = undefined> {
|
|
175
189
|
/**
|
|
176
190
|
* Choose the source table.
|
|
177
191
|
*
|
|
178
192
|
* @param table - Table handle or Drizzle table
|
|
179
193
|
*/
|
|
180
|
-
from(
|
|
194
|
+
from<TTable>(
|
|
195
|
+
table: TTable,
|
|
196
|
+
): SelectFromBuilder<TLocked extends undefined ? InferSelectRow<TTable> : TLocked>;
|
|
181
197
|
}
|
|
182
198
|
|
|
183
199
|
/** Fluent insert builder. */
|
|
@@ -244,12 +260,13 @@ export interface SqlStoreHandle {
|
|
|
244
260
|
/** Underlying driver id. */
|
|
245
261
|
readonly driverId: SqlConnection["driverId"];
|
|
246
262
|
/**
|
|
247
|
-
* Start a select.
|
|
248
|
-
* (`select({ clicks: links.clicks })`).
|
|
263
|
+
* Start a select. No-arg form infers the row from `.from(table)`.
|
|
264
|
+
* A column map (`select({ clicks: links.clicks })`) stays {@link SqlRow}.
|
|
249
265
|
*
|
|
250
266
|
* @param columns - Optional `{ alias: drizzleColumn }` map
|
|
251
267
|
*/
|
|
252
|
-
select(
|
|
268
|
+
select(): SelectBuilder<undefined>;
|
|
269
|
+
select(columns: unknown): SelectBuilder<SqlRow>;
|
|
253
270
|
/**
|
|
254
271
|
* Start an insert into `table`.
|
|
255
272
|
*
|
|
@@ -600,20 +617,20 @@ export function createSqlStoreHandle(
|
|
|
600
617
|
};
|
|
601
618
|
}
|
|
602
619
|
|
|
603
|
-
const handle
|
|
620
|
+
const handle = {
|
|
604
621
|
ref,
|
|
605
622
|
routedRole: options.routedRole,
|
|
606
623
|
driverId: connection.driverId,
|
|
607
624
|
|
|
608
|
-
select(columns
|
|
625
|
+
select(columns?: unknown) {
|
|
609
626
|
return {
|
|
610
|
-
from(table) {
|
|
627
|
+
from(table: TableHandle | unknown) {
|
|
611
628
|
return selectFrom(table, columns);
|
|
612
629
|
},
|
|
613
630
|
};
|
|
614
631
|
},
|
|
615
632
|
|
|
616
|
-
insert(table): InsertBuilder {
|
|
633
|
+
insert(table: TableHandle | unknown): InsertBuilder {
|
|
617
634
|
const name = resolveTableName(table);
|
|
618
635
|
return {
|
|
619
636
|
values(row) {
|
|
@@ -648,7 +665,7 @@ export function createSqlStoreHandle(
|
|
|
648
665
|
};
|
|
649
666
|
},
|
|
650
667
|
|
|
651
|
-
update(table): UpdateBuilder {
|
|
668
|
+
update(table: TableHandle | unknown): UpdateBuilder {
|
|
652
669
|
const name = resolveTableName(table);
|
|
653
670
|
return {
|
|
654
671
|
set(row) {
|
|
@@ -675,7 +692,7 @@ export function createSqlStoreHandle(
|
|
|
675
692
|
};
|
|
676
693
|
},
|
|
677
694
|
|
|
678
|
-
async findById(table, idValue) {
|
|
695
|
+
async findById(table: TableHandle | unknown, idValue: string) {
|
|
679
696
|
await ensureFromMeta(table);
|
|
680
697
|
const name = resolveTableName(table);
|
|
681
698
|
const pk = resolvePkColumn(table);
|
|
@@ -716,7 +733,7 @@ export function createSqlStoreHandle(
|
|
|
716
733
|
return builder;
|
|
717
734
|
}) as SqlStoreHandle["delete"],
|
|
718
735
|
|
|
719
|
-
async exists(table, idOrWhere) {
|
|
736
|
+
async exists(table: TableHandle | unknown, idOrWhere: string | WhereMap) {
|
|
720
737
|
await ensureFromMeta(table);
|
|
721
738
|
const name = resolveTableName(table);
|
|
722
739
|
const where: WhereMap =
|
|
@@ -734,7 +751,12 @@ export function createSqlStoreHandle(
|
|
|
734
751
|
return rows.length > 0;
|
|
735
752
|
},
|
|
736
753
|
|
|
737
|
-
async upsert(
|
|
754
|
+
async upsert(
|
|
755
|
+
table: TableHandle | unknown,
|
|
756
|
+
matchOn: WhereMap | unknown,
|
|
757
|
+
values: SqlRow,
|
|
758
|
+
upsertOptions?: { readonly onExisting?: "update" },
|
|
759
|
+
) {
|
|
738
760
|
await ensureFromMeta(table);
|
|
739
761
|
const name = resolveTableName(table);
|
|
740
762
|
const compiled = compileTableWhere(table, matchOn);
|
|
@@ -766,7 +788,7 @@ export function createSqlStoreHandle(
|
|
|
766
788
|
return { status: "changed" as const };
|
|
767
789
|
},
|
|
768
790
|
|
|
769
|
-
async increment(table, idValue, column, by = 1) {
|
|
791
|
+
async increment(table: TableHandle | unknown, idValue: string, column: string, by = 1) {
|
|
770
792
|
await ensureFromMeta(table);
|
|
771
793
|
const name = resolveTableName(table);
|
|
772
794
|
const pk = resolvePkColumn(table);
|
|
@@ -785,7 +807,7 @@ export function createSqlStoreHandle(
|
|
|
785
807
|
return asNumber(row[sqlCol], sqlCol);
|
|
786
808
|
},
|
|
787
809
|
|
|
788
|
-
async raw(sql, params = []) {
|
|
810
|
+
async raw(sql: string, params: readonly unknown[] = []) {
|
|
789
811
|
const table = tableFromSql(sql);
|
|
790
812
|
// Memory SQL (console-next seed) implements DML on `exec`, not `query`.
|
|
791
813
|
// SELECT / RETURNING still go through `query` so callers get rows.
|
|
@@ -797,7 +819,7 @@ export function createSqlStoreHandle(
|
|
|
797
819
|
return mask(rows, table);
|
|
798
820
|
},
|
|
799
821
|
|
|
800
|
-
async count(table, where) {
|
|
822
|
+
async count(table: TableHandle | unknown, where?: unknown) {
|
|
801
823
|
await ensureFromMeta(table);
|
|
802
824
|
const name = resolveTableName(table);
|
|
803
825
|
let sql = `SELECT COUNT(*) AS "count" FROM ${quoteIdent(name)}`;
|
|
@@ -813,7 +835,7 @@ export function createSqlStoreHandle(
|
|
|
813
835
|
return asNumber(rows[0]?.count ?? rows[0]?.COUNT ?? 0, "count");
|
|
814
836
|
},
|
|
815
837
|
|
|
816
|
-
async page(table, options) {
|
|
838
|
+
async page(table: TableHandle | unknown, options: SqlPageOptions) {
|
|
817
839
|
if (options.after !== undefined && options.before !== undefined) {
|
|
818
840
|
throw new Error("page(): after and before (keyset) cannot combine");
|
|
819
841
|
}
|
|
@@ -838,7 +860,7 @@ export function createSqlStoreHandle(
|
|
|
838
860
|
});
|
|
839
861
|
},
|
|
840
862
|
|
|
841
|
-
async ensureTable(table) {
|
|
863
|
+
async ensureTable(table: TableHandle) {
|
|
842
864
|
const cols = Object.values(table.columns);
|
|
843
865
|
const pk = resolvePkColumn(table);
|
|
844
866
|
const intType =
|
|
@@ -863,7 +885,7 @@ export function createSqlStoreHandle(
|
|
|
863
885
|
},
|
|
864
886
|
};
|
|
865
887
|
|
|
866
|
-
return handle;
|
|
888
|
+
return handle as SqlStoreHandle;
|
|
867
889
|
}
|
|
868
890
|
|
|
869
891
|
/**
|
package/src/elements/store.ts
CHANGED
|
@@ -47,6 +47,8 @@ export type {
|
|
|
47
47
|
SchemaColumnInput,
|
|
48
48
|
SchemaTableDecl,
|
|
49
49
|
SchemaTableWithColumns,
|
|
50
|
+
InferColumnJs,
|
|
51
|
+
FinalizeColumn,
|
|
50
52
|
SchemaRelationsDecl,
|
|
51
53
|
SchemaPolicyDecl,
|
|
52
54
|
SchemaTableExtra,
|
|
@@ -121,6 +123,7 @@ export type {
|
|
|
121
123
|
SelectFromBuilder,
|
|
122
124
|
SelectWhereBuilder,
|
|
123
125
|
SelectOrderBuilder,
|
|
126
|
+
InferSelectRow,
|
|
124
127
|
InsertBuilder,
|
|
125
128
|
InsertValuesBuilder,
|
|
126
129
|
SqlSessionOptions,
|