okengine 0.14.1 → 0.15.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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +3 -0
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +86 -27
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +120 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/{observability-page-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/http.ts +20 -3
- package/src/index.ts +10 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +131 -14
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx.ts +23 -0
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +13 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expand flow `in` / `out` AST into Manifest JSON Schema.
|
|
3
|
+
*
|
|
4
|
+
* Covers handwritten `z.object({…})` and `drizzle-orm/zod`
|
|
5
|
+
* (`createSelectSchema` / `createInsertSchema` / `createUpdateSchema`,
|
|
6
|
+
* plus Keel's `tableZod(table).select|insert|update`). Opaque identifiers
|
|
7
|
+
* stay `"…"` at the call site.
|
|
8
|
+
*
|
|
9
|
+
* @see https://orm.drizzle.team/docs/zod
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { DeclaredColumn } from "../manifest/types.ts";
|
|
13
|
+
import {
|
|
14
|
+
identifierName,
|
|
15
|
+
stringArg,
|
|
16
|
+
type AstNode,
|
|
17
|
+
type CallExpression,
|
|
18
|
+
type Identifier,
|
|
19
|
+
type InferBinding,
|
|
20
|
+
type Literal,
|
|
21
|
+
} from "./effects-infer.ts";
|
|
22
|
+
|
|
23
|
+
/** Table columns already extracted from `store.schema.table`. */
|
|
24
|
+
export type SchemaTableColumns = Record<string, DeclaredColumn>;
|
|
25
|
+
|
|
26
|
+
/** One `const Name = <init>` and the file it was declared in. */
|
|
27
|
+
export type ConstInit = {
|
|
28
|
+
readonly node: AstNode;
|
|
29
|
+
readonly file: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Bindings the expander may follow. */
|
|
33
|
+
export interface SchemaExpandContext {
|
|
34
|
+
/** File that contains the expression being expanded. */
|
|
35
|
+
readonly filePath: string;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve `const Name` — same-file first, then a unique project-wide
|
|
38
|
+
* export. Shared names like `createIn` must not leak across units.
|
|
39
|
+
*
|
|
40
|
+
* @param name - Binding name
|
|
41
|
+
* @param fromFile - File of the referring expression
|
|
42
|
+
*/
|
|
43
|
+
readonly resolveConst: (name: string, fromFile: string) => ConstInit | undefined;
|
|
44
|
+
/** Store / table bindings (`spaces` → table name). */
|
|
45
|
+
readonly bindings: ReadonlyMap<string, InferBinding>;
|
|
46
|
+
/**
|
|
47
|
+
* Columns for a table binding or SQL name.
|
|
48
|
+
*
|
|
49
|
+
* @param name - Binding (`spaces`) or table (`spaces`)
|
|
50
|
+
*/
|
|
51
|
+
readonly tableColumns: (name: string) => SchemaTableColumns | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DRIZZLE_SCHEMA_FNS = {
|
|
55
|
+
createSelectSchema: "select",
|
|
56
|
+
createInsertSchema: "insert",
|
|
57
|
+
createUpdateSchema: "update",
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
type DrizzleShape = (typeof DRIZZLE_SCHEMA_FNS)[keyof typeof DRIZZLE_SCHEMA_FNS];
|
|
61
|
+
|
|
62
|
+
const LIST_IN_PROPERTIES: Record<string, Record<string, unknown>> = {
|
|
63
|
+
q: { type: "string" },
|
|
64
|
+
search: { type: "string" },
|
|
65
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
66
|
+
order: { type: "string" },
|
|
67
|
+
orderBy: { type: "string" },
|
|
68
|
+
select: { type: "string" },
|
|
69
|
+
or: { type: "string" },
|
|
70
|
+
and: { type: "string" },
|
|
71
|
+
offset: { type: "integer", minimum: 0 },
|
|
72
|
+
cursor: { type: "string" },
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const MAX_DEPTH = 24;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Expand a schema expression to a JSON Schema object, or `undefined`
|
|
79
|
+
* when the node is opaque (fixture placeholder, unknown helper).
|
|
80
|
+
*
|
|
81
|
+
* @param node - `in` / `out` AST
|
|
82
|
+
* @param ctx - Const inits + table columns
|
|
83
|
+
*/
|
|
84
|
+
export function jsonSchemaFromAst(
|
|
85
|
+
node: AstNode | undefined,
|
|
86
|
+
ctx: SchemaExpandContext,
|
|
87
|
+
): Record<string, unknown> | undefined {
|
|
88
|
+
return expand(node, ctx, new Set(), 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* JSON Schema object for a `store.schema.table` under drizzle-orm/zod rules.
|
|
93
|
+
*
|
|
94
|
+
* - **select** — every column; `notNull` columns are required
|
|
95
|
+
* - **insert** — omit generated primary keys; required = not-null without default
|
|
96
|
+
* - **update** — every mutable column, all optional
|
|
97
|
+
*
|
|
98
|
+
* @param columns - Declared columns
|
|
99
|
+
* @param shape - drizzle-orm/zod factory
|
|
100
|
+
*/
|
|
101
|
+
export function jsonSchemaFromTableColumns(
|
|
102
|
+
columns: SchemaTableColumns,
|
|
103
|
+
shape: DrizzleShape,
|
|
104
|
+
): Record<string, unknown> {
|
|
105
|
+
const properties: Record<string, Record<string, unknown>> = {};
|
|
106
|
+
const required: string[] = [];
|
|
107
|
+
for (const [name, col] of Object.entries(columns)) {
|
|
108
|
+
if (shape !== "select" && isGeneratedPrimaryKey(col)) continue;
|
|
109
|
+
properties[name] = columnToProperty(col);
|
|
110
|
+
if (shape === "select" && col.nullable === false) required.push(name);
|
|
111
|
+
if (shape === "insert" && col.nullable === false && !columnHasDefault(col)) {
|
|
112
|
+
required.push(name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return objectSchema(properties, required);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function expand(
|
|
119
|
+
node: AstNode | undefined,
|
|
120
|
+
ctx: SchemaExpandContext,
|
|
121
|
+
seen: Set<string>,
|
|
122
|
+
depth: number,
|
|
123
|
+
): Record<string, unknown> | undefined {
|
|
124
|
+
if (!node || depth > MAX_DEPTH) return undefined;
|
|
125
|
+
const unwrapped = unwrapTsExpr(node);
|
|
126
|
+
|
|
127
|
+
if (unwrapped.type === "Identifier") {
|
|
128
|
+
const name = (unwrapped as Identifier).name;
|
|
129
|
+
if (seen.has(name)) return undefined;
|
|
130
|
+
const next = new Set(seen);
|
|
131
|
+
next.add(name);
|
|
132
|
+
const init = ctx.resolveConst(name, ctx.filePath);
|
|
133
|
+
if (init) return expand(init.node, { ...ctx, filePath: init.file }, next, depth + 1);
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (unwrapped.type === "MemberExpression") {
|
|
138
|
+
const member = unwrapped as AstNode & { object: AstNode; property: AstNode };
|
|
139
|
+
const prop = identifierName(member.property);
|
|
140
|
+
const drizzle = drizzleShapeFromMember(member, ctx, seen, depth);
|
|
141
|
+
if (drizzle) return drizzle;
|
|
142
|
+
if (prop === "shape" || prop === "def") {
|
|
143
|
+
return expand(member.object, ctx, seen, depth + 1);
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (unwrapped.type !== "CallExpression") return undefined;
|
|
149
|
+
return expandCall(unwrapped as CallExpression, ctx, seen, depth);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function expandCall(
|
|
153
|
+
call: CallExpression,
|
|
154
|
+
ctx: SchemaExpandContext,
|
|
155
|
+
seen: Set<string>,
|
|
156
|
+
depth: number,
|
|
157
|
+
): Record<string, unknown> | undefined {
|
|
158
|
+
const callee = unwrapTsExpr(call.callee);
|
|
159
|
+
const fnName = identifierName(callee);
|
|
160
|
+
|
|
161
|
+
if (fnName && fnName in DRIZZLE_SCHEMA_FNS) {
|
|
162
|
+
const shape = DRIZZLE_SCHEMA_FNS[fnName as keyof typeof DRIZZLE_SCHEMA_FNS];
|
|
163
|
+
return expandDrizzleFactory(call, shape, ctx, seen, depth);
|
|
164
|
+
}
|
|
165
|
+
if (fnName === "tableZod") {
|
|
166
|
+
const table = resolveTableName(call.arguments[0], ctx);
|
|
167
|
+
if (!table) return undefined;
|
|
168
|
+
const cols = ctx.tableColumns(table);
|
|
169
|
+
return cols ? jsonSchemaFromTableColumns(cols, "select") : undefined;
|
|
170
|
+
}
|
|
171
|
+
if (fnName === "listIn") return expandListIn(call, ctx, seen, depth);
|
|
172
|
+
if (fnName === "pageOut" || fnName === "listPage") {
|
|
173
|
+
const items = expand(call.arguments[0], ctx, seen, depth + 1);
|
|
174
|
+
return items ? { type: "array", items } : undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (callee.type !== "MemberExpression") return undefined;
|
|
178
|
+
const member = callee as AstNode & { object: AstNode; property: AstNode };
|
|
179
|
+
const method = identifierName(member.property);
|
|
180
|
+
if (!method) return undefined;
|
|
181
|
+
|
|
182
|
+
if (identifierName(member.object) === "z") {
|
|
183
|
+
return expandZodRoot(method, call, ctx, seen, depth);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const inner = expand(member.object, ctx, seen, depth + 1);
|
|
187
|
+
if (!inner) return undefined;
|
|
188
|
+
return applyZodMethod(inner, method, call, ctx, seen, depth);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function expandZodRoot(
|
|
192
|
+
method: string,
|
|
193
|
+
call: CallExpression,
|
|
194
|
+
ctx: SchemaExpandContext,
|
|
195
|
+
seen: Set<string>,
|
|
196
|
+
depth: number,
|
|
197
|
+
): Record<string, unknown> | undefined {
|
|
198
|
+
if (method === "object") {
|
|
199
|
+
const propsNode = objectArg(call.arguments[0]);
|
|
200
|
+
if (!propsNode) return { type: "object", properties: {} };
|
|
201
|
+
return objectFromZodProps(propsNode, ctx, seen, depth);
|
|
202
|
+
}
|
|
203
|
+
if (method === "array") {
|
|
204
|
+
const items = expand(call.arguments[0], ctx, seen, depth + 1);
|
|
205
|
+
return items ? { type: "array", items } : { type: "array" };
|
|
206
|
+
}
|
|
207
|
+
if (method === "enum") {
|
|
208
|
+
const values = stringArrayArg(call.arguments[0]);
|
|
209
|
+
return values ? { type: "string", enum: values } : { type: "string" };
|
|
210
|
+
}
|
|
211
|
+
if (method === "literal") {
|
|
212
|
+
const value = literalValue(call.arguments[0]);
|
|
213
|
+
if (value === undefined) return undefined;
|
|
214
|
+
return { const: value, type: literalJsonType(value) };
|
|
215
|
+
}
|
|
216
|
+
if (method === "intersection") {
|
|
217
|
+
const left = expand(call.arguments[0], ctx, seen, depth + 1);
|
|
218
|
+
const right = expand(call.arguments[1], ctx, seen, depth + 1);
|
|
219
|
+
if (!left && !right) return undefined;
|
|
220
|
+
return mergeObjects(left, right);
|
|
221
|
+
}
|
|
222
|
+
if (method === "union" || method === "discriminatedUnion") return undefined;
|
|
223
|
+
return leafFromZodType(method);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function applyZodMethod(
|
|
227
|
+
schema: Record<string, unknown>,
|
|
228
|
+
method: string,
|
|
229
|
+
call: CallExpression,
|
|
230
|
+
ctx: SchemaExpandContext,
|
|
231
|
+
seen: Set<string>,
|
|
232
|
+
depth: number,
|
|
233
|
+
): Record<string, unknown> | undefined {
|
|
234
|
+
if (
|
|
235
|
+
method === "optional" ||
|
|
236
|
+
method === "nullable" ||
|
|
237
|
+
method === "nullish" ||
|
|
238
|
+
method === "default" ||
|
|
239
|
+
method === "catch" ||
|
|
240
|
+
method === "readonly" ||
|
|
241
|
+
method === "brand" ||
|
|
242
|
+
method === "describe" ||
|
|
243
|
+
method === "meta" ||
|
|
244
|
+
method === "refine" ||
|
|
245
|
+
method === "superRefine" ||
|
|
246
|
+
method === "transform" ||
|
|
247
|
+
method === "pipe" ||
|
|
248
|
+
method === "passthrough" ||
|
|
249
|
+
method === "strict" ||
|
|
250
|
+
method === "strip" ||
|
|
251
|
+
method === "catchall"
|
|
252
|
+
) {
|
|
253
|
+
if (method === "describe") {
|
|
254
|
+
const text = stringArg(call.arguments[0]);
|
|
255
|
+
return text ? { ...schema, description: text } : schema;
|
|
256
|
+
}
|
|
257
|
+
return schema;
|
|
258
|
+
}
|
|
259
|
+
if (method === "min") {
|
|
260
|
+
const n = numberArg(call.arguments[0]);
|
|
261
|
+
if (n === undefined) return schema;
|
|
262
|
+
return schema.type === "string" ? { ...schema, minLength: n } : { ...schema, minimum: n };
|
|
263
|
+
}
|
|
264
|
+
if (method === "max") {
|
|
265
|
+
const n = numberArg(call.arguments[0]);
|
|
266
|
+
if (n === undefined) return schema;
|
|
267
|
+
return schema.type === "string" ? { ...schema, maxLength: n } : { ...schema, maximum: n };
|
|
268
|
+
}
|
|
269
|
+
if (method === "length") {
|
|
270
|
+
const n = numberArg(call.arguments[0]);
|
|
271
|
+
return n === undefined ? schema : { ...schema, minLength: n, maxLength: n };
|
|
272
|
+
}
|
|
273
|
+
if (method === "int") return { ...schema, type: "integer" };
|
|
274
|
+
if (method === "email") return { ...schema, type: "string", format: "email" };
|
|
275
|
+
if (method === "uuid") return { ...schema, type: "string", format: "uuid" };
|
|
276
|
+
if (method === "url") return { ...schema, type: "string", format: "uri" };
|
|
277
|
+
if (method === "datetime") return { ...schema, type: "string", format: "date-time" };
|
|
278
|
+
if (method === "pick") return pickOmit(schema, call.arguments[0], "pick");
|
|
279
|
+
if (method === "omit") return pickOmit(schema, call.arguments[0], "omit");
|
|
280
|
+
if (method === "extend") {
|
|
281
|
+
const extra = objectArg(call.arguments[0]);
|
|
282
|
+
if (!extra) return schema;
|
|
283
|
+
return mergeObjects(schema, objectFromZodProps(extra, ctx, seen, depth));
|
|
284
|
+
}
|
|
285
|
+
if (method === "partial") {
|
|
286
|
+
const { required: _req, ...rest } = schema;
|
|
287
|
+
return rest;
|
|
288
|
+
}
|
|
289
|
+
return schema;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function expandDrizzleFactory(
|
|
293
|
+
call: CallExpression,
|
|
294
|
+
shape: DrizzleShape,
|
|
295
|
+
ctx: SchemaExpandContext,
|
|
296
|
+
seen: Set<string>,
|
|
297
|
+
depth: number,
|
|
298
|
+
): Record<string, unknown> | undefined {
|
|
299
|
+
const table = resolveTableName(call.arguments[0], ctx);
|
|
300
|
+
if (!table) return undefined;
|
|
301
|
+
const cols = ctx.tableColumns(table);
|
|
302
|
+
if (!cols) return undefined;
|
|
303
|
+
const base = jsonSchemaFromTableColumns(cols, shape);
|
|
304
|
+
const refine = objectArg(call.arguments[1]);
|
|
305
|
+
if (!refine) return base;
|
|
306
|
+
return applyDrizzleRefinements(base, refine, ctx, seen, depth);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Second-arg refinements: a Zod schema overwrites the field (docs);
|
|
311
|
+
* a callback is ignored (we cannot apply `.max()` without the inner schema).
|
|
312
|
+
*/
|
|
313
|
+
function applyDrizzleRefinements(
|
|
314
|
+
base: Record<string, unknown>,
|
|
315
|
+
refine: AstNode,
|
|
316
|
+
ctx: SchemaExpandContext,
|
|
317
|
+
seen: Set<string>,
|
|
318
|
+
depth: number,
|
|
319
|
+
): Record<string, unknown> {
|
|
320
|
+
const properties = { ...((base.properties as Record<string, Record<string, unknown>>) ?? {}) };
|
|
321
|
+
const required = Array.isArray(base.required) ? [...(base.required as string[])] : [];
|
|
322
|
+
for (const prop of objectProperties(refine)) {
|
|
323
|
+
const key = propKey(prop);
|
|
324
|
+
const value = (prop as AstNode & { value?: AstNode }).value;
|
|
325
|
+
if (!key || !value) continue;
|
|
326
|
+
if (value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const overwritten = expand(value, ctx, seen, depth + 1);
|
|
330
|
+
if (!overwritten) continue;
|
|
331
|
+
properties[key] = overwritten;
|
|
332
|
+
if (!("const" in overwritten) && overwritten.type !== undefined && !required.includes(key)) {
|
|
333
|
+
// Overwrite replaces nullability (docs) — keep required if the replacement
|
|
334
|
+
// is a non-optional object field we cannot see `.optional()` on.
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return objectSchema(
|
|
338
|
+
properties,
|
|
339
|
+
required.filter((name) => name in properties),
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function drizzleShapeFromMember(
|
|
344
|
+
member: AstNode & { object: AstNode; property: AstNode },
|
|
345
|
+
ctx: SchemaExpandContext,
|
|
346
|
+
seen: Set<string>,
|
|
347
|
+
depth: number,
|
|
348
|
+
): Record<string, unknown> | undefined {
|
|
349
|
+
const prop = identifierName(member.property);
|
|
350
|
+
if (prop !== "select" && prop !== "insert" && prop !== "update") return undefined;
|
|
351
|
+
const table = tableZodTable(member.object, ctx, seen, depth);
|
|
352
|
+
if (!table) return undefined;
|
|
353
|
+
const cols = ctx.tableColumns(table);
|
|
354
|
+
return cols ? jsonSchemaFromTableColumns(cols, prop) : undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function tableZodTable(
|
|
358
|
+
node: AstNode,
|
|
359
|
+
ctx: SchemaExpandContext,
|
|
360
|
+
seen: Set<string>,
|
|
361
|
+
depth: number,
|
|
362
|
+
): string | undefined {
|
|
363
|
+
const unwrapped = unwrapTsExpr(node);
|
|
364
|
+
if (unwrapped.type === "Identifier") {
|
|
365
|
+
const name = (unwrapped as Identifier).name;
|
|
366
|
+
if (seen.has(`zod:${name}`)) return undefined;
|
|
367
|
+
const init = ctx.resolveConst(name, ctx.filePath);
|
|
368
|
+
if (!init) return undefined;
|
|
369
|
+
const next = new Set(seen);
|
|
370
|
+
next.add(`zod:${name}`);
|
|
371
|
+
return tableZodTable(init.node, { ...ctx, filePath: init.file }, next, depth + 1);
|
|
372
|
+
}
|
|
373
|
+
if (unwrapped.type !== "CallExpression") return undefined;
|
|
374
|
+
const call = unwrapped as CallExpression;
|
|
375
|
+
if (identifierName(call.callee) !== "tableZod") return undefined;
|
|
376
|
+
return resolveTableName(call.arguments[0], ctx);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function expandListIn(
|
|
380
|
+
call: CallExpression,
|
|
381
|
+
ctx: SchemaExpandContext,
|
|
382
|
+
seen: Set<string>,
|
|
383
|
+
depth: number,
|
|
384
|
+
): Record<string, unknown> {
|
|
385
|
+
const properties = { ...LIST_IN_PROPERTIES };
|
|
386
|
+
const extra = objectArg(call.arguments[1]);
|
|
387
|
+
if (extra) {
|
|
388
|
+
const added = objectFromZodProps(extra, ctx, seen, depth);
|
|
389
|
+
const extraProps = (added.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
390
|
+
Object.assign(properties, extraProps);
|
|
391
|
+
}
|
|
392
|
+
return { type: "object", properties };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function objectFromZodProps(
|
|
396
|
+
obj: AstNode,
|
|
397
|
+
ctx: SchemaExpandContext,
|
|
398
|
+
seen: Set<string>,
|
|
399
|
+
depth: number,
|
|
400
|
+
): Record<string, unknown> {
|
|
401
|
+
const properties: Record<string, Record<string, unknown>> = {};
|
|
402
|
+
const required: string[] = [];
|
|
403
|
+
for (const prop of objectProperties(obj)) {
|
|
404
|
+
const key = propKey(prop);
|
|
405
|
+
const value = (prop as AstNode & { value?: AstNode }).value;
|
|
406
|
+
if (!key || !value) continue;
|
|
407
|
+
const schema = expand(value, ctx, seen, depth + 1);
|
|
408
|
+
if (!schema) continue;
|
|
409
|
+
properties[key] = schema;
|
|
410
|
+
if (!isOptionalExpr(value)) required.push(key);
|
|
411
|
+
}
|
|
412
|
+
return objectSchema(properties, required);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function isOptionalExpr(node: AstNode): boolean {
|
|
416
|
+
let cur: AstNode | undefined = unwrapTsExpr(node);
|
|
417
|
+
while (cur && cur.type === "CallExpression") {
|
|
418
|
+
const call = cur as CallExpression;
|
|
419
|
+
const callee = unwrapTsExpr(call.callee);
|
|
420
|
+
if (callee.type === "MemberExpression") {
|
|
421
|
+
const member = callee as AstNode & { object: AstNode; property: AstNode };
|
|
422
|
+
const method = identifierName(member.property);
|
|
423
|
+
if (method === "optional" || method === "nullish") return true;
|
|
424
|
+
cur = member.object;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function resolveTableName(node: AstNode | undefined, ctx: SchemaExpandContext): string | undefined {
|
|
433
|
+
const id = identifierName(node ? unwrapTsExpr(node) : undefined);
|
|
434
|
+
if (!id) return undefined;
|
|
435
|
+
const binding = ctx.bindings.get(id);
|
|
436
|
+
if (binding?.kind === "table") return binding.ref;
|
|
437
|
+
if (ctx.tableColumns(id)) return id;
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function columnToProperty(col: DeclaredColumn): Record<string, unknown> {
|
|
442
|
+
const type = col.type === "integer" ? "integer" : "string";
|
|
443
|
+
return {
|
|
444
|
+
type,
|
|
445
|
+
...(col.description !== undefined ? { description: col.description } : {}),
|
|
446
|
+
...(col.pii ? { pii: true } : {}),
|
|
447
|
+
...(col.sensitive ? { sensitive: true } : {}),
|
|
448
|
+
...(col.primaryKey ? { primaryKey: true } : {}),
|
|
449
|
+
...(col.unique ? { unique: true } : {}),
|
|
450
|
+
...(col.references ? { references: col.references } : {}),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function isGeneratedPrimaryKey(col: DeclaredColumn): boolean {
|
|
455
|
+
// OKE `field.text().primaryKey().defaultFn(id)` — same as drizzle
|
|
456
|
+
// `generatedAlwaysAsIdentity()`: omitted from insert / update.
|
|
457
|
+
return col.primaryKey === true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function columnHasDefault(col: DeclaredColumn): boolean {
|
|
461
|
+
return col.default !== undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function pickOmit(
|
|
465
|
+
schema: Record<string, unknown>,
|
|
466
|
+
keysNode: AstNode | undefined,
|
|
467
|
+
mode: "pick" | "omit",
|
|
468
|
+
): Record<string, unknown> {
|
|
469
|
+
const keys = trueKeys(keysNode);
|
|
470
|
+
if (!keys) return schema;
|
|
471
|
+
const properties = {
|
|
472
|
+
...((schema.properties as Record<string, Record<string, unknown>>) ?? {}),
|
|
473
|
+
};
|
|
474
|
+
const next: Record<string, Record<string, unknown>> = {};
|
|
475
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
476
|
+
const keep = mode === "pick" ? keys.has(name) : !keys.has(name);
|
|
477
|
+
if (keep) next[name] = prop;
|
|
478
|
+
}
|
|
479
|
+
const required = Array.isArray(schema.required)
|
|
480
|
+
? (schema.required as string[]).filter((name) => name in next)
|
|
481
|
+
: [];
|
|
482
|
+
return objectSchema(next, required);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function mergeObjects(
|
|
486
|
+
left: Record<string, unknown> | undefined,
|
|
487
|
+
right: Record<string, unknown> | undefined,
|
|
488
|
+
): Record<string, unknown> {
|
|
489
|
+
const leftProps = (left?.properties as Record<string, Record<string, unknown>> | undefined) ?? {};
|
|
490
|
+
const rightProps =
|
|
491
|
+
(right?.properties as Record<string, Record<string, unknown>> | undefined) ?? {};
|
|
492
|
+
const properties = { ...leftProps, ...rightProps };
|
|
493
|
+
const required = [
|
|
494
|
+
...new Set([
|
|
495
|
+
...(Array.isArray(left?.required) ? (left.required as string[]) : []),
|
|
496
|
+
...(Array.isArray(right?.required) ? (right.required as string[]) : []),
|
|
497
|
+
]),
|
|
498
|
+
].filter((name) => name in properties);
|
|
499
|
+
return objectSchema(properties, required);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function objectSchema(
|
|
503
|
+
properties: Record<string, Record<string, unknown>>,
|
|
504
|
+
required: readonly string[],
|
|
505
|
+
): Record<string, unknown> {
|
|
506
|
+
return {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties,
|
|
509
|
+
...(required.length > 0 ? { required: [...required] } : {}),
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Default `listIn({ mode: "offset" })` query shape (pagination on HTTP meta).
|
|
515
|
+
*/
|
|
516
|
+
export function defaultListInSchema(): Record<string, unknown> {
|
|
517
|
+
return { type: "object", properties: { ...LIST_IN_PROPERTIES } };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function leafFromZodType(method: string): Record<string, unknown> | undefined {
|
|
521
|
+
if (method === "string") return { type: "string" };
|
|
522
|
+
if (method === "number") return { type: "number" };
|
|
523
|
+
if (method === "int") return { type: "integer" };
|
|
524
|
+
if (method === "bigint") return { type: "integer" };
|
|
525
|
+
if (method === "boolean") return { type: "boolean" };
|
|
526
|
+
if (method === "date") return { type: "string", format: "date-time" };
|
|
527
|
+
if (method === "unknown" || method === "any") return {};
|
|
528
|
+
if (method === "void" || method === "undefined" || method === "null") return { type: "null" };
|
|
529
|
+
if (method === "coerce") return undefined;
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function trueKeys(node: AstNode | undefined): Set<string> | undefined {
|
|
534
|
+
const obj = objectArg(node);
|
|
535
|
+
if (!obj) return undefined;
|
|
536
|
+
const keys = new Set<string>();
|
|
537
|
+
for (const prop of objectProperties(obj)) {
|
|
538
|
+
const key = propKey(prop);
|
|
539
|
+
const value = (prop as AstNode & { value?: AstNode }).value;
|
|
540
|
+
if (!key) continue;
|
|
541
|
+
if (!value || (value.type === "Literal" && (value as Literal).value === true)) {
|
|
542
|
+
keys.add(key);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return keys;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function stringArrayArg(node: AstNode | undefined): string[] | undefined {
|
|
549
|
+
if (!node || node.type !== "ArrayExpression") return undefined;
|
|
550
|
+
const els = (node as AstNode & { elements?: readonly (AstNode | null)[] }).elements ?? [];
|
|
551
|
+
const out: string[] = [];
|
|
552
|
+
for (const el of els) {
|
|
553
|
+
const s = stringArg(el ?? undefined);
|
|
554
|
+
if (s) out.push(s);
|
|
555
|
+
}
|
|
556
|
+
return out.length > 0 ? out : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function numberArg(node: AstNode | undefined): number | undefined {
|
|
560
|
+
if (!node || node.type !== "Literal") return undefined;
|
|
561
|
+
const v = (node as Literal).value;
|
|
562
|
+
return typeof v === "number" ? v : undefined;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function literalValue(node: AstNode | undefined): string | number | boolean | null | undefined {
|
|
566
|
+
if (!node || node.type !== "Literal") return undefined;
|
|
567
|
+
const v = (node as Literal).value;
|
|
568
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
569
|
+
return v;
|
|
570
|
+
}
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function literalJsonType(value: string | number | boolean | null): string {
|
|
575
|
+
if (value === null) return "null";
|
|
576
|
+
if (typeof value === "string") return "string";
|
|
577
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
|
|
578
|
+
return "boolean";
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function unwrapTsExpr(node: AstNode): AstNode {
|
|
582
|
+
if (node.type === "TSAsExpression" || node.type === "TSTypeAssertion") {
|
|
583
|
+
const expr = (node as AstNode & { expression?: AstNode }).expression;
|
|
584
|
+
if (expr) return unwrapTsExpr(expr);
|
|
585
|
+
}
|
|
586
|
+
return node;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function objectArg(node: AstNode | undefined): AstNode | undefined {
|
|
590
|
+
if (!node) return undefined;
|
|
591
|
+
const unwrapped = unwrapTsExpr(node);
|
|
592
|
+
return unwrapped.type === "ObjectExpression" ? unwrapped : undefined;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function objectProperties(obj: AstNode): AstNode[] {
|
|
596
|
+
return ((obj as AstNode & { properties?: AstNode[] }).properties ?? []).filter(
|
|
597
|
+
(p) => p.type === "Property" || p.type === "ObjectProperty",
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function propKey(prop: AstNode): string | undefined {
|
|
602
|
+
const key = (prop as AstNode & { key?: AstNode }).key;
|
|
603
|
+
if (!key) return undefined;
|
|
604
|
+
if (key.type === "Identifier") return (key as Identifier).name;
|
|
605
|
+
if (key.type === "Literal" && typeof (key as Literal).value === "string") {
|
|
606
|
+
return (key as Literal).value as string;
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
@@ -10,7 +10,7 @@ import { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Bind an HTTP trigger to a flow without registering on the global `on` list.
|
|
13
|
-
* Attaches `.
|
|
13
|
+
* Attaches `.public()` or {@link consoleOperatorGate} when the trigger
|
|
14
14
|
* has no declared auth posture yet.
|
|
15
15
|
*
|
|
16
16
|
* @param trigger - HTTP trigger
|
|
@@ -21,7 +21,7 @@ export function bindHttp(trigger: HttpTrigger, flowDef: AnyFlowDef): Binding {
|
|
|
21
21
|
trigger.gates.length > 0
|
|
22
22
|
? trigger
|
|
23
23
|
: PUBLIC_CONSOLE_FLOWS.has(flowDef.name)
|
|
24
|
-
? trigger.
|
|
24
|
+
? trigger.public()
|
|
25
25
|
: trigger.gate(consoleOperatorGate);
|
|
26
26
|
const normalized = normalizeTrigger(withPosture);
|
|
27
27
|
const list = flowDef.triggers as Trigger[];
|
|
@@ -391,6 +391,9 @@ describe("console serve security", () => {
|
|
|
391
391
|
expect(res.status).toBe(200);
|
|
392
392
|
const csp = res.headers.get("content-security-policy") ?? "";
|
|
393
393
|
expect(csp).toContain("frame-ancestors 'none'");
|
|
394
|
+
expect(csp).toContain("frame-src 'self' blob:");
|
|
395
|
+
expect(csp).toContain("img-src 'self' data: blob:");
|
|
396
|
+
expect(csp).toContain("media-src 'self' blob:");
|
|
394
397
|
expect(csp).toBe(CONSOLE_CSP);
|
|
395
398
|
});
|
|
396
399
|
|