@kozou/svelte-ui 1.3.0 → 1.4.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/build/client/_app/immutable/assets/0.BpzpdRYx.css +2 -0
- package/build/client/_app/immutable/assets/0.BpzpdRYx.css.br +0 -0
- package/build/client/_app/immutable/assets/0.BpzpdRYx.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{DxikiVqj.js → BAfs2EwQ.js} +1 -1
- package/build/client/_app/immutable/chunks/BAfs2EwQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAfs2EwQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BgT5WiOF.js → ClT4djUO.js} +1 -1
- package/build/client/_app/immutable/chunks/ClT4djUO.js.br +0 -0
- package/build/client/_app/immutable/chunks/{BgT5WiOF.js.gz → ClT4djUO.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/CrFfElwr.js +1 -0
- package/build/client/_app/immutable/chunks/CrFfElwr.js.br +0 -0
- package/build/client/_app/immutable/chunks/CrFfElwr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DHwlcjtp.js +214 -0
- package/build/client/_app/immutable/chunks/DHwlcjtp.js.br +0 -0
- package/build/client/_app/immutable/chunks/DHwlcjtp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BLwQsTD2.js → DqFR9xzq.js} +1 -1
- package/build/client/_app/immutable/chunks/DqFR9xzq.js.br +0 -0
- package/build/client/_app/immutable/chunks/DqFR9xzq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{rPPoD7Sp.js → POk9Ad1v.js} +2 -2
- package/build/client/_app/immutable/chunks/POk9Ad1v.js.br +0 -0
- package/build/client/_app/immutable/chunks/POk9Ad1v.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DX7U95N5.js +2 -0
- package/build/client/_app/immutable/entry/app.DX7U95N5.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DX7U95N5.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CTKpPXDp.js +1 -0
- package/build/client/_app/immutable/entry/start.CTKpPXDp.js.br +1 -0
- package/build/client/_app/immutable/entry/start.CTKpPXDp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.BTi1ueXu.js → 0.ahUg4RPH.js} +1 -1
- package/build/client/_app/immutable/nodes/0.ahUg4RPH.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.ahUg4RPH.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.D09foNRb.js → 1.BCx2aLZ9.js} +1 -1
- package/build/client/_app/immutable/nodes/1.BCx2aLZ9.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BCx2aLZ9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DXYbQoLe.js +1 -0
- package/build/client/_app/immutable/nodes/2.DXYbQoLe.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DXYbQoLe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.D_kabCAZ.js +1 -0
- package/build/client/_app/immutable/nodes/3.D_kabCAZ.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.D_kabCAZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.BfuWEZdX.js → 4.C486-zEy.js} +1 -1
- package/build/client/_app/immutable/nodes/4.C486-zEy.js.br +2 -0
- package/build/client/_app/immutable/nodes/4.C486-zEy.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.5H5WIc-o.js → 5.D1OoUXND.js} +1 -1
- package/build/client/_app/immutable/nodes/5.D1OoUXND.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.D1OoUXND.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.B3cCGwsZ.js +1 -0
- package/build/client/_app/immutable/nodes/6.B3cCGwsZ.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B3cCGwsZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.cAEaWzyw.js +1 -0
- package/build/client/_app/immutable/nodes/7.cAEaWzyw.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.cAEaWzyw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.CjEmXoAF.js → 8.Cb70WGbk.js} +1 -1
- package/build/client/_app/immutable/nodes/8.Cb70WGbk.js.br +1 -0
- package/build/client/_app/immutable/nodes/8.Cb70WGbk.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-C4CgvBlc.js → 0-CTaiiMQp.js} +3 -3
- package/build/server/chunks/{0-C4CgvBlc.js.map → 0-CTaiiMQp.js.map} +1 -1
- package/build/server/chunks/{1-BvxsIwwy.js → 1-Dni7ax6A.js} +3 -3
- package/build/server/chunks/{1-BvxsIwwy.js.map → 1-Dni7ax6A.js.map} +1 -1
- package/build/server/chunks/{2-D9il_3ty.js → 2--1dA0mCa.js} +19 -5
- package/build/server/chunks/2--1dA0mCa.js.map +1 -0
- package/build/server/chunks/3-N0Oe_-32.js +144 -0
- package/build/server/chunks/3-N0Oe_-32.js.map +1 -0
- package/build/server/chunks/{3-DfrqlU53.js → 4-BB3nsMwf.js} +5 -5
- package/build/server/chunks/{3-DfrqlU53.js.map → 4-BB3nsMwf.js.map} +1 -1
- package/build/server/chunks/{4-1chJCfPL.js → 5-QdpSeSEB.js} +6 -6
- package/build/server/chunks/{4-1chJCfPL.js.map → 5-QdpSeSEB.js.map} +1 -1
- package/build/server/chunks/{5-DUCxshKE.js → 6-DQ9D4Rjj.js} +11 -10
- package/build/server/chunks/6-DQ9D4Rjj.js.map +1 -0
- package/build/server/chunks/{6-Dh7Tg-XB.js → 7-yY-k4F-J.js} +11 -10
- package/build/server/chunks/7-yY-k4F-J.js.map +1 -0
- package/build/server/chunks/{7-CKkRKfs8.js → 8-C6Rfqnpk.js} +5 -5
- package/build/server/chunks/{7-CKkRKfs8.js.map → 8-C6Rfqnpk.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-DaJwoT9F.js → _page.svelte-6bUnnrWp.js} +3 -3
- package/build/server/chunks/{_page.svelte-DaJwoT9F.js.map → _page.svelte-6bUnnrWp.js.map} +1 -1
- package/build/server/chunks/_page.svelte-BAaP0Gbn.js +143 -0
- package/build/server/chunks/_page.svelte-BAaP0Gbn.js.map +1 -0
- package/build/server/chunks/{_page.svelte-BN1au56r.js → _page.svelte-Cup_HkLy.js} +19 -3
- package/build/server/chunks/_page.svelte-Cup_HkLy.js.map +1 -0
- package/build/server/chunks/{_page.svelte-DI_FY3zW.js → _page.svelte-D1coeZL7.js} +7 -6
- package/build/server/chunks/_page.svelte-D1coeZL7.js.map +1 -0
- package/build/server/chunks/{_page.svelte-CBUJNhG1.js → _page.svelte-DsOlSzf6.js} +7 -6
- package/build/server/chunks/_page.svelte-DsOlSzf6.js.map +1 -0
- package/build/server/chunks/{_server.ts-C-Wy675T.js → _server.ts-DlnyELsA.js} +4 -4
- package/build/server/chunks/{_server.ts-C-Wy675T.js.map → _server.ts-DlnyELsA.js.map} +1 -1
- package/build/server/chunks/{adapter-CXNsjV1V.js → adapter-B1wOda2X.js} +13 -1
- package/build/server/chunks/adapter-B1wOda2X.js.map +1 -0
- package/build/server/chunks/{client-C004jkBy.js → client-CF_AG7xO.js} +2 -2
- package/build/server/chunks/{client-C004jkBy.js.map → client-CF_AG7xO.js.map} +1 -1
- package/build/server/chunks/{composite-form-_tg7iknp.js → composite-form-Khrcy6Jk.js} +60 -21
- package/build/server/chunks/composite-form-Khrcy6Jk.js.map +1 -0
- package/build/server/chunks/{error.svelte-u8Vsq8vF.js → error.svelte-CrnPC1Ez.js} +3 -3
- package/build/server/chunks/{error.svelte-u8Vsq8vF.js.map → error.svelte-CrnPC1Ez.js.map} +1 -1
- package/build/server/chunks/{hooks.server-DtCXcOVD.js → hooks.server-Oa_UD2Wo.js} +636 -12
- package/build/server/chunks/hooks.server-Oa_UD2Wo.js.map +1 -0
- package/build/server/chunks/{index-5kYmxIr9.js → index-CVekYGHP.js} +2 -2
- package/build/server/chunks/{index-5kYmxIr9.js.map → index-CVekYGHP.js.map} +1 -1
- package/build/server/chunks/{internal-fxcuSPWe.js → internal-Z9Jwi6lX.js} +3 -3
- package/build/server/chunks/{internal-fxcuSPWe.js.map → internal-Z9Jwi6lX.js.map} +1 -1
- package/build/server/chunks/privilege-readonly-CK4k2aYO.js +15 -0
- package/build/server/chunks/privilege-readonly-CK4k2aYO.js.map +1 -0
- package/build/server/chunks/{relation-options-Dzy9DaQG.js → relation-options-BQ3XnftO.js} +2 -2
- package/build/server/chunks/{relation-options-Dzy9DaQG.js.map → relation-options-BQ3XnftO.js.map} +1 -1
- package/build/server/chunks/relation-select-composite-field-D4LZijR_.js +101 -0
- package/build/server/chunks/relation-select-composite-field-D4LZijR_.js.map +1 -0
- package/build/server/chunks/{widget-registry-BcgMXjkA.js → widget-registry-6ku8orXa.js} +3 -98
- package/build/server/chunks/widget-registry-6ku8orXa.js.map +1 -0
- package/build/server/index.js +2 -2
- package/build/server/manifest.js +23 -15
- package/build/server/manifest.js.map +1 -1
- package/package.json +4 -4
- package/build/client/_app/immutable/assets/0._vgqT-Ja.css +0 -2
- package/build/client/_app/immutable/assets/0._vgqT-Ja.css.br +0 -0
- package/build/client/_app/immutable/assets/0._vgqT-Ja.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BLwQsTD2.js.br +0 -0
- package/build/client/_app/immutable/chunks/BLwQsTD2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BgT5WiOF.js.br +0 -0
- package/build/client/_app/immutable/chunks/DY0oakY5.js +0 -214
- package/build/client/_app/immutable/chunks/DY0oakY5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DY0oakY5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DxikiVqj.js.br +0 -0
- package/build/client/_app/immutable/chunks/DxikiVqj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/rPPoD7Sp.js.br +0 -0
- package/build/client/_app/immutable/chunks/rPPoD7Sp.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.G2Y8IfhN.js +0 -2
- package/build/client/_app/immutable/entry/app.G2Y8IfhN.js.br +0 -0
- package/build/client/_app/immutable/entry/app.G2Y8IfhN.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.sPBaDl_M.js +0 -1
- package/build/client/_app/immutable/entry/start.sPBaDl_M.js.br +0 -1
- package/build/client/_app/immutable/entry/start.sPBaDl_M.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BTi1ueXu.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BTi1ueXu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.D09foNRb.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.D09foNRb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js +0 -1
- package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CWOXCYvQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BfuWEZdX.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BfuWEZdX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.5H5WIc-o.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.5H5WIc-o.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.BR2iBXwV.js +0 -1
- package/build/client/_app/immutable/nodes/5.BR2iBXwV.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.BR2iBXwV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.ChubZePh.js +0 -1
- package/build/client/_app/immutable/nodes/6.ChubZePh.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.ChubZePh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.CjEmXoAF.js.br +0 -1
- package/build/client/_app/immutable/nodes/7.CjEmXoAF.js.gz +0 -0
- package/build/server/chunks/2-D9il_3ty.js.map +0 -1
- package/build/server/chunks/5-DUCxshKE.js.map +0 -1
- package/build/server/chunks/6-Dh7Tg-XB.js.map +0 -1
- package/build/server/chunks/_page.svelte-BN1au56r.js.map +0 -1
- package/build/server/chunks/_page.svelte-CBUJNhG1.js.map +0 -1
- package/build/server/chunks/_page.svelte-DI_FY3zW.js.map +0 -1
- package/build/server/chunks/adapter-CXNsjV1V.js.map +0 -1
- package/build/server/chunks/composite-form-_tg7iknp.js.map +0 -1
- package/build/server/chunks/hooks.server-DtCXcOVD.js.map +0 -1
- package/build/server/chunks/widget-registry-BcgMXjkA.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as __commonJSMin, b as __require } from './internal-
|
|
1
|
+
import { _ as __commonJSMin, b as __require } from './internal-Z9Jwi6lX.js';
|
|
2
2
|
import { e as encodeResourceId } from './resource-id-PDcQeAnc.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import require$$0$3 from 'events';
|
|
@@ -6478,7 +6478,9 @@ var KNOWN_TAGS = new Set([
|
|
|
6478
6478
|
"ai",
|
|
6479
6479
|
"widget",
|
|
6480
6480
|
"policy",
|
|
6481
|
-
"example"
|
|
6481
|
+
"example",
|
|
6482
|
+
"expose",
|
|
6483
|
+
"arg"
|
|
6482
6484
|
]);
|
|
6483
6485
|
var TAG_RE = /^\s*@([a-zA-Z_][a-zA-Z0-9_]*)\s*:(.*)$/;
|
|
6484
6486
|
var INDENT_RE = /^[ \t]/;
|
|
@@ -6516,13 +6518,66 @@ function warnMidlineTag(token) {
|
|
|
6516
6518
|
function isWidgetType(value) {
|
|
6517
6519
|
return KNOWN_WIDGETS.has(value);
|
|
6518
6520
|
}
|
|
6521
|
+
/** Index of the first whitespace character, or -1. Manual scan (no regex) to
|
|
6522
|
+
* stay off CodeQL's polynomial-ReDoS radar, like the rest of this module. */
|
|
6523
|
+
function firstWhitespaceIndex(s) {
|
|
6524
|
+
for (let i = 0; i < s.length; i += 1) if (LINE_WS_RE.test(s[i])) return i;
|
|
6525
|
+
return -1;
|
|
6526
|
+
}
|
|
6527
|
+
/** Extract `<inner>` from a `<name>(<inner>)` directive token (trimmed), or
|
|
6528
|
+
* null when the token is not exactly that shape. Manual scan, no regex. */
|
|
6529
|
+
function parenDirective(directive, name) {
|
|
6530
|
+
const prefix = `${name}(`;
|
|
6531
|
+
if (!directive.startsWith(prefix) || !directive.endsWith(")")) return null;
|
|
6532
|
+
return directive.slice(prefix.length, -1).trim();
|
|
6533
|
+
}
|
|
6534
|
+
/** Parse an `@arg:` value — "<argname> relation(<ref>)" or
|
|
6535
|
+
* "<argname> widget(<type>)". Returns null on any malformed shape so the
|
|
6536
|
+
* caller can warn (no silent drop). `<ref>` is `table.col` (schema defaulted
|
|
6537
|
+
* later) or `schema.table.col`. */
|
|
6538
|
+
function parseArgHint(value) {
|
|
6539
|
+
const ws = firstWhitespaceIndex(value);
|
|
6540
|
+
if (ws === -1) return null;
|
|
6541
|
+
const name = value.slice(0, ws);
|
|
6542
|
+
const directive = value.slice(ws + 1).trim();
|
|
6543
|
+
if (name === "" || directive === "") return null;
|
|
6544
|
+
const rel = parenDirective(directive, "relation");
|
|
6545
|
+
if (rel !== null) {
|
|
6546
|
+
const parts = rel.split(".").map((p) => p.trim());
|
|
6547
|
+
if (parts.length === 2 && parts.every((p) => p !== "")) return {
|
|
6548
|
+
name,
|
|
6549
|
+
relation: {
|
|
6550
|
+
schema: null,
|
|
6551
|
+
table: parts[0],
|
|
6552
|
+
column: parts[1]
|
|
6553
|
+
}
|
|
6554
|
+
};
|
|
6555
|
+
if (parts.length === 3 && parts.every((p) => p !== "")) return {
|
|
6556
|
+
name,
|
|
6557
|
+
relation: {
|
|
6558
|
+
schema: parts[0],
|
|
6559
|
+
table: parts[1],
|
|
6560
|
+
column: parts[2]
|
|
6561
|
+
}
|
|
6562
|
+
};
|
|
6563
|
+
return null;
|
|
6564
|
+
}
|
|
6565
|
+
const wid = parenDirective(directive, "widget");
|
|
6566
|
+
if (wid !== null) return isWidgetType(wid) ? {
|
|
6567
|
+
name,
|
|
6568
|
+
widget: wid
|
|
6569
|
+
} : null;
|
|
6570
|
+
return null;
|
|
6571
|
+
}
|
|
6519
6572
|
function parseCommentTags(comment) {
|
|
6520
6573
|
const result = {
|
|
6521
6574
|
body: "",
|
|
6522
6575
|
ai: [],
|
|
6523
6576
|
widget: null,
|
|
6524
6577
|
policy: [],
|
|
6525
|
-
examples: []
|
|
6578
|
+
examples: [],
|
|
6579
|
+
expose: "none",
|
|
6580
|
+
args: []
|
|
6526
6581
|
};
|
|
6527
6582
|
if (comment === null || comment === "") return result;
|
|
6528
6583
|
const lines = comment.split("\n");
|
|
@@ -6598,6 +6653,22 @@ function parseCommentTags(comment) {
|
|
|
6598
6653
|
};
|
|
6599
6654
|
continue;
|
|
6600
6655
|
}
|
|
6656
|
+
if (tag === "expose") {
|
|
6657
|
+
const tokens = value.toLowerCase().split(/\s+/).filter((t) => t !== "");
|
|
6658
|
+
if (tokens.length === 1 && tokens[0] === "rpc") result.expose = "rpc";
|
|
6659
|
+
else if (tokens.length === 2 && tokens[0] === "rpc" && tokens[1] === "public") result.expose = "rpc-public";
|
|
6660
|
+
else {
|
|
6661
|
+
result.expose = "none";
|
|
6662
|
+
console.warn(`[@kozou/core] parseCommentTags: invalid @expose value "${value}" (expected "rpc" or "rpc public"; not exposed)`);
|
|
6663
|
+
}
|
|
6664
|
+
continue;
|
|
6665
|
+
}
|
|
6666
|
+
if (tag === "arg") {
|
|
6667
|
+
const hint = parseArgHint(value);
|
|
6668
|
+
if (hint !== null) result.args.push(hint);
|
|
6669
|
+
else console.warn(`[@kozou/core] parseCommentTags: invalid @arg value "${value}" (expected "<name> relation(<table.col>)" or "<name> widget(<type>)"; skip)`);
|
|
6670
|
+
continue;
|
|
6671
|
+
}
|
|
6601
6672
|
if (!KNOWN_TAGS.has(tag)) {
|
|
6602
6673
|
console.warn(`[@kozou/core] parseCommentTags: unknown tag "@${tag}" (forward compat: kept in body)`);
|
|
6603
6674
|
bodyLines.push(line);
|
|
@@ -6663,6 +6734,252 @@ function inferWidget(input) {
|
|
|
6663
6734
|
return "text";
|
|
6664
6735
|
}
|
|
6665
6736
|
//#endregion
|
|
6737
|
+
//#region ../core/dist/buildFunctionContext.js
|
|
6738
|
+
var POLYMORPHIC_TYPES = new Set([
|
|
6739
|
+
"anyelement",
|
|
6740
|
+
"anyarray",
|
|
6741
|
+
"anynonarray",
|
|
6742
|
+
"anyenum",
|
|
6743
|
+
"anyrange",
|
|
6744
|
+
"anymultirange",
|
|
6745
|
+
"anycompatible",
|
|
6746
|
+
"anycompatiblearray",
|
|
6747
|
+
"anycompatiblenonarray",
|
|
6748
|
+
"anycompatiblerange",
|
|
6749
|
+
"anycompatiblemultirange"
|
|
6750
|
+
]);
|
|
6751
|
+
/** True for an argument that is part of the call's input (so it appears in the
|
|
6752
|
+
* named-args body). OUT (`out`) and RETURNS TABLE columns (`table`) are
|
|
6753
|
+
* return-side and excluded. */
|
|
6754
|
+
function isInputArg(arg) {
|
|
6755
|
+
return arg.mode === "in" || arg.mode === "inout" || arg.mode === "variadic";
|
|
6756
|
+
}
|
|
6757
|
+
/** Validate an allowlist: every entry must be a schema-qualified name of
|
|
6758
|
+
* exactly the form `schema.function`, with both parts non-empty and no extra
|
|
6759
|
+
* dot (§5.0). A bare name is ambiguous (which schema?); an entry with two+
|
|
6760
|
+
* dots is ambiguous against a quoted identifier that itself contains a dot, so
|
|
6761
|
+
* it could authorize the wrong function — both are dropped with a build issue
|
|
6762
|
+
* rather than matched loosely (fail-closed for the definer / PUBLIC gates).
|
|
6763
|
+
* Returns the set of valid entries. */
|
|
6764
|
+
function normalizeAllowlist(entries, configKey, issues) {
|
|
6765
|
+
const set = /* @__PURE__ */ new Set();
|
|
6766
|
+
if (entries === void 0) return set;
|
|
6767
|
+
for (const entry of entries) {
|
|
6768
|
+
const parts = entry.split(".");
|
|
6769
|
+
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|
|
6770
|
+
issues.push({
|
|
6771
|
+
path: `rpc.${configKey}`,
|
|
6772
|
+
message: `"${entry}" is not a schema-qualified function name (expected exactly "schema.function"); ignored.`
|
|
6773
|
+
});
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
6776
|
+
set.add(entry);
|
|
6777
|
+
}
|
|
6778
|
+
return set;
|
|
6779
|
+
}
|
|
6780
|
+
/** The owner-relative safe-search_path predicate for `security definer`
|
|
6781
|
+
* functions (RPC design §3.2). A definer function runs as its owner, so an
|
|
6782
|
+
* unqualified name resolved through a schema that someone else can write to
|
|
6783
|
+
* can be hijacked. Safe requires: a declared search_path; `pg_temp` present
|
|
6784
|
+
* exactly once and last (else the temp schema is searched implicitly first);
|
|
6785
|
+
* and every other element a schema only the owner may CREATE in. Anything
|
|
6786
|
+
* unresolvable is unsafe (fail-closed). */
|
|
6787
|
+
function checkSafeSearchPath(fn) {
|
|
6788
|
+
const sp = fn.searchPath;
|
|
6789
|
+
if (sp === null || sp.length === 0) return {
|
|
6790
|
+
safe: false,
|
|
6791
|
+
reason: "no SET search_path is declared"
|
|
6792
|
+
};
|
|
6793
|
+
const tempCount = sp.filter((e) => e.isTemp).length;
|
|
6794
|
+
if (tempCount === 0) return {
|
|
6795
|
+
safe: false,
|
|
6796
|
+
reason: "pg_temp is not listed, so the session temp schema is searched implicitly first"
|
|
6797
|
+
};
|
|
6798
|
+
if (tempCount > 1 || !sp[sp.length - 1].isTemp) return {
|
|
6799
|
+
safe: false,
|
|
6800
|
+
reason: "pg_temp must appear exactly once and as the last element"
|
|
6801
|
+
};
|
|
6802
|
+
for (const el of sp) {
|
|
6803
|
+
if (el.isTemp) continue;
|
|
6804
|
+
if (el.schema === null) return {
|
|
6805
|
+
safe: false,
|
|
6806
|
+
reason: `element "${el.raw}" does not resolve to a fixed schema`
|
|
6807
|
+
};
|
|
6808
|
+
if (el.writableByOthers === null) return {
|
|
6809
|
+
safe: false,
|
|
6810
|
+
reason: `cannot determine who may CREATE in schema "${el.schema}"`
|
|
6811
|
+
};
|
|
6812
|
+
if (el.writableByOthers === true) return {
|
|
6813
|
+
safe: false,
|
|
6814
|
+
reason: `schema "${el.schema}" is writable by PUBLIC or a role other than the owner`
|
|
6815
|
+
};
|
|
6816
|
+
}
|
|
6817
|
+
return {
|
|
6818
|
+
safe: true,
|
|
6819
|
+
reason: ""
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
/** Build a synthetic RawColumn so argument widget inference reuses the exact
|
|
6823
|
+
* column heuristics (`inferWidget`). Arguments are not foreign keys, so a
|
|
6824
|
+
* relation-select only comes from an `@arg` relation hint, handled in
|
|
6825
|
+
* `buildArg`; here we only need name + udtName for the type-based path. */
|
|
6826
|
+
function inferArgWidget(arg, enumValues) {
|
|
6827
|
+
return inferWidget({
|
|
6828
|
+
column: {
|
|
6829
|
+
name: arg.name,
|
|
6830
|
+
dataType: arg.typeName,
|
|
6831
|
+
udtName: arg.udtName,
|
|
6832
|
+
nullable: true,
|
|
6833
|
+
defaultExpr: null,
|
|
6834
|
+
comment: null,
|
|
6835
|
+
position: 0
|
|
6836
|
+
},
|
|
6837
|
+
isForeignKey: false,
|
|
6838
|
+
relationSelectable: false,
|
|
6839
|
+
enumValues,
|
|
6840
|
+
commentBody: ""
|
|
6841
|
+
});
|
|
6842
|
+
}
|
|
6843
|
+
/** ENUM members for an argument's type, matched by type name (preferring the
|
|
6844
|
+
* function's own schema when an enum name is reused across schemas). */
|
|
6845
|
+
function findEnumValues(enums, udtName, fnSchema) {
|
|
6846
|
+
return (enums.find((e) => e.name === udtName && e.schema === fnSchema) ?? enums.find((e) => e.name === udtName))?.values;
|
|
6847
|
+
}
|
|
6848
|
+
function buildArg(arg, parsed, fnSchema, enums) {
|
|
6849
|
+
const hint = parsed.args.find((h) => h.name === arg.name);
|
|
6850
|
+
const relation = hint?.relation ? {
|
|
6851
|
+
schema: hint.relation.schema ?? fnSchema,
|
|
6852
|
+
table: hint.relation.table,
|
|
6853
|
+
column: hint.relation.column
|
|
6854
|
+
} : void 0;
|
|
6855
|
+
const enumValues = findEnumValues(enums, arg.udtName, fnSchema);
|
|
6856
|
+
let widget;
|
|
6857
|
+
if (hint?.widget) widget = hint.widget;
|
|
6858
|
+
else if (relation) widget = "relation-select";
|
|
6859
|
+
else widget = inferArgWidget(arg, enumValues ?? null);
|
|
6860
|
+
return {
|
|
6861
|
+
name: arg.name,
|
|
6862
|
+
typeName: arg.typeName,
|
|
6863
|
+
hasDefault: arg.hasDefault,
|
|
6864
|
+
...enumValues ? { enumValues } : {},
|
|
6865
|
+
...relation ? { relation } : {},
|
|
6866
|
+
widget
|
|
6867
|
+
};
|
|
6868
|
+
}
|
|
6869
|
+
function mapReturn(r) {
|
|
6870
|
+
return {
|
|
6871
|
+
kind: r.kind === "unsupported" ? "void" : r.kind,
|
|
6872
|
+
typeName: r.typeName,
|
|
6873
|
+
...r.columns ? { columns: r.columns.map((c) => ({
|
|
6874
|
+
name: c.name,
|
|
6875
|
+
typeName: c.typeName
|
|
6876
|
+
})) } : {}
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
function decideAndBuild(input) {
|
|
6880
|
+
const { fn, parsed, qualifiedName, enums, allowDefiner, allowPublicExecute, issues } = input;
|
|
6881
|
+
const skip = (message) => {
|
|
6882
|
+
issues.push({
|
|
6883
|
+
path: `functions.${qualifiedName}`,
|
|
6884
|
+
message: `"${qualifiedName}" ${message}`
|
|
6885
|
+
});
|
|
6886
|
+
return null;
|
|
6887
|
+
};
|
|
6888
|
+
const inputArgs = fn.arguments.filter(isInputArg);
|
|
6889
|
+
for (const arg of inputArgs) {
|
|
6890
|
+
if (arg.mode === "variadic") return skip(`has a VARIADIC argument ("${arg.name}"), unsupported in v1; not exposed.`);
|
|
6891
|
+
if (arg.name === "") return skip("has an unnamed argument; the named-args RPC body requires names, so it is not exposed.");
|
|
6892
|
+
if (POLYMORPHIC_TYPES.has(arg.udtName)) return skip(`has a polymorphic argument ("${arg.name}" ${arg.typeName}), unsupported in v1; not exposed.`);
|
|
6893
|
+
}
|
|
6894
|
+
if (fn.returns.kind === "unsupported") return skip(`returns "${fn.returns.typeName}", an unsupported return shape in v1 (OUT/INOUT composite, record, or polymorphic); not exposed.`);
|
|
6895
|
+
if (fn.security === "definer") {
|
|
6896
|
+
if (!allowDefiner.has(qualifiedName)) return skip("is SECURITY DEFINER and tagged @expose: rpc, but is not listed in api.rpc.allowDefiner; not exposed (the operator must opt in to a privilege-bypassing endpoint, §3.2).");
|
|
6897
|
+
const sp = checkSafeSearchPath(fn);
|
|
6898
|
+
if (!sp.safe) return skip(`is SECURITY DEFINER but its search_path is not owner-safe (${sp.reason}); not exposed. Declare SET search_path with owner-only schemas and a trailing pg_temp (§3.2).`);
|
|
6899
|
+
}
|
|
6900
|
+
let publicCallable = false;
|
|
6901
|
+
if (fn.publicExecute) {
|
|
6902
|
+
if (!(parsed.expose === "rpc-public" || allowPublicExecute.has(qualifiedName))) return skip("still grants EXECUTE to PUBLIC (the CREATE FUNCTION default); not exposed. REVOKE EXECUTE FROM PUBLIC and GRANT it to the intended role, or declare the public endpoint intentional with @expose: rpc public / api.rpc.allowPublicExecute (§6.1).");
|
|
6903
|
+
publicCallable = true;
|
|
6904
|
+
}
|
|
6905
|
+
const args = inputArgs.map((arg) => buildArg(arg, parsed, fn.schema, enums));
|
|
6906
|
+
const inputArgNames = new Set(inputArgs.map((a) => a.name));
|
|
6907
|
+
for (const hint of parsed.args) if (!inputArgNames.has(hint.name)) issues.push({
|
|
6908
|
+
path: `functions.${qualifiedName}.args.${hint.name}`,
|
|
6909
|
+
message: `@arg hint references argument "${hint.name}", which "${qualifiedName}" does not have; ignored.`
|
|
6910
|
+
});
|
|
6911
|
+
const body = parsed.body !== "" ? parsed.body : null;
|
|
6912
|
+
const label = body !== null ? body.split("\n")[0].trim() : fn.name;
|
|
6913
|
+
return {
|
|
6914
|
+
schema: fn.schema,
|
|
6915
|
+
name: fn.name,
|
|
6916
|
+
qualifiedName,
|
|
6917
|
+
label,
|
|
6918
|
+
description: body,
|
|
6919
|
+
aiDescription: parsed.ai.length > 0 ? parsed.ai.join("\n") : null,
|
|
6920
|
+
policy: parsed.policy,
|
|
6921
|
+
args,
|
|
6922
|
+
returns: mapReturn(fn.returns),
|
|
6923
|
+
volatility: fn.volatility,
|
|
6924
|
+
security: fn.security,
|
|
6925
|
+
publicCallable,
|
|
6926
|
+
rawFunction: fn
|
|
6927
|
+
};
|
|
6928
|
+
}
|
|
6929
|
+
/** Decide which functions are exposed as RPC actions and shape them into
|
|
6930
|
+
* FunctionContexts (RPC design §3–§5). Skipped-but-tagged functions are pushed
|
|
6931
|
+
* onto `issues`. Output is sorted by qualified name for stable surfaces. */
|
|
6932
|
+
function buildFunctionContexts(input) {
|
|
6933
|
+
const { functions, enums, issues } = input;
|
|
6934
|
+
const allowDefiner = normalizeAllowlist(input.rpc?.allowDefiner, "allowDefiner", issues);
|
|
6935
|
+
const allowPublicExecute = normalizeAllowlist(input.rpc?.allowPublicExecute, "allowPublicExecute", issues);
|
|
6936
|
+
const byQualified = /* @__PURE__ */ new Map();
|
|
6937
|
+
for (const fn of functions) {
|
|
6938
|
+
const parsed = parseCommentTags(fn.comment);
|
|
6939
|
+
const q = `${fn.schema}.${fn.name}`;
|
|
6940
|
+
const group = byQualified.get(q);
|
|
6941
|
+
if (group) group.push({
|
|
6942
|
+
fn,
|
|
6943
|
+
parsed
|
|
6944
|
+
});
|
|
6945
|
+
else byQualified.set(q, [{
|
|
6946
|
+
fn,
|
|
6947
|
+
parsed
|
|
6948
|
+
}]);
|
|
6949
|
+
}
|
|
6950
|
+
const result = [];
|
|
6951
|
+
for (const [qualifiedName, group] of byQualified) {
|
|
6952
|
+
if (!group.some((e) => e.parsed.expose !== "none")) continue;
|
|
6953
|
+
if (group.length > 1) {
|
|
6954
|
+
issues.push({
|
|
6955
|
+
path: `functions.${qualifiedName}`,
|
|
6956
|
+
message: `"${qualifiedName}" has ${group.length} overloaded definitions and at least one is tagged for RPC exposure; v1 cannot disambiguate overloads by signature, so none are exposed. Rename the overloads or keep a single definition for this name.`
|
|
6957
|
+
});
|
|
6958
|
+
continue;
|
|
6959
|
+
}
|
|
6960
|
+
const entry = group[0];
|
|
6961
|
+
if (entry.fn.schema.includes(".") || entry.fn.name.includes(".")) {
|
|
6962
|
+
issues.push({
|
|
6963
|
+
path: `functions.${entry.fn.schema}.${entry.fn.name}`,
|
|
6964
|
+
message: `"${entry.fn.schema}"."${entry.fn.name}" has a "." in its schema or name; the schema-qualified RPC identity (schema.function) would be ambiguous, so it is not exposed.`
|
|
6965
|
+
});
|
|
6966
|
+
continue;
|
|
6967
|
+
}
|
|
6968
|
+
const ctx = decideAndBuild({
|
|
6969
|
+
fn: entry.fn,
|
|
6970
|
+
parsed: entry.parsed,
|
|
6971
|
+
qualifiedName,
|
|
6972
|
+
enums,
|
|
6973
|
+
allowDefiner,
|
|
6974
|
+
allowPublicExecute,
|
|
6975
|
+
issues
|
|
6976
|
+
});
|
|
6977
|
+
if (ctx !== null) result.push(ctx);
|
|
6978
|
+
}
|
|
6979
|
+
result.sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName));
|
|
6980
|
+
return result;
|
|
6981
|
+
}
|
|
6982
|
+
//#endregion
|
|
6666
6983
|
//#region ../core/dist/displayField.js
|
|
6667
6984
|
var CANDIDATES = [
|
|
6668
6985
|
"name",
|
|
@@ -6928,7 +7245,7 @@ function buildConcept(view) {
|
|
|
6928
7245
|
};
|
|
6929
7246
|
}
|
|
6930
7247
|
async function buildSchemaContext(opts) {
|
|
6931
|
-
const { raw, uiHints, strict = false } = opts;
|
|
7248
|
+
const { raw, uiHints, strict = false, rpc } = opts;
|
|
6932
7249
|
const issues = [];
|
|
6933
7250
|
const knownTables = new Set(raw.tables.map((t) => `${t.schema}.${t.name}`));
|
|
6934
7251
|
raw.views.forEach((v) => knownTables.add(`${v.schema}.${v.name}`));
|
|
@@ -6981,6 +7298,12 @@ async function buildSchemaContext(opts) {
|
|
|
6981
7298
|
description: null
|
|
6982
7299
|
}));
|
|
6983
7300
|
const concepts = visibleRawViews.map(buildConcept);
|
|
7301
|
+
const functions = buildFunctionContexts({
|
|
7302
|
+
functions: raw.functions,
|
|
7303
|
+
enums: raw.enums,
|
|
7304
|
+
rpc,
|
|
7305
|
+
issues
|
|
7306
|
+
});
|
|
6984
7307
|
if (issues.length > 0) {
|
|
6985
7308
|
if (strict) throw new KozouBuildError(`buildSchemaContext: ${issues.length} validation issue(s) (strict=true)`, issues);
|
|
6986
7309
|
for (const issue of issues) console.warn(`[@kozou/core] ${issue.path}: ${issue.message}`);
|
|
@@ -6994,7 +7317,8 @@ async function buildSchemaContext(opts) {
|
|
|
6994
7317
|
tables,
|
|
6995
7318
|
views,
|
|
6996
7319
|
enums,
|
|
6997
|
-
concepts
|
|
7320
|
+
concepts,
|
|
7321
|
+
functions
|
|
6998
7322
|
};
|
|
6999
7323
|
}
|
|
7000
7324
|
//#endregion
|
|
@@ -13945,7 +14269,7 @@ async function fetchEnums(client, schemas) {
|
|
|
13945
14269
|
return (await runQuery(client, `SELECT
|
|
13946
14270
|
n.nspname AS schema,
|
|
13947
14271
|
t.typname AS name,
|
|
13948
|
-
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
|
14272
|
+
array_agg(e.enumlabel::text ORDER BY e.enumsortorder) AS values
|
|
13949
14273
|
FROM pg_type t
|
|
13950
14274
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
13951
14275
|
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
@@ -13959,6 +14283,294 @@ async function fetchEnums(client, schemas) {
|
|
|
13959
14283
|
}));
|
|
13960
14284
|
}
|
|
13961
14285
|
//#endregion
|
|
14286
|
+
//#region ../introspect/dist/functions.js
|
|
14287
|
+
var FUNCTIONS_SQL = `
|
|
14288
|
+
SELECT
|
|
14289
|
+
n.nspname AS schema,
|
|
14290
|
+
p.proname AS name,
|
|
14291
|
+
pg_get_function_arguments(p.oid) AS argument_signature,
|
|
14292
|
+
p.pronargdefaults AS ndef,
|
|
14293
|
+
p.provolatile AS volatility,
|
|
14294
|
+
p.prosecdef AS security_definer,
|
|
14295
|
+
p.proowner AS owner_oid,
|
|
14296
|
+
ro.rolname AS owner_name,
|
|
14297
|
+
CASE
|
|
14298
|
+
WHEN p.proacl IS NULL THEN true
|
|
14299
|
+
WHEN EXISTS (
|
|
14300
|
+
SELECT 1 FROM aclexplode(p.proacl) ae
|
|
14301
|
+
WHERE ae.grantee = 0 AND ae.privilege_type = 'EXECUTE'
|
|
14302
|
+
) THEN true
|
|
14303
|
+
ELSE false
|
|
14304
|
+
END AS public_execute,
|
|
14305
|
+
obj_description(p.oid, 'pg_proc') AS comment,
|
|
14306
|
+
(p.prorettype = 'pg_catalog.void'::regtype) AS returns_void,
|
|
14307
|
+
p.proretset AS returns_set,
|
|
14308
|
+
format_type(p.prorettype, NULL) AS return_type,
|
|
14309
|
+
-- Resolve one level of DOMAIN to its base type so a domain over a composite
|
|
14310
|
+
-- is not mistaken for a scalar (and a domain over a scalar stays scalar). A
|
|
14311
|
+
-- still-domain result (domain over domain) is left as 'd' and treated as
|
|
14312
|
+
-- unsupported downstream (fail-closed for that exotic nesting).
|
|
14313
|
+
eff.eff_typtype AS return_typtype,
|
|
14314
|
+
(
|
|
14315
|
+
SELECT json_agg(json_build_object(
|
|
14316
|
+
'name', COALESCE(an.argname, ''),
|
|
14317
|
+
'typeName', format_type(at.typeoid, NULL),
|
|
14318
|
+
'udtName', t.typname,
|
|
14319
|
+
'typeOid', at.typeoid::int,
|
|
14320
|
+
'mode', COALESCE(am.mode, 'i'),
|
|
14321
|
+
'ord', at.ord
|
|
14322
|
+
) ORDER BY at.ord)
|
|
14323
|
+
FROM unnest(COALESCE(p.proallargtypes, p.proargtypes::oid[]))
|
|
14324
|
+
WITH ORDINALITY AS at(typeoid, ord)
|
|
14325
|
+
LEFT JOIN unnest(p.proargmodes) WITH ORDINALITY AS am(mode, ord) ON am.ord = at.ord
|
|
14326
|
+
LEFT JOIN unnest(p.proargnames) WITH ORDINALITY AS an(argname, ord) ON an.ord = at.ord
|
|
14327
|
+
JOIN pg_type t ON t.oid = at.typeoid
|
|
14328
|
+
) AS args,
|
|
14329
|
+
CASE
|
|
14330
|
+
WHEN eff.eff_typrelid <> 0 THEN (
|
|
14331
|
+
SELECT json_agg(json_build_object(
|
|
14332
|
+
'name', a.attname,
|
|
14333
|
+
'typeName', format_type(a.atttypid, a.atttypmod),
|
|
14334
|
+
'typeOid', a.atttypid::int
|
|
14335
|
+
) ORDER BY a.attnum)
|
|
14336
|
+
FROM pg_attribute a
|
|
14337
|
+
WHERE a.attrelid = eff.eff_typrelid AND a.attnum > 0 AND NOT a.attisdropped
|
|
14338
|
+
)
|
|
14339
|
+
ELSE (
|
|
14340
|
+
SELECT json_agg(json_build_object(
|
|
14341
|
+
'name', COALESCE(an.argname, ''),
|
|
14342
|
+
'typeName', format_type(at.typeoid, NULL),
|
|
14343
|
+
'typeOid', at.typeoid::int
|
|
14344
|
+
) ORDER BY at.ord)
|
|
14345
|
+
FROM unnest(COALESCE(p.proallargtypes, p.proargtypes::oid[]))
|
|
14346
|
+
WITH ORDINALITY AS at(typeoid, ord)
|
|
14347
|
+
JOIN unnest(p.proargmodes) WITH ORDINALITY AS am(mode, ord) ON am.ord = at.ord
|
|
14348
|
+
LEFT JOIN unnest(p.proargnames) WITH ORDINALITY AS an(argname, ord) ON an.ord = at.ord
|
|
14349
|
+
WHERE am.mode IN ('o', 'b', 't')
|
|
14350
|
+
)
|
|
14351
|
+
END AS return_columns,
|
|
14352
|
+
p.proconfig AS proconfig
|
|
14353
|
+
FROM pg_proc p
|
|
14354
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
14355
|
+
JOIN pg_roles ro ON ro.oid = p.proowner
|
|
14356
|
+
JOIN pg_type rt ON rt.oid = p.prorettype
|
|
14357
|
+
LEFT JOIN pg_type bt ON bt.oid = rt.typbasetype
|
|
14358
|
+
CROSS JOIN LATERAL (
|
|
14359
|
+
SELECT
|
|
14360
|
+
CASE WHEN rt.typtype = 'd' AND bt.oid IS NOT NULL THEN bt.typtype ELSE rt.typtype END
|
|
14361
|
+
AS eff_typtype,
|
|
14362
|
+
CASE WHEN rt.typtype = 'd' AND bt.oid IS NOT NULL THEN bt.typrelid ELSE rt.typrelid END
|
|
14363
|
+
AS eff_typrelid
|
|
14364
|
+
) eff
|
|
14365
|
+
WHERE p.prokind = 'f'
|
|
14366
|
+
AND n.nspname = ANY($1)
|
|
14367
|
+
ORDER BY n.nspname, p.proname`;
|
|
14368
|
+
var SCHEMA_WRITABILITY_SQL = `
|
|
14369
|
+
SELECT
|
|
14370
|
+
n.nspname AS schema,
|
|
14371
|
+
EXISTS (
|
|
14372
|
+
SELECT 1 FROM aclexplode(COALESCE(n.nspacl, acldefault('n', n.nspowner))) ae
|
|
14373
|
+
WHERE ae.privilege_type = 'CREATE' AND ae.grantee = 0
|
|
14374
|
+
) AS public_create,
|
|
14375
|
+
COALESCE((
|
|
14376
|
+
SELECT array_agg(r.oid)
|
|
14377
|
+
FROM pg_roles r
|
|
14378
|
+
WHERE NOT r.rolsuper
|
|
14379
|
+
AND has_schema_privilege(r.oid, n.oid, 'CREATE')
|
|
14380
|
+
), ARRAY[]::oid[]) AS creator_roles
|
|
14381
|
+
FROM pg_namespace n
|
|
14382
|
+
WHERE n.nspname = ANY($1)`;
|
|
14383
|
+
function mapVolatility(c) {
|
|
14384
|
+
if (c === "i") return "immutable";
|
|
14385
|
+
if (c === "s") return "stable";
|
|
14386
|
+
return "volatile";
|
|
14387
|
+
}
|
|
14388
|
+
function mapArgMode(c) {
|
|
14389
|
+
switch (c) {
|
|
14390
|
+
case "o": return "out";
|
|
14391
|
+
case "b": return "inout";
|
|
14392
|
+
case "v": return "variadic";
|
|
14393
|
+
case "t": return "table";
|
|
14394
|
+
default: return "in";
|
|
14395
|
+
}
|
|
14396
|
+
}
|
|
14397
|
+
function buildArgs(row) {
|
|
14398
|
+
const args = (row.args ?? []).slice().sort((a, b) => a.ord - b.ord).map((a) => ({
|
|
14399
|
+
name: a.name,
|
|
14400
|
+
typeName: a.typeName,
|
|
14401
|
+
udtName: a.udtName,
|
|
14402
|
+
typeOid: a.typeOid,
|
|
14403
|
+
mode: mapArgMode(a.mode),
|
|
14404
|
+
hasDefault: false
|
|
14405
|
+
}));
|
|
14406
|
+
if (row.ndef > 0) {
|
|
14407
|
+
const inputIdx = args.map((a, i) => ({
|
|
14408
|
+
a,
|
|
14409
|
+
i
|
|
14410
|
+
})).filter((e) => e.a.mode === "in" || e.a.mode === "inout" || e.a.mode === "variadic").map((e) => e.i);
|
|
14411
|
+
for (const i of inputIdx.slice(Math.max(0, inputIdx.length - row.ndef))) args[i].hasDefault = true;
|
|
14412
|
+
}
|
|
14413
|
+
return args;
|
|
14414
|
+
}
|
|
14415
|
+
function classifyReturn(row) {
|
|
14416
|
+
const typeName = row.return_type;
|
|
14417
|
+
const columns = row.return_columns && row.return_columns.length > 0 ? row.return_columns.map((c) => ({
|
|
14418
|
+
name: c.name,
|
|
14419
|
+
typeName: c.typeName,
|
|
14420
|
+
typeOid: c.typeOid
|
|
14421
|
+
})) : void 0;
|
|
14422
|
+
const isScalarType = [
|
|
14423
|
+
"b",
|
|
14424
|
+
"e",
|
|
14425
|
+
"r",
|
|
14426
|
+
"m"
|
|
14427
|
+
].includes(row.return_typtype);
|
|
14428
|
+
if (row.returns_void) return {
|
|
14429
|
+
kind: "void",
|
|
14430
|
+
typeName,
|
|
14431
|
+
returnsSet: false
|
|
14432
|
+
};
|
|
14433
|
+
if (row.returns_set) {
|
|
14434
|
+
if (columns) return {
|
|
14435
|
+
kind: "setof",
|
|
14436
|
+
typeName,
|
|
14437
|
+
returnsSet: true,
|
|
14438
|
+
columns
|
|
14439
|
+
};
|
|
14440
|
+
if (isScalarType) return {
|
|
14441
|
+
kind: "setof",
|
|
14442
|
+
typeName,
|
|
14443
|
+
returnsSet: true
|
|
14444
|
+
};
|
|
14445
|
+
return {
|
|
14446
|
+
kind: "unsupported",
|
|
14447
|
+
typeName,
|
|
14448
|
+
returnsSet: true
|
|
14449
|
+
};
|
|
14450
|
+
}
|
|
14451
|
+
if (row.return_typtype === "c") return columns ? {
|
|
14452
|
+
kind: "composite",
|
|
14453
|
+
typeName,
|
|
14454
|
+
returnsSet: false,
|
|
14455
|
+
columns
|
|
14456
|
+
} : {
|
|
14457
|
+
kind: "unsupported",
|
|
14458
|
+
typeName,
|
|
14459
|
+
returnsSet: false
|
|
14460
|
+
};
|
|
14461
|
+
if (isScalarType) return {
|
|
14462
|
+
kind: "scalar",
|
|
14463
|
+
typeName,
|
|
14464
|
+
returnsSet: false
|
|
14465
|
+
};
|
|
14466
|
+
return {
|
|
14467
|
+
kind: "unsupported",
|
|
14468
|
+
typeName,
|
|
14469
|
+
returnsSet: false
|
|
14470
|
+
};
|
|
14471
|
+
}
|
|
14472
|
+
/** Split a SET search_path GUC value on commas that are not inside double
|
|
14473
|
+
* quotes, then unquote each element. Handles `"$user", public` and a quoted
|
|
14474
|
+
* identifier that itself contains a comma. */
|
|
14475
|
+
function splitSearchPath(value) {
|
|
14476
|
+
const elements = [];
|
|
14477
|
+
let current = "";
|
|
14478
|
+
let inQuotes = false;
|
|
14479
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
14480
|
+
const ch = value[i];
|
|
14481
|
+
if (ch === "\"") if (inQuotes && value[i + 1] === "\"") {
|
|
14482
|
+
current += "\"";
|
|
14483
|
+
i += 1;
|
|
14484
|
+
} else inQuotes = !inQuotes;
|
|
14485
|
+
else if (ch === "," && !inQuotes) {
|
|
14486
|
+
elements.push(current.trim());
|
|
14487
|
+
current = "";
|
|
14488
|
+
} else current += ch;
|
|
14489
|
+
}
|
|
14490
|
+
elements.push(current.trim());
|
|
14491
|
+
return elements.filter((e) => e !== "");
|
|
14492
|
+
}
|
|
14493
|
+
/** Parse the proconfig `search_path` setting into elements, or null when the
|
|
14494
|
+
* function declares no SET search_path. Each element records whether it is
|
|
14495
|
+
* pg_temp and (for a fixed schema) is resolved to a schema name; a dynamic
|
|
14496
|
+
* element (`$user`) resolves to null. Writability is filled in afterwards. */
|
|
14497
|
+
function parseSearchPath(proconfig) {
|
|
14498
|
+
if (proconfig === null) return null;
|
|
14499
|
+
const entry = proconfig.find((c) => c.toLowerCase().startsWith("search_path="));
|
|
14500
|
+
if (entry === void 0) return null;
|
|
14501
|
+
return splitSearchPath(entry.slice(entry.indexOf("=") + 1)).map((raw) => {
|
|
14502
|
+
if (raw === "pg_temp") return {
|
|
14503
|
+
raw,
|
|
14504
|
+
schema: null,
|
|
14505
|
+
writableByOthers: null,
|
|
14506
|
+
isTemp: true
|
|
14507
|
+
};
|
|
14508
|
+
return {
|
|
14509
|
+
raw,
|
|
14510
|
+
schema: raw.startsWith("$") ? null : raw,
|
|
14511
|
+
writableByOthers: null,
|
|
14512
|
+
isTemp: false
|
|
14513
|
+
};
|
|
14514
|
+
});
|
|
14515
|
+
}
|
|
14516
|
+
/** Fetch CREATE-writability info for the given schema names. */
|
|
14517
|
+
async function fetchSchemaWritability(client, schemaNames) {
|
|
14518
|
+
const map = /* @__PURE__ */ new Map();
|
|
14519
|
+
if (schemaNames.length === 0) return map;
|
|
14520
|
+
const rows = await runQuery(client, SCHEMA_WRITABILITY_SQL, [schemaNames], "fetchFunctions (schema writability)");
|
|
14521
|
+
for (const row of rows) map.set(row.schema, {
|
|
14522
|
+
publicCreate: row.public_create,
|
|
14523
|
+
creatorRoles: row.creator_roles ?? []
|
|
14524
|
+
});
|
|
14525
|
+
return map;
|
|
14526
|
+
}
|
|
14527
|
+
async function fetchFunctions(client, schemas) {
|
|
14528
|
+
if (schemas.length === 0) return [];
|
|
14529
|
+
const rows = await runQuery(client, FUNCTIONS_SQL, [schemas], "fetchFunctions (functions)");
|
|
14530
|
+
if (rows.length === 0) return [];
|
|
14531
|
+
const parsed = rows.map((row) => ({
|
|
14532
|
+
row,
|
|
14533
|
+
searchPath: parseSearchPath(row.proconfig)
|
|
14534
|
+
}));
|
|
14535
|
+
const schemaNames = /* @__PURE__ */ new Set();
|
|
14536
|
+
for (const { row, searchPath } of parsed) {
|
|
14537
|
+
if (!row.security_definer || searchPath === null) continue;
|
|
14538
|
+
for (const el of searchPath) if (!el.isTemp && el.schema !== null) schemaNames.add(el.schema);
|
|
14539
|
+
}
|
|
14540
|
+
const writability = await fetchSchemaWritability(client, [...schemaNames]);
|
|
14541
|
+
return parsed.map(({ row, searchPath }) => {
|
|
14542
|
+
const resolvedSearchPath = searchPath === null ? null : searchPath.map((el) => {
|
|
14543
|
+
if (el.isTemp || el.schema === null) return el;
|
|
14544
|
+
const w = writability.get(el.schema);
|
|
14545
|
+
if (w === void 0) return {
|
|
14546
|
+
...el,
|
|
14547
|
+
writableByOthers: null
|
|
14548
|
+
};
|
|
14549
|
+
const writableByOthers = w.publicCreate || w.creatorRoles.some((r) => r !== row.owner_oid);
|
|
14550
|
+
return {
|
|
14551
|
+
...el,
|
|
14552
|
+
writableByOthers
|
|
14553
|
+
};
|
|
14554
|
+
});
|
|
14555
|
+
return {
|
|
14556
|
+
schema: row.schema,
|
|
14557
|
+
name: row.name,
|
|
14558
|
+
argumentSignature: row.argument_signature,
|
|
14559
|
+
arguments: buildArgs(row),
|
|
14560
|
+
returns: classifyReturn(row),
|
|
14561
|
+
volatility: mapVolatility(row.volatility),
|
|
14562
|
+
security: row.security_definer ? "definer" : "invoker",
|
|
14563
|
+
owner: {
|
|
14564
|
+
oid: row.owner_oid,
|
|
14565
|
+
name: row.owner_name
|
|
14566
|
+
},
|
|
14567
|
+
publicExecute: row.public_execute,
|
|
14568
|
+
searchPath: resolvedSearchPath,
|
|
14569
|
+
comment: row.comment
|
|
14570
|
+
};
|
|
14571
|
+
});
|
|
14572
|
+
}
|
|
14573
|
+
//#endregion
|
|
13962
14574
|
//#region ../introspect/dist/privileges.js
|
|
13963
14575
|
var tableKey = (schema, name) => `${schema}.${name}`;
|
|
13964
14576
|
/** Throw a clear error if the configured privilege role does not exist, rather
|
|
@@ -14119,6 +14731,7 @@ async function introspect(opts) {
|
|
|
14119
14731
|
mergeTableMetadata(allTables, await fetchForeignKeys(client, validSchemas), await fetchChecks(client, validSchemas));
|
|
14120
14732
|
const allViews = await fetchViews(client, validSchemas);
|
|
14121
14733
|
const enums = await fetchEnums(client, validSchemas);
|
|
14734
|
+
const functions = await fetchFunctions(client, validSchemas);
|
|
14122
14735
|
if (opts.privilegeRole !== void 0) await fetchAndAttachPrivileges(client, validSchemas, opts.privilegeRole, allTables, allViews);
|
|
14123
14736
|
const filterOpts = {
|
|
14124
14737
|
include: opts.include,
|
|
@@ -14134,7 +14747,7 @@ async function introspect(opts) {
|
|
|
14134
14747
|
tables,
|
|
14135
14748
|
views,
|
|
14136
14749
|
enums,
|
|
14137
|
-
functions
|
|
14750
|
+
functions
|
|
14138
14751
|
};
|
|
14139
14752
|
} finally {
|
|
14140
14753
|
try {
|
|
@@ -14230,11 +14843,22 @@ var cache = new SchemaCache({ loader: async () => {
|
|
|
14230
14843
|
const connection = process.env.DATABASE_URL;
|
|
14231
14844
|
if (typeof connection !== "string" || connection.length === 0) throw new Error("hooks.server: DATABASE_URL is required to introspect the schema.");
|
|
14232
14845
|
const privilegeRole = process.env.KOZOU_INTROSPECTION_ROLE;
|
|
14233
|
-
return buildSchemaContext({
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14846
|
+
return buildSchemaContext({
|
|
14847
|
+
raw: await introspect({
|
|
14848
|
+
connection,
|
|
14849
|
+
...typeof privilegeRole === "string" && privilegeRole.length > 0 ? { privilegeRole } : {}
|
|
14850
|
+
}),
|
|
14851
|
+
rpc: {
|
|
14852
|
+
allowDefiner: parseList(process.env.KOZOU_RPC_ALLOW_DEFINER),
|
|
14853
|
+
allowPublicExecute: parseList(process.env.KOZOU_RPC_ALLOW_PUBLIC_EXECUTE)
|
|
14854
|
+
}
|
|
14855
|
+
});
|
|
14237
14856
|
} });
|
|
14857
|
+
/** Parse a comma-separated env list into trimmed, non-empty entries. */
|
|
14858
|
+
function parseList(raw) {
|
|
14859
|
+
if (raw === void 0 || raw.length === 0) return [];
|
|
14860
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
14861
|
+
}
|
|
14238
14862
|
var fkRowCache = new FkRowCache();
|
|
14239
14863
|
var handle = async ({ event, resolve }) => {
|
|
14240
14864
|
event.locals.schema = await cache.get();
|
|
@@ -14243,4 +14867,4 @@ var handle = async ({ event, resolve }) => {
|
|
|
14243
14867
|
};
|
|
14244
14868
|
|
|
14245
14869
|
export { handle };
|
|
14246
|
-
//# sourceMappingURL=hooks.server-
|
|
14870
|
+
//# sourceMappingURL=hooks.server-Oa_UD2Wo.js.map
|