@caelo-cms/provisioning 0.11.5 → 0.11.7
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 +575 -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/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/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);
|
|
@@ -9214,6 +9358,36 @@ var revertPageInput = object({
|
|
|
9214
9358
|
pageId: string2().uuid(),
|
|
9215
9359
|
snapshotId: string2().uuid()
|
|
9216
9360
|
}).strict();
|
|
9361
|
+
// ../shared/src/stage-status.ts
|
|
9362
|
+
var stagePhaseSchema = _enum([
|
|
9363
|
+
"queued",
|
|
9364
|
+
"preparing",
|
|
9365
|
+
"building",
|
|
9366
|
+
"publishing",
|
|
9367
|
+
"succeeded",
|
|
9368
|
+
"failed"
|
|
9369
|
+
]);
|
|
9370
|
+
var stageEtaBasisSchema = _enum(["this_run_rate", "last_successful_run"]);
|
|
9371
|
+
var stageEtaSchema = object({
|
|
9372
|
+
remainingMs: number2().int().nonnegative(),
|
|
9373
|
+
basis: stageEtaBasisSchema,
|
|
9374
|
+
includesPublish: boolean2()
|
|
9375
|
+
});
|
|
9376
|
+
var stageStatusRunSchema = object({
|
|
9377
|
+
runId: string2(),
|
|
9378
|
+
targetName: string2(),
|
|
9379
|
+
status: _enum(["queued", "running", "succeeded", "failed"]),
|
|
9380
|
+
phase: stagePhaseSchema,
|
|
9381
|
+
pagesDone: number2().int().nonnegative().nullable(),
|
|
9382
|
+
pagesTotal: number2().int().nonnegative().nullable(),
|
|
9383
|
+
startedAt: string2(),
|
|
9384
|
+
finishedAt: string2().nullable(),
|
|
9385
|
+
elapsedMs: number2().int().nonnegative(),
|
|
9386
|
+
error: string2().nullable(),
|
|
9387
|
+
failedPhase: string2().nullable(),
|
|
9388
|
+
eta: stageEtaSchema.nullable(),
|
|
9389
|
+
etaUnavailable: string2().nullable()
|
|
9390
|
+
});
|
|
9217
9391
|
// ../shared/src/staging-gate.ts
|
|
9218
9392
|
var STAGING_SESSION_TTL_SECONDS = 12 * 60 * 60;
|
|
9219
9393
|
var ENCODER = new TextEncoder;
|
|
@@ -18665,7 +18839,8 @@ function extractPgFields(error) {
|
|
|
18665
18839
|
let current = error;
|
|
18666
18840
|
for (let depth = 0;depth < 5 && typeof current === "object" && current !== null; depth++) {
|
|
18667
18841
|
const e = current;
|
|
18668
|
-
const
|
|
18842
|
+
const sqlstate = typeof e.errno === "string" && /^[0-9A-Z]{5}$/.test(e.errno) ? e.errno : undefined;
|
|
18843
|
+
const code = sqlstate ?? (typeof e.code === "string" ? e.code : undefined);
|
|
18669
18844
|
const constraint = typeof e.constraint === "string" ? e.constraint : undefined;
|
|
18670
18845
|
const table = typeof e.table === "string" ? e.table : undefined;
|
|
18671
18846
|
const column = typeof e.column === "string" ? e.column : undefined;
|
|
@@ -18682,6 +18857,11 @@ function extractPgFields(error) {
|
|
|
18682
18857
|
out.column = column;
|
|
18683
18858
|
if (detail)
|
|
18684
18859
|
out.detail = detail;
|
|
18860
|
+
const message = typeof e.message === "string" ? e.message : "";
|
|
18861
|
+
if (code === "57014" && message.includes("statement timeout"))
|
|
18862
|
+
out.timeout = "statement";
|
|
18863
|
+
if (code === "55P03" && message.includes("lock timeout"))
|
|
18864
|
+
out.timeout = "lock";
|
|
18685
18865
|
return out;
|
|
18686
18866
|
}
|
|
18687
18867
|
current = e.cause;
|
|
@@ -18737,6 +18917,88 @@ function defineOperation(def) {
|
|
|
18737
18917
|
};
|
|
18738
18918
|
}
|
|
18739
18919
|
|
|
18920
|
+
// ../query-api/src/pg-failure.ts
|
|
18921
|
+
var DATABASE_STATEMENT_TIMEOUT_SECONDS = 60;
|
|
18922
|
+
var DATABASE_LOCK_TIMEOUT_SECONDS = 15;
|
|
18923
|
+
function parseKey(detail) {
|
|
18924
|
+
if (!detail)
|
|
18925
|
+
return null;
|
|
18926
|
+
const start = detail.indexOf("Key (");
|
|
18927
|
+
if (start === -1)
|
|
18928
|
+
return null;
|
|
18929
|
+
const sep = detail.indexOf(")=(", start + 5);
|
|
18930
|
+
const end = detail.lastIndexOf(")");
|
|
18931
|
+
if (sep === -1 || end <= sep + 2)
|
|
18932
|
+
return null;
|
|
18933
|
+
const columns = detail.slice(start + 5, sep);
|
|
18934
|
+
if (columns.includes(")"))
|
|
18935
|
+
return null;
|
|
18936
|
+
return { columns, values: detail.slice(sep + 3, end) };
|
|
18937
|
+
}
|
|
18938
|
+
function keyPhrase(key) {
|
|
18939
|
+
return key.columns.includes(",") ? `the combination \`${key.columns}\` = (${key.values})` : `\`${key.columns}\` "${key.values}"`;
|
|
18940
|
+
}
|
|
18941
|
+
function describePgFailure(fields) {
|
|
18942
|
+
const key = parseKey(fields.detail);
|
|
18943
|
+
switch (fields.code) {
|
|
18944
|
+
case "23505":
|
|
18945
|
+
return {
|
|
18946
|
+
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",
|
|
18947
|
+
internal: false
|
|
18948
|
+
};
|
|
18949
|
+
case "23503":
|
|
18950
|
+
if (fields.detail?.includes("still referenced")) {
|
|
18951
|
+
return {
|
|
18952
|
+
message: "the row is still referenced by other content \u2014 remove those references first",
|
|
18953
|
+
internal: false
|
|
18954
|
+
};
|
|
18955
|
+
}
|
|
18956
|
+
return {
|
|
18957
|
+
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",
|
|
18958
|
+
internal: false
|
|
18959
|
+
};
|
|
18960
|
+
case "23502":
|
|
18961
|
+
return {
|
|
18962
|
+
message: fields.column ? `\`${fields.column}\` is required but was empty` : "a required value was empty",
|
|
18963
|
+
internal: false
|
|
18964
|
+
};
|
|
18965
|
+
case "23514":
|
|
18966
|
+
return { message: "a value is outside the allowed range or set", internal: false };
|
|
18967
|
+
case "22P02":
|
|
18968
|
+
return {
|
|
18969
|
+
message: "a value has the wrong format (e.g. an id that is not a UUID) \u2014 copy ids exactly",
|
|
18970
|
+
internal: false
|
|
18971
|
+
};
|
|
18972
|
+
case "22001":
|
|
18973
|
+
return { message: "a text value is longer than allowed \u2014 shorten it", internal: false };
|
|
18974
|
+
case "22003":
|
|
18975
|
+
return { message: "a number is out of range", internal: false };
|
|
18976
|
+
case "57014":
|
|
18977
|
+
if (fields.timeout === "statement") {
|
|
18978
|
+
return {
|
|
18979
|
+
message: `timed out after ${DATABASE_STATEMENT_TIMEOUT_SECONDS} s (the database's statement timeout) \u2014 a defect in Caelo, reported automatically`,
|
|
18980
|
+
internal: true
|
|
18981
|
+
};
|
|
18982
|
+
}
|
|
18983
|
+
break;
|
|
18984
|
+
case "55P03":
|
|
18985
|
+
if (fields.timeout === "lock") {
|
|
18986
|
+
return {
|
|
18987
|
+
message: `waited ${DATABASE_LOCK_TIMEOUT_SECONDS} s for a row another change holds \u2014 try the call again once that change finished`,
|
|
18988
|
+
internal: false
|
|
18989
|
+
};
|
|
18990
|
+
}
|
|
18991
|
+
break;
|
|
18992
|
+
}
|
|
18993
|
+
return {
|
|
18994
|
+
message: "a database error the call cannot fix \u2014 a defect in Caelo, reported automatically",
|
|
18995
|
+
internal: true
|
|
18996
|
+
};
|
|
18997
|
+
}
|
|
18998
|
+
function rlsDenialMessage(operation, access) {
|
|
18999
|
+
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.";
|
|
19000
|
+
}
|
|
19001
|
+
|
|
18740
19002
|
// ../query-api/src/plugin-schema-provision.ts
|
|
18741
19003
|
import { createHash } from "crypto";
|
|
18742
19004
|
var DROPPED_COLUMNS_SETTING = "caelo.plugin_dropped_columns";
|
|
@@ -18763,6 +19025,9 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
|
|
|
18763
19025
|
return { schemaName, droppedColumns: [] };
|
|
18764
19026
|
await tx.execute(sql.raw(opts.sql));
|
|
18765
19027
|
const dropped = await tx.execute(sql.raw(`SELECT coalesce(current_setting('${DROPPED_COLUMNS_SETTING}', true), '') AS v`));
|
|
19028
|
+
if (opts.journal === true) {
|
|
19029
|
+
await tx.execute(sql`SELECT caelo_journal_attach_plugin_schema(${schemaName}, ${opts.pluginId}::uuid)`);
|
|
19030
|
+
}
|
|
18766
19031
|
await tx.execute(sql`
|
|
18767
19032
|
INSERT INTO plugin_schema_provisions (schema_name, sql_digest)
|
|
18768
19033
|
VALUES (${schemaName}, ${digest})
|
|
@@ -18778,18 +19043,25 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
|
|
|
18778
19043
|
// ../query-api/src/pool-guard.ts
|
|
18779
19044
|
import { AsyncLocalStorage } from "async_hooks";
|
|
18780
19045
|
var POOL_ACQUIRE_TIMEOUT_MS = 1e4;
|
|
18781
|
-
var NESTED_CHECKOUTS = [
|
|
18782
|
-
{ holder: "plugins.render_hook", issue: 1220 }
|
|
18783
|
-
];
|
|
18784
19046
|
var LONG_TRANSACTIONS = [
|
|
18785
19047
|
{
|
|
18786
19048
|
holder: "static-generator.build",
|
|
18787
19049
|
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
19050
|
}
|
|
18789
19051
|
];
|
|
18790
|
-
var NESTED_CHECKOUT_ALLOWLIST = new Map(NESTED_CHECKOUTS.map((entry) => [entry.holder, entry]));
|
|
18791
19052
|
var LONG_TRANSACTION_ALLOWLIST = new Map(LONG_TRANSACTIONS.map((entry) => [entry.holder, entry]));
|
|
18792
19053
|
|
|
19054
|
+
class SerialQueue {
|
|
19055
|
+
#tail = Promise.resolve();
|
|
19056
|
+
run(fn) {
|
|
19057
|
+
const next = this.#tail.then(fn, fn);
|
|
19058
|
+
this.#tail = next.catch(() => {
|
|
19059
|
+
return;
|
|
19060
|
+
});
|
|
19061
|
+
return next;
|
|
19062
|
+
}
|
|
19063
|
+
}
|
|
19064
|
+
|
|
18793
19065
|
class NestedPoolCheckoutError extends Error {
|
|
18794
19066
|
pool;
|
|
18795
19067
|
holder;
|
|
@@ -18815,24 +19087,55 @@ class PoolAcquireTimeoutError extends Error {
|
|
|
18815
19087
|
this.timeoutMs = timeoutMs;
|
|
18816
19088
|
}
|
|
18817
19089
|
}
|
|
18818
|
-
var
|
|
19090
|
+
var joining = new AsyncLocalStorage;
|
|
19091
|
+
function newMarker(holder, root) {
|
|
19092
|
+
return {
|
|
19093
|
+
holder,
|
|
19094
|
+
active: true,
|
|
19095
|
+
tx: null,
|
|
19096
|
+
identity: null,
|
|
19097
|
+
identityOpName: null,
|
|
19098
|
+
root,
|
|
19099
|
+
joined: new SerialQueue,
|
|
19100
|
+
afterEnd: []
|
|
19101
|
+
};
|
|
19102
|
+
}
|
|
19103
|
+
|
|
18819
19104
|
class PoolGuard {
|
|
18820
19105
|
#stores = {
|
|
18821
19106
|
admin: new AsyncLocalStorage,
|
|
18822
19107
|
public: new AsyncLocalStorage
|
|
18823
19108
|
};
|
|
18824
|
-
|
|
18825
|
-
const
|
|
18826
|
-
|
|
19109
|
+
plan(pool, holder) {
|
|
19110
|
+
const outer = this.held(pool);
|
|
19111
|
+
if (outer === null)
|
|
19112
|
+
return { kind: "own", marker: newMarker(holder, null) };
|
|
19113
|
+
this.assertMayCheckout(pool, holder);
|
|
19114
|
+
return { kind: "join", outer, marker: newMarker(holder, outer.root ?? outer) };
|
|
19115
|
+
}
|
|
19116
|
+
held(pool) {
|
|
19117
|
+
const outer = this.#stores[pool].getStore();
|
|
19118
|
+
return outer?.active ? outer : null;
|
|
18827
19119
|
}
|
|
18828
19120
|
assertMayCheckout(pool, requester) {
|
|
18829
|
-
const outer = this
|
|
18830
|
-
if (outer ===
|
|
18831
|
-
return
|
|
18832
|
-
|
|
18833
|
-
|
|
19121
|
+
const outer = this.held(pool);
|
|
19122
|
+
if (outer === null || joining.getStore() === true)
|
|
19123
|
+
return;
|
|
19124
|
+
throw new NestedPoolCheckoutError(pool, outer.holder, requester);
|
|
19125
|
+
}
|
|
19126
|
+
assertNoHeldTransaction(pool, requester) {
|
|
19127
|
+
const outer = this.held(pool);
|
|
19128
|
+
if (outer !== null)
|
|
18834
19129
|
throw new NestedPoolCheckoutError(pool, outer.holder, requester);
|
|
18835
|
-
|
|
19130
|
+
}
|
|
19131
|
+
async deferUntilEnd(pool, work) {
|
|
19132
|
+
const outer = this.held(pool);
|
|
19133
|
+
if (outer === null) {
|
|
19134
|
+
await work();
|
|
19135
|
+
return false;
|
|
19136
|
+
}
|
|
19137
|
+
(outer.root ?? outer).afterEnd.push(work);
|
|
19138
|
+
return true;
|
|
18836
19139
|
}
|
|
18837
19140
|
async run(pool, marker, fn) {
|
|
18838
19141
|
try {
|
|
@@ -18870,6 +19173,15 @@ function collectPostCommitEffects(tx) {
|
|
|
18870
19173
|
pendingByTx.set(tx, pending);
|
|
18871
19174
|
return pending;
|
|
18872
19175
|
}
|
|
19176
|
+
function mergeSavepointEffects(effects, parentTx) {
|
|
19177
|
+
if (effects.length === 0)
|
|
19178
|
+
return;
|
|
19179
|
+
const pending = pendingByTx.get(parentTx);
|
|
19180
|
+
if (pending === undefined) {
|
|
19181
|
+
throw new Error(`afterCommit(${effects[0]?.label}): the transaction this call joined collects no post-commit effects`);
|
|
19182
|
+
}
|
|
19183
|
+
pending.push(...effects);
|
|
19184
|
+
}
|
|
18873
19185
|
async function runPostCommitEffects(effects) {
|
|
18874
19186
|
const failures = [];
|
|
18875
19187
|
for (const effect of effects) {
|
|
@@ -18894,6 +19206,10 @@ function notifyOperationCommitted(operationName) {
|
|
|
18894
19206
|
}
|
|
18895
19207
|
}
|
|
18896
19208
|
}
|
|
19209
|
+
var commitNotice = null;
|
|
19210
|
+
function commitNoticeOf(operationName) {
|
|
19211
|
+
return commitNotice === null ? null : commitNotice(operationName);
|
|
19212
|
+
}
|
|
18897
19213
|
|
|
18898
19214
|
// ../query-api/src/adapter.ts
|
|
18899
19215
|
var SQL3 = globalThis.Bun?.SQL;
|
|
@@ -18932,13 +19248,21 @@ class AdminDatabaseUnavailableError extends Error {
|
|
|
18932
19248
|
function resolvePoolMax(explicit) {
|
|
18933
19249
|
if (explicit !== undefined)
|
|
18934
19250
|
return validatePoolMax(explicit, "poolMax option");
|
|
18935
|
-
const raw = process.env
|
|
19251
|
+
const raw = process.env[DB_POOL_MAX_ENV];
|
|
18936
19252
|
if (raw === undefined || raw === "")
|
|
18937
19253
|
return;
|
|
18938
|
-
return validatePoolMax(Number(raw),
|
|
19254
|
+
return validatePoolMax(Number(raw), DB_POOL_MAX_ENV);
|
|
19255
|
+
}
|
|
19256
|
+
function resolvePublicPoolMax(explicitPublic, explicitAll) {
|
|
19257
|
+
if (explicitPublic !== undefined)
|
|
19258
|
+
return validatePoolMax(explicitPublic, "publicPoolMax option");
|
|
19259
|
+
const raw = process.env[DB_PUBLIC_POOL_MAX_ENV];
|
|
19260
|
+
if (raw !== undefined && raw !== "")
|
|
19261
|
+
return validatePoolMax(Number(raw), DB_PUBLIC_POOL_MAX_ENV);
|
|
19262
|
+
return resolvePoolMax(explicitAll);
|
|
18939
19263
|
}
|
|
18940
19264
|
function validatePoolMax(value, source) {
|
|
18941
|
-
if (!Number.isInteger(value) || value <
|
|
19265
|
+
if (!Number.isInteger(value) || value < MIN_POOL_MAX) {
|
|
18942
19266
|
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
19267
|
}
|
|
18944
19268
|
return value;
|
|
@@ -18962,12 +19286,18 @@ function postCommitFailureMessage(failures) {
|
|
|
18962
19286
|
const list = failures.map((f) => `${f.effect.label} (${f.message})`).join("; ");
|
|
18963
19287
|
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
19288
|
}
|
|
19289
|
+
var LISTEN_CONNECTIONS_PER_INSTANCE = 1;
|
|
18965
19290
|
|
|
18966
19291
|
class DatabaseAdapter {
|
|
18967
19292
|
#admin;
|
|
18968
19293
|
#public;
|
|
18969
19294
|
#adminRaw;
|
|
18970
19295
|
#publicRaw;
|
|
19296
|
+
#adminUrl;
|
|
19297
|
+
#publicUrl;
|
|
19298
|
+
#listeners = new Map;
|
|
19299
|
+
#listenLost = new Map;
|
|
19300
|
+
#listenersClosing = false;
|
|
18971
19301
|
#skipVerify;
|
|
18972
19302
|
#roles;
|
|
18973
19303
|
#acquireTimeoutMs;
|
|
@@ -18983,10 +19313,13 @@ class DatabaseAdapter {
|
|
|
18983
19313
|
}
|
|
18984
19314
|
assertPoolSafeBun(globalThis.Bun?.version ?? "0");
|
|
18985
19315
|
const poolMax = resolvePoolMax(config.poolMax);
|
|
19316
|
+
const publicPoolMax = resolvePublicPoolMax(config.publicPoolMax, config.poolMax);
|
|
18986
19317
|
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);
|
|
19318
|
+
const open = (url, max) => new SQL3(url, max === undefined ? { idleTimeout } : { max, idleTimeout });
|
|
19319
|
+
this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl, poolMax);
|
|
19320
|
+
this.#publicRaw = open(config.publicDatabaseUrl, publicPoolMax);
|
|
19321
|
+
this.#adminUrl = config.adminDatabaseUrl;
|
|
19322
|
+
this.#publicUrl = config.publicDatabaseUrl;
|
|
18990
19323
|
this.#onStatement = config.onStatement ?? null;
|
|
18991
19324
|
const observe = this.#onStatement;
|
|
18992
19325
|
this.#drizzleOptions = observe ? { logger: { logQuery: (query) => observe(query) } } : {};
|
|
@@ -18999,22 +19332,64 @@ class DatabaseAdapter {
|
|
|
18999
19332
|
const raw = pool === "admin" ? this.#adminRaw : this.#publicRaw;
|
|
19000
19333
|
if (raw === null)
|
|
19001
19334
|
throw new AdminDatabaseUnavailableError(holder);
|
|
19002
|
-
const
|
|
19335
|
+
const plan = this.#guard.plan(pool, holder);
|
|
19336
|
+
if (plan.kind === "join")
|
|
19337
|
+
return this.#joinHeld(pool, plan.outer, plan.marker, fn);
|
|
19338
|
+
const marker = plan.marker;
|
|
19003
19339
|
const reserved = await this.#reserve(pool, raw, holder);
|
|
19004
19340
|
try {
|
|
19005
19341
|
const db = drizzle({ client: reserved, ...this.#drizzleOptions });
|
|
19006
19342
|
this.#onStatement?.("BEGIN");
|
|
19007
19343
|
const out = await db.transaction((tx) => this.#guard.run(pool, marker, async () => {
|
|
19344
|
+
marker.tx = tx;
|
|
19008
19345
|
if (exemptFromSessionTimeouts(holder))
|
|
19009
19346
|
await liftSessionTimeouts(tx);
|
|
19010
|
-
return fn(tx);
|
|
19347
|
+
return fn(tx, false);
|
|
19011
19348
|
}));
|
|
19012
19349
|
this.#onStatement?.("COMMIT");
|
|
19013
19350
|
return out;
|
|
19014
19351
|
} finally {
|
|
19015
19352
|
reserved.release();
|
|
19353
|
+
await this.#runAfterEnd(marker);
|
|
19016
19354
|
}
|
|
19017
19355
|
}
|
|
19356
|
+
#joinHeld(pool, outer, marker, fn) {
|
|
19357
|
+
return outer.joined.run(async () => {
|
|
19358
|
+
const outerTx = outer.tx;
|
|
19359
|
+
if (outerTx === null || !outer.active) {
|
|
19360
|
+
throw new NestedPoolCheckoutError(pool, outer.holder, marker.holder);
|
|
19361
|
+
}
|
|
19362
|
+
let effects = [];
|
|
19363
|
+
const out = await outerTx.transaction((savepoint) => this.#guard.run(pool, marker, async () => {
|
|
19364
|
+
marker.tx = savepoint;
|
|
19365
|
+
effects = collectPostCommitEffects(savepoint);
|
|
19366
|
+
return fn(savepoint, true);
|
|
19367
|
+
}));
|
|
19368
|
+
mergeSavepointEffects(effects, outerTx);
|
|
19369
|
+
if (outer.identity !== null) {
|
|
19370
|
+
await this.#setIdentity(outerTx, outer.identity, outer.identityOpName);
|
|
19371
|
+
}
|
|
19372
|
+
return out;
|
|
19373
|
+
});
|
|
19374
|
+
}
|
|
19375
|
+
async#runAfterEnd(marker) {
|
|
19376
|
+
for (const work of marker.afterEnd.splice(0)) {
|
|
19377
|
+
try {
|
|
19378
|
+
await work();
|
|
19379
|
+
} catch (thrown) {
|
|
19380
|
+
console.error("[query-api] work deferred until a transaction ended failed", {
|
|
19381
|
+
holder: marker.holder,
|
|
19382
|
+
error: thrown instanceof Error ? thrown.message : String(thrown)
|
|
19383
|
+
});
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19386
|
+
}
|
|
19387
|
+
deferUntilHeldTransactionEnds(work) {
|
|
19388
|
+
return this.#guard.deferUntilEnd("admin", work);
|
|
19389
|
+
}
|
|
19390
|
+
holdsTransaction(pool) {
|
|
19391
|
+
return this.#guard.held(pool) !== null;
|
|
19392
|
+
}
|
|
19018
19393
|
async#reserve(pool, raw, requester) {
|
|
19019
19394
|
const deadline = new AbortController;
|
|
19020
19395
|
const timer = setTimeout(() => deadline.abort(), this.#acquireTimeoutMs);
|
|
@@ -19129,10 +19504,13 @@ class DatabaseAdapter {
|
|
|
19129
19504
|
let postCommit = [];
|
|
19130
19505
|
let result;
|
|
19131
19506
|
let handlerError = null;
|
|
19507
|
+
let joinedHolder = false;
|
|
19132
19508
|
try {
|
|
19133
|
-
result = await this.#transaction(pool, op.name, async (tx) => {
|
|
19134
|
-
|
|
19135
|
-
|
|
19509
|
+
result = await this.#transaction(pool, op.name, async (tx, joined) => {
|
|
19510
|
+
joinedHolder = joined;
|
|
19511
|
+
if (!joined)
|
|
19512
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19513
|
+
await this.#setIdentity(tx, ctx, op.name);
|
|
19136
19514
|
if (op.database === "cms_admin") {
|
|
19137
19515
|
const denied = await firstInvisibleBranch(tx, namedBranches(ctx, validatedInput));
|
|
19138
19516
|
if (denied !== null)
|
|
@@ -19142,7 +19520,12 @@ class DatabaseAdapter {
|
|
|
19142
19520
|
if (!out.ok)
|
|
19143
19521
|
handlerError = out;
|
|
19144
19522
|
if (out.ok && op.database === "cms_admin") {
|
|
19145
|
-
|
|
19523
|
+
if (!joined)
|
|
19524
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
19525
|
+
const notice = commitNoticeOf(op.name);
|
|
19526
|
+
if (notice !== null) {
|
|
19527
|
+
await tx.execute(sql`SELECT pg_notify(${notice.channel}, ${notice.payload})`);
|
|
19528
|
+
}
|
|
19146
19529
|
}
|
|
19147
19530
|
return out;
|
|
19148
19531
|
});
|
|
@@ -19152,23 +19535,11 @@ class DatabaseAdapter {
|
|
|
19152
19535
|
if (thrown instanceof OperationAbortError) {
|
|
19153
19536
|
return err(thrown.queryError);
|
|
19154
19537
|
}
|
|
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
19538
|
const pgDetail = extractPgFields(thrown);
|
|
19163
19539
|
if (pgDetail?.code === COMMIT_ROLLED_BACK && handlerError !== null) {
|
|
19164
19540
|
return handlerError;
|
|
19165
19541
|
}
|
|
19166
|
-
return err(
|
|
19167
|
-
kind: "HandlerError",
|
|
19168
|
-
operation: op.name,
|
|
19169
|
-
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
19170
|
-
...pgDetail ? { pgDetail } : {}
|
|
19171
|
-
});
|
|
19542
|
+
return err(thrownToQueryError(op.name, thrown, "write"));
|
|
19172
19543
|
}
|
|
19173
19544
|
const effectFailures = await this.#settlePostCommit(op.name, ctx, postCommit);
|
|
19174
19545
|
let publishFailure = null;
|
|
@@ -19179,7 +19550,7 @@ class DatabaseAdapter {
|
|
|
19179
19550
|
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
19551
|
}
|
|
19181
19552
|
}
|
|
19182
|
-
if (result.ok)
|
|
19553
|
+
if (result.ok && !joinedHolder)
|
|
19183
19554
|
notifyOperationCommitted(op.name);
|
|
19184
19555
|
if (effectFailures.length > 0) {
|
|
19185
19556
|
const effects = postCommitFailureMessage(effectFailures);
|
|
@@ -19213,22 +19584,77 @@ class DatabaseAdapter {
|
|
|
19213
19584
|
throw thrown;
|
|
19214
19585
|
if (thrown instanceof OperationAbortError)
|
|
19215
19586
|
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
|
-
});
|
|
19587
|
+
return err(thrownToQueryError(op.name, thrown, "write"));
|
|
19223
19588
|
}
|
|
19224
19589
|
}
|
|
19225
|
-
async#setIdentity(tx, ctx) {
|
|
19590
|
+
async#setIdentity(tx, ctx, opName = null) {
|
|
19226
19591
|
await tx.execute(sql`SELECT
|
|
19227
19592
|
set_config('caelo.actor_id', ${ctx.actorId}, true),
|
|
19228
19593
|
set_config('caelo.actor_kind', ${ctx.actorKind}, true),
|
|
19229
19594
|
set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true),
|
|
19230
19595
|
set_config('caelo.chat_branch_id', ${ctx.chatBranchId ?? ""}, true),
|
|
19231
|
-
set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true)
|
|
19596
|
+
set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true),
|
|
19597
|
+
set_config('caelo.op_name', ${opName ?? ""}, true)`);
|
|
19598
|
+
this.#recordIdentity(tx, ctx, opName);
|
|
19599
|
+
}
|
|
19600
|
+
async#setPublicIdentity(tx, ctx) {
|
|
19601
|
+
await tx.execute(sql`SELECT
|
|
19602
|
+
set_config('caelo.actor_id', ${ctx.actorId}, true),
|
|
19603
|
+
set_config('caelo.actor_kind', ${ctx.actorKind}, true),
|
|
19604
|
+
set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
|
|
19605
|
+
this.#recordIdentity(tx, ctx, null);
|
|
19606
|
+
}
|
|
19607
|
+
#recordIdentity(tx, ctx, opName) {
|
|
19608
|
+
for (const pool of ["admin", "public"]) {
|
|
19609
|
+
const marker = this.#guard.held(pool);
|
|
19610
|
+
if (marker !== null && marker.tx === tx) {
|
|
19611
|
+
marker.identity = ctx;
|
|
19612
|
+
marker.identityOpName = opName;
|
|
19613
|
+
}
|
|
19614
|
+
}
|
|
19615
|
+
}
|
|
19616
|
+
async listen(pool, channel, callbacks) {
|
|
19617
|
+
const url = pool === "admin" ? this.#adminUrl : this.#publicUrl;
|
|
19618
|
+
if (url === null)
|
|
19619
|
+
throw new AdminDatabaseUnavailableError(`listen(${channel})`);
|
|
19620
|
+
let lost = this.#listenLost.get(pool);
|
|
19621
|
+
if (!lost) {
|
|
19622
|
+
lost = new Set;
|
|
19623
|
+
this.#listenLost.set(pool, lost);
|
|
19624
|
+
}
|
|
19625
|
+
const lostSet = lost;
|
|
19626
|
+
let conn = this.#listeners.get(pool);
|
|
19627
|
+
if (!conn) {
|
|
19628
|
+
conn = new SQL3(url, {
|
|
19629
|
+
max: LISTEN_CONNECTIONS_PER_INSTANCE,
|
|
19630
|
+
onclose: (cause) => {
|
|
19631
|
+
if (this.#listenersClosing)
|
|
19632
|
+
return;
|
|
19633
|
+
for (const onLost of lostSet) {
|
|
19634
|
+
try {
|
|
19635
|
+
onLost(cause);
|
|
19636
|
+
} catch (thrown) {
|
|
19637
|
+
console.error(`[query-api] a LISTEN onLost callback failed`, thrown);
|
|
19638
|
+
}
|
|
19639
|
+
}
|
|
19640
|
+
}
|
|
19641
|
+
});
|
|
19642
|
+
this.#listeners.set(pool, conn);
|
|
19643
|
+
}
|
|
19644
|
+
const onLost = callbacks.onLost;
|
|
19645
|
+
lostSet.add(onLost);
|
|
19646
|
+
const handle = await conn.listen(channel, callbacks.onNotify, callbacks.onListen);
|
|
19647
|
+
return {
|
|
19648
|
+
unlisten: async () => {
|
|
19649
|
+
lostSet.delete(onLost);
|
|
19650
|
+
await handle.unlisten();
|
|
19651
|
+
}
|
|
19652
|
+
};
|
|
19653
|
+
}
|
|
19654
|
+
async notify(channel, payload) {
|
|
19655
|
+
await this.#transaction("admin", "notify", async (tx) => {
|
|
19656
|
+
await tx.execute(sql`SELECT pg_notify(${channel}, ${payload})`);
|
|
19657
|
+
});
|
|
19232
19658
|
}
|
|
19233
19659
|
async#runReadOperation(op, ctx, validatedInput) {
|
|
19234
19660
|
try {
|
|
@@ -19244,20 +19670,7 @@ class DatabaseAdapter {
|
|
|
19244
19670
|
throw thrown;
|
|
19245
19671
|
if (thrown instanceof OperationAbortError)
|
|
19246
19672
|
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
|
-
});
|
|
19673
|
+
return err(thrownToQueryError(op.name, thrown, "read"));
|
|
19261
19674
|
}
|
|
19262
19675
|
}
|
|
19263
19676
|
readScope(ctx, holder = "reads.read") {
|
|
@@ -19306,7 +19719,14 @@ class DatabaseAdapter {
|
|
|
19306
19719
|
return failures;
|
|
19307
19720
|
}
|
|
19308
19721
|
async close() {
|
|
19309
|
-
|
|
19722
|
+
this.#listenersClosing = true;
|
|
19723
|
+
const listeners = [...this.#listeners.values()];
|
|
19724
|
+
this.#listeners.clear();
|
|
19725
|
+
await Promise.all([
|
|
19726
|
+
this.#adminRaw?.end(),
|
|
19727
|
+
this.#publicRaw.end(),
|
|
19728
|
+
...listeners.map((l) => l.close())
|
|
19729
|
+
]);
|
|
19310
19730
|
}
|
|
19311
19731
|
async withAdminTransaction(ctx, fn, options = {}) {
|
|
19312
19732
|
const holder = options.holder ?? "withAdminTransaction";
|
|
@@ -19316,15 +19736,17 @@ class DatabaseAdapter {
|
|
|
19316
19736
|
await this.verifyRoles();
|
|
19317
19737
|
let publishAfterCommit = false;
|
|
19318
19738
|
let postCommit = [];
|
|
19319
|
-
const out = await this.#transaction("admin", holder, async (tx) => {
|
|
19320
|
-
|
|
19321
|
-
|
|
19739
|
+
const out = await this.#transaction("admin", holder, async (tx, joined) => {
|
|
19740
|
+
if (!joined)
|
|
19741
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19742
|
+
await this.#setIdentity(tx, ctx, options.holder ?? null);
|
|
19322
19743
|
const denied = await firstInvisibleBranch(tx, namedBranches(ctx, undefined));
|
|
19323
19744
|
if (denied !== null) {
|
|
19324
19745
|
throw new OperationAbortError(branchNotFound("withAdminTransaction", denied));
|
|
19325
19746
|
}
|
|
19326
19747
|
const result = await fn(tx);
|
|
19327
|
-
|
|
19748
|
+
if (!joined)
|
|
19749
|
+
publishAfterCommit = await this.#projectionDirty(tx);
|
|
19328
19750
|
return result;
|
|
19329
19751
|
});
|
|
19330
19752
|
const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
|
|
@@ -19340,11 +19762,10 @@ class DatabaseAdapter {
|
|
|
19340
19762
|
if (!this.#skipVerify)
|
|
19341
19763
|
await this.verifyRoles();
|
|
19342
19764
|
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)`);
|
|
19765
|
+
const out = await this.#transaction("public", holder, async (tx, joined) => {
|
|
19766
|
+
if (!joined)
|
|
19767
|
+
postCommit = collectPostCommitEffects(tx);
|
|
19768
|
+
await this.#setPublicIdentity(tx, ctx);
|
|
19348
19769
|
return await fn(tx);
|
|
19349
19770
|
});
|
|
19350
19771
|
const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
|
|
@@ -19355,11 +19776,11 @@ class DatabaseAdapter {
|
|
|
19355
19776
|
rawAdmin() {
|
|
19356
19777
|
if (this.#adminRaw === null)
|
|
19357
19778
|
throw new AdminDatabaseUnavailableError("rawAdmin");
|
|
19358
|
-
this.#guard.
|
|
19779
|
+
this.#guard.assertNoHeldTransaction("admin", "rawAdmin()");
|
|
19359
19780
|
return this.#adminRaw;
|
|
19360
19781
|
}
|
|
19361
19782
|
rawPublic() {
|
|
19362
|
-
this.#guard.
|
|
19783
|
+
this.#guard.assertNoHeldTransaction("public", "rawPublic()");
|
|
19363
19784
|
return this.#publicRaw;
|
|
19364
19785
|
}
|
|
19365
19786
|
async provisionPluginPublicSchema(opts) {
|
|
@@ -19386,7 +19807,10 @@ class DatabaseAdapter {
|
|
|
19386
19807
|
if (!this.#skipVerify)
|
|
19387
19808
|
await this.verifyRoles();
|
|
19388
19809
|
await this.#transaction("admin", "provisionPluginAdminSchema", async (tx) => {
|
|
19389
|
-
const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema",
|
|
19810
|
+
const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", {
|
|
19811
|
+
...opts,
|
|
19812
|
+
journal: true
|
|
19813
|
+
});
|
|
19390
19814
|
if (outcome.droppedColumns.length > 0) {
|
|
19391
19815
|
await auditDroppedColumns(tx, "cms_admin", opts.pluginId, outcome);
|
|
19392
19816
|
}
|
|
@@ -19419,11 +19843,11 @@ class DatabaseAdapter {
|
|
|
19419
19843
|
}
|
|
19420
19844
|
get admin() {
|
|
19421
19845
|
const db = this.#adminDb("admin pool");
|
|
19422
|
-
this.#guard.
|
|
19846
|
+
this.#guard.assertNoHeldTransaction("admin", "adapter.admin");
|
|
19423
19847
|
return db;
|
|
19424
19848
|
}
|
|
19425
19849
|
get public() {
|
|
19426
|
-
this.#guard.
|
|
19850
|
+
this.#guard.assertNoHeldTransaction("public", "adapter.public");
|
|
19427
19851
|
return this.#public;
|
|
19428
19852
|
}
|
|
19429
19853
|
}
|
|
@@ -19437,6 +19861,39 @@ async function auditDroppedColumns(tx, database, pluginId, outcome) {
|
|
|
19437
19861
|
resultSummary: `${database}.${outcome.schemaName}: dropped ${outcome.droppedColumns.join(", ")}`
|
|
19438
19862
|
});
|
|
19439
19863
|
}
|
|
19864
|
+
function thrownToQueryError(operation, thrown, access) {
|
|
19865
|
+
const raw = thrown instanceof Error ? thrown.message : String(thrown);
|
|
19866
|
+
if (isRlsDenial(thrown)) {
|
|
19867
|
+
console.warn(`[query-api] ${operation}: RLS denied the ${access}: ${raw}`);
|
|
19868
|
+
return { kind: "RLSDenied", operation, detail: rlsDenialMessage(operation, access) };
|
|
19869
|
+
}
|
|
19870
|
+
const pgDetail = extractPgFields(thrown);
|
|
19871
|
+
if (pgDetail?.code) {
|
|
19872
|
+
const described = describePgFailure(pgDetail);
|
|
19873
|
+
if (described.internal)
|
|
19874
|
+
console.error(`[query-api] ${operation} failed: ${raw}`, pgDetail);
|
|
19875
|
+
return {
|
|
19876
|
+
kind: "HandlerError",
|
|
19877
|
+
operation,
|
|
19878
|
+
message: described.message,
|
|
19879
|
+
pgDetail,
|
|
19880
|
+
...described.internal ? { internal: true } : {}
|
|
19881
|
+
};
|
|
19882
|
+
}
|
|
19883
|
+
return {
|
|
19884
|
+
kind: "HandlerError",
|
|
19885
|
+
operation,
|
|
19886
|
+
message: raw,
|
|
19887
|
+
internal: true,
|
|
19888
|
+
...pgDetail ? { pgDetail } : {}
|
|
19889
|
+
};
|
|
19890
|
+
}
|
|
19891
|
+
// ../query-api/src/change-journal.ts
|
|
19892
|
+
function textArray(values) {
|
|
19893
|
+
return sql`ARRAY(SELECT jsonb_array_elements_text((${JSON.stringify(values)}::text)::jsonb))`;
|
|
19894
|
+
}
|
|
19895
|
+
var JOURNAL_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
19896
|
+
var JOURNAL_STALLED_CONSUMER_MS = 7 * 24 * 60 * 60 * 1000;
|
|
19440
19897
|
// ../query-api/src/execute.ts
|
|
19441
19898
|
var allowAllRateLimiter = {
|
|
19442
19899
|
async check() {
|
|
@@ -19472,8 +19929,15 @@ async function execute(registry, adapter, ctx, name, rawInput, options = {}) {
|
|
|
19472
19929
|
adapter,
|
|
19473
19930
|
...options.signal ? { signal: options.signal } : {}
|
|
19474
19931
|
});
|
|
19475
|
-
if (result.ok)
|
|
19932
|
+
if (result.ok) {
|
|
19476
19933
|
notifyOperationCommitted(op.name);
|
|
19934
|
+
const notice = commitNoticeOf(op.name);
|
|
19935
|
+
if (notice !== null) {
|
|
19936
|
+
await adapter.notify(notice.channel, notice.payload).catch((thrown) => {
|
|
19937
|
+
console.error(`[query-api] could not notify other instances of ${op.name}`, thrown);
|
|
19938
|
+
});
|
|
19939
|
+
}
|
|
19940
|
+
}
|
|
19477
19941
|
return result;
|
|
19478
19942
|
}
|
|
19479
19943
|
return adapter.runOperation(op, ctx, parsed.data);
|
|
@@ -19537,6 +20001,7 @@ var claimOutput = object({
|
|
|
19537
20001
|
});
|
|
19538
20002
|
var jobClaimOp = defineOperation({
|
|
19539
20003
|
name: "platform_updates.job_claim",
|
|
20004
|
+
readScope: "request",
|
|
19540
20005
|
actorScope: ["system"],
|
|
19541
20006
|
database: "cms_admin",
|
|
19542
20007
|
input: object({
|
|
@@ -19637,6 +20102,7 @@ var jobClaimOp = defineOperation({
|
|
|
19637
20102
|
});
|
|
19638
20103
|
var jobHandoverOp = defineOperation({
|
|
19639
20104
|
name: "platform_updates.job_handover",
|
|
20105
|
+
readScope: "request",
|
|
19640
20106
|
actorScope: ["system"],
|
|
19641
20107
|
database: "cms_admin",
|
|
19642
20108
|
input: object({
|
|
@@ -19667,6 +20133,7 @@ var jobHandoverOp = defineOperation({
|
|
|
19667
20133
|
});
|
|
19668
20134
|
var jobEventOp = defineOperation({
|
|
19669
20135
|
name: "platform_updates.job_event",
|
|
20136
|
+
readScope: "request",
|
|
19670
20137
|
actorScope: ["system"],
|
|
19671
20138
|
database: "cms_admin",
|
|
19672
20139
|
input: object({
|
|
@@ -19687,6 +20154,7 @@ var jobEventOp = defineOperation({
|
|
|
19687
20154
|
var digest2 = string2().regex(/^sha256:[0-9a-f]{64}$/);
|
|
19688
20155
|
var jobRecordOp = defineOperation({
|
|
19689
20156
|
name: "platform_updates.job_record",
|
|
20157
|
+
readScope: "request",
|
|
19690
20158
|
actorScope: ["system"],
|
|
19691
20159
|
database: "cms_admin",
|
|
19692
20160
|
input: object({
|
|
@@ -19722,6 +20190,7 @@ var jobRecordOp = defineOperation({
|
|
|
19722
20190
|
});
|
|
19723
20191
|
var jobFinishOp = defineOperation({
|
|
19724
20192
|
name: "platform_updates.job_finish",
|
|
20193
|
+
readScope: "request",
|
|
19725
20194
|
actorScope: ["system"],
|
|
19726
20195
|
database: "cms_admin",
|
|
19727
20196
|
input: object({
|
|
@@ -19733,7 +20202,7 @@ var jobFinishOp = defineOperation({
|
|
|
19733
20202
|
}).strict(),
|
|
19734
20203
|
output: object({ finished: boolean2() }),
|
|
19735
20204
|
handler: async (ctx, input, tx) => {
|
|
19736
|
-
const codes =
|
|
20205
|
+
const codes = textArray(input.codes);
|
|
19737
20206
|
const rows = await tx.execute(sql`
|
|
19738
20207
|
UPDATE platform_update_requests
|
|
19739
20208
|
SET status = ${input.status}, status_reason = ${input.reason},
|
|
@@ -19763,6 +20232,7 @@ var jobFinishOp = defineOperation({
|
|
|
19763
20232
|
});
|
|
19764
20233
|
var agentHeartbeatOp = defineOperation({
|
|
19765
20234
|
name: "platform_updates.agent_heartbeat",
|
|
20235
|
+
readScope: "request",
|
|
19766
20236
|
actorScope: ["system"],
|
|
19767
20237
|
database: "cms_admin",
|
|
19768
20238
|
input: object({
|