@kozou/svelte-ui 1.2.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/{M-rQODG_.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/POk9Ad1v.js +4 -0
- 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.BI5dO6h8.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/5.D1OoUXND.js +1 -0
- 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-CVLmaMi2.js → 1-Dni7ax6A.js} +3 -3
- package/build/server/chunks/{1-CVLmaMi2.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-Dc-NPvAO.js → 5-QdpSeSEB.js} +8 -6
- package/build/server/chunks/5-QdpSeSEB.js.map +1 -0
- package/build/server/chunks/{5-DvwPrYwd.js → 6-DQ9D4Rjj.js} +18 -15
- package/build/server/chunks/6-DQ9D4Rjj.js.map +1 -0
- package/build/server/chunks/{6-DxT9TB6A.js → 7-yY-k4F-J.js} +18 -15
- 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-FkcquIeL.js → _page.svelte-6bUnnrWp.js} +9 -4
- package/build/server/chunks/_page.svelte-6bUnnrWp.js.map +1 -0
- 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-BGxCzjmZ.js → _page.svelte-D1coeZL7.js} +8 -7
- package/build/server/chunks/_page.svelte-D1coeZL7.js.map +1 -0
- package/build/server/chunks/{_page.svelte-DvcE6zrM.js → _page.svelte-DsOlSzf6.js} +8 -7
- 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/{app-DST8OmzT.js → app-MIcRQ3KA.js} +2 -2
- package/build/server/chunks/{app-DST8OmzT.js.map → app-MIcRQ3KA.js.map} +1 -1
- package/build/server/chunks/{client-BfR0iysy.js → client-CF_AG7xO.js} +3 -3
- package/build/server/chunks/{client-BfR0iysy.js.map → client-CF_AG7xO.js.map} +1 -1
- package/build/server/chunks/{composite-form-DeAbT97s.js → composite-form-Khrcy6Jk.js} +83 -31
- package/build/server/chunks/composite-form-Khrcy6Jk.js.map +1 -0
- package/build/server/chunks/{error.svelte-CgmjtCM8.js → error.svelte-CrnPC1Ez.js} +6 -6
- package/build/server/chunks/{error.svelte-CgmjtCM8.js.map → error.svelte-CrnPC1Ez.js.map} +1 -1
- package/build/server/chunks/{hooks.server-BCX7tjO4.js → hooks.server-Oa_UD2Wo.js} +743 -13
- 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/index-DBqjc0Yf.js.map +1 -1
- package/build/server/chunks/{internal-D3PcExp3.js → internal-Z9Jwi6lX.js} +13 -13
- package/build/server/chunks/{internal-D3PcExp3.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-COKz4xxc.js → widget-registry-6ku8orXa.js} +3 -98
- package/build/server/chunks/widget-registry-6ku8orXa.js.map +1 -0
- package/build/server/index.js +32 -32
- package/build/server/index.js.map +1 -1
- 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/0-MunKvN.js +0 -4
- package/build/client/_app/immutable/chunks/0-MunKvN.js.br +0 -0
- package/build/client/_app/immutable/chunks/0-MunKvN.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BgT5WiOF.js.br +0 -0
- package/build/client/_app/immutable/chunks/CrTaJnzY.js +0 -214
- package/build/client/_app/immutable/chunks/CrTaJnzY.js.br +0 -0
- package/build/client/_app/immutable/chunks/CrTaJnzY.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/M-rQODG_.js.br +0 -0
- package/build/client/_app/immutable/chunks/M-rQODG_.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.ByhtJ88k.js +0 -2
- package/build/client/_app/immutable/entry/app.ByhtJ88k.js.br +0 -0
- package/build/client/_app/immutable/entry/app.ByhtJ88k.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.C_VgULzk.js +0 -1
- package/build/client/_app/immutable/entry/start.C_VgULzk.js.br +0 -0
- package/build/client/_app/immutable/entry/start.C_VgULzk.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.BI5dO6h8.js.br +0 -3
- package/build/client/_app/immutable/nodes/1.BI5dO6h8.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.PUtoqAEs.js +0 -1
- package/build/client/_app/immutable/nodes/4.PUtoqAEs.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.PUtoqAEs.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.CEXAqzYO.js +0 -1
- package/build/client/_app/immutable/nodes/5.CEXAqzYO.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.CEXAqzYO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.4fTWRTA8.js +0 -1
- package/build/client/_app/immutable/nodes/6.4fTWRTA8.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.4fTWRTA8.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/4-Dc-NPvAO.js.map +0 -1
- package/build/server/chunks/5-DvwPrYwd.js.map +0 -1
- package/build/server/chunks/6-DxT9TB6A.js.map +0 -1
- package/build/server/chunks/_page.svelte-BGxCzjmZ.js.map +0 -1
- package/build/server/chunks/_page.svelte-BN1au56r.js.map +0 -1
- package/build/server/chunks/_page.svelte-DvcE6zrM.js.map +0 -1
- package/build/server/chunks/_page.svelte-FkcquIeL.js.map +0 -1
- package/build/server/chunks/adapter-CXNsjV1V.js.map +0 -1
- package/build/server/chunks/composite-form-DeAbT97s.js.map +0 -1
- package/build/server/chunks/hooks.server-BCX7tjO4.js.map +0 -1
- package/build/server/chunks/widget-registry-COKz4xxc.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",
|
|
@@ -6743,6 +7060,9 @@ function buildColumn(input) {
|
|
|
6743
7060
|
commentBody: parsed.body
|
|
6744
7061
|
});
|
|
6745
7062
|
const label = hints?.label ?? deriveLabel(column.name);
|
|
7063
|
+
const priv = column.privileges;
|
|
7064
|
+
const insertable = priv === void 0 ? void 0 : priv.insert;
|
|
7065
|
+
const updatable = priv === void 0 ? void 0 : priv.update;
|
|
6746
7066
|
return {
|
|
6747
7067
|
name: column.name,
|
|
6748
7068
|
dataType: column.dataType,
|
|
@@ -6756,7 +7076,9 @@ function buildColumn(input) {
|
|
|
6756
7076
|
policy: parsed.policy,
|
|
6757
7077
|
widget,
|
|
6758
7078
|
enumValues,
|
|
6759
|
-
readonly: hints?.readonly ?? false
|
|
7079
|
+
readonly: hints?.readonly ?? false,
|
|
7080
|
+
...insertable === void 0 ? {} : { insertable },
|
|
7081
|
+
...updatable === void 0 ? {} : { updatable }
|
|
6760
7082
|
};
|
|
6761
7083
|
}
|
|
6762
7084
|
function buildRelations(table, issues) {
|
|
@@ -6923,7 +7245,7 @@ function buildConcept(view) {
|
|
|
6923
7245
|
};
|
|
6924
7246
|
}
|
|
6925
7247
|
async function buildSchemaContext(opts) {
|
|
6926
|
-
const { raw, uiHints, strict = false } = opts;
|
|
7248
|
+
const { raw, uiHints, strict = false, rpc } = opts;
|
|
6927
7249
|
const issues = [];
|
|
6928
7250
|
const knownTables = new Set(raw.tables.map((t) => `${t.schema}.${t.name}`));
|
|
6929
7251
|
raw.views.forEach((v) => knownTables.add(`${v.schema}.${v.name}`));
|
|
@@ -6939,13 +7261,32 @@ async function buildSchemaContext(opts) {
|
|
|
6939
7261
|
message: `UIHints view "${viewName}" does not exist in raw.views`
|
|
6940
7262
|
});
|
|
6941
7263
|
}
|
|
6942
|
-
const
|
|
7264
|
+
const hiddenNames = [];
|
|
7265
|
+
const visibleRawTables = raw.tables.filter((t) => {
|
|
7266
|
+
if (t.privileges?.select === false) {
|
|
7267
|
+
hiddenNames.push(t.name);
|
|
7268
|
+
return false;
|
|
7269
|
+
}
|
|
7270
|
+
return true;
|
|
7271
|
+
});
|
|
7272
|
+
const visibleRawViews = raw.views.filter((v) => {
|
|
7273
|
+
if (v.privileges?.select === false) {
|
|
7274
|
+
hiddenNames.push(v.name);
|
|
7275
|
+
return false;
|
|
7276
|
+
}
|
|
7277
|
+
return true;
|
|
7278
|
+
});
|
|
7279
|
+
if (hiddenNames.length > 0) {
|
|
7280
|
+
const role = raw.tables.find((t) => t.privileges !== void 0)?.privileges?.role ?? raw.views.find((v) => v.privileges !== void 0)?.privileges?.role ?? "the role";
|
|
7281
|
+
console.warn(`[@kozou/core] privilege-aware introspection: hid ${hiddenNames.length} relation(s) that "${role}" cannot SELECT: ${hiddenNames.join(", ")}`);
|
|
7282
|
+
}
|
|
7283
|
+
const tables = visibleRawTables.map((t) => buildTableContext({
|
|
6943
7284
|
table: t,
|
|
6944
7285
|
hints: uiHints?.tables?.[t.name],
|
|
6945
7286
|
issues,
|
|
6946
7287
|
knownTables
|
|
6947
7288
|
}));
|
|
6948
|
-
const views =
|
|
7289
|
+
const views = visibleRawViews.map((v) => buildViewContext({
|
|
6949
7290
|
view: v,
|
|
6950
7291
|
hints: uiHints?.views?.[v.name],
|
|
6951
7292
|
issues
|
|
@@ -6956,7 +7297,13 @@ async function buildSchemaContext(opts) {
|
|
|
6956
7297
|
values: e.values,
|
|
6957
7298
|
description: null
|
|
6958
7299
|
}));
|
|
6959
|
-
const concepts =
|
|
7300
|
+
const concepts = visibleRawViews.map(buildConcept);
|
|
7301
|
+
const functions = buildFunctionContexts({
|
|
7302
|
+
functions: raw.functions,
|
|
7303
|
+
enums: raw.enums,
|
|
7304
|
+
rpc,
|
|
7305
|
+
issues
|
|
7306
|
+
});
|
|
6960
7307
|
if (issues.length > 0) {
|
|
6961
7308
|
if (strict) throw new KozouBuildError(`buildSchemaContext: ${issues.length} validation issue(s) (strict=true)`, issues);
|
|
6962
7309
|
for (const issue of issues) console.warn(`[@kozou/core] ${issue.path}: ${issue.message}`);
|
|
@@ -6970,7 +7317,8 @@ async function buildSchemaContext(opts) {
|
|
|
6970
7317
|
tables,
|
|
6971
7318
|
views,
|
|
6972
7319
|
enums,
|
|
6973
|
-
concepts
|
|
7320
|
+
concepts,
|
|
7321
|
+
functions
|
|
6974
7322
|
};
|
|
6975
7323
|
}
|
|
6976
7324
|
//#endregion
|
|
@@ -13921,7 +14269,7 @@ async function fetchEnums(client, schemas) {
|
|
|
13921
14269
|
return (await runQuery(client, `SELECT
|
|
13922
14270
|
n.nspname AS schema,
|
|
13923
14271
|
t.typname AS name,
|
|
13924
|
-
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
|
14272
|
+
array_agg(e.enumlabel::text ORDER BY e.enumsortorder) AS values
|
|
13925
14273
|
FROM pg_type t
|
|
13926
14274
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
13927
14275
|
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
@@ -13935,6 +14283,371 @@ async function fetchEnums(client, schemas) {
|
|
|
13935
14283
|
}));
|
|
13936
14284
|
}
|
|
13937
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
|
|
14574
|
+
//#region ../introspect/dist/privileges.js
|
|
14575
|
+
var tableKey = (schema, name) => `${schema}.${name}`;
|
|
14576
|
+
/** Throw a clear error if the configured privilege role does not exist, rather
|
|
14577
|
+
* than letting `has_table_privilege` fail mid-query with a terse pg message. */
|
|
14578
|
+
async function assertRoleExists(client, role) {
|
|
14579
|
+
if ((await runQuery(client, "SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = $1) AS exists", [role], "fetchPrivileges (role check)"))[0]?.exists !== true) throw new KozouIntrospectError(`introspection.respectPrivileges is on but the role "${role}" does not exist. Set it to the role the Admin UI assumes (auth.ui.role / auth.defaultRole), or turn privilege-aware introspection off.`);
|
|
14580
|
+
}
|
|
14581
|
+
/**
|
|
14582
|
+
* Evaluate the privileges of `role` on every base table / view (and base-table
|
|
14583
|
+
* columns) in `schemas`, and attach them to the matching `RawTable` /
|
|
14584
|
+
* `RawColumn` / `RawView`. Mutates the inputs in place (mirrors
|
|
14585
|
+
* `mergeTableMetadata`). Call only when privilege-aware mode is on; otherwise
|
|
14586
|
+
* the privilege fields stay `undefined`.
|
|
14587
|
+
*/
|
|
14588
|
+
async function fetchAndAttachPrivileges(client, schemas, role, tables, views) {
|
|
14589
|
+
if (schemas.length === 0 || tables.length === 0 && views.length === 0) return;
|
|
14590
|
+
await assertRoleExists(client, role);
|
|
14591
|
+
const tableRows = await runQuery(client, `SELECT
|
|
14592
|
+
n.nspname AS schema,
|
|
14593
|
+
c.relname AS name,
|
|
14594
|
+
has_schema_privilege($2, n.nspname, 'USAGE') AS usage,
|
|
14595
|
+
has_table_privilege($2, c.oid, 'SELECT') AS sel,
|
|
14596
|
+
has_table_privilege($2, c.oid, 'INSERT') AS ins,
|
|
14597
|
+
has_table_privilege($2, c.oid, 'UPDATE') AS upd,
|
|
14598
|
+
has_table_privilege($2, c.oid, 'DELETE') AS del
|
|
14599
|
+
FROM pg_class c
|
|
14600
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
14601
|
+
WHERE c.relkind IN ('r', 'v', 'm')
|
|
14602
|
+
AND n.nspname = ANY($1)`, [schemas, role], "fetchPrivileges (relations)");
|
|
14603
|
+
const columnRows = await runQuery(client, `SELECT
|
|
14604
|
+
n.nspname AS schema,
|
|
14605
|
+
c.relname AS table,
|
|
14606
|
+
a.attname AS name,
|
|
14607
|
+
has_schema_privilege($2, n.nspname, 'USAGE') AS usage,
|
|
14608
|
+
has_column_privilege($2, c.oid, a.attname, 'INSERT') AS ins,
|
|
14609
|
+
has_column_privilege($2, c.oid, a.attname, 'UPDATE') AS upd
|
|
14610
|
+
FROM pg_attribute a
|
|
14611
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
14612
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
14613
|
+
WHERE c.relkind = 'r'
|
|
14614
|
+
AND n.nspname = ANY($1)
|
|
14615
|
+
AND a.attnum > 0
|
|
14616
|
+
AND NOT a.attisdropped`, [schemas, role], "fetchPrivileges (columns)");
|
|
14617
|
+
const tablePrivByKey = /* @__PURE__ */ new Map();
|
|
14618
|
+
for (const row of tableRows) tablePrivByKey.set(tableKey(row.schema, row.name), row);
|
|
14619
|
+
const columnPrivByKey = /* @__PURE__ */ new Map();
|
|
14620
|
+
for (const row of columnRows) {
|
|
14621
|
+
const key = tableKey(row.schema, row.table);
|
|
14622
|
+
if (!columnPrivByKey.has(key)) columnPrivByKey.set(key, /* @__PURE__ */ new Map());
|
|
14623
|
+
columnPrivByKey.get(key).set(row.name, row);
|
|
14624
|
+
}
|
|
14625
|
+
const gate = (tp) => ({
|
|
14626
|
+
role,
|
|
14627
|
+
select: tp.usage && tp.sel,
|
|
14628
|
+
insert: tp.usage && tp.ins,
|
|
14629
|
+
update: tp.usage && tp.upd,
|
|
14630
|
+
delete: tp.usage && tp.del
|
|
14631
|
+
});
|
|
14632
|
+
for (const table of tables) {
|
|
14633
|
+
const key = tableKey(table.schema, table.name);
|
|
14634
|
+
const tp = tablePrivByKey.get(key);
|
|
14635
|
+
if (tp !== void 0) table.privileges = gate(tp);
|
|
14636
|
+
const cols = columnPrivByKey.get(key);
|
|
14637
|
+
if (cols !== void 0) for (const column of table.columns) {
|
|
14638
|
+
const cp = cols.get(column.name);
|
|
14639
|
+
if (cp !== void 0) column.privileges = {
|
|
14640
|
+
insert: cp.usage && cp.ins,
|
|
14641
|
+
update: cp.usage && cp.upd
|
|
14642
|
+
};
|
|
14643
|
+
}
|
|
14644
|
+
}
|
|
14645
|
+
for (const view of views) {
|
|
14646
|
+
const vp = tablePrivByKey.get(tableKey(view.schema, view.name));
|
|
14647
|
+
if (vp !== void 0) view.privileges = gate(vp);
|
|
14648
|
+
}
|
|
14649
|
+
}
|
|
14650
|
+
//#endregion
|
|
13938
14651
|
//#region ../introspect/dist/filter.js
|
|
13939
14652
|
function compilePattern(pattern) {
|
|
13940
14653
|
const qualified = pattern.includes(".") ? pattern : `*.${pattern}`;
|
|
@@ -14018,6 +14731,8 @@ async function introspect(opts) {
|
|
|
14018
14731
|
mergeTableMetadata(allTables, await fetchForeignKeys(client, validSchemas), await fetchChecks(client, validSchemas));
|
|
14019
14732
|
const allViews = await fetchViews(client, validSchemas);
|
|
14020
14733
|
const enums = await fetchEnums(client, validSchemas);
|
|
14734
|
+
const functions = await fetchFunctions(client, validSchemas);
|
|
14735
|
+
if (opts.privilegeRole !== void 0) await fetchAndAttachPrivileges(client, validSchemas, opts.privilegeRole, allTables, allViews);
|
|
14021
14736
|
const filterOpts = {
|
|
14022
14737
|
include: opts.include,
|
|
14023
14738
|
exclude: opts.exclude
|
|
@@ -14032,7 +14747,7 @@ async function introspect(opts) {
|
|
|
14032
14747
|
tables,
|
|
14033
14748
|
views,
|
|
14034
14749
|
enums,
|
|
14035
|
-
functions
|
|
14750
|
+
functions
|
|
14036
14751
|
};
|
|
14037
14752
|
} finally {
|
|
14038
14753
|
try {
|
|
@@ -14127,8 +14842,23 @@ var SchemaCache = class {
|
|
|
14127
14842
|
var cache = new SchemaCache({ loader: async () => {
|
|
14128
14843
|
const connection = process.env.DATABASE_URL;
|
|
14129
14844
|
if (typeof connection !== "string" || connection.length === 0) throw new Error("hooks.server: DATABASE_URL is required to introspect the schema.");
|
|
14130
|
-
|
|
14845
|
+
const privilegeRole = process.env.KOZOU_INTROSPECTION_ROLE;
|
|
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
|
+
});
|
|
14131
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
|
+
}
|
|
14132
14862
|
var fkRowCache = new FkRowCache();
|
|
14133
14863
|
var handle = async ({ event, resolve }) => {
|
|
14134
14864
|
event.locals.schema = await cache.get();
|
|
@@ -14137,4 +14867,4 @@ var handle = async ({ event, resolve }) => {
|
|
|
14137
14867
|
};
|
|
14138
14868
|
|
|
14139
14869
|
export { handle };
|
|
14140
|
-
//# sourceMappingURL=hooks.server-
|
|
14870
|
+
//# sourceMappingURL=hooks.server-Oa_UD2Wo.js.map
|