@caelo-cms/provisioning 0.11.4 → 0.11.6
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/dist/agent-db.js +523 -105
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +20 -0
- package/dist/compose.js.map +1 -1
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +6 -2
- package/dist/converge-job.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +78 -0
- package/dist/pool-budget-stack.d.ts.map +1 -0
- package/dist/pool-budget-stack.js +78 -0
- package/dist/pool-budget-stack.js.map +1 -0
- package/package.json +2 -2
- package/src/compose.ts +22 -0
- package/src/converge-job.test.ts +28 -0
- package/src/converge-job.ts +6 -2
- package/src/gateway-env-isolation.test.ts +2 -0
- package/src/pool-budget-stack.ts +116 -0
- package/src/pool-budget-stacks.test.ts +100 -0
- package/src/self-hosted-compose.test.ts +23 -0
- package/src/stack-contract-parity.test.ts +6 -2
- package/stacks/aws/Pulumi.yaml +12 -0
- package/stacks/aws/README.md +1 -0
- package/stacks/aws/index.ts +36 -0
- package/stacks/azure/Pulumi.yaml +4 -0
- package/stacks/azure/README.md +1 -0
- package/stacks/azure/index.ts +35 -2
- package/stacks/gcp/Pulumi.yaml +16 -8
- package/stacks/gcp/README.md +7 -3
- package/stacks/gcp/index.ts +45 -20
- package/stacks/gcp-firebase/Pulumi.yaml +5 -1
- package/stacks/gcp-firebase/index.ts +27 -3
- package/stacks/self-hosted/README.md +4 -0
package/dist/agent-db.js
CHANGED
|
@@ -6614,6 +6614,8 @@ var LIST_RESERVED_NAMES = new Set([
|
|
|
6614
6614
|
"site_text",
|
|
6615
6615
|
"recurse"
|
|
6616
6616
|
]);
|
|
6617
|
+
var LIST_RESERVED_PREFIXES = ["is_", "has_", "count_"];
|
|
6618
|
+
var LIST_RESERVED_NAMES_CLAUSE = `reserved: ${[...LIST_RESERVED_NAMES].join(", ")}, ${LIST_RESERVED_PREFIXES.map((p) => `${p}*`).join(", ")}`;
|
|
6617
6619
|
var listNameSchema = string2().regex(/^[a-z][a-z0-9_]{0,63}$/, "must be snake_case (lowercase letters, digits, underscores)");
|
|
6618
6620
|
var fieldLabel = string2().min(1).max(128);
|
|
6619
6621
|
var fieldExtSchema = record(string2().regex(/^[a-z0-9-]+$/), unknown());
|
|
@@ -6769,6 +6771,22 @@ var listItemInputSchema = lazy(() => object({
|
|
|
6769
6771
|
query: record(string2(), unknown()).optional()
|
|
6770
6772
|
}).strict());
|
|
6771
6773
|
|
|
6774
|
+
// ../shared/src/lists/page-type.ts
|
|
6775
|
+
var pageTypeDefSchema = object({
|
|
6776
|
+
slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
|
|
6777
|
+
name: string2().min(1).max(200),
|
|
6778
|
+
purpose: string2().min(1).max(1000),
|
|
6779
|
+
collection: boolean2(),
|
|
6780
|
+
templates: array(string2().uuid()).min(1).max(64),
|
|
6781
|
+
defaultTemplate: string2().uuid(),
|
|
6782
|
+
properties: array(fieldDefSchema).max(32).default([]),
|
|
6783
|
+
vocabularies: array(object({
|
|
6784
|
+
list: string2().uuid(),
|
|
6785
|
+
cardinality: _enum(["one", "many"]),
|
|
6786
|
+
required: boolean2().optional()
|
|
6787
|
+
}).strict()).max(16).default([])
|
|
6788
|
+
}).strict();
|
|
6789
|
+
|
|
6772
6790
|
// ../shared/src/lists/pagination.ts
|
|
6773
6791
|
var PAGINATION_FOLLOW_UPS = ["index", "noindex"];
|
|
6774
6792
|
var listPaginationSchema = object({
|
|
@@ -6827,7 +6845,7 @@ var noCorruptCss = (value, ctx) => {
|
|
|
6827
6845
|
var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html"));
|
|
6828
6846
|
var CDATA_OPEN = "<?$/, "page type slug: lowercase letters, digits, hyphens");
|
|
7047
|
+
var pageTypeDistinctFromSchema = array(pageTypeSlugSchema).max(16);
|
|
7048
|
+
var newPageTypeSchema = pageTypeDefSchema.omit({ templates: true, defaultTemplate: true }).extend({ distinctFrom: pageTypeDistinctFromSchema.optional() }).strict();
|
|
7029
7049
|
var templateCreateSchema = object({
|
|
7030
7050
|
slug: slugSchema,
|
|
7031
7051
|
displayName: displayNameSchema,
|
|
7032
7052
|
pageType: pageTypeSlugSchema.optional(),
|
|
7053
|
+
newPageType: newPageTypeSchema.optional(),
|
|
7033
7054
|
html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html")),
|
|
7034
7055
|
css: string2().max(TEMPLATE_CSS_MAX, `css exceeds ${TEMPLATE_CSS_MAX} bytes`).default(""),
|
|
7035
7056
|
layoutId: string2().uuid().optional(),
|
|
@@ -7366,7 +7387,8 @@ var chatCreateSessionInput = object({
|
|
|
7366
7387
|
templateId: string2().uuid().optional(),
|
|
7367
7388
|
subagentRole: string2().min(1).max(120).optional(),
|
|
7368
7389
|
parentChatSessionId: string2().uuid().nullable().optional(),
|
|
7369
|
-
isolation: _enum(["experiment", "migration"]).optional()
|
|
7390
|
+
isolation: _enum(["experiment", "migration"]).optional(),
|
|
7391
|
+
agentClient: string2().min(1).max(200).optional()
|
|
7370
7392
|
}).strict();
|
|
7371
7393
|
var CHAT_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
7372
7394
|
var CHAT_MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
@@ -7457,6 +7479,7 @@ var createTemplateToolInput = object({
|
|
|
7457
7479
|
css: sourceTextArg(string2().max(2000000), "css").default(""),
|
|
7458
7480
|
layoutId: string2().uuid().optional(),
|
|
7459
7481
|
pageType: pageTypeSlugSchema.optional(),
|
|
7482
|
+
newPageType: newPageTypeSchema.optional(),
|
|
7460
7483
|
blocks: array(object({
|
|
7461
7484
|
name: string2().min(1).max(80),
|
|
7462
7485
|
displayName: string2().min(1).max(200),
|
|
@@ -7484,17 +7507,15 @@ var createLayoutToolInput = object({
|
|
|
7484
7507
|
}).strict()).min(1).max(20)
|
|
7485
7508
|
}).strict();
|
|
7486
7509
|
var setSiteDefaultsToolInput = object({
|
|
7487
|
-
defaultLayoutSlug: slugInputSchema
|
|
7488
|
-
|
|
7489
|
-
}).strict().refine((v) => v.defaultLayoutSlug !== undefined || v.defaultTemplateSlug !== undefined, {
|
|
7490
|
-
message: "must provide at least one of defaultLayoutSlug, defaultTemplateSlug"
|
|
7491
|
-
});
|
|
7510
|
+
defaultLayoutSlug: slugInputSchema
|
|
7511
|
+
}).strict();
|
|
7492
7512
|
var duplicatePageToolInput = object({
|
|
7493
7513
|
sourcePageId: string2().uuid(),
|
|
7494
7514
|
newSlug: slugInputSchema,
|
|
7495
7515
|
newTitle: string2().min(1).max(256).optional(),
|
|
7496
7516
|
newShortTitle: pageShortTitleSchema.optional(),
|
|
7497
7517
|
targetTemplateId: string2().uuid().optional(),
|
|
7518
|
+
pageType: pageTypeSlugSchema.optional(),
|
|
7498
7519
|
newParentPageId: string2().uuid().nullable().optional()
|
|
7499
7520
|
}).strict();
|
|
7500
7521
|
var repointPageTemplateToolInput = object({
|
|
@@ -7628,6 +7649,7 @@ var bootstrapSiteScaffoldToolInput = object({
|
|
|
7628
7649
|
})).max(8).optional(),
|
|
7629
7650
|
templateSlug: string2().min(1).max(120).optional(),
|
|
7630
7651
|
templateDisplayName: string2().min(1).max(256).optional(),
|
|
7652
|
+
templatePageType: newPageTypeSchema.optional(),
|
|
7631
7653
|
setAsDefaults: boolean2().optional()
|
|
7632
7654
|
}).strict();
|
|
7633
7655
|
var revertChatChangesToolInput = object({
|
|
@@ -7964,6 +7986,98 @@ var pageModuleContentSetManySchema = object({
|
|
|
7964
7986
|
}).strict();
|
|
7965
7987
|
// ../shared/src/cap-failures.ts
|
|
7966
7988
|
var counters = new Map;
|
|
7989
|
+
// ../shared/src/change-journal.ts
|
|
7990
|
+
var JOURNAL_KINDS = [
|
|
7991
|
+
"page",
|
|
7992
|
+
"page_terms",
|
|
7993
|
+
"page_seo",
|
|
7994
|
+
"page_layout",
|
|
7995
|
+
"page_module_content",
|
|
7996
|
+
"module",
|
|
7997
|
+
"template",
|
|
7998
|
+
"template_blocks",
|
|
7999
|
+
"layout",
|
|
8000
|
+
"layout_blocks",
|
|
8001
|
+
"layout_module",
|
|
8002
|
+
"content_instance",
|
|
8003
|
+
"list",
|
|
8004
|
+
"list_type",
|
|
8005
|
+
"page_type",
|
|
8006
|
+
"theme",
|
|
8007
|
+
"media",
|
|
8008
|
+
"media_variant",
|
|
8009
|
+
"media_crop",
|
|
8010
|
+
"font",
|
|
8011
|
+
"redirect",
|
|
8012
|
+
"domain",
|
|
8013
|
+
"site_defaults",
|
|
8014
|
+
"site_settings",
|
|
8015
|
+
"site_memory",
|
|
8016
|
+
"skill",
|
|
8017
|
+
"plugin",
|
|
8018
|
+
"plugin_installation",
|
|
8019
|
+
"plugin_grant",
|
|
8020
|
+
"plugin_file",
|
|
8021
|
+
"plugin_row",
|
|
8022
|
+
"plugin_storage",
|
|
8023
|
+
"snapshot"
|
|
8024
|
+
];
|
|
8025
|
+
var KIND_SET = new Set(JOURNAL_KINDS);
|
|
8026
|
+
var row = (kind, key, pair, extra = {}) => ({ kind, mode: "row", key, pair, ...extra });
|
|
8027
|
+
var snapshot = (kind, key, extra = {}) => ({ kind, mode: "snapshot", key, pair: ["id"], ...extra });
|
|
8028
|
+
var JOURNALED_TABLES = {
|
|
8029
|
+
pages: row("page", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8030
|
+
page_terms: row("page_terms", "page_id", ["page_id", "list_id", "item_id"]),
|
|
8031
|
+
pages_seo: row("page_seo", "page_id", ["page_id"]),
|
|
8032
|
+
page_modules: row("page_layout", "page_id", ["page_id", "block_name", "position"]),
|
|
8033
|
+
page_module_content: row("page_module_content", "id", ["id"], { parent: "page_id" }),
|
|
8034
|
+
modules: row("module", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8035
|
+
templates: row("template", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8036
|
+
template_blocks: row("template_blocks", "template_id", ["template_id", "name"]),
|
|
8037
|
+
layouts: row("layout", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8038
|
+
layout_blocks: row("layout_blocks", "layout_id", ["layout_id", "name"]),
|
|
8039
|
+
layout_modules: row("layout_module", "layout_id", ["layout_id", "block_name", "position"]),
|
|
8040
|
+
content_instances: row("content_instance", "id", ["id"], {
|
|
8041
|
+
branch: "chat_branch_id",
|
|
8042
|
+
parent: "module_id"
|
|
8043
|
+
}),
|
|
8044
|
+
lists: row("list", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8045
|
+
list_types: row("list_type", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8046
|
+
page_types: row("page_type", "id", ["id"], { branch: "chat_branch_id" }),
|
|
8047
|
+
themes: row("theme", "id", ["id"]),
|
|
8048
|
+
media_assets: row("media", "id", ["id"]),
|
|
8049
|
+
media_variants: row("media_variant", "asset_id", ["asset_id", "variant"]),
|
|
8050
|
+
media_crops: row("media_crop", "id", ["id"], { parent: "asset_id" }),
|
|
8051
|
+
font_assets: row("font", "id", ["id"]),
|
|
8052
|
+
redirects: row("redirect", "id", ["id"]),
|
|
8053
|
+
domains: row("domain", "id", ["id"]),
|
|
8054
|
+
site_defaults: row("site_defaults", "id", ["id"]),
|
|
8055
|
+
site_settings: row("site_settings", "id", ["id"]),
|
|
8056
|
+
site_ai_memory: row("site_memory", "id", ["id"]),
|
|
8057
|
+
skills: row("skill", "id", ["id"]),
|
|
8058
|
+
plugins: row("plugin", "id", ["id"]),
|
|
8059
|
+
plugin_installation_versions: row("plugin_installation", "id", ["id"], { parent: "plugin_id" }),
|
|
8060
|
+
plugin_capability_grants: row("plugin_grant", "id", ["id"], { parent: "plugin_id" }),
|
|
8061
|
+
plugin_private_files: row("plugin_file", "id", ["plugin_id", "id"], { plugin: "plugin_id" }),
|
|
8062
|
+
site_snapshots: { kind: "snapshot", mode: "header", key: "id", pair: ["id"] },
|
|
8063
|
+
page_snapshots: snapshot("page", "page_id"),
|
|
8064
|
+
page_layout_snapshots: snapshot("page_layout", "page_id"),
|
|
8065
|
+
page_module_content_snapshots: snapshot("page_module_content", "page_module_content_id", {
|
|
8066
|
+
parent: "page_id"
|
|
8067
|
+
}),
|
|
8068
|
+
module_snapshots: snapshot("module", "module_id"),
|
|
8069
|
+
template_snapshots: snapshot("template", "template_id"),
|
|
8070
|
+
layout_module_snapshots: snapshot("layout_module", "layout_id"),
|
|
8071
|
+
content_instance_snapshots: snapshot("content_instance", "content_instance_id"),
|
|
8072
|
+
list_snapshots: snapshot("list", "list_id"),
|
|
8073
|
+
list_type_snapshots: snapshot("list_type", "list_type_id"),
|
|
8074
|
+
page_type_snapshots: snapshot("page_type", "page_type_id"),
|
|
8075
|
+
theme_snapshots: snapshot("theme", "theme_id"),
|
|
8076
|
+
plugin_row_snapshots: snapshot("plugin_row", "row_id", {
|
|
8077
|
+
parent: "table_name",
|
|
8078
|
+
plugin: "plugin_id"
|
|
8079
|
+
})
|
|
8080
|
+
};
|
|
7967
8081
|
// ../shared/src/css-scope-scan.ts
|
|
7968
8082
|
var GROUPING_AT_RULES = new Set(["media", "supports", "layer", "container", "document"]);
|
|
7969
8083
|
// ../shared/src/interactions.ts
|
|
@@ -8026,6 +8140,13 @@ var RASTER_IMAGE_MIMES = new Set([
|
|
|
8026
8140
|
]);
|
|
8027
8141
|
var mediaUrlPattern = new RegExp(`${MEDIA_URL_PREFIX}/([a-z0-9][a-z0-9-]{0,63})(?:/([a-z][a-z0-9-]{0,63}))?`, "g");
|
|
8028
8142
|
var sha256Schema = string2().regex(/^[0-9a-f]{64}$/, "must be hex sha256");
|
|
8143
|
+
var publishedDigestSchema = object({
|
|
8144
|
+
contentSha256: sha256Schema,
|
|
8145
|
+
size: number2().int().nonnegative(),
|
|
8146
|
+
gzipSha256: sha256Schema,
|
|
8147
|
+
gzipSize: number2().int().nonnegative(),
|
|
8148
|
+
profile: string2().min(1).max(64)
|
|
8149
|
+
}).strict();
|
|
8029
8150
|
var mediaVisibilitySchema = _enum(["library", "reference"]);
|
|
8030
8151
|
var mediaUploadInputSchema = object({
|
|
8031
8152
|
sha256: sha256Schema,
|
|
@@ -8049,7 +8170,8 @@ var mediaUploadInputSchema = object({
|
|
|
8049
8170
|
width: number2().int().positive().nullable(),
|
|
8050
8171
|
height: number2().int().positive().nullable(),
|
|
8051
8172
|
sizeBytes: number2().int().positive(),
|
|
8052
|
-
storageKey: string2().min(1)
|
|
8173
|
+
storageKey: string2().min(1),
|
|
8174
|
+
digest: publishedDigestSchema
|
|
8053
8175
|
})).min(1)
|
|
8054
8176
|
}).strict();
|
|
8055
8177
|
var mediaListInputSchema = object({
|
|
@@ -9058,20 +9180,6 @@ var NESTED_KINDS = new Set(["module", "module-list"]);
|
|
|
9058
9180
|
// ../shared/src/lists/address.ts
|
|
9059
9181
|
var SEG = "[a-z0-9_-]+";
|
|
9060
9182
|
var ADDRESS_RE = new RegExp(`^(?:list/(${SEG})/item/(${SEG})/(${SEG})` + `|instance/(${SEG})/(${SEG})/item/(${SEG})/(${SEG})` + `|page/(${SEG})/props/(${SEG})` + `|list_type/(${SEG})/node/(${SEG})/(${SEG}))$`);
|
|
9061
|
-
// ../shared/src/lists/page-type.ts
|
|
9062
|
-
var pageTypeDefSchema = object({
|
|
9063
|
-
slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
|
|
9064
|
-
name: string2().min(1).max(200),
|
|
9065
|
-
purpose: string2().min(1).max(1000),
|
|
9066
|
-
templates: array(string2().uuid()).min(1).max(64),
|
|
9067
|
-
defaultTemplate: string2().uuid(),
|
|
9068
|
-
properties: array(fieldDefSchema).max(32).default([]),
|
|
9069
|
-
vocabularies: array(object({
|
|
9070
|
-
list: string2().uuid(),
|
|
9071
|
-
cardinality: _enum(["one", "many"]),
|
|
9072
|
-
required: boolean2().optional()
|
|
9073
|
-
}).strict()).max(16).default([])
|
|
9074
|
-
}).strict();
|
|
9075
9183
|
// ../shared/src/lists/resolve.ts
|
|
9076
9184
|
var SEG2 = "[a-z0-9_-]+";
|
|
9077
9185
|
var LIST_VALUE_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
|
|
@@ -9087,7 +9195,8 @@ var LIVE_TOPICS = [
|
|
|
9087
9195
|
"credentials",
|
|
9088
9196
|
"quality",
|
|
9089
9197
|
"crawl",
|
|
9090
|
-
"deploys"
|
|
9198
|
+
"deploys",
|
|
9199
|
+
"draft"
|
|
9091
9200
|
];
|
|
9092
9201
|
var TOPIC_SET = new Set(LIVE_TOPICS);
|
|
9093
9202
|
// ../shared/src/markup-structure.ts
|
|
@@ -9122,8 +9231,43 @@ var pageLogEntrySchema = object({
|
|
|
9122
9231
|
// ../shared/src/phase-timer.ts
|
|
9123
9232
|
var phaseTimingSchema = object({
|
|
9124
9233
|
phase: string2().min(1),
|
|
9125
|
-
ms: number2().int().nonnegative()
|
|
9234
|
+
ms: number2().int().nonnegative(),
|
|
9235
|
+
peakRssMb: number2().int().nonnegative().optional(),
|
|
9236
|
+
cpuMs: number2().int().nonnegative().optional()
|
|
9126
9237
|
});
|
|
9238
|
+
var RSS_SAMPLE_MS = 25;
|
|
9239
|
+
var MIB = 1024 * 1024;
|
|
9240
|
+
function rssNow() {
|
|
9241
|
+
return typeof process !== "undefined" && typeof process.memoryUsage === "function" ? process.memoryUsage().rss : 0;
|
|
9242
|
+
}
|
|
9243
|
+
var sampler = (() => {
|
|
9244
|
+
const listeners = new Set;
|
|
9245
|
+
let timer = null;
|
|
9246
|
+
return {
|
|
9247
|
+
add(fn) {
|
|
9248
|
+
listeners.add(fn);
|
|
9249
|
+
if (timer === null) {
|
|
9250
|
+
timer = setInterval(() => {
|
|
9251
|
+
const rss = rssNow();
|
|
9252
|
+
for (const l of listeners)
|
|
9253
|
+
l(rss);
|
|
9254
|
+
}, RSS_SAMPLE_MS);
|
|
9255
|
+
timer.unref?.();
|
|
9256
|
+
}
|
|
9257
|
+
},
|
|
9258
|
+
remove(fn) {
|
|
9259
|
+
listeners.delete(fn);
|
|
9260
|
+
if (listeners.size === 0 && timer !== null) {
|
|
9261
|
+
clearInterval(timer);
|
|
9262
|
+
timer = null;
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
};
|
|
9266
|
+
})();
|
|
9267
|
+
// ../shared/src/pool-budget.ts
|
|
9268
|
+
var DB_POOL_MAX_ENV = "CAELO_DB_POOL_MAX";
|
|
9269
|
+
var DB_PUBLIC_POOL_MAX_ENV = "CAELO_DB_PUBLIC_POOL_MAX";
|
|
9270
|
+
var MIN_POOL_MAX = 2;
|
|
9127
9271
|
// ../shared/src/proposal-status.ts
|
|
9128
9272
|
var PROPOSAL_STATUSES = ["pending", "applied", "rejected", "superseded"];
|
|
9129
9273
|
var proposalStatus = _enum(PROPOSAL_STATUSES);
|
|
@@ -18665,7 +18809,8 @@ function extractPgFields(error) {
|
|
|
18665
18809
|
let current = error;
|
|
18666
18810
|
for (let depth = 0;depth < 5 && typeof current === "object" && current !== null; depth++) {
|
|
18667
18811
|
const e = current;
|
|
18668
|
-
const
|
|
18812
|
+
const sqlstate = typeof e.errno === "string" && /^[0-9A-Z]{5}$/.test(e.errno) ? e.errno : undefined;
|
|
18813
|
+
const code = sqlstate ?? (typeof e.code === "string" ? e.code : undefined);
|
|
18669
18814
|
const constraint = typeof e.constraint === "string" ? e.constraint : undefined;
|
|
18670
18815
|
const table = typeof e.table === "string" ? e.table : undefined;
|
|
18671
18816
|
const column = typeof e.column === "string" ? e.column : undefined;
|
|
@@ -18737,6 +18882,71 @@ function defineOperation(def) {
|
|
|
18737
18882
|
};
|
|
18738
18883
|
}
|
|
18739
18884
|
|
|
18885
|
+
// ../query-api/src/pg-failure.ts
|
|
18886
|
+
function parseKey(detail) {
|
|
18887
|
+
if (!detail)
|
|
18888
|
+
return null;
|
|
18889
|
+
const start = detail.indexOf("Key (");
|
|
18890
|
+
if (start === -1)
|
|
18891
|
+
return null;
|
|
18892
|
+
const sep = detail.indexOf(")=(", start + 5);
|
|
18893
|
+
const end = detail.lastIndexOf(")");
|
|
18894
|
+
if (sep === -1 || end <= sep + 2)
|
|
18895
|
+
return null;
|
|
18896
|
+
const columns = detail.slice(start + 5, sep);
|
|
18897
|
+
if (columns.includes(")"))
|
|
18898
|
+
return null;
|
|
18899
|
+
return { columns, values: detail.slice(sep + 3, end) };
|
|
18900
|
+
}
|
|
18901
|
+
function keyPhrase(key) {
|
|
18902
|
+
return key.columns.includes(",") ? `the combination \`${key.columns}\` = (${key.values})` : `\`${key.columns}\` "${key.values}"`;
|
|
18903
|
+
}
|
|
18904
|
+
function describePgFailure(fields) {
|
|
18905
|
+
const key = parseKey(fields.detail);
|
|
18906
|
+
switch (fields.code) {
|
|
18907
|
+
case "23505":
|
|
18908
|
+
return {
|
|
18909
|
+
message: key ? `${keyPhrase(key)} already exists \u2014 choose a different value` : "a row with the same unique value already exists \u2014 choose a different value",
|
|
18910
|
+
internal: false
|
|
18911
|
+
};
|
|
18912
|
+
case "23503":
|
|
18913
|
+
if (fields.detail?.includes("still referenced")) {
|
|
18914
|
+
return {
|
|
18915
|
+
message: "the row is still referenced by other content \u2014 remove those references first",
|
|
18916
|
+
internal: false
|
|
18917
|
+
};
|
|
18918
|
+
}
|
|
18919
|
+
return {
|
|
18920
|
+
message: key ? `${keyPhrase(key)} names a row that does not exist or was deleted \u2014 use an id from the matching list tool` : "a referenced row does not exist or was deleted \u2014 use an id from the matching list tool",
|
|
18921
|
+
internal: false
|
|
18922
|
+
};
|
|
18923
|
+
case "23502":
|
|
18924
|
+
return {
|
|
18925
|
+
message: fields.column ? `\`${fields.column}\` is required but was empty` : "a required value was empty",
|
|
18926
|
+
internal: false
|
|
18927
|
+
};
|
|
18928
|
+
case "23514":
|
|
18929
|
+
return { message: "a value is outside the allowed range or set", internal: false };
|
|
18930
|
+
case "22P02":
|
|
18931
|
+
return {
|
|
18932
|
+
message: "a value has the wrong format (e.g. an id that is not a UUID) \u2014 copy ids exactly",
|
|
18933
|
+
internal: false
|
|
18934
|
+
};
|
|
18935
|
+
case "22001":
|
|
18936
|
+
return { message: "a text value is longer than allowed \u2014 shorten it", internal: false };
|
|
18937
|
+
case "22003":
|
|
18938
|
+
return { message: "a number is out of range", internal: false };
|
|
18939
|
+
default:
|
|
18940
|
+
return {
|
|
18941
|
+
message: "a database error the call cannot fix \u2014 a defect in Caelo, reported automatically",
|
|
18942
|
+
internal: true
|
|
18943
|
+
};
|
|
18944
|
+
}
|
|
18945
|
+
}
|
|
18946
|
+
function rlsDenialMessage(operation, access) {
|
|
18947
|
+
return `permission denied: ${operation} may not ${access} that row as the current actor ` + "(it is not visible on this chat's branch, or belongs to another actor or plugin). " + "Check the ids against the matching list tool; if they are right, the change needs a human with the permission.";
|
|
18948
|
+
}
|
|
18949
|
+
|
|
18740
18950
|
// ../query-api/src/plugin-schema-provision.ts
|
|
18741
18951
|
import { createHash } from "crypto";
|
|
18742
18952
|
var DROPPED_COLUMNS_SETTING = "caelo.plugin_dropped_columns";
|
|
@@ -18763,6 +18973,9 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
|
|
|
18763
18973
|
return { schemaName, droppedColumns: [] };
|
|
18764
18974
|
await tx.execute(sql.raw(opts.sql));
|
|
18765
18975
|
const dropped = await tx.execute(sql.raw(`SELECT coalesce(current_setting('${DROPPED_COLUMNS_SETTING}', true), '') AS v`));
|
|
18976
|
+
if (opts.journal === true) {
|
|
18977
|
+
await tx.execute(sql`SELECT caelo_journal_attach_plugin_schema(${schemaName}, ${opts.pluginId}::uuid)`);
|
|
18978
|
+
}
|
|
18766
18979
|
await tx.execute(sql`
|
|
18767
18980
|
INSERT INTO plugin_schema_provisions (schema_name, sql_digest)
|
|
18768
18981
|
VALUES (${schemaName}, ${digest})
|
|
@@ -18778,18 +18991,25 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
|
|
|
18778
18991
|
// ../query-api/src/pool-guard.ts
|
|
18779
18992
|
import { AsyncLocalStorage } from "async_hooks";
|
|
18780
18993
|
var POOL_ACQUIRE_TIMEOUT_MS = 1e4;
|
|
18781
|
-
var NESTED_CHECKOUTS = [
|
|
18782
|
-
{ holder: "plugins.render_hook", issue: 1220 }
|
|
18783
|
-
];
|
|
18784
18994
|
var LONG_TRANSACTIONS = [
|
|
18785
18995
|
{
|
|
18786
18996
|
holder: "static-generator.build",
|
|
18787
18997
|
reason: "a static site build runs in one transaction for as long as the build takes, idle while plugins render and files are written."
|
|
18788
18998
|
}
|
|
18789
18999
|
];
|
|
18790
|
-
var NESTED_CHECKOUT_ALLOWLIST = new Map(NESTED_CHECKOUTS.map((entry) => [entry.holder, entry]));
|
|
18791
19000
|
var LONG_TRANSACTION_ALLOWLIST = new Map(LONG_TRANSACTIONS.map((entry) => [entry.holder, entry]));
|
|
18792
19001
|
|
|
19002
|
+
class SerialQueue {
|
|
19003
|
+
#tail = Promise.resolve();
|
|
19004
|
+
run(fn) {
|
|
19005
|
+
const next = this.#tail.then(fn, fn);
|
|
19006
|
+
this.#tail = next.catch(() => {
|
|
19007
|
+
return;
|
|
19008
|
+
});
|
|
19009
|
+
return next;
|
|
19010
|
+
}
|
|
19011
|
+
}
|
|
19012
|
+
|
|
18793
19013
|
class NestedPoolCheckoutError extends Error {
|
|
18794
19014
|
pool;
|
|
18795
19015
|
holder;
|
|
@@ -18815,24 +19035,55 @@ class PoolAcquireTimeoutError extends Error {
|
|
|
18815
19035
|
this.timeoutMs = timeoutMs;
|
|
18816
19036
|
}
|
|
18817
19037
|
}
|
|
18818
|
-
var
|
|
19038
|
+
var joining = new AsyncLocalStorage;
|
|
19039
|
+
function newMarker(holder, root) {
|
|
19040
|
+
return {
|
|
19041
|
+
holder,
|
|
19042
|
+
active: true,
|
|
19043
|
+
tx: null,
|
|
19044
|
+
identity: null,
|
|
19045
|
+
identityOpName: null,
|
|
19046
|
+
root,
|
|
19047
|
+
joined: new SerialQueue,
|
|
19048
|
+
afterEnd: []
|
|
19049
|
+
};
|
|
19050
|
+
}
|
|
19051
|
+
|
|
18819
19052
|
class PoolGuard {
|
|
18820
19053
|
#stores = {
|
|
18821
19054
|
admin: new AsyncLocalStorage,
|
|
18822
19055
|
public: new AsyncLocalStorage
|
|
18823
19056
|
};
|
|
18824
|
-
|
|
18825
|
-
const
|
|
18826
|
-
|
|
19057
|
+
plan(pool, holder) {
|
|
19058
|
+
const outer = this.held(pool);
|
|
19059
|
+
if (outer === null)
|
|
19060
|
+
return { kind: "own", marker: newMarker(holder, null) };
|
|
19061
|
+
this.assertMayCheckout(pool, holder);
|
|
19062
|
+
return { kind: "join", outer, marker: newMarker(holder, outer.root ?? outer) };
|
|
19063
|
+
}
|
|
19064
|
+
held(pool) {
|
|
19065
|
+
const outer = this.#stores[pool].getStore();
|
|
19066
|
+
return outer?.active ? outer : null;
|
|
18827
19067
|
}
|
|
18828
19068
|
assertMayCheckout(pool, requester) {
|
|
18829
|
-
const outer = this
|
|
18830
|
-
if (outer ===
|
|
18831
|
-
return
|
|
18832
|
-
|
|
18833
|
-
|
|
19069
|
+
const outer = this.held(pool);
|
|
19070
|
+
if (outer === null || joining.getStore() === true)
|
|
19071
|
+
return;
|
|
19072
|
+
throw new NestedPoolCheckoutError(pool, outer.holder, requester);
|
|
19073
|
+
}
|
|
19074
|
+
assertNoHeldTransaction(pool, requester) {
|
|
19075
|
+
const outer = this.held(pool);
|
|
19076
|
+
if (outer !== null)
|
|
18834
19077
|
throw new NestedPoolCheckoutError(pool, outer.holder, requester);
|
|
18835
|
-
|
|
19078
|
+
}
|
|
19079
|
+
async deferUntilEnd(pool, work) {
|
|
19080
|
+
const outer = this.held(pool);
|
|
19081
|
+
if (outer === null) {
|
|
19082
|
+
await work();
|
|
19083
|
+
return false;
|
|
19084
|
+
}
|
|
19085
|
+
(outer.root ?? outer).afterEnd.push(work);
|
|
19086
|
+
return true;
|
|
18836
19087
|
}
|
|
18837
19088
|
async run(pool, marker, fn) {
|
|
18838
19089
|
try {
|
|
@@ -18870,6 +19121,15 @@ function collectPostCommitEffects(tx) {
|
|
|
18870
19121
|
pendingByTx.set(tx, pending);
|
|
18871
19122
|
return pending;
|
|
18872
19123
|
}
|
|
19124
|
+
function mergeSavepointEffects(effects, parentTx) {
|
|
19125
|
+
if (effects.length === 0)
|
|
19126
|
+
return;
|
|
19127
|
+
const pending = pendingByTx.get(parentTx);
|
|
19128
|
+
if (pending === undefined) {
|
|
19129
|
+
throw new Error(`afterCommit(${effects[0]?.label}): the transaction this call joined collects no post-commit effects`);
|
|
19130
|
+
}
|
|
19131
|
+
pending.push(...effects);
|
|
19132
|
+
}
|
|
18873
19133
|
async function runPostCommitEffects(effects) {
|
|
18874
19134
|
const failures = [];
|
|
18875
19135
|
for (const effect of effects) {
|
|
@@ -18894,6 +19154,10 @@ function notifyOperationCommitted(operationName) {
|
|
|
18894
19154
|
}
|
|
18895
19155
|
}
|
|
18896
19156
|
}
|
|
19157
|
+
var commitNotice = null;
|
|
19158
|
+
function commitNoticeOf(operationName) {
|
|
19159
|
+
return commitNotice === null ? null : commitNotice(operationName);
|
|
19160
|
+
}
|
|
18897
19161
|
|
|
18898
19162
|
// ../query-api/src/adapter.ts
|
|
18899
19163
|
var SQL3 = globalThis.Bun?.SQL;
|
|
@@ -18932,13 +19196,21 @@ class AdminDatabaseUnavailableError extends Error {
|
|
|
18932
19196
|
function resolvePoolMax(explicit) {
|
|
18933
19197
|
if (explicit !== undefined)
|
|
18934
19198
|
return validatePoolMax(explicit, "poolMax option");
|
|
18935
|
-
const raw = process.env
|
|
19199
|
+
const raw = process.env[DB_POOL_MAX_ENV];
|
|
18936
19200
|
if (raw === undefined || raw === "")
|
|
18937
19201
|
return;
|
|
18938
|
-
return validatePoolMax(Number(raw),
|
|
19202
|
+
return validatePoolMax(Number(raw), DB_POOL_MAX_ENV);
|
|
19203
|
+
}
|
|
19204
|
+
function resolvePublicPoolMax(explicitPublic, explicitAll) {
|
|
19205
|
+
if (explicitPublic !== undefined)
|
|
19206
|
+
return validatePoolMax(explicitPublic, "publicPoolMax option");
|
|
19207
|
+
const raw = process.env[DB_PUBLIC_POOL_MAX_ENV];
|
|
19208
|
+
if (raw !== undefined && raw !== "")
|
|
19209
|
+
return validatePoolMax(Number(raw), DB_PUBLIC_POOL_MAX_ENV);
|
|
19210
|
+
return resolvePoolMax(explicitAll);
|
|
18939
19211
|
}
|
|
18940
19212
|
function validatePoolMax(value, source) {
|
|
18941
|
-
if (!Number.isInteger(value) || value <
|
|
19213
|
+
if (!Number.isInteger(value) || value < MIN_POOL_MAX) {
|
|
18942
19214
|
throw new Error(`${source} must be an integer >= 2 (got ${value}); ` + `1 self-deadlocks the pool. Leave it unset for the Bun default of 10.`);
|
|
18943
19215
|
}
|
|
18944
19216
|
return value;
|
|
@@ -18962,12 +19234,18 @@ function postCommitFailureMessage(failures) {
|
|
|
18962
19234
|
const list = failures.map((f) => `${f.effect.label} (${f.message})`).join("; ");
|
|
18963
19235
|
return `The change is saved, but its follow-up in the running server failed: ${list}. Do not repeat the change. Restart the admin server so the running state is rebuilt from the saved data; the failure is in the audit log.`;
|
|
18964
19236
|
}
|
|
19237
|
+
var LISTEN_CONNECTIONS_PER_INSTANCE = 1;
|
|
18965
19238
|
|
|
18966
19239
|
class DatabaseAdapter {
|
|
18967
19240
|
#admin;
|
|
18968
19241
|
#public;
|
|
18969
19242
|
#adminRaw;
|
|
18970
19243
|
#publicRaw;
|
|
19244
|
+
#adminUrl;
|
|
19245
|
+
#publicUrl;
|
|
19246
|
+
#listeners = new Map;
|
|
19247
|
+
#listenLost = new Map;
|
|
19248
|
+
#listenersClosing = false;
|
|
18971
19249
|
#skipVerify;
|
|
18972
19250
|
#roles;
|
|
18973
19251
|
#acquireTimeoutMs;
|
|
@@ -18983,10 +19261,13 @@ class DatabaseAdapter {
|
|
|
18983
19261
|
}
|
|
18984
19262
|
assertPoolSafeBun(globalThis.Bun?.version ?? "0");
|
|
18985
19263
|
const poolMax = resolvePoolMax(config.poolMax);
|
|
19264
|
+
const publicPoolMax = resolvePublicPoolMax(config.publicPoolMax, config.poolMax);
|
|
18986
19265
|
const idleTimeout = config.poolIdleTimeoutSeconds ?? POOL_IDLE_TIMEOUT_SECONDS;
|
|
18987
|
-
const open = (url) => new SQL3(url,
|
|
18988
|
-
this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl);
|
|
18989
|
-
this.#publicRaw = open(config.publicDatabaseUrl);
|
|
19266
|
+
const open = (url, max) => new SQL3(url, max === undefined ? { idleTimeout } : { max, idleTimeout });
|
|
19267
|
+
this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl, poolMax);
|
|
19268
|
+
this.#publicRaw = open(config.publicDatabaseUrl, publicPoolMax);
|
|
19269
|
+
this.#adminUrl = config.adminDatabaseUrl;
|
|
19270
|
+
this.#publicUrl = config.publicDatabaseUrl;
|
|
18990
19271
|
this.#onStatement = config.onStatement ?? null;
|
|
18991
19272
|
const observe = this.#onStatement;
|
|
18992
19273
|
this.#drizzleOptions = observe ? { logger: { logQuery: (query) => observe(query) } } : {};
|
|
@@ -18999,22 +19280,64 @@ class DatabaseAdapter {
|
|
|
18999
19280
|
const raw = pool === "admin" ? this.#adminRaw : this.#publicRaw;
|
|
19000
19281
|
if (raw === null)
|
|
19001
19282
|
throw new AdminDatabaseUnavailableError(holder);
|
|
19002
|
-
const
|
|
19283
|
+
const plan = this.#guard.plan(pool, holder);
|
|
19284
|
+
if (plan.kind === "join")
|
|
19285
|
+
return this.#joinHeld(pool, plan.outer, plan.marker, fn);
|
|
19286
|
+
const marker = plan.marker;
|
|
19003
19287
|
const reserved = await this.#reserve(pool, raw, holder);
|
|
19004
19288
|
try {
|
|
19005
19289
|
const db = drizzle({ client: reserved, ...this.#drizzleOptions });
|
|
19006
19290
|
this.#onStatement?.("BEGIN");
|
|
19007
19291
|
const out = await db.transaction((tx) => this.#guard.run(pool, marker, async () => {
|
|
19292
|
+
marker.tx = tx;
|
|
19008
19293
|
if (exemptFromSessionTimeouts(holder))
|
|
19009
19294
|
await liftSessionTimeouts(tx);
|
|
19010
|
-
return fn(tx);
|
|
19295
|
+
return fn(tx, false);
|
|
19011
19296
|
}));
|
|
19012
19297
|
this.#onStatement?.("COMMIT");
|
|
19013
19298
|
return out;
|
|
19014
19299
|
} finally {
|
|
19015
19300
|
reserved.release();
|
|
19301
|
+
await this.#runAfterEnd(marker);
|
|
19302
|
+
}
|
|
19303
|
+
}
|
|
19304
|
+
#joinHeld(pool, outer, marker, fn) {
|
|
19305
|
+
return outer.joined.run(async () => {
|
|
19306
|
+
const outerTx = outer.tx;
|
|
19307
|
+
if (outerTx === null || !outer.active) {
|
|
19308
|
+
throw new NestedPoolCheckoutError(pool, outer.holder, marker.holder);
|
|
19309
|
+
}
|
|
19310
|
+
let effects = [];
|
|
19311
|
+
const out = await outerTx.transaction((savepoint) => this.#guard.run(pool, marker, async () => {
|
|
19312
|
+
marker.tx = savepoint;
|
|
19313
|
+
effects = collectPostCommitEffects(savepoint);
|
|
19314
|
+
return fn(savepoint, true);
|
|
19315
|
+
}));
|
|
19316
|
+
mergeSavepointEffects(effects, outerTx);
|
|
19317
|
+
if (outer.identity !== null) {
|
|
19318
|
+
await this.#setIdentity(outerTx, outer.identity, outer.identityOpName);
|
|
19319
|
+
}
|
|
19320
|
+
return out;
|
|
19321
|
+
});
|
|
19322
|
+
}
|
|
19323
|
+
async#runAfterEnd(marker) {
|
|
19324
|
+
for (const work of marker.afterEnd.splice(0)) {
|
|
19325
|
+
try {
|
|
19326
|
+
await work();
|
|
19327
|
+
} catch (thrown) {
|
|
19328
|
+
console.error("[query-api] work deferred until a transaction ended failed", {
|
|
19329
|
+
holder: marker.holder,
|
|
19330
|
+
error: thrown instanceof Error ? thrown.message : String(thrown)
|
|
19331
|
+
});
|
|
19332
|
+
}
|
|
19016
19333
|
}
|
|
19017
19334
|
}
|
|
19335
|
+
deferUntilHeldTransactionEnds(work) {
|
|
19336
|
+
return this.#guard.deferUntilEnd("admin", work);
|
|
19337
|
+
}
|
|
19338
|
+
holdsTransaction(pool) {
|
|
19339
|
+
return this.#guard.held(pool) !== null;
|
|
19340
|
+
}
|
|
19018
19341
|
async#reserve(pool, raw, requester) {
|
|
19019
19342
|
const deadline = new AbortController;
|
|
19020
19343
|
const timer = setTimeout(() => deadline.abort(), this.#acquireTimeoutMs);
|
|
@@ -19129,10 +19452,13 @@ class DatabaseAdapter {
|
|
|
19129
19452
|
let postCommit = [];
|
|
19130
19453
|
let result;
|
|
19131
19454
|
let handlerError = null;
|
|
19455
|
+
let joinedHolder = false;
|
|
19132
19456
|
try {
|
|
19133
|
-
result = await this.#transaction(pool, op.name, async (tx) => {
|
|
19134
|
-
|
|
19135
|
-
|
|
19457
|
+
result = await this.#transaction(pool, op.name, async (tx, joined) => {
|
|
19458
|
+
joinedHolder = joined;
|
|
19459
|
+
if (!joined)
|
|
19460
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19461
|
+
await this.#setIdentity(tx, ctx, op.name);
|
|
19136
19462
|
if (op.database === "cms_admin") {
|
|
19137
19463
|
const denied = await firstInvisibleBranch(tx, namedBranches(ctx, validatedInput));
|
|
19138
19464
|
if (denied !== null)
|
|
@@ -19142,7 +19468,12 @@ class DatabaseAdapter {
|
|
|
19142
19468
|
if (!out.ok)
|
|
19143
19469
|
handlerError = out;
|
|
19144
19470
|
if (out.ok && op.database === "cms_admin") {
|
|
19145
|
-
|
|
19471
|
+
if (!joined)
|
|
19472
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
19473
|
+
const notice = commitNoticeOf(op.name);
|
|
19474
|
+
if (notice !== null) {
|
|
19475
|
+
await tx.execute(sql`SELECT pg_notify(${notice.channel}, ${notice.payload})`);
|
|
19476
|
+
}
|
|
19146
19477
|
}
|
|
19147
19478
|
return out;
|
|
19148
19479
|
});
|
|
@@ -19152,23 +19483,11 @@ class DatabaseAdapter {
|
|
|
19152
19483
|
if (thrown instanceof OperationAbortError) {
|
|
19153
19484
|
return err(thrown.queryError);
|
|
19154
19485
|
}
|
|
19155
|
-
if (isRlsDenial(thrown)) {
|
|
19156
|
-
return err({
|
|
19157
|
-
kind: "RLSDenied",
|
|
19158
|
-
operation: op.name,
|
|
19159
|
-
detail: thrown.message ?? "RLS policy denied the write"
|
|
19160
|
-
});
|
|
19161
|
-
}
|
|
19162
19486
|
const pgDetail = extractPgFields(thrown);
|
|
19163
19487
|
if (pgDetail?.code === COMMIT_ROLLED_BACK && handlerError !== null) {
|
|
19164
19488
|
return handlerError;
|
|
19165
19489
|
}
|
|
19166
|
-
return err(
|
|
19167
|
-
kind: "HandlerError",
|
|
19168
|
-
operation: op.name,
|
|
19169
|
-
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
19170
|
-
...pgDetail ? { pgDetail } : {}
|
|
19171
|
-
});
|
|
19490
|
+
return err(thrownToQueryError(op.name, thrown, "write"));
|
|
19172
19491
|
}
|
|
19173
19492
|
const effectFailures = await this.#settlePostCommit(op.name, ctx, postCommit);
|
|
19174
19493
|
let publishFailure = null;
|
|
@@ -19179,7 +19498,7 @@ class DatabaseAdapter {
|
|
|
19179
19498
|
publishFailure = `The change is saved, but the public site does not serve it yet: publishing it failed (${thrown instanceof Error ? thrown.message : String(thrown)}). Do not repeat the change; it is published automatically within seconds once the public database answers.`;
|
|
19180
19499
|
}
|
|
19181
19500
|
}
|
|
19182
|
-
if (result.ok)
|
|
19501
|
+
if (result.ok && !joinedHolder)
|
|
19183
19502
|
notifyOperationCommitted(op.name);
|
|
19184
19503
|
if (effectFailures.length > 0) {
|
|
19185
19504
|
const effects = postCommitFailureMessage(effectFailures);
|
|
@@ -19213,22 +19532,77 @@ class DatabaseAdapter {
|
|
|
19213
19532
|
throw thrown;
|
|
19214
19533
|
if (thrown instanceof OperationAbortError)
|
|
19215
19534
|
return err(thrown.queryError);
|
|
19216
|
-
|
|
19217
|
-
return err({
|
|
19218
|
-
kind: "HandlerError",
|
|
19219
|
-
operation: op.name,
|
|
19220
|
-
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
19221
|
-
...pgDetail ? { pgDetail } : {}
|
|
19222
|
-
});
|
|
19535
|
+
return err(thrownToQueryError(op.name, thrown, "write"));
|
|
19223
19536
|
}
|
|
19224
19537
|
}
|
|
19225
|
-
async#setIdentity(tx, ctx) {
|
|
19538
|
+
async#setIdentity(tx, ctx, opName = null) {
|
|
19226
19539
|
await tx.execute(sql`SELECT
|
|
19227
19540
|
set_config('caelo.actor_id', ${ctx.actorId}, true),
|
|
19228
19541
|
set_config('caelo.actor_kind', ${ctx.actorKind}, true),
|
|
19229
19542
|
set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true),
|
|
19230
19543
|
set_config('caelo.chat_branch_id', ${ctx.chatBranchId ?? ""}, true),
|
|
19231
|
-
set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true)
|
|
19544
|
+
set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true),
|
|
19545
|
+
set_config('caelo.op_name', ${opName ?? ""}, true)`);
|
|
19546
|
+
this.#recordIdentity(tx, ctx, opName);
|
|
19547
|
+
}
|
|
19548
|
+
async#setPublicIdentity(tx, ctx) {
|
|
19549
|
+
await tx.execute(sql`SELECT
|
|
19550
|
+
set_config('caelo.actor_id', ${ctx.actorId}, true),
|
|
19551
|
+
set_config('caelo.actor_kind', ${ctx.actorKind}, true),
|
|
19552
|
+
set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
19553
|
+
this.#recordIdentity(tx, ctx, null);
|
|
19554
|
+
}
|
|
19555
|
+
#recordIdentity(tx, ctx, opName) {
|
|
19556
|
+
for (const pool of ["admin", "public"]) {
|
|
19557
|
+
const marker = this.#guard.held(pool);
|
|
19558
|
+
if (marker !== null && marker.tx === tx) {
|
|
19559
|
+
marker.identity = ctx;
|
|
19560
|
+
marker.identityOpName = opName;
|
|
19561
|
+
}
|
|
19562
|
+
}
|
|
19563
|
+
}
|
|
19564
|
+
async listen(pool, channel, callbacks) {
|
|
19565
|
+
const url = pool === "admin" ? this.#adminUrl : this.#publicUrl;
|
|
19566
|
+
if (url === null)
|
|
19567
|
+
throw new AdminDatabaseUnavailableError(`listen(${channel})`);
|
|
19568
|
+
let lost = this.#listenLost.get(pool);
|
|
19569
|
+
if (!lost) {
|
|
19570
|
+
lost = new Set;
|
|
19571
|
+
this.#listenLost.set(pool, lost);
|
|
19572
|
+
}
|
|
19573
|
+
const lostSet = lost;
|
|
19574
|
+
let conn = this.#listeners.get(pool);
|
|
19575
|
+
if (!conn) {
|
|
19576
|
+
conn = new SQL3(url, {
|
|
19577
|
+
max: LISTEN_CONNECTIONS_PER_INSTANCE,
|
|
19578
|
+
onclose: (cause) => {
|
|
19579
|
+
if (this.#listenersClosing)
|
|
19580
|
+
return;
|
|
19581
|
+
for (const onLost of lostSet) {
|
|
19582
|
+
try {
|
|
19583
|
+
onLost(cause);
|
|
19584
|
+
} catch (thrown) {
|
|
19585
|
+
console.error(`[query-api] a LISTEN onLost callback failed`, thrown);
|
|
19586
|
+
}
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19589
|
+
});
|
|
19590
|
+
this.#listeners.set(pool, conn);
|
|
19591
|
+
}
|
|
19592
|
+
const onLost = callbacks.onLost;
|
|
19593
|
+
lostSet.add(onLost);
|
|
19594
|
+
const handle = await conn.listen(channel, callbacks.onNotify, callbacks.onListen);
|
|
19595
|
+
return {
|
|
19596
|
+
unlisten: async () => {
|
|
19597
|
+
lostSet.delete(onLost);
|
|
19598
|
+
await handle.unlisten();
|
|
19599
|
+
}
|
|
19600
|
+
};
|
|
19601
|
+
}
|
|
19602
|
+
async notify(channel, payload) {
|
|
19603
|
+
await this.#transaction("admin", "notify", async (tx) => {
|
|
19604
|
+
await tx.execute(sql`SELECT pg_notify(${channel}, ${payload})`);
|
|
19605
|
+
});
|
|
19232
19606
|
}
|
|
19233
19607
|
async#runReadOperation(op, ctx, validatedInput) {
|
|
19234
19608
|
try {
|
|
@@ -19244,20 +19618,7 @@ class DatabaseAdapter {
|
|
|
19244
19618
|
throw thrown;
|
|
19245
19619
|
if (thrown instanceof OperationAbortError)
|
|
19246
19620
|
return err(thrown.queryError);
|
|
19247
|
-
|
|
19248
|
-
return err({
|
|
19249
|
-
kind: "RLSDenied",
|
|
19250
|
-
operation: op.name,
|
|
19251
|
-
detail: thrown.message ?? "RLS policy denied the read"
|
|
19252
|
-
});
|
|
19253
|
-
}
|
|
19254
|
-
const pgDetail = extractPgFields(thrown);
|
|
19255
|
-
return err({
|
|
19256
|
-
kind: "HandlerError",
|
|
19257
|
-
operation: op.name,
|
|
19258
|
-
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
19259
|
-
...pgDetail ? { pgDetail } : {}
|
|
19260
|
-
});
|
|
19621
|
+
return err(thrownToQueryError(op.name, thrown, "read"));
|
|
19261
19622
|
}
|
|
19262
19623
|
}
|
|
19263
19624
|
readScope(ctx, holder = "reads.read") {
|
|
@@ -19306,7 +19667,14 @@ class DatabaseAdapter {
|
|
|
19306
19667
|
return failures;
|
|
19307
19668
|
}
|
|
19308
19669
|
async close() {
|
|
19309
|
-
|
|
19670
|
+
this.#listenersClosing = true;
|
|
19671
|
+
const listeners = [...this.#listeners.values()];
|
|
19672
|
+
this.#listeners.clear();
|
|
19673
|
+
await Promise.all([
|
|
19674
|
+
this.#adminRaw?.end(),
|
|
19675
|
+
this.#publicRaw.end(),
|
|
19676
|
+
...listeners.map((l) => l.close())
|
|
19677
|
+
]);
|
|
19310
19678
|
}
|
|
19311
19679
|
async withAdminTransaction(ctx, fn, options = {}) {
|
|
19312
19680
|
const holder = options.holder ?? "withAdminTransaction";
|
|
@@ -19316,15 +19684,17 @@ class DatabaseAdapter {
|
|
|
19316
19684
|
await this.verifyRoles();
|
|
19317
19685
|
let publishAfterCommit = false;
|
|
19318
19686
|
let postCommit = [];
|
|
19319
|
-
const out = await this.#transaction("admin", holder, async (tx) => {
|
|
19320
|
-
|
|
19321
|
-
|
|
19687
|
+
const out = await this.#transaction("admin", holder, async (tx, joined) => {
|
|
19688
|
+
if (!joined)
|
|
19689
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19690
|
+
await this.#setIdentity(tx, ctx, options.holder ?? null);
|
|
19322
19691
|
const denied = await firstInvisibleBranch(tx, namedBranches(ctx, undefined));
|
|
19323
19692
|
if (denied !== null) {
|
|
19324
19693
|
throw new OperationAbortError(branchNotFound("withAdminTransaction", denied));
|
|
19325
19694
|
}
|
|
19326
19695
|
const result = await fn(tx);
|
|
19327
|
-
|
|
19696
|
+
if (!joined)
|
|
19697
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
19328
19698
|
return result;
|
|
19329
19699
|
});
|
|
19330
19700
|
const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
|
|
@@ -19340,11 +19710,10 @@ class DatabaseAdapter {
|
|
|
19340
19710
|
if (!this.#skipVerify)
|
|
19341
19711
|
await this.verifyRoles();
|
|
19342
19712
|
let postCommit = [];
|
|
19343
|
-
const out = await this.#transaction("public", holder, async (tx) => {
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
await tx
|
|
19347
|
-
await tx.execute(sql`SELECT set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
19713
|
+
const out = await this.#transaction("public", holder, async (tx, joined) => {
|
|
19714
|
+
if (!joined)
|
|
19715
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19716
|
+
await this.#setPublicIdentity(tx, ctx);
|
|
19348
19717
|
return await fn(tx);
|
|
19349
19718
|
});
|
|
19350
19719
|
const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
|
|
@@ -19355,11 +19724,11 @@ class DatabaseAdapter {
|
|
|
19355
19724
|
rawAdmin() {
|
|
19356
19725
|
if (this.#adminRaw === null)
|
|
19357
19726
|
throw new AdminDatabaseUnavailableError("rawAdmin");
|
|
19358
|
-
this.#guard.
|
|
19727
|
+
this.#guard.assertNoHeldTransaction("admin", "rawAdmin()");
|
|
19359
19728
|
return this.#adminRaw;
|
|
19360
19729
|
}
|
|
19361
19730
|
rawPublic() {
|
|
19362
|
-
this.#guard.
|
|
19731
|
+
this.#guard.assertNoHeldTransaction("public", "rawPublic()");
|
|
19363
19732
|
return this.#publicRaw;
|
|
19364
19733
|
}
|
|
19365
19734
|
async provisionPluginPublicSchema(opts) {
|
|
@@ -19386,7 +19755,10 @@ class DatabaseAdapter {
|
|
|
19386
19755
|
if (!this.#skipVerify)
|
|
19387
19756
|
await this.verifyRoles();
|
|
19388
19757
|
await this.#transaction("admin", "provisionPluginAdminSchema", async (tx) => {
|
|
19389
|
-
const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema",
|
|
19758
|
+
const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", {
|
|
19759
|
+
...opts,
|
|
19760
|
+
journal: true
|
|
19761
|
+
});
|
|
19390
19762
|
if (outcome.droppedColumns.length > 0) {
|
|
19391
19763
|
await auditDroppedColumns(tx, "cms_admin", opts.pluginId, outcome);
|
|
19392
19764
|
}
|
|
@@ -19419,11 +19791,11 @@ class DatabaseAdapter {
|
|
|
19419
19791
|
}
|
|
19420
19792
|
get admin() {
|
|
19421
19793
|
const db = this.#adminDb("admin pool");
|
|
19422
|
-
this.#guard.
|
|
19794
|
+
this.#guard.assertNoHeldTransaction("admin", "adapter.admin");
|
|
19423
19795
|
return db;
|
|
19424
19796
|
}
|
|
19425
19797
|
get public() {
|
|
19426
|
-
this.#guard.
|
|
19798
|
+
this.#guard.assertNoHeldTransaction("public", "adapter.public");
|
|
19427
19799
|
return this.#public;
|
|
19428
19800
|
}
|
|
19429
19801
|
}
|
|
@@ -19437,6 +19809,39 @@ async function auditDroppedColumns(tx, database, pluginId, outcome) {
|
|
|
19437
19809
|
resultSummary: `${database}.${outcome.schemaName}: dropped ${outcome.droppedColumns.join(", ")}`
|
|
19438
19810
|
});
|
|
19439
19811
|
}
|
|
19812
|
+
function thrownToQueryError(operation, thrown, access) {
|
|
19813
|
+
const raw = thrown instanceof Error ? thrown.message : String(thrown);
|
|
19814
|
+
if (isRlsDenial(thrown)) {
|
|
19815
|
+
console.warn(`[query-api] ${operation}: RLS denied the ${access}: ${raw}`);
|
|
19816
|
+
return { kind: "RLSDenied", operation, detail: rlsDenialMessage(operation, access) };
|
|
19817
|
+
}
|
|
19818
|
+
const pgDetail = extractPgFields(thrown);
|
|
19819
|
+
if (pgDetail?.code) {
|
|
19820
|
+
const described = describePgFailure(pgDetail);
|
|
19821
|
+
if (described.internal)
|
|
19822
|
+
console.error(`[query-api] ${operation} failed: ${raw}`, pgDetail);
|
|
19823
|
+
return {
|
|
19824
|
+
kind: "HandlerError",
|
|
19825
|
+
operation,
|
|
19826
|
+
message: described.message,
|
|
19827
|
+
pgDetail,
|
|
19828
|
+
...described.internal ? { internal: true } : {}
|
|
19829
|
+
};
|
|
19830
|
+
}
|
|
19831
|
+
return {
|
|
19832
|
+
kind: "HandlerError",
|
|
19833
|
+
operation,
|
|
19834
|
+
message: raw,
|
|
19835
|
+
internal: true,
|
|
19836
|
+
...pgDetail ? { pgDetail } : {}
|
|
19837
|
+
};
|
|
19838
|
+
}
|
|
19839
|
+
// ../query-api/src/change-journal.ts
|
|
19840
|
+
function textArray(values) {
|
|
19841
|
+
return sql`ARRAY(SELECT jsonb_array_elements_text((${JSON.stringify(values)}::text)::jsonb))`;
|
|
19842
|
+
}
|
|
19843
|
+
var JOURNAL_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
19844
|
+
var JOURNAL_STALLED_CONSUMER_MS = 7 * 24 * 60 * 60 * 1000;
|
|
19440
19845
|
// ../query-api/src/execute.ts
|
|
19441
19846
|
var allowAllRateLimiter = {
|
|
19442
19847
|
async check() {
|
|
@@ -19472,8 +19877,15 @@ async function execute(registry, adapter, ctx, name, rawInput, options = {}) {
|
|
|
19472
19877
|
adapter,
|
|
19473
19878
|
...options.signal ? { signal: options.signal } : {}
|
|
19474
19879
|
});
|
|
19475
|
-
if (result.ok)
|
|
19880
|
+
if (result.ok) {
|
|
19476
19881
|
notifyOperationCommitted(op.name);
|
|
19882
|
+
const notice = commitNoticeOf(op.name);
|
|
19883
|
+
if (notice !== null) {
|
|
19884
|
+
await adapter.notify(notice.channel, notice.payload).catch((thrown) => {
|
|
19885
|
+
console.error(`[query-api] could not notify other instances of ${op.name}`, thrown);
|
|
19886
|
+
});
|
|
19887
|
+
}
|
|
19888
|
+
}
|
|
19477
19889
|
return result;
|
|
19478
19890
|
}
|
|
19479
19891
|
return adapter.runOperation(op, ctx, parsed.data);
|
|
@@ -19537,6 +19949,7 @@ var claimOutput = object({
|
|
|
19537
19949
|
});
|
|
19538
19950
|
var jobClaimOp = defineOperation({
|
|
19539
19951
|
name: "platform_updates.job_claim",
|
|
19952
|
+
readScope: "request",
|
|
19540
19953
|
actorScope: ["system"],
|
|
19541
19954
|
database: "cms_admin",
|
|
19542
19955
|
input: object({
|
|
@@ -19637,6 +20050,7 @@ var jobClaimOp = defineOperation({
|
|
|
19637
20050
|
});
|
|
19638
20051
|
var jobHandoverOp = defineOperation({
|
|
19639
20052
|
name: "platform_updates.job_handover",
|
|
20053
|
+
readScope: "request",
|
|
19640
20054
|
actorScope: ["system"],
|
|
19641
20055
|
database: "cms_admin",
|
|
19642
20056
|
input: object({
|
|
@@ -19667,6 +20081,7 @@ var jobHandoverOp = defineOperation({
|
|
|
19667
20081
|
});
|
|
19668
20082
|
var jobEventOp = defineOperation({
|
|
19669
20083
|
name: "platform_updates.job_event",
|
|
20084
|
+
readScope: "request",
|
|
19670
20085
|
actorScope: ["system"],
|
|
19671
20086
|
database: "cms_admin",
|
|
19672
20087
|
input: object({
|
|
@@ -19687,6 +20102,7 @@ var jobEventOp = defineOperation({
|
|
|
19687
20102
|
var digest2 = string2().regex(/^sha256:[0-9a-f]{64}$/);
|
|
19688
20103
|
var jobRecordOp = defineOperation({
|
|
19689
20104
|
name: "platform_updates.job_record",
|
|
20105
|
+
readScope: "request",
|
|
19690
20106
|
actorScope: ["system"],
|
|
19691
20107
|
database: "cms_admin",
|
|
19692
20108
|
input: object({
|
|
@@ -19722,6 +20138,7 @@ var jobRecordOp = defineOperation({
|
|
|
19722
20138
|
});
|
|
19723
20139
|
var jobFinishOp = defineOperation({
|
|
19724
20140
|
name: "platform_updates.job_finish",
|
|
20141
|
+
readScope: "request",
|
|
19725
20142
|
actorScope: ["system"],
|
|
19726
20143
|
database: "cms_admin",
|
|
19727
20144
|
input: object({
|
|
@@ -19733,7 +20150,7 @@ var jobFinishOp = defineOperation({
|
|
|
19733
20150
|
}).strict(),
|
|
19734
20151
|
output: object({ finished: boolean2() }),
|
|
19735
20152
|
handler: async (ctx, input, tx) => {
|
|
19736
|
-
const codes =
|
|
20153
|
+
const codes = textArray(input.codes);
|
|
19737
20154
|
const rows = await tx.execute(sql`
|
|
19738
20155
|
UPDATE platform_update_requests
|
|
19739
20156
|
SET status = ${input.status}, status_reason = ${input.reason},
|
|
@@ -19763,6 +20180,7 @@ var jobFinishOp = defineOperation({
|
|
|
19763
20180
|
});
|
|
19764
20181
|
var agentHeartbeatOp = defineOperation({
|
|
19765
20182
|
name: "platform_updates.agent_heartbeat",
|
|
20183
|
+
readScope: "request",
|
|
19766
20184
|
actorScope: ["system"],
|
|
19767
20185
|
database: "cms_admin",
|
|
19768
20186
|
input: object({
|