@shapio/cms 0.4.0 → 0.4.1
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/admin/assets/{AssetTile-C-Mu0BRF.js → AssetTile-CoNClUYS.js} +2 -2
- package/dist/admin/assets/{AssetTile-C-Mu0BRF.js.map → AssetTile-CoNClUYS.js.map} +1 -1
- package/dist/admin/assets/{AssistError-B-bnaHhJ.js → AssistError-BoejyShH.js} +2 -2
- package/dist/admin/assets/{AssistError-B-bnaHhJ.js.map → AssistError-BoejyShH.js.map} +1 -1
- package/dist/admin/assets/{BlockMenuItems-Dn9WULjL.js → BlockMenuItems-B2Jugisa.js} +2 -2
- package/dist/admin/assets/{BlockMenuItems-Dn9WULjL.js.map → BlockMenuItems-B2Jugisa.js.map} +1 -1
- package/dist/admin/assets/{Body-C0SwduLi.js → Body-D3GwLGaB.js} +2 -2
- package/dist/admin/assets/{Body-C0SwduLi.js.map → Body-D3GwLGaB.js.map} +1 -1
- package/dist/admin/assets/Builder-CmT5-8hI.js +2 -0
- package/dist/admin/assets/Builder-CmT5-8hI.js.map +1 -0
- package/dist/admin/assets/{ConfirmDialog-BfpN2MQx.js → ConfirmDialog-HS8Gc2t3.js} +2 -2
- package/dist/admin/assets/{ConfirmDialog-BfpN2MQx.js.map → ConfirmDialog-HS8Gc2t3.js.map} +1 -1
- package/dist/admin/assets/{CopyButton-Dxbzp7hC.js → CopyButton-DS-bAA23.js} +2 -2
- package/dist/admin/assets/{CopyButton-Dxbzp7hC.js.map → CopyButton-DS-bAA23.js.map} +1 -1
- package/dist/admin/assets/{CursorPager-DkZs6rPg.js → CursorPager-C6PyD3wE.js} +2 -2
- package/dist/admin/assets/{CursorPager-DkZs6rPg.js.map → CursorPager-C6PyD3wE.js.map} +1 -1
- package/dist/admin/assets/{FocalPoint-r1MG_y-r.js → FocalPoint-C62fRhmM.js} +2 -2
- package/dist/admin/assets/{FocalPoint-r1MG_y-r.js.map → FocalPoint-C62fRhmM.js.map} +1 -1
- package/dist/admin/assets/{Form-D8vp4Kd_.js → Form-CLEr5Csd.js} +2 -2
- package/dist/admin/assets/{Form-D8vp4Kd_.js.map → Form-CLEr5Csd.js.map} +1 -1
- package/dist/admin/assets/{FormCheckboxGroup-8ObPh9cH.js → FormCheckboxGroup-BrXY6qBg.js} +2 -2
- package/dist/admin/assets/{FormCheckboxGroup-8ObPh9cH.js.map → FormCheckboxGroup-BrXY6qBg.js.map} +1 -1
- package/dist/admin/assets/{FormRadioGroup-CAB3qzVS.js → FormRadioGroup-BjE1Jhar.js} +2 -2
- package/dist/admin/assets/{FormRadioGroup-CAB3qzVS.js.map → FormRadioGroup-BjE1Jhar.js.map} +1 -1
- package/dist/admin/assets/{FormSelectField-C7hE05Wq.js → FormSelectField-g4zrUlhv.js} +2 -2
- package/dist/admin/assets/{FormSelectField-C7hE05Wq.js.map → FormSelectField-g4zrUlhv.js.map} +1 -1
- package/dist/admin/assets/{FormSwitchField-7EEYkGOH.js → FormSwitchField-gciThNGV.js} +2 -2
- package/dist/admin/assets/{FormSwitchField-7EEYkGOH.js.map → FormSwitchField-gciThNGV.js.map} +1 -1
- package/dist/admin/assets/{FormTextareaField-BasuRoXW.js → FormTextareaField-tsS-UXym.js} +2 -2
- package/dist/admin/assets/{FormTextareaField-BasuRoXW.js.map → FormTextareaField-tsS-UXym.js.map} +1 -1
- package/dist/admin/assets/{HintedLabel-BDV81kIT.js → HintedLabel-CVe-PIyv.js} +2 -2
- package/dist/admin/assets/{HintedLabel-BDV81kIT.js.map → HintedLabel-CVe-PIyv.js.map} +1 -1
- package/dist/admin/assets/{InfoHint-DWn9kLf1.js → InfoHint-DtaHU1Cg.js} +2 -2
- package/dist/admin/assets/{InfoHint-DWn9kLf1.js.map → InfoHint-DtaHU1Cg.js.map} +1 -1
- package/dist/admin/assets/{InsertMenuItem-DhJk5BeH.js → InsertMenuItem-DlzaD9zr.js} +2 -2
- package/dist/admin/assets/{InsertMenuItem-DhJk5BeH.js.map → InsertMenuItem-DlzaD9zr.js.map} +1 -1
- package/dist/admin/assets/{Match-Zu4pwfCC.js → Match-ErAxnNAY.js} +2 -2
- package/dist/admin/assets/{Match-Zu4pwfCC.js.map → Match-ErAxnNAY.js.map} +1 -1
- package/dist/admin/assets/{QueryView-Cu-KPtQI.js → QueryView-C630REML.js} +2 -2
- package/dist/admin/assets/{QueryView-Cu-KPtQI.js.map → QueryView-C630REML.js.map} +1 -1
- package/dist/admin/assets/{RadioTile-Ci0LucKI.js → RadioTile-CD4mel7U.js} +2 -2
- package/dist/admin/assets/{RadioTile-Ci0LucKI.js.map → RadioTile-CD4mel7U.js.map} +1 -1
- package/dist/admin/assets/{RichTextField-BcYOFkeW.js → RichTextField-CxeVX6C-.js} +2 -2
- package/dist/admin/assets/{RichTextField-BcYOFkeW.js.map → RichTextField-CxeVX6C-.js.map} +1 -1
- package/dist/admin/assets/{RowTitle-BdDgA5Lb.js → RowTitle-B-7RptyN.js} +2 -2
- package/dist/admin/assets/{RowTitle-BdDgA5Lb.js.map → RowTitle-B-7RptyN.js.map} +1 -1
- package/dist/admin/assets/{SecretReveal-ai1uMW_b.js → SecretReveal-Dhw_dYxe.js} +2 -2
- package/dist/admin/assets/{SecretReveal-ai1uMW_b.js.map → SecretReveal-Dhw_dYxe.js.map} +1 -1
- package/dist/admin/assets/{SharedGlyph-y6ZsA_gz.js → SharedGlyph-97RaO6Bh.js} +2 -2
- package/dist/admin/assets/{SharedGlyph-y6ZsA_gz.js.map → SharedGlyph-97RaO6Bh.js.map} +1 -1
- package/dist/admin/assets/{StepList-xmD9CPZl.js → StepList--0Fiqsq-.js} +2 -2
- package/dist/admin/assets/{StepList-xmD9CPZl.js.map → StepList--0Fiqsq-.js.map} +1 -1
- package/dist/admin/assets/Structure-CoO3iah3.js +2 -0
- package/dist/admin/assets/{Structure-DGt-TDk5.js.map → Structure-CoO3iah3.js.map} +1 -1
- package/dist/admin/assets/{SuggestAltButton-D1-v_0wB.js → SuggestAltButton-Dlyxoypu.js} +2 -2
- package/dist/admin/assets/{SuggestAltButton-D1-v_0wB.js.map → SuggestAltButton-Dlyxoypu.js.map} +1 -1
- package/dist/admin/assets/{Thumbnail-cjA7fSWh.js → Thumbnail-BUYOr4XJ.js} +2 -2
- package/dist/admin/assets/{Thumbnail-cjA7fSWh.js.map → Thumbnail-BUYOr4XJ.js.map} +1 -1
- package/dist/admin/assets/{UnsavedChangesGuard-B57D0278.js → UnsavedChangesGuard-BfzHgUkF.js} +2 -2
- package/dist/admin/assets/{UnsavedChangesGuard-B57D0278.js.map → UnsavedChangesGuard-BfzHgUkF.js.map} +1 -1
- package/dist/admin/assets/{UserAvatar--pMv2i3c.js → UserAvatar-DNOWhSWS.js} +2 -2
- package/dist/admin/assets/{UserAvatar--pMv2i3c.js.map → UserAvatar-DNOWhSWS.js.map} +1 -1
- package/dist/admin/assets/{badge-C5oMRtwJ.js → badge-lJ4Ih_Zn.js} +2 -2
- package/dist/admin/assets/{badge-C5oMRtwJ.js.map → badge-lJ4Ih_Zn.js.map} +1 -1
- package/dist/admin/assets/{changeSets-CObZV8H2.js → changeSets-JmSyu3tu.js} +2 -2
- package/dist/admin/assets/{changeSets-CObZV8H2.js.map → changeSets-JmSyu3tu.js.map} +1 -1
- package/dist/admin/assets/{constants-DT4WPUvz.js → constants-ahk0YSYl.js} +2 -2
- package/dist/admin/assets/{constants-DT4WPUvz.js.map → constants-ahk0YSYl.js.map} +1 -1
- package/dist/admin/assets/content-DcvNzKGe.js +3 -0
- package/dist/admin/assets/content-DcvNzKGe.js.map +1 -0
- package/dist/admin/assets/{contentTypes-ArLEfM29.js → contentTypes-YAd-AzOF.js} +2 -2
- package/dist/admin/assets/{contentTypes-ArLEfM29.js.map → contentTypes-YAd-AzOF.js.map} +1 -1
- package/dist/admin/assets/{develop-DvKYqVeK.js → develop-DYwiTd6R.js} +3 -3
- package/dist/admin/assets/{develop-DvKYqVeK.js.map → develop-DYwiTd6R.js.map} +1 -1
- package/dist/admin/assets/{dropdown-menu-DPzDthV7.js → dropdown-menu-DiYSm-g3.js} +2 -2
- package/dist/admin/assets/{dropdown-menu-DPzDthV7.js.map → dropdown-menu-DiYSm-g3.js.map} +1 -1
- package/dist/admin/assets/{fileRoute-sJvv4Ipn.js → fileRoute-CVQpW8cj.js} +2 -2
- package/dist/admin/assets/{fileRoute-sJvv4Ipn.js.map → fileRoute-CVQpW8cj.js.map} +1 -1
- package/dist/admin/assets/{followPlural-tOh6gDgT.js → followPlural-BdqfEe48.js} +2 -2
- package/dist/admin/assets/{followPlural-tOh6gDgT.js.map → followPlural-BdqfEe48.js.map} +1 -1
- package/dist/admin/assets/{inbox-nfR2K7f6.js → inbox-CnzNfqt9.js} +2 -2
- package/dist/admin/assets/{inbox-nfR2K7f6.js.map → inbox-CnzNfqt9.js.map} +1 -1
- package/dist/admin/assets/index-6iVBpUhu.css +2 -0
- package/dist/admin/assets/index-D9KL7arJ.js +4 -0
- package/dist/admin/assets/index-D9KL7arJ.js.map +1 -0
- package/dist/admin/assets/{media-BzivlEh2.js → media-Cs9LCQcz.js} +2 -2
- package/dist/admin/assets/{media-BzivlEh2.js.map → media-Cs9LCQcz.js.map} +1 -1
- package/dist/admin/assets/{network-Cj04CpFi.js → network-DS8MW3AZ.js} +2 -2
- package/dist/admin/assets/{network-Cj04CpFi.js.map → network-DS8MW3AZ.js.map} +1 -1
- package/dist/admin/assets/{publishing-ChHkrwvr.js → publishing-C4YQ4XKt.js} +2 -2
- package/dist/admin/assets/{publishing-ChHkrwvr.js.map → publishing-C4YQ4XKt.js.map} +1 -1
- package/dist/admin/assets/{radio-group-DSK5LPaM.js → radio-group-zOsGAYRP.js} +2 -2
- package/dist/admin/assets/{radio-group-DSK5LPaM.js.map → radio-group-zOsGAYRP.js.map} +1 -1
- package/dist/admin/assets/{searchSchemas-BdwAFaVO.js → searchSchemas-BCRZ1jyL.js} +2 -2
- package/dist/admin/assets/{searchSchemas-BdwAFaVO.js.map → searchSchemas-BCRZ1jyL.js.map} +1 -1
- package/dist/admin/assets/{sections-iNOzo7pL.js → sections-v5Flaor9.js} +2 -2
- package/dist/admin/assets/{sections-iNOzo7pL.js.map → sections-v5Flaor9.js.map} +1 -1
- package/dist/admin/assets/{select-DyD7qIkJ.js → select-CpHp3YOH.js} +2 -2
- package/dist/admin/assets/{select-DyD7qIkJ.js.map → select-CpHp3YOH.js.map} +1 -1
- package/dist/admin/assets/{seo-uMm2fGPB.js → seo-1n9s7YuX.js} +2 -2
- package/dist/admin/assets/{seo-uMm2fGPB.js.map → seo-1n9s7YuX.js.map} +1 -1
- package/dist/admin/assets/{settings-CJVORrIV.js → settings-CuEO5l07.js} +2 -2
- package/dist/admin/assets/{settings-CJVORrIV.js.map → settings-CuEO5l07.js.map} +1 -1
- package/dist/admin/assets/{skeleton-B5GJgv7y.js → skeleton-DElTumgk.js} +2 -2
- package/dist/admin/assets/skeleton-DElTumgk.js.map +1 -0
- package/dist/admin/assets/src-Be3Ds5Dc.js +4 -0
- package/dist/admin/assets/src-Be3Ds5Dc.js.map +1 -0
- package/dist/admin/assets/{switch-DbIrjP3h.js → switch-CijNGYkG.js} +2 -2
- package/dist/admin/assets/{switch-DbIrjP3h.js.map → switch-CijNGYkG.js.map} +1 -1
- package/dist/admin/assets/tabs-CSqGW9Du.js +5 -0
- package/dist/admin/assets/tabs-CSqGW9Du.js.map +1 -0
- package/dist/admin/assets/{theme-C0Tv6M38.js → theme-CHHVK8Ps.js} +2 -2
- package/dist/admin/assets/{theme-C0Tv6M38.js.map → theme-CHHVK8Ps.js.map} +1 -1
- package/dist/admin/assets/{titles-uL13bpi8.js → titles-CQYc9nDQ.js} +2 -2
- package/dist/admin/assets/{titles-uL13bpi8.js.map → titles-CQYc9nDQ.js.map} +1 -1
- package/dist/admin/assets/{tooltip-BK5FqmJ0.js → tooltip-BXqFQv9N.js} +2 -2
- package/dist/admin/assets/{tooltip-BK5FqmJ0.js.map → tooltip-BXqFQv9N.js.map} +1 -1
- package/dist/admin/assets/{useContentSchema-BCdLYsKa.js → useContentSchema-Cvtes34f.js} +2 -2
- package/dist/admin/assets/{useContentSchema-BCdLYsKa.js.map → useContentSchema-Cvtes34f.js.map} +1 -1
- package/dist/admin/assets/{useDiscardGuard-CGFgC3xY.js → useDiscardGuard-BAEGkGK9.js} +2 -2
- package/dist/admin/assets/{useDiscardGuard-CGFgC3xY.js.map → useDiscardGuard-BAEGkGK9.js.map} +1 -1
- package/dist/admin/assets/{useMediaPickerDialog-BOkGEyG7.js → useMediaPickerDialog-CiN_pias.js} +2 -2
- package/dist/admin/assets/{useMediaPickerDialog-BOkGEyG7.js.map → useMediaPickerDialog-CiN_pias.js.map} +1 -1
- package/dist/admin/assets/{useMediaUploader-D1ouV8jc.js → useMediaUploader-CapEHBIt.js} +2 -2
- package/dist/admin/assets/{useMediaUploader-D1ouV8jc.js.map → useMediaUploader-CapEHBIt.js.map} +1 -1
- package/dist/admin/assets/usePaletteItems-wpn3ms6o.js +2 -0
- package/dist/admin/assets/usePaletteItems-wpn3ms6o.js.map +1 -0
- package/dist/admin/assets/{useResolvedScheme-D0moXjaP.js → useResolvedScheme-i1EwGHuF.js} +2 -2
- package/dist/admin/assets/{useResolvedScheme-D0moXjaP.js.map → useResolvedScheme-i1EwGHuF.js.map} +1 -1
- package/dist/admin/assets/{zod-CS0EvbwG.js → zod-79xzBIrW.js} +2 -2
- package/dist/admin/assets/{zod-CS0EvbwG.js.map → zod-79xzBIrW.js.map} +1 -1
- package/dist/admin/index.html +27 -27
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{public-BSbOGbK-.d.ts → public-CTK82y3t.d.ts} +2 -1
- package/dist/{worker-i1rY2TIZ.js → worker-BDYDbaea.js} +404 -201
- package/dist/worker-BDYDbaea.js.map +1 -0
- package/package.json +5 -5
- package/dist/admin/assets/Builder-C11yUgu9.js +0 -2
- package/dist/admin/assets/Builder-C11yUgu9.js.map +0 -1
- package/dist/admin/assets/Structure-DGt-TDk5.js +0 -2
- package/dist/admin/assets/content-HvisCB5m.js +0 -3
- package/dist/admin/assets/content-HvisCB5m.js.map +0 -1
- package/dist/admin/assets/index-Bbb2ER3l.css +0 -2
- package/dist/admin/assets/index-C6FrF34G.js +0 -4
- package/dist/admin/assets/index-C6FrF34G.js.map +0 -1
- package/dist/admin/assets/skeleton-B5GJgv7y.js.map +0 -1
- package/dist/admin/assets/src-B2qcqjNM.js +0 -4
- package/dist/admin/assets/src-B2qcqjNM.js.map +0 -1
- package/dist/admin/assets/tabs-BJhfL-lw.js +0 -5
- package/dist/admin/assets/tabs-BJhfL-lw.js.map +0 -1
- package/dist/admin/assets/usePaletteItems-neKzBlys.js +0 -2
- package/dist/admin/assets/usePaletteItems-neKzBlys.js.map +0 -1
- package/dist/worker-i1rY2TIZ.js.map +0 -1
|
@@ -1084,13 +1084,15 @@ const MYSQL_TABLES = {
|
|
|
1084
1084
|
"change_seq",
|
|
1085
1085
|
"created_at",
|
|
1086
1086
|
"updated_at",
|
|
1087
|
-
"site_id"
|
|
1087
|
+
"site_id",
|
|
1088
|
+
"entry_created_at"
|
|
1088
1089
|
],
|
|
1089
|
-
nullable: ["autosaved_at"],
|
|
1090
|
+
nullable: ["autosaved_at", "entry_created_at"],
|
|
1090
1091
|
timestamps: [
|
|
1091
1092
|
"autosaved_at",
|
|
1092
1093
|
"created_at",
|
|
1093
|
-
"updated_at"
|
|
1094
|
+
"updated_at",
|
|
1095
|
+
"entry_created_at"
|
|
1094
1096
|
],
|
|
1095
1097
|
uniqueKeys: [{
|
|
1096
1098
|
name: "entry_heads_pkey",
|
|
@@ -4590,6 +4592,8 @@ const countLiveByModel = (siteId, modelIds, executor = db) => modelIds.length ==
|
|
|
4590
4592
|
* Pass the outer query's executor; the subquery is compiled into the outer statement.
|
|
4591
4593
|
*/
|
|
4592
4594
|
const entrySiteOf = (executor, entryId) => executor.selectFrom("entries").select("entries.site_id").where("entries.id", "=", entryId);
|
|
4595
|
+
/** The entry's creation time, copied onto each head (`entry_heads.entry_created_at`, the default list order). */
|
|
4596
|
+
const entryCreatedAtOf = (executor, entryId) => executor.selectFrom("entries").select("entries.created_at").where("entries.id", "=", entryId);
|
|
4593
4597
|
/**
|
|
4594
4598
|
* Live entries of a model on sites other than `siteId`, per site (a definition can only move to one site
|
|
4595
4599
|
* while no other site holds entries of it: plan site-schema, rule 2). Deleted ones do not count: once the
|
|
@@ -4704,6 +4708,7 @@ const findForEntry = (entryId, executor = db) => executor.selectFrom("entry_head
|
|
|
4704
4708
|
const insert$28 = (head, trx = db) => trx.insertInto("entry_heads").values({
|
|
4705
4709
|
entry_id: head.entryId,
|
|
4706
4710
|
site_id: entrySiteOf(trx, head.entryId),
|
|
4711
|
+
entry_created_at: entryCreatedAtOf(trx, head.entryId),
|
|
4707
4712
|
model_id: head.modelId,
|
|
4708
4713
|
locale: head.locale,
|
|
4709
4714
|
state: head.state,
|
|
@@ -6345,6 +6350,11 @@ const effectiveTitleField = (definition) => {
|
|
|
6345
6350
|
};
|
|
6346
6351
|
/** Title types that the document edits inline as its H1; any other title type stays a property. */
|
|
6347
6352
|
const INLINE_TITLE_TYPES = /* @__PURE__ */ new Set(["string", "text"]);
|
|
6353
|
+
/**
|
|
6354
|
+
* The automatic rule: the fields written in the document while `display.canvasFieldIds` is unset (rich
|
|
6355
|
+
* text, dynamic zones, repeatable components, media with several files). Also what a canvas field can take
|
|
6356
|
+
* between blocks: only these hold blocks.
|
|
6357
|
+
*/
|
|
6348
6358
|
const isCanvasEligible = (field) => {
|
|
6349
6359
|
switch (field.type) {
|
|
6350
6360
|
case "richtext":
|
|
@@ -6354,6 +6364,12 @@ const isCanvasEligible = (field) => {
|
|
|
6354
6364
|
default: return false;
|
|
6355
6365
|
}
|
|
6356
6366
|
};
|
|
6367
|
+
/**
|
|
6368
|
+
* Whether `display.canvasFieldIds` may list the field: any field but the entry's title (`effectiveTitleField`),
|
|
6369
|
+
* which the document shows as its heading. A configured cover is refused by the validator; an automatically
|
|
6370
|
+
* chosen one placed in the document simply stops being the cover.
|
|
6371
|
+
*/
|
|
6372
|
+
const isDocumentPlaceable = (field, title) => field.id !== title?.id;
|
|
6357
6373
|
/** A single `media` field that may hold an image. */
|
|
6358
6374
|
const isCoverEligible = (field) => field.type === "media" && !field.settings.multiple && (field.settings.allowedKinds === void 0 || field.settings.allowedKinds.includes("image"));
|
|
6359
6375
|
const byIds = (live, ids) => (ids ?? []).map((id) => live.find((field) => field.id === id)).filter((field) => field !== void 0);
|
|
@@ -6378,16 +6394,16 @@ const coverOf = (model, live, canvas) => {
|
|
|
6378
6394
|
return live.find((field) => isCoverEligible(field) && !canvas.has(field.id));
|
|
6379
6395
|
};
|
|
6380
6396
|
/**
|
|
6381
|
-
* The entry document's layout: title, cover,
|
|
6382
|
-
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every
|
|
6383
|
-
*
|
|
6384
|
-
* skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
6397
|
+
* The entry document's layout: title, cover, document fields and properties. Configured `display` ids win
|
|
6398
|
+
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every block
|
|
6399
|
+
* field in field order; cover: the first single image field not in the canvas). Deprecated, unknown ids and
|
|
6400
|
+
* the title are skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
6385
6401
|
*/
|
|
6386
6402
|
const effectiveLayout = (model) => {
|
|
6387
6403
|
const live = model.fields.filter((field) => !field.deprecated);
|
|
6388
6404
|
const title = effectiveTitleField(model);
|
|
6389
6405
|
const titleInline = title !== void 0 && INLINE_TITLE_TYPES.has(title.type);
|
|
6390
|
-
const configuredCanvas = unique$1(byIds(live, model.display.canvasFieldIds).filter(
|
|
6406
|
+
const configuredCanvas = unique$1(byIds(live, model.display.canvasFieldIds).filter((field) => isDocumentPlaceable(field, title)));
|
|
6391
6407
|
const canvasCandidates = model.display.canvasFieldIds === void 0 ? live.filter(isCanvasEligible) : configuredCanvas;
|
|
6392
6408
|
const cover = coverOf(model, live, new Set(configuredCanvas.map((field) => field.id)));
|
|
6393
6409
|
const canvas = canvasCandidates.filter((field) => field.id !== cover?.id);
|
|
@@ -6974,17 +6990,18 @@ const checkFieldList = (ids, check) => {
|
|
|
6974
6990
|
});
|
|
6975
6991
|
};
|
|
6976
6992
|
/**
|
|
6977
|
-
* The entry document's layout (`effectiveLayout`):
|
|
6978
|
-
* are disjoint
|
|
6979
|
-
* entries are properties, so never the cover or a
|
|
6993
|
+
* The entry document's layout (`effectiveLayout`): document fields, cover and strip. Title, cover and
|
|
6994
|
+
* canvas are disjoint: any field but the title (configured or automatic) may be in the canvas, the
|
|
6995
|
+
* configured cover may not; strip entries are properties, so never the cover or a document field.
|
|
6980
6996
|
*/
|
|
6981
6997
|
const checkDocumentLayout = (model, fields) => {
|
|
6982
6998
|
const { canvasFieldIds, coverFieldId, stripFieldIds } = model.display;
|
|
6983
6999
|
const canvas = new Set(canvasFieldIds ?? []);
|
|
7000
|
+
const title = effectiveTitleField(model);
|
|
6984
7001
|
const issues = checkFieldList(canvasFieldIds, {
|
|
6985
7002
|
path: "/display/canvasFieldIds",
|
|
6986
7003
|
fields,
|
|
6987
|
-
reject: (field) =>
|
|
7004
|
+
reject: (field) => isDocumentPlaceable(field, title) ? void 0 : "the title is the heading of the document, not a field in it"
|
|
6988
7005
|
});
|
|
6989
7006
|
if (coverFieldId !== void 0) issues.push(...checkFieldList([coverFieldId], {
|
|
6990
7007
|
path: "/display/coverFieldId",
|
|
@@ -7001,11 +7018,6 @@ const checkDocumentLayout = (model, fields) => {
|
|
|
7001
7018
|
}));
|
|
7002
7019
|
return issues;
|
|
7003
7020
|
};
|
|
7004
|
-
const canvasRejection = (field) => {
|
|
7005
|
-
if (field.type === "component") return "only a repeatable component can be in the canvas";
|
|
7006
|
-
if (field.type === "media") return "only a media field with several files can be in the canvas";
|
|
7007
|
-
return `a ${field.type} field cannot be in the canvas`;
|
|
7008
|
-
};
|
|
7009
7021
|
const checkDisplay = (definition) => {
|
|
7010
7022
|
const fields = new Map(definition.fields.map((field) => [field.id, field]));
|
|
7011
7023
|
const fieldIds = new Set(fields.keys());
|
|
@@ -12561,9 +12573,11 @@ const mysqlContentDialect = {
|
|
|
12561
12573
|
},
|
|
12562
12574
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12563
12575
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published' as state, r.data, 0 as version,
|
|
12564
|
-
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at
|
|
12576
|
+
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at,
|
|
12577
|
+
en.created_at as entry_created_at
|
|
12565
12578
|
from publication_log pl
|
|
12566
12579
|
join content_revisions r on r.id = pl.revision_id
|
|
12580
|
+
join entries en on en.id = pl.entry_id
|
|
12567
12581
|
where pl.site_id = ${siteId} and pl.model_id = ${modelId}
|
|
12568
12582
|
and pl.from_seq <= ${seq}
|
|
12569
12583
|
and (pl.to_seq is null or pl.to_seq > ${seq})
|
|
@@ -12586,8 +12600,9 @@ const mysqlContentDialect = {
|
|
|
12586
12600
|
//#region src/content/compiler/dialect/postgres.ts
|
|
12587
12601
|
/**
|
|
12588
12602
|
* PostgreSQL content SQL (ADR 0001): JSONB with a GIN `jsonb_path_ops` index serving containment, so
|
|
12589
|
-
*
|
|
12590
|
-
* built `CONCURRENTLY` with a
|
|
12603
|
+
* equality on a field without an index is emitted as `data @> {...}`; ranges, sorts and equality on indexed
|
|
12604
|
+
* fields (filterable or sortable) use per-field B-tree expression indexes built `CONCURRENTLY` with a
|
|
12605
|
+
* matching `CREATE STATISTICS` object.
|
|
12591
12606
|
*/
|
|
12592
12607
|
const DATA$1 = sql.ref(CONTENT_HEADS_COLUMNS.data);
|
|
12593
12608
|
const ARRAY_TYPES = {
|
|
@@ -12639,6 +12654,10 @@ const postgresContentDialect = {
|
|
|
12639
12654
|
castParameter: castParameter$2,
|
|
12640
12655
|
fieldEquals: (target, value) => {
|
|
12641
12656
|
if (target.numericString && target.cast) return sql`(${fieldValue$2(target.fieldId, target.cast)} = ${castParameter$2(value, target.cast)})`;
|
|
12657
|
+
if (target.indexed && target.cast) {
|
|
12658
|
+
const expression = fieldValue$2(target.fieldId, target.cast);
|
|
12659
|
+
return sql`(${expression} = ${castParameter$2(value, target.cast)} and ${expression} is not null)`;
|
|
12660
|
+
}
|
|
12642
12661
|
return containment({ [target.fieldId]: target.list ? [value] : value });
|
|
12643
12662
|
},
|
|
12644
12663
|
fieldMissing: (fieldId) => {
|
|
@@ -12651,9 +12670,11 @@ const postgresContentDialect = {
|
|
|
12651
12670
|
sortDirection: (direction) => direction === "desc" ? sql`desc` : sql`asc`,
|
|
12652
12671
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12653
12672
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published'::text as state, r.data, 0 as version,
|
|
12654
|
-
r.id as revision_id, pl.published_at as updated_at, null::timestamptz as autosaved_at
|
|
12673
|
+
r.id as revision_id, pl.published_at as updated_at, null::timestamptz as autosaved_at,
|
|
12674
|
+
en.created_at as entry_created_at
|
|
12655
12675
|
from publication_log pl
|
|
12656
12676
|
join content_revisions r on r.id = pl.revision_id
|
|
12677
|
+
join entries en on en.id = pl.entry_id
|
|
12657
12678
|
where pl.site_id = ${siteId}::uuid and pl.model_id = ${modelId}
|
|
12658
12679
|
and pl.from_seq <= ${seq}::bigint
|
|
12659
12680
|
and (pl.to_seq is null or pl.to_seq > ${seq}::bigint)
|
|
@@ -12742,9 +12763,11 @@ const DIALECTS = {
|
|
|
12742
12763
|
},
|
|
12743
12764
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12744
12765
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published' as state, r.data, 0 as version,
|
|
12745
|
-
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at
|
|
12766
|
+
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at,
|
|
12767
|
+
en.created_at as entry_created_at
|
|
12746
12768
|
from publication_log pl
|
|
12747
12769
|
join content_revisions r on r.id = pl.revision_id
|
|
12770
|
+
join entries en on en.id = pl.entry_id
|
|
12748
12771
|
where pl.site_id = ${siteId} and pl.model_id = ${modelId}
|
|
12749
12772
|
and pl.from_seq <= ${seq}
|
|
12750
12773
|
and (pl.to_seq is null or pl.to_seq > ${seq})
|
|
@@ -13385,6 +13408,12 @@ const NUMERIC_TYPES = /* @__PURE__ */ new Set([
|
|
|
13385
13408
|
]);
|
|
13386
13409
|
/** Numbers stored as JSON strings (exact decimals, big integers). */
|
|
13387
13410
|
const NUMERIC_STRING_TYPES$1 = /* @__PURE__ */ new Set(["decimal", "biginteger"]);
|
|
13411
|
+
/**
|
|
13412
|
+
* Whether a field has a B-tree expression index: filterable or sortable, and not deprecated. The schema
|
|
13413
|
+
* planner builds an index for exactly these fields (`indexStepsOf`), and PostgreSQL equality compares on
|
|
13414
|
+
* the index expression for them (`EqualityTarget.indexed`).
|
|
13415
|
+
*/
|
|
13416
|
+
const hasFieldIndex = (field) => (field.filterable || field.sortable) && !field.deprecated;
|
|
13388
13417
|
const valueCastFor = (type) => {
|
|
13389
13418
|
if (NUMERIC_TYPES.has(type)) return "numeric";
|
|
13390
13419
|
return type === "boolean" ? "boolean" : "text";
|
|
@@ -13408,8 +13437,9 @@ const fieldValueExpression = (fieldId, type, dialect = contentDialect()) => {
|
|
|
13408
13437
|
return dialect.fieldValue(fieldId, valueCastFor(type), NUMERIC_STRING_TYPES$1.has(type));
|
|
13409
13438
|
};
|
|
13410
13439
|
/**
|
|
13411
|
-
* Equality on a top-level field (lists: "contains"). PostgreSQL
|
|
13412
|
-
*
|
|
13440
|
+
* Equality on a top-level field (lists: "contains"). PostgreSQL compares the field's index expression when
|
|
13441
|
+
* the field has one (`EqualityTarget.indexed`) and emits containment, which its GIN index serves, otherwise;
|
|
13442
|
+
* SQLite and MySQL compare the field's index expression.
|
|
13413
13443
|
*/
|
|
13414
13444
|
const fieldEqualsExpression = (target, value, dialect = contentDialect()) => {
|
|
13415
13445
|
assertStableId(target.fieldId, "a field ID");
|
|
@@ -13750,17 +13780,32 @@ const RANGE_SQL = {
|
|
|
13750
13780
|
$ne: sql`<>`
|
|
13751
13781
|
};
|
|
13752
13782
|
const anyOf = (parts) => parts.length === 0 ? sql`false` : sql`(${sql.join(parts, sql` or `)})`;
|
|
13753
|
-
const equalityTarget = ({ field }) =>
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13783
|
+
const equalityTarget = ({ field }) => {
|
|
13784
|
+
const cast = SCALAR_DATA_TYPES.has(field.type) ? valueCastFor(field.type) : null;
|
|
13785
|
+
const list = isListValued(field);
|
|
13786
|
+
return {
|
|
13787
|
+
fieldId: field.id,
|
|
13788
|
+
cast,
|
|
13789
|
+
list,
|
|
13790
|
+
numericString: NUMERIC_STRING_TYPES.has(field.type),
|
|
13791
|
+
indexed: cast !== null && !list && hasFieldIndex(field)
|
|
13792
|
+
};
|
|
13793
|
+
};
|
|
13794
|
+
/** System filter columns. `createdAt` is the entry's, copied on every head (`h.entry_created_at`). */
|
|
13759
13795
|
const SYSTEM_COLUMNS = {
|
|
13760
13796
|
id: "h.entry_id",
|
|
13761
|
-
createdAt: "
|
|
13797
|
+
createdAt: "h.entry_created_at",
|
|
13762
13798
|
updatedAt: "h.updated_at"
|
|
13763
13799
|
};
|
|
13800
|
+
/** Whether a filter names a column of `entries` (the head query's `e`), so its COUNT must join entries. */
|
|
13801
|
+
const filterReadsEntries = (node) => {
|
|
13802
|
+
switch (node.kind) {
|
|
13803
|
+
case "and":
|
|
13804
|
+
case "or": return node.nodes.some(filterReadsEntries);
|
|
13805
|
+
case "not": return filterReadsEntries(node.node);
|
|
13806
|
+
case "condition": return node.target.kind === "system" && SYSTEM_COLUMNS[node.target.name].startsWith("e.");
|
|
13807
|
+
}
|
|
13808
|
+
};
|
|
13764
13809
|
const systemCondition = (name, operator, value, dialect) => {
|
|
13765
13810
|
const column = sql.ref(SYSTEM_COLUMNS[name]);
|
|
13766
13811
|
const element = name === "id" ? "uuid" : "timestamp";
|
|
@@ -13862,20 +13907,13 @@ const whereOf = (plan, table, dialect) => {
|
|
|
13862
13907
|
return sql.join(conditions, sql` and `);
|
|
13863
13908
|
};
|
|
13864
13909
|
/**
|
|
13865
|
-
* The joined entry's site and model, which always equal the head's. Written out on the row query of a plain
|
|
13866
|
-
* list in `entries` order (the default newest-first sort) so the planner can walk `entries_model_idx` in that
|
|
13867
|
-
* order and stop at the page limit instead of sorting every head of the model. Not added when there are
|
|
13868
|
-
* conditions or the sort is on the head: SQLite then prefers the entries index over a field index that
|
|
13869
|
-
* serves the filter or the sort. Left off the COUNT, which reads every match either way.
|
|
13870
|
-
*/
|
|
13871
|
-
const entryScope = (plan, dialect) => plan.orderedByEntry === true && plan.conditions.length === 0 ? sql` and e.site_id = ${dialect.uuid(plan.siteId)} and e.model_id = ${modelIdLiteral(plan.modelId)}` : sql``;
|
|
13872
|
-
/**
|
|
13873
13910
|
* The COUNT's FROM. Live heads exist only for live entries (deleting an entry removes its heads in the same
|
|
13874
|
-
* transaction; entries soft-deleted for having no head have none), so a
|
|
13875
|
-
*
|
|
13876
|
-
*
|
|
13911
|
+
* transaction; entries soft-deleted for having no head have none; `headLiveEntry.int.test.ts` pins it), so a
|
|
13912
|
+
* count of heads whose conditions name no `e` column needs no join: it is read from the heads' indexes alone
|
|
13913
|
+
* (the model index, or a field index for an indexed filter). A snapshot keeps the join: the publication log
|
|
13914
|
+
* still lists entries deleted since.
|
|
13877
13915
|
*/
|
|
13878
|
-
const countFrom = (plan, table, where, joined) => plan.source.kind === "heads" && plan.conditions.length
|
|
13916
|
+
const countFrom = (plan, table, where, joined) => plan.source.kind === "heads" && !(plan.conditionsReadEntries ?? plan.conditions.length > 0) ? sql`from ${table} h where ${where}` : joined;
|
|
13879
13917
|
const HEAD_COLUMNS$1 = sql`h.entry_id, h.locale, h.data, h.version, h.revision_id, h.updated_at,
|
|
13880
13918
|
h.autosaved_at, e.created_at, e.updated_at as entry_updated_at, e.created_by_admin_id, e.owner_app_user_id`;
|
|
13881
13919
|
/** The parts every statement over a plan's heads shares. */
|
|
@@ -13888,7 +13926,7 @@ const headQueryParts = (plan, dialect) => {
|
|
|
13888
13926
|
const offset = plan.offset ? sql` offset ${plan.offset}` : sql``;
|
|
13889
13927
|
return {
|
|
13890
13928
|
prefix: cte ?? sql``,
|
|
13891
|
-
rows: sql`${from}${
|
|
13929
|
+
rows: sql`${from}${orderBy}${limit}${offset}`,
|
|
13892
13930
|
countFrom: countFrom(plan, table, where, from)
|
|
13893
13931
|
};
|
|
13894
13932
|
};
|
|
@@ -14219,13 +14257,15 @@ const parseContentQuery = (tree, context) => {
|
|
|
14219
14257
|
/**
|
|
14220
14258
|
* ORDER BY for a content query. Field sorts use the same expression as the field's index (so the index can
|
|
14221
14259
|
* serve them); the entry ID is always the final tie-breaker so pages are stable. Without an explicit sort
|
|
14222
|
-
* the model's default sort applies when the caller may read that field, else newest first.
|
|
14223
|
-
*
|
|
14224
|
-
*
|
|
14260
|
+
* the model's default sort applies when the caller may read that field, else newest first. `createdAt` is the
|
|
14261
|
+
* entry's creation time copied on the head (`h.entry_created_at`), so the default order and its tie-breaker
|
|
14262
|
+
* are both head columns, which `entry_heads_entry_order_idx` (site, model, state, `entry_created_at desc`,
|
|
14263
|
+
* `entry_id`) serves: a page is read in index order and the scan stops at the limit (ADR 0001, "Delivery perf
|
|
14264
|
+
* 2").
|
|
14225
14265
|
*/
|
|
14226
14266
|
const SYSTEM_SORT_COLUMNS = {
|
|
14227
14267
|
id: "h.entry_id",
|
|
14228
|
-
createdAt: "
|
|
14268
|
+
createdAt: "h.entry_created_at",
|
|
14229
14269
|
updatedAt: "h.updated_at"
|
|
14230
14270
|
};
|
|
14231
14271
|
const termSql = (term, dialect) => {
|
|
@@ -14252,9 +14292,7 @@ const defaultSortTerms = (model, isReadable) => {
|
|
|
14252
14292
|
direction: "desc"
|
|
14253
14293
|
}];
|
|
14254
14294
|
};
|
|
14255
|
-
|
|
14256
|
-
const leadsWithEntryColumn = (terms) => terms[0]?.target.kind === "system" && SYSTEM_SORT_COLUMNS[terms[0].target.name].startsWith("e.");
|
|
14257
|
-
const compileOrderBy = (terms, dialect = contentDialect()) => [...terms.map((term) => termSql(term, dialect)), sql`e.id asc`];
|
|
14295
|
+
const compileOrderBy = (terms, dialect = contentDialect()) => [...terms.map((term) => termSql(term, dialect)), sql`h.entry_id asc`];
|
|
14258
14296
|
//#endregion
|
|
14259
14297
|
//#region src/content/errors.ts
|
|
14260
14298
|
/** Errors the content services throw; the central error handler renders them. */
|
|
@@ -14524,8 +14562,10 @@ const needsFollowUpReads = (env, model, rows, query, policy) => {
|
|
|
14524
14562
|
if (query.populate.size > 0) return true;
|
|
14525
14563
|
const fields = selectFields(model, policy.readMask, query.fields);
|
|
14526
14564
|
const documents = rows.map((row) => row.data);
|
|
14527
|
-
return env.audience === "delivery" && targetsByModel(model, documents, fields).size > 0 || env
|
|
14565
|
+
return env.audience === "delivery" && targetsByModel(model, documents, fields).size > 0 || needsMediaReads(env, model, rows, fields);
|
|
14528
14566
|
};
|
|
14567
|
+
/** Whether projecting these rows reads asset views (media fields, rich-text images, a default SEO image). */
|
|
14568
|
+
const needsMediaReads = (env, model, rows, fields) => env.media !== void 0 && (mediaIdsOf(model, rows.map((row) => row.data), fields).size > 0 || seoImageIdOf(env, fields) !== void 0);
|
|
14529
14569
|
/** The site's default social image a `?seo=resolved` read needs (when the response carries an SEO field). */
|
|
14530
14570
|
const seoImageIdOf = (env, fields) => env.seo?.imageId && seoFieldsOf(fields).length > 0 ? env.seo.imageId : void 0;
|
|
14531
14571
|
/** A default SEO image is shown only while it is a public image (never through a signed URL). */
|
|
@@ -14808,6 +14848,8 @@ const getAdminEntry = async (context, modelKey, id, locale) => {
|
|
|
14808
14848
|
return buildAdminEntryView(context, model, policy, entry, locale);
|
|
14809
14849
|
};
|
|
14810
14850
|
/** Filters, search and row filter of a parsed query as SQL conditions. */
|
|
14851
|
+
/** Whether `queryConditions` names an `entries` column (`HeadQueryPlan.conditionsReadEntries`). */
|
|
14852
|
+
const queryReadsEntries = (query, policy) => query.filter !== null && filterReadsEntries(query.filter) || query.author !== void 0 || policy.rowFilter !== null;
|
|
14811
14853
|
const queryConditions = (context, query, policy) => {
|
|
14812
14854
|
const rowFilter = compileRowFilter(policy.rowFilter, context.actor);
|
|
14813
14855
|
return [
|
|
@@ -14846,8 +14888,8 @@ const listAdminEntries = async (context, modelKey, rawQuery) => {
|
|
|
14846
14888
|
},
|
|
14847
14889
|
locales: readScopeFor(context.snapshot, model.definition, query.locale, { fallback: true }),
|
|
14848
14890
|
conditions: queryConditions(context, query, policy),
|
|
14891
|
+
conditionsReadEntries: queryReadsEntries(query, policy),
|
|
14849
14892
|
orderBy: compileOrderBy(sort),
|
|
14850
|
-
orderedByEntry: leadsWithEntryColumn(sort),
|
|
14851
14893
|
limit,
|
|
14852
14894
|
offset
|
|
14853
14895
|
});
|
|
@@ -18693,7 +18735,8 @@ const getDeliverySite$1 = async (context) => {
|
|
|
18693
18735
|
* delivery-perf). The heads, the page total and the site's publication sequence come from one statement, so
|
|
18694
18736
|
* a read that needs nothing else (no relation, populate or asset follow-up) runs without a transaction.
|
|
18695
18737
|
* Anything that needs a follow-up statement runs in one REPEATABLE READ, read-only transaction, so every
|
|
18696
|
-
* statement of the response sees the moment `meta.snapshot` names
|
|
18738
|
+
* statement of the response sees the moment `meta.snapshot` names, except a live published read whose only
|
|
18739
|
+
* follow-up is relation visibility: that runs on the pool (`readConsistently` explains why it is safe).
|
|
18697
18740
|
*/
|
|
18698
18741
|
const deliveryEnvironment = (context, executor, source, locale, richText, seo) => ({
|
|
18699
18742
|
executor,
|
|
@@ -18715,6 +18758,11 @@ const environmentOf = (read, executor) => deliveryEnvironment(read.context, exec
|
|
|
18715
18758
|
*/
|
|
18716
18759
|
const followsUp = (read) => read.query.populate.size > 0 || selectFields(read.model, read.policy.readMask, read.query.fields).some((field) => field.type === "relation" || field.type === "media" || Boolean(read.seo?.imageId) && isSeoField(field));
|
|
18717
18760
|
/**
|
|
18761
|
+
* A live read of published heads (no `?snapshot`, not a preview or draft read, no extra conditions) without
|
|
18762
|
+
* populate: its only possible follow-up is relation visibility (asset views are checked on the rows).
|
|
18763
|
+
*/
|
|
18764
|
+
const isLiveVisibilityRead = (read) => read.source.kind === "heads" && read.source.state === "published" && read.query.populate.size === 0 && (read.conditions?.length ?? 0) === 0;
|
|
18765
|
+
/**
|
|
18718
18766
|
* Loads the site's SEO defaults when the query asks for resolved SEO. Read before the heads, outside their
|
|
18719
18767
|
* consistent moment: defaults are not part of a snapshot (`?snapshot=N&seo=resolved` uses today's).
|
|
18720
18768
|
*/
|
|
@@ -18729,20 +18777,29 @@ const seqOf = (seq) => {
|
|
|
18729
18777
|
/**
|
|
18730
18778
|
* Runs the plan's statement on the pool when the read cannot need a follow-up, and keeps that result when
|
|
18731
18779
|
* the rows indeed need none; otherwise reads again inside one REPEATABLE READ transaction. `finish` projects
|
|
18732
|
-
* the rows (through the same executor), so its own statements see the same moment.
|
|
18780
|
+
* the rows (through the same executor), so its own statements see the same moment. A live published read
|
|
18781
|
+
* whose only follow-up is relation visibility also stays on the pool (see below).
|
|
18733
18782
|
*/
|
|
18734
18783
|
const readConsistently = async (read, plan, options, finish) => {
|
|
18735
18784
|
const compiled = compileHeadPage(plan, options);
|
|
18736
18785
|
const { db } = read.context;
|
|
18737
|
-
|
|
18786
|
+
const live = isLiveVisibilityRead(read);
|
|
18787
|
+
if (!followsUp(read) || live) {
|
|
18738
18788
|
const env = environmentOf(read, db);
|
|
18739
18789
|
const { rows, meta } = await runHeadPage(compiled.rows, db);
|
|
18790
|
+
const fields = selectFields(read.model, read.policy.readMask, read.query.fields);
|
|
18740
18791
|
if (meta && !needsFollowUpReads(env, read.model, rows, read.query, read.policy)) return finish({
|
|
18741
18792
|
env,
|
|
18742
18793
|
rows,
|
|
18743
18794
|
total: meta.total,
|
|
18744
18795
|
seq: seqOf(meta.seq)
|
|
18745
18796
|
});
|
|
18797
|
+
if (meta && live && !needsMediaReads(env, read.model, rows, fields)) return finish({
|
|
18798
|
+
env,
|
|
18799
|
+
rows,
|
|
18800
|
+
total: meta.total,
|
|
18801
|
+
seq: seqOf(meta.seq)
|
|
18802
|
+
});
|
|
18746
18803
|
}
|
|
18747
18804
|
return withConsistentRead(async (trx) => {
|
|
18748
18805
|
const env = environmentOf(read, trx);
|
|
@@ -18776,8 +18833,8 @@ const readEntryPage = async (unloaded, options = { total: true }) => {
|
|
|
18776
18833
|
source: read.source,
|
|
18777
18834
|
locales: localesOf(read),
|
|
18778
18835
|
conditions: [...queryConditions(context, query, policy), ...read.conditions ?? []],
|
|
18836
|
+
conditionsReadEntries: queryReadsEntries(query, policy) || (read.conditions?.length ?? 0) > 0,
|
|
18779
18837
|
orderBy: compileOrderBy(sort),
|
|
18780
|
-
orderedByEntry: leadsWithEntryColumn(sort),
|
|
18781
18838
|
limit,
|
|
18782
18839
|
offset
|
|
18783
18840
|
};
|
|
@@ -24188,13 +24245,15 @@ const summarizePrompt = (input) => ({
|
|
|
24188
24245
|
//#region src/services/assist/summarize.ts
|
|
24189
24246
|
const notSummarizable = (fieldApiKey, reason) => new AppError(400, "ASSIST_FIELD_NOT_SUMMARIZABLE", `"${fieldApiKey}" cannot be summarized into: ${reason}`, { field: fieldApiKey });
|
|
24190
24247
|
/**
|
|
24191
|
-
* The target must be a string or text
|
|
24192
|
-
* canvas field
|
|
24248
|
+
* The target must be a live string or text field (not the title) of a model whose document has a rich-text
|
|
24249
|
+
* canvas field, wherever the field is placed (a property or in the document): the summary is made from
|
|
24250
|
+
* that canvas (plan §I).
|
|
24193
24251
|
*/
|
|
24194
24252
|
const summaryTarget = (definition, fieldApiKey) => {
|
|
24195
24253
|
const layout = effectiveLayout(definition);
|
|
24196
|
-
|
|
24197
|
-
|
|
24254
|
+
if (layout.title?.apiKey === fieldApiKey) throw notSummarizable(fieldApiKey, "it is the title");
|
|
24255
|
+
const field = definition.fields.find((candidate) => candidate.apiKey === fieldApiKey && !candidate.deprecated);
|
|
24256
|
+
if (!field) throw notSummarizable(fieldApiKey, "it is not a field of this model");
|
|
24198
24257
|
if (field.type !== "string" && field.type !== "text") throw notSummarizable(fieldApiKey, "only string and text fields hold summaries");
|
|
24199
24258
|
const canvas = layout.canvas.filter((candidate) => candidate.type === "richtext");
|
|
24200
24259
|
if (canvas.length === 0) throw notSummarizable(fieldApiKey, "the model has no rich-text body to summarize");
|
|
@@ -25551,11 +25610,11 @@ const scopedDefinitionsOf = (definitions) => definitions.map(({ definition, site
|
|
|
25551
25610
|
/** Site IDs to keys, so schema issues found in one site's view can name the site. */
|
|
25552
25611
|
const loadSiteKeys = async (executor) => new Map((await list$10(executor)).map((site) => [site.id, site.key]));
|
|
25553
25612
|
/** The most filterable or sortable fields the database can index, or undefined without a limit. */
|
|
25554
|
-
const maxFieldIndexes = () => isMysql() ?
|
|
25613
|
+
const maxFieldIndexes = () => isMysql() ? 55 : void 0;
|
|
25555
25614
|
//#endregion
|
|
25556
25615
|
//#region src/schema/planner/plan.ts
|
|
25557
25616
|
/** The field indexes a definition needs (filterable or sortable, not deprecated), in the current layout. */
|
|
25558
|
-
const indexStepsOf = (definition) => definition && definition.kind !== "component" ? definition.fields.filter((field) => (field
|
|
25617
|
+
const indexStepsOf = (definition) => definition && definition.kind !== "component" ? definition.fields.filter((field) => hasFieldIndex(field)).map((field) => {
|
|
25559
25618
|
const spec = {
|
|
25560
25619
|
modelId: definition.id,
|
|
25561
25620
|
fieldId: field.id,
|
|
@@ -33433,6 +33492,19 @@ const executeIdempotentDdl = async (db, statement) => {
|
|
|
33433
33492
|
}
|
|
33434
33493
|
};
|
|
33435
33494
|
//#endregion
|
|
33495
|
+
//#region src/db/entryOrderIndex.ts
|
|
33496
|
+
/**
|
|
33497
|
+
* `entry_heads_entry_order_idx` (ADR 0001, "Delivery perf 2"): the default newest-first list order on the
|
|
33498
|
+
* heads. The `20261005120000_entry_heads_entry_created_at` migration builds it on an empty table, else the
|
|
33499
|
+
* `schema.entryOrderIndex` job builds it here, `CONCURRENTLY` (PostgreSQL only: SQLite and MySQL build it in
|
|
33500
|
+
* their migrations).
|
|
33501
|
+
*/
|
|
33502
|
+
const ENTRY_ORDER_INDEX = "entry_heads_entry_order_idx";
|
|
33503
|
+
const createEntryOrderIndexConcurrently = (db) => sql`create index concurrently if not exists ${sql.id(ENTRY_ORDER_INDEX)}
|
|
33504
|
+
on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)`.execute(db);
|
|
33505
|
+
/** Drops an INVALID index left by an interrupted build, so the next build starts over. */
|
|
33506
|
+
const dropEntryOrderIndexConcurrently = (db) => sql`drop index concurrently if exists ${sql.id(ENTRY_ORDER_INDEX)}`.execute(db);
|
|
33507
|
+
//#endregion
|
|
33436
33508
|
//#region src/db/indexCatalog.ts
|
|
33437
33509
|
/** Whether a relation exists in the current schema (e.g. content tables before package E's migration). */
|
|
33438
33510
|
const tableExists = async (db, table) => {
|
|
@@ -33552,6 +33624,30 @@ const dropFieldIndex = (db, indexName) => withIndexBuildLock(db, async () => {
|
|
|
33552
33624
|
await executeIfAny(db, dropStatisticsStatement(indexName));
|
|
33553
33625
|
}
|
|
33554
33626
|
});
|
|
33627
|
+
/** The job that builds `entry_heads_entry_order_idx` on an instance upgraded with heads present (PostgreSQL). */
|
|
33628
|
+
const ENTRY_ORDER_INDEX_JOB = "schema.entryOrderIndex";
|
|
33629
|
+
/**
|
|
33630
|
+
* Builds the default list order's index `CONCURRENTLY`, under the same lock as field index builds (two
|
|
33631
|
+
* concurrent builds on `entry_heads` deadlock). Idempotent: a valid index is kept, an INVALID one dropped
|
|
33632
|
+
* and rebuilt.
|
|
33633
|
+
*/
|
|
33634
|
+
const buildEntryOrderIndex = (db, log) => withIndexBuildLock(db, async () => {
|
|
33635
|
+
const state = await getIndexState(db, ENTRY_ORDER_INDEX);
|
|
33636
|
+
if (state === "valid") return "exists";
|
|
33637
|
+
if (state === "invalid") {
|
|
33638
|
+
log.warn({ indexName: ENTRY_ORDER_INDEX }, "dropping invalid index left by an earlier build");
|
|
33639
|
+
await dropEntryOrderIndexConcurrently(db);
|
|
33640
|
+
}
|
|
33641
|
+
const startedAt = performance.now();
|
|
33642
|
+
await createEntryOrderIndexConcurrently(db);
|
|
33643
|
+
if (await getIndexState(db, "entry_heads_entry_order_idx") !== "valid") throw new Error(`Index ${ENTRY_ORDER_INDEX} is not valid after building`);
|
|
33644
|
+
await analyzeHeadsStatement().execute(db);
|
|
33645
|
+
log.info({
|
|
33646
|
+
indexName: ENTRY_ORDER_INDEX,
|
|
33647
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
33648
|
+
}, "entry order index built");
|
|
33649
|
+
return "built";
|
|
33650
|
+
});
|
|
33555
33651
|
//#endregion
|
|
33556
33652
|
//#region src/schema/planner/indexLayout.ts
|
|
33557
33653
|
/**
|
|
@@ -33735,7 +33831,8 @@ const createFollowUpHandler = (deps) => async (context) => {
|
|
|
33735
33831
|
const createSchemaJobHandlers = (deps) => [
|
|
33736
33832
|
[SCHEMA_CHANGE_JOB, createSchemaChangeHandler(deps)],
|
|
33737
33833
|
[SCHEMA_FOLLOW_UP_JOB, createFollowUpHandler(deps)],
|
|
33738
|
-
[FIELD_INDEX_LAYOUT_JOB, createFieldIndexLayoutHandler(deps.db)]
|
|
33834
|
+
[FIELD_INDEX_LAYOUT_JOB, createFieldIndexLayoutHandler(deps.db)],
|
|
33835
|
+
[ENTRY_ORDER_INDEX_JOB, async (context) => ({ outcome: await buildEntryOrderIndex(deps.db, context.log) })]
|
|
33739
33836
|
];
|
|
33740
33837
|
//#endregion
|
|
33741
33838
|
//#region src/publishing/failures.ts
|
|
@@ -38085,6 +38182,7 @@ const insertRevision = (revision, trx = db) => trx.insertInto("content_revisions
|
|
|
38085
38182
|
const insertHead = (head, trx = db) => trx.insertInto("entry_heads").values({
|
|
38086
38183
|
entry_id: head.entryId,
|
|
38087
38184
|
site_id: entrySiteOf(trx, head.entryId),
|
|
38185
|
+
entry_created_at: entryCreatedAtOf(trx, head.entryId),
|
|
38088
38186
|
model_id: head.modelId,
|
|
38089
38187
|
locale: head.locale,
|
|
38090
38188
|
state: head.state,
|
|
@@ -38290,7 +38388,7 @@ const EMPTY_LOCK = {
|
|
|
38290
38388
|
schemaVersion: 0,
|
|
38291
38389
|
definitions: {}
|
|
38292
38390
|
};
|
|
38293
|
-
const BATCH = 200;
|
|
38391
|
+
const BATCH$2 = 200;
|
|
38294
38392
|
const pushLimited = (list, item) => {
|
|
38295
38393
|
if (list.length < 100) list.push(item);
|
|
38296
38394
|
};
|
|
@@ -38437,9 +38535,9 @@ const scanBundle = async (context, input, acc) => {
|
|
|
38437
38535
|
const modelKey = (id) => definitionKeys.get(id) ?? context.snapshot.byId.get(id)?.definition.apiKey;
|
|
38438
38536
|
const knownLocale = (code) => bundleLocales.has(code) || context.snapshot.locales.some((locale) => locale.code === code);
|
|
38439
38537
|
const flush = async (force) => {
|
|
38440
|
-
if (users.length >= BATCH || force && users.length > 0) await planUsers(acc, users.splice(0));
|
|
38441
|
-
if (assets.length >= BATCH || force && assets.length > 0) await planAssets(acc, assets.splice(0));
|
|
38442
|
-
if (entries.length >= BATCH / 2 || force && entries.length > 0) await planEntries(acc, entries.splice(0), modelKey, knownLocale);
|
|
38538
|
+
if (users.length >= BATCH$2 || force && users.length > 0) await planUsers(acc, users.splice(0));
|
|
38539
|
+
if (assets.length >= BATCH$2 || force && assets.length > 0) await planAssets(acc, assets.splice(0));
|
|
38540
|
+
if (entries.length >= BATCH$2 / 2 || force && entries.length > 0) await planEntries(acc, entries.splice(0), modelKey, knownLocale);
|
|
38443
38541
|
};
|
|
38444
38542
|
for await (const { record } of readBundle(input)) {
|
|
38445
38543
|
switch (record.type) {
|
|
@@ -38901,7 +38999,7 @@ const openStoredBundle = (storage, stored) => storage.get(stored.driver).getStre
|
|
|
38901
38999
|
const removeStoredBundle = (storage, stored) => storage.get(stored.driver).delete(stored.key);
|
|
38902
39000
|
//#endregion
|
|
38903
39001
|
//#region src/constants/version.ts
|
|
38904
|
-
const SHAPIO_VERSION = "0.4.
|
|
39002
|
+
const SHAPIO_VERSION = "0.4.1";
|
|
38905
39003
|
//#endregion
|
|
38906
39004
|
//#region src/repositories/transferExport.ts
|
|
38907
39005
|
/**
|
|
@@ -42195,14 +42293,14 @@ const docsRoutes = async (app) => {
|
|
|
42195
42293
|
//#endregion
|
|
42196
42294
|
//#region src/db/migrations/20261001120000_create_platform_tables.ts
|
|
42197
42295
|
var _20261001120000_create_platform_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42198
|
-
down: () => down$
|
|
42199
|
-
up: () => up$
|
|
42296
|
+
down: () => down$33,
|
|
42297
|
+
up: () => up$33
|
|
42200
42298
|
});
|
|
42201
42299
|
/**
|
|
42202
42300
|
* Foundation tables shared by every package: durable versions, the job queue, the transactional outbox
|
|
42203
42301
|
* and the audit log. Statuses are text with CHECK constraints so new states need only a constraint change.
|
|
42204
42302
|
*/
|
|
42205
|
-
const up$
|
|
42303
|
+
const up$33 = async (db) => {
|
|
42206
42304
|
await db.schema.createTable("system_versions").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("schema_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("permissions_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42207
42305
|
await sql`insert into system_versions default values`.execute(db);
|
|
42208
42306
|
await db.schema.createTable("jobs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("type", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'running', 'succeeded', 'dead')`)).addColumn("priority", "integer", (col) => col.notNull().defaultTo(0)).addColumn("run_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("max_attempts", "integer", (col) => col.notNull().defaultTo(10).check(sql`max_attempts > 0`)).addColumn("idempotency_key", "text").addColumn("locked_by", "text").addColumn("locked_until", "timestamptz").addColumn("checkpoint", "jsonb").addColumn("result", "jsonb").addColumn("last_error", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
@@ -42224,7 +42322,7 @@ const up$30 = async (db) => {
|
|
|
42224
42322
|
"occurred_at desc"
|
|
42225
42323
|
]).execute();
|
|
42226
42324
|
};
|
|
42227
|
-
const down$
|
|
42325
|
+
const down$33 = async (db) => {
|
|
42228
42326
|
await db.schema.dropTable("audit_events").execute();
|
|
42229
42327
|
await db.schema.dropTable("outbox_events").execute();
|
|
42230
42328
|
await db.schema.dropTable("jobs").execute();
|
|
@@ -42233,8 +42331,8 @@ const down$30 = async (db) => {
|
|
|
42233
42331
|
//#endregion
|
|
42234
42332
|
//#region src/db/migrations/20261001130000_create_admin_identity.ts
|
|
42235
42333
|
var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll({
|
|
42236
|
-
down: () => down$
|
|
42237
|
-
up: () => up$
|
|
42334
|
+
down: () => down$32,
|
|
42335
|
+
up: () => up$32
|
|
42238
42336
|
});
|
|
42239
42337
|
/**
|
|
42240
42338
|
* Admin identity (package B, ADR 0005): admin users, roles and their permission grants, server-side
|
|
@@ -42243,7 +42341,7 @@ var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll(
|
|
|
42243
42341
|
*/
|
|
42244
42342
|
const withTimestamps$3 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
|
|
42245
42343
|
const uuidPrimaryKey$3 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42246
|
-
const up$
|
|
42344
|
+
const up$32 = async (db) => {
|
|
42247
42345
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_users")).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("name", "text", (col) => col.notNull().defaultTo("")).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("active").check(sql`status in ('active', 'disabled')`)).addColumn("last_login_at", "timestamptz").addColumn("password_changed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`))).execute();
|
|
42248
42346
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_roles")).addColumn("key", "text", (col) => col.notNull().unique().check(sql`key ~ '^[a-z][a-z0-9-]{0,62}$'`)).addColumn("name", "text", (col) => col.notNull()).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("kind", "text", (col) => col.notNull().defaultTo("admin").check(sql`kind in ('admin', 'delivery')`)).addColumn("is_system", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42249
42347
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_role_permissions")).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("cascade")).addColumn("action", "text", (col) => col.notNull()).addColumn("model_id", "text").addColumn("condition", "text", (col) => col.check(sql`condition in ('ownedByPrincipal')`)).addColumn("field_ids", sql`text[]`).addUniqueConstraint("admin_role_permissions_grant_uq", [
|
|
@@ -42263,7 +42361,7 @@ const up$29 = async (db) => {
|
|
|
42263
42361
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("api_tokens")).addColumn("name", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("restrict")).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("expires_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("revoked_at", "timestamptz")).execute();
|
|
42264
42362
|
await db.schema.createIndex("api_tokens_role_idx").on("api_tokens").column("role_id").execute();
|
|
42265
42363
|
};
|
|
42266
|
-
const down$
|
|
42364
|
+
const down$32 = async (db) => {
|
|
42267
42365
|
await db.schema.dropTable("api_tokens").execute();
|
|
42268
42366
|
await db.schema.dropTable("setup_tokens").execute();
|
|
42269
42367
|
await db.schema.dropTable("password_resets").execute();
|
|
@@ -42277,31 +42375,31 @@ const down$29 = async (db) => {
|
|
|
42277
42375
|
//#endregion
|
|
42278
42376
|
//#region src/db/migrations/20261001130500_create_system_settings.ts
|
|
42279
42377
|
var _20261001130500_create_system_settings_exports = /* @__PURE__ */ __exportAll({
|
|
42280
|
-
down: () => down$
|
|
42281
|
-
up: () => up$
|
|
42378
|
+
down: () => down$31,
|
|
42379
|
+
up: () => up$31
|
|
42282
42380
|
});
|
|
42283
42381
|
/**
|
|
42284
42382
|
* Instance-wide key/value settings that must survive restarts and be shared by every instance, e.g. the
|
|
42285
42383
|
* generated signing secret. General purpose: later packages add keys, not tables.
|
|
42286
42384
|
*/
|
|
42287
|
-
const up$
|
|
42385
|
+
const up$31 = async (db) => {
|
|
42288
42386
|
await db.schema.createTable("system_settings").addColumn("key", "text", (col) => col.primaryKey()).addColumn("value", "text", (col) => col.notNull()).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42289
42387
|
};
|
|
42290
|
-
const down$
|
|
42388
|
+
const down$31 = async (db) => {
|
|
42291
42389
|
await db.schema.dropTable("system_settings").execute();
|
|
42292
42390
|
};
|
|
42293
42391
|
//#endregion
|
|
42294
42392
|
//#region src/db/migrations/20261001140000_create_schema_tables.ts
|
|
42295
42393
|
var _20261001140000_create_schema_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42296
|
-
down: () => down$
|
|
42297
|
-
up: () => up$
|
|
42394
|
+
down: () => down$30,
|
|
42395
|
+
up: () => up$30
|
|
42298
42396
|
});
|
|
42299
42397
|
/**
|
|
42300
42398
|
* Schema registry (package D, ADR 0002): models and components (one table, `kind` discriminator), immutable
|
|
42301
42399
|
* schema revisions, the per-model active pointer, schema change jobs, locales and schema settings.
|
|
42302
42400
|
* Model definitions are data: creating or changing a model never runs DDL (CONTRIBUTING.md rule 1).
|
|
42303
42401
|
*/
|
|
42304
|
-
const up$
|
|
42402
|
+
const up$30 = async (db) => {
|
|
42305
42403
|
await db.schema.createTable("models").addColumn("id", "uuid", (col) => col.primaryKey()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('collection', 'singleton', 'component')`)).addColumn("api_key", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42306
42404
|
await sql`create unique index models_api_key_uq on models (lower(api_key)) where deleted_at is null`.execute(db);
|
|
42307
42405
|
await db.schema.createTable("schema_revisions").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("version", "integer", (col) => col.notNull().check(sql`version > 0`)).addColumn("definition", "jsonb", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("parent_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("created_by_type", "text", (col) => col.notNull()).addColumn("created_by_id", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addUniqueConstraint("schema_revisions_model_version_uq", ["model_id", "version"]).execute();
|
|
@@ -42329,7 +42427,7 @@ const up$27 = async (db) => {
|
|
|
42329
42427
|
await db.schema.createTable("schema_settings").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("read_only", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("read_only_reason", "text").addColumn("updated_by_type", "text").addColumn("updated_by_id", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42330
42428
|
await sql`insert into schema_settings default values`.execute(db);
|
|
42331
42429
|
};
|
|
42332
|
-
const down$
|
|
42430
|
+
const down$30 = async (db) => {
|
|
42333
42431
|
await db.schema.dropTable("schema_settings").execute();
|
|
42334
42432
|
await db.schema.dropTable("locales").execute();
|
|
42335
42433
|
await db.schema.dropTable("schema_change_jobs").execute();
|
|
@@ -42341,8 +42439,8 @@ const down$27 = async (db) => {
|
|
|
42341
42439
|
//#endregion
|
|
42342
42440
|
//#region src/db/migrations/20261001150000_create_content_tables.ts
|
|
42343
42441
|
var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42344
|
-
down: () => down$
|
|
42345
|
-
up: () => up$
|
|
42442
|
+
down: () => down$29,
|
|
42443
|
+
up: () => up$29
|
|
42346
42444
|
});
|
|
42347
42445
|
/**
|
|
42348
42446
|
* Content storage (package E, ADR 0001): entries, immutable content revisions, mutable heads (one per
|
|
@@ -42350,7 +42448,7 @@ var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll(
|
|
|
42350
42448
|
* and the publication log behind `?snapshot=N`. User content models never get tables of their own
|
|
42351
42449
|
* (CONTRIBUTING.md rule 1): their values are JSONB keyed by stable field IDs.
|
|
42352
42450
|
*/
|
|
42353
|
-
const up$
|
|
42451
|
+
const up$29 = async (db) => {
|
|
42354
42452
|
await db.schema.createTable("entries").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("owner_app_user_id", "uuid").addColumn("created_by_admin_id", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42355
42453
|
await sql`create index entries_model_idx on entries (model_id, created_at) where deleted_at is null`.execute(db);
|
|
42356
42454
|
await sql`create index entries_owner_idx on entries (owner_app_user_id) where owner_app_user_id is not null`.execute(db);
|
|
@@ -42414,7 +42512,7 @@ const up$26 = async (db) => {
|
|
|
42414
42512
|
await sql`create unique index publication_log_open_uq on publication_log (entry_id, locale) where to_seq is null`.execute(db);
|
|
42415
42513
|
await db.schema.createIndex("publication_log_model_seq_idx").on("publication_log").columns(["model_id", "from_seq"]).execute();
|
|
42416
42514
|
};
|
|
42417
|
-
const down$
|
|
42515
|
+
const down$29 = async (db) => {
|
|
42418
42516
|
await db.schema.dropTable("publication_log").execute();
|
|
42419
42517
|
await db.schema.dropTable("publication_state").execute();
|
|
42420
42518
|
await db.schema.dropTable("unique_values").execute();
|
|
@@ -42427,8 +42525,8 @@ const down$26 = async (db) => {
|
|
|
42427
42525
|
//#endregion
|
|
42428
42526
|
//#region src/db/migrations/20261001160000_create_media_tables.ts
|
|
42429
42527
|
var _20261001160000_create_media_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42430
|
-
down: () => down$
|
|
42431
|
-
up: () => up$
|
|
42528
|
+
down: () => down$28,
|
|
42529
|
+
up: () => up$28
|
|
42432
42530
|
});
|
|
42433
42531
|
/**
|
|
42434
42532
|
* Media library (package G): folders, assets, image variants, references from content and upload grants.
|
|
@@ -42439,7 +42537,7 @@ const withTimestamps$2 = (table) => table.addColumn("created_at", "timestamptz",
|
|
|
42439
42537
|
const uuidPrimaryKey$2 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42440
42538
|
const STORAGE_DRIVER_CHECK = sql`storage_driver in ('local', 's3')`;
|
|
42441
42539
|
const VISIBILITY_CHECK = sql`visibility in ('public', 'private')`;
|
|
42442
|
-
const up$
|
|
42540
|
+
const up$28 = async (db) => {
|
|
42443
42541
|
await withTimestamps$2(uuidPrimaryKey$2(db.schema.createTable("media_folders")).addColumn("parent_id", "uuid", (col) => col.references("media_folders.id").onDelete("restrict")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 255`)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42444
42542
|
await sql`create unique index media_folders_sibling_name_uq on media_folders
|
|
42445
42543
|
(coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
|
|
@@ -42468,7 +42566,7 @@ const up$25 = async (db) => {
|
|
|
42468
42566
|
]).execute();
|
|
42469
42567
|
await db.schema.createTable("media_upload_grants").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("asset_id", "uuid", (col) => col.notNull()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('create', 'replace')`)).addColumn("storage_driver", "text", (col) => col.notNull().check(STORAGE_DRIVER_CHECK)).addColumn("storage_key", "text", (col) => col.notNull().unique()).addColumn("original_filename", "text", (col) => col.notNull()).addColumn("declared_mime_type", "text", (col) => col.notNull()).addColumn("max_size_bytes", "bigint", (col) => col.notNull()).addColumn("folder_id", "uuid").addColumn("visibility", "text", (col) => col.notNull().check(VISIBILITY_CHECK)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_by_principal", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'consumed', 'rejected', 'expired')`)).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("consumed_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42470
42568
|
};
|
|
42471
|
-
const down$
|
|
42569
|
+
const down$28 = async (db) => {
|
|
42472
42570
|
await db.schema.dropTable("media_upload_grants").execute();
|
|
42473
42571
|
await db.schema.dropTable("media_references").execute();
|
|
42474
42572
|
await db.schema.dropTable("media_variants").execute();
|
|
@@ -42478,8 +42576,8 @@ const down$25 = async (db) => {
|
|
|
42478
42576
|
//#endregion
|
|
42479
42577
|
//#region src/db/migrations/20261001170000_create_publishing_tables.ts
|
|
42480
42578
|
var _20261001170000_create_publishing_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42481
|
-
down: () => down$
|
|
42482
|
-
up: () => up$
|
|
42579
|
+
down: () => down$27,
|
|
42580
|
+
up: () => up$27
|
|
42483
42581
|
});
|
|
42484
42582
|
/**
|
|
42485
42583
|
* Publishing (package H): scheduled publications, releases, webhooks and their delivery log, deployment
|
|
@@ -42518,13 +42616,13 @@ const createPreviewTables = async (db) => {
|
|
|
42518
42616
|
await uuidPrimaryKey$1(db.schema.createTable("preview_tokens")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("entry_id", "uuid", (col) => col.references("entries.id").onDelete("cascade")).addColumn("locale", "text").addColumn("connection_id", "uuid", (col) => col.references("deployment_connections.id").onDelete("set null")).addColumn("delivery_role_id", "uuid", (col) => col.references("admin_roles.id").onDelete("set null")).addColumn("created_by", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("revoked_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42519
42617
|
await db.schema.createIndex("preview_tokens_entry_idx").on("preview_tokens").columns(["entry_id", "created_at desc"]).execute();
|
|
42520
42618
|
};
|
|
42521
|
-
const up$
|
|
42619
|
+
const up$27 = async (db) => {
|
|
42522
42620
|
await createSchedulingTables(db);
|
|
42523
42621
|
await createWebhookTables(db);
|
|
42524
42622
|
await createDeploymentTables(db);
|
|
42525
42623
|
await createPreviewTables(db);
|
|
42526
42624
|
};
|
|
42527
|
-
const down$
|
|
42625
|
+
const down$27 = async (db) => {
|
|
42528
42626
|
await db.schema.dropTable("preview_tokens").execute();
|
|
42529
42627
|
await db.schema.dropTable("deployment_runs").execute();
|
|
42530
42628
|
await db.schema.dropTable("deployment_connections").execute();
|
|
@@ -42537,8 +42635,8 @@ const down$24 = async (db) => {
|
|
|
42537
42635
|
//#endregion
|
|
42538
42636
|
//#region src/db/migrations/20261001180000_create_app_users.ts
|
|
42539
42637
|
var _20261001180000_create_app_users_exports = /* @__PURE__ */ __exportAll({
|
|
42540
|
-
down: () => down$
|
|
42541
|
-
up: () => up$
|
|
42638
|
+
down: () => down$26,
|
|
42639
|
+
up: () => up$26
|
|
42542
42640
|
});
|
|
42543
42641
|
/**
|
|
42544
42642
|
* App users (package I, ADR 0005): the end users of sites and apps built on Shapio, with their own roles
|
|
@@ -42589,7 +42687,7 @@ const createCredentials = async (db) => {
|
|
|
42589
42687
|
}
|
|
42590
42688
|
await db.schema.createTable("app_login_codes").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("code_hash", "text", (col) => col.notNull().unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42591
42689
|
};
|
|
42592
|
-
const up$
|
|
42690
|
+
const up$26 = async (db) => {
|
|
42593
42691
|
await createUsers(db);
|
|
42594
42692
|
await createRoles(db);
|
|
42595
42693
|
await createCredentials(db);
|
|
@@ -42599,7 +42697,7 @@ const up$23 = async (db) => {
|
|
|
42599
42697
|
foreign key (owner_app_user_id) references app_users (id) on delete set null
|
|
42600
42698
|
`.execute(db);
|
|
42601
42699
|
};
|
|
42602
|
-
const down$
|
|
42700
|
+
const down$26 = async (db) => {
|
|
42603
42701
|
await sql`alter table entries drop constraint if exists entries_owner_app_user_fk`.execute(db);
|
|
42604
42702
|
await db.schema.dropTable("app_login_codes").execute();
|
|
42605
42703
|
await db.schema.dropTable("app_password_resets").execute();
|
|
@@ -42614,8 +42712,8 @@ const down$23 = async (db) => {
|
|
|
42614
42712
|
//#endregion
|
|
42615
42713
|
//#region src/db/migrations/20261002120000_create_extension_hook_runs.ts
|
|
42616
42714
|
var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __exportAll({
|
|
42617
|
-
down: () => down$
|
|
42618
|
-
up: () => up$
|
|
42715
|
+
down: () => down$25,
|
|
42716
|
+
up: () => up$25
|
|
42619
42717
|
});
|
|
42620
42718
|
/**
|
|
42621
42719
|
* Extension points (package K, ADR 0009): the ledger of post-commit lifecycle hooks (`afterCreate`,
|
|
@@ -42623,17 +42721,17 @@ var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __expor
|
|
|
42623
42721
|
* as the hook's own database work, so a hook re-run after a worker crash finds its row and is skipped:
|
|
42624
42722
|
* exactly once for everything the hook does through its transaction.
|
|
42625
42723
|
*/
|
|
42626
|
-
const up$
|
|
42724
|
+
const up$25 = async (db) => {
|
|
42627
42725
|
await db.schema.createTable("extension_hook_runs").addColumn("event_id", "uuid", (col) => col.notNull()).addColumn("hook", "text", (col) => col.notNull()).addColumn("job_id", "uuid").addColumn("completed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("extension_hook_runs_pkey", ["event_id", "hook"]).execute();
|
|
42628
42726
|
};
|
|
42629
|
-
const down$
|
|
42727
|
+
const down$25 = async (db) => {
|
|
42630
42728
|
await db.schema.dropTable("extension_hook_runs").execute();
|
|
42631
42729
|
};
|
|
42632
42730
|
//#endregion
|
|
42633
42731
|
//#region src/db/migrations/20261002130000_app_auth_token_version_and_login_code_pkce.ts
|
|
42634
42732
|
var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__PURE__ */ __exportAll({
|
|
42635
|
-
down: () => down$
|
|
42636
|
-
up: () => up$
|
|
42733
|
+
down: () => down$24,
|
|
42734
|
+
up: () => up$24
|
|
42637
42735
|
});
|
|
42638
42736
|
/**
|
|
42639
42737
|
* App users (ADR 0005):
|
|
@@ -42644,20 +42742,20 @@ var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__P
|
|
|
42644
42742
|
* sign-in; the one-time login code is only exchanged with the matching verifier. Codes live a minute, so
|
|
42645
42743
|
* any pending ones (issued without a challenge) are dropped rather than given a placeholder.
|
|
42646
42744
|
*/
|
|
42647
|
-
const up$
|
|
42745
|
+
const up$24 = async (db) => {
|
|
42648
42746
|
await db.schema.alterTable("app_users").addColumn("token_version", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
42649
42747
|
await sql`delete from app_login_codes`.execute(db);
|
|
42650
42748
|
await db.schema.alterTable("app_login_codes").addColumn("code_challenge", "text", (col) => col.notNull()).execute();
|
|
42651
42749
|
};
|
|
42652
|
-
const down$
|
|
42750
|
+
const down$24 = async (db) => {
|
|
42653
42751
|
await db.schema.alterTable("app_login_codes").dropColumn("code_challenge").execute();
|
|
42654
42752
|
await db.schema.alterTable("app_users").dropColumn("token_version").execute();
|
|
42655
42753
|
};
|
|
42656
42754
|
//#endregion
|
|
42657
42755
|
//#region src/db/migrations/20261002180000_add_publication_log_diff_indexes.ts
|
|
42658
42756
|
var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
42659
|
-
down: () => down$
|
|
42660
|
-
up: () => up$
|
|
42757
|
+
down: () => down$23,
|
|
42758
|
+
up: () => up$23
|
|
42661
42759
|
});
|
|
42662
42760
|
/**
|
|
42663
42761
|
* The snapshot diff (repositories/snapshotDiff.ts) finds the (entry, locale) pairs whose live period opened
|
|
@@ -42666,7 +42764,7 @@ var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ _
|
|
|
42666
42764
|
* - `(entry_id, locale, from_seq)`: "the row live at seq N" is the latest row starting at or before N.
|
|
42667
42765
|
* Periods that opened in the range use the existing `(model_id, from_seq)` index.
|
|
42668
42766
|
*/
|
|
42669
|
-
const up$
|
|
42767
|
+
const up$23 = async (db) => {
|
|
42670
42768
|
await sql`create index publication_log_to_seq_idx on publication_log (to_seq) where to_seq is not null`.execute(db);
|
|
42671
42769
|
await db.schema.createIndex("publication_log_entry_locale_from_idx").on("publication_log").columns([
|
|
42672
42770
|
"entry_id",
|
|
@@ -42674,15 +42772,15 @@ const up$20 = async (db) => {
|
|
|
42674
42772
|
"from_seq"
|
|
42675
42773
|
]).execute();
|
|
42676
42774
|
};
|
|
42677
|
-
const down$
|
|
42775
|
+
const down$23 = async (db) => {
|
|
42678
42776
|
await db.schema.dropIndex("publication_log_entry_locale_from_idx").execute();
|
|
42679
42777
|
await db.schema.dropIndex("publication_log_to_seq_idx").execute();
|
|
42680
42778
|
};
|
|
42681
42779
|
//#endregion
|
|
42682
42780
|
//#region src/db/migrations/20261002180100_create_usage_tables.ts
|
|
42683
42781
|
var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42684
|
-
down: () => down$
|
|
42685
|
-
up: () => up$
|
|
42782
|
+
down: () => down$22,
|
|
42783
|
+
up: () => up$22
|
|
42686
42784
|
});
|
|
42687
42785
|
/**
|
|
42688
42786
|
* Field usage from delivery traffic (plan developer-face §5): daily counters written by the in-memory
|
|
@@ -42690,7 +42788,7 @@ var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
|
42690
42788
|
* payloads. `principal_key` is `token:<id>`, `app_users` or `anonymous`. Pruned by the retention job
|
|
42691
42789
|
* after USAGE_RETENTION_DAYS.
|
|
42692
42790
|
*/
|
|
42693
|
-
const up$
|
|
42791
|
+
const up$22 = async (db) => {
|
|
42694
42792
|
await db.schema.createTable("field_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("field_path", "text", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("selection", "text", (col) => col.notNull().check(sql`selection in ('explicit', 'implicit')`)).addColumn("reads", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("field_reads_pkey", [
|
|
42695
42793
|
"day",
|
|
42696
42794
|
"model_id",
|
|
@@ -42701,15 +42799,15 @@ const up$19 = async (db) => {
|
|
|
42701
42799
|
await db.schema.createIndex("field_reads_model_day_idx").on("field_reads").columns(["model_id", "day"]).execute();
|
|
42702
42800
|
await db.schema.createTable("token_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("requests", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_snapshot", "bigint").addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("token_reads_pkey", ["day", "principal_key"]).execute();
|
|
42703
42801
|
};
|
|
42704
|
-
const down$
|
|
42802
|
+
const down$22 = async (db) => {
|
|
42705
42803
|
await db.schema.dropTable("token_reads").execute();
|
|
42706
42804
|
await db.schema.dropTable("field_reads").execute();
|
|
42707
42805
|
};
|
|
42708
42806
|
//#endregion
|
|
42709
42807
|
//#region src/db/migrations/20261002180200_create_change_sets.ts
|
|
42710
42808
|
var _20261002180200_create_change_sets_exports = /* @__PURE__ */ __exportAll({
|
|
42711
|
-
down: () => down$
|
|
42712
|
-
up: () => up$
|
|
42809
|
+
down: () => down$21,
|
|
42810
|
+
up: () => up$21
|
|
42713
42811
|
});
|
|
42714
42812
|
/**
|
|
42715
42813
|
* Change sets (developer-face plan §5, ADR 0010): schema drafts and entry publications that ship as ONE
|
|
@@ -42888,7 +42986,7 @@ const migrateReleases = async (db) => {
|
|
|
42888
42986
|
await sql`drop table release_items`.execute(db);
|
|
42889
42987
|
await sql`drop table releases`.execute(db);
|
|
42890
42988
|
};
|
|
42891
|
-
const up$
|
|
42989
|
+
const up$21 = async (db) => {
|
|
42892
42990
|
await createTables(db);
|
|
42893
42991
|
await linkExecutionRecords(db);
|
|
42894
42992
|
await allowConversionRevisions(db);
|
|
@@ -42960,7 +43058,7 @@ const restoreReleases = async (db) => {
|
|
|
42960
43058
|
await sql`update webhooks set events = array_remove(array_remove(events, 'change_set.shipping'),
|
|
42961
43059
|
'change_set.discarded')`.execute(db);
|
|
42962
43060
|
};
|
|
42963
|
-
const down$
|
|
43061
|
+
const down$21 = async (db) => {
|
|
42964
43062
|
await restoreReleases(db);
|
|
42965
43063
|
await sql`alter table content_revisions drop constraint content_revisions_reason_check`.execute(db);
|
|
42966
43064
|
await sql`alter table content_revisions add constraint content_revisions_reason_check
|
|
@@ -42981,8 +43079,8 @@ const down$18 = async (db) => {
|
|
|
42981
43079
|
//#endregion
|
|
42982
43080
|
//#region src/db/migrations/20261002190000_create_editor_presence_and_content_health.ts
|
|
42983
43081
|
var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PURE__ */ __exportAll({
|
|
42984
|
-
down: () => down$
|
|
42985
|
-
up: () => up$
|
|
43082
|
+
down: () => down$20,
|
|
43083
|
+
up: () => up$20
|
|
42986
43084
|
});
|
|
42987
43085
|
/**
|
|
42988
43086
|
* Editor experience (plan editor-experience §9):
|
|
@@ -42993,7 +43091,7 @@ var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PU
|
|
|
42993
43091
|
* - `content_health_findings`: open and recently resolved findings of the health rules per entry and
|
|
42994
43092
|
* locale (`*` for the entry as a whole). An evaluation upserts what it found and resolves the rest.
|
|
42995
43093
|
*/
|
|
42996
|
-
const up$
|
|
43094
|
+
const up$20 = async (db) => {
|
|
42997
43095
|
await sql`
|
|
42998
43096
|
create unlogged table editor_presence (
|
|
42999
43097
|
entry_id uuid not null,
|
|
@@ -43030,15 +43128,15 @@ const up$17 = async (db) => {
|
|
|
43030
43128
|
await sql`create index content_health_findings_resolved_idx on content_health_findings (resolved_at)
|
|
43031
43129
|
where resolved_at is not null`.execute(db);
|
|
43032
43130
|
};
|
|
43033
|
-
const down$
|
|
43131
|
+
const down$20 = async (db) => {
|
|
43034
43132
|
await sql`drop table content_health_findings`.execute(db);
|
|
43035
43133
|
await sql`drop table editor_presence`.execute(db);
|
|
43036
43134
|
};
|
|
43037
43135
|
//#endregion
|
|
43038
43136
|
//#region src/db/migrations/20261003120000_add_changes_ship_permission.ts
|
|
43039
43137
|
var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __exportAll({
|
|
43040
|
-
down: () => down$
|
|
43041
|
-
up: () => up$
|
|
43138
|
+
down: () => down$19,
|
|
43139
|
+
up: () => up$19
|
|
43042
43140
|
});
|
|
43043
43141
|
/**
|
|
43044
43142
|
* `changes.ship` (agentic plan §I): shipping a change set (and scheduling a ship) becomes its own global
|
|
@@ -43046,7 +43144,7 @@ var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __expo
|
|
|
43046
43144
|
* live. Every role that holds `changes.manage` today keeps what it could do: it gets `changes.ship` too.
|
|
43047
43145
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43048
43146
|
*/
|
|
43049
|
-
const up$
|
|
43147
|
+
const up$19 = async (db) => {
|
|
43050
43148
|
await sql`
|
|
43051
43149
|
with granted as (
|
|
43052
43150
|
insert into admin_role_permissions (role_id, action, model_id, condition, field_ids)
|
|
@@ -43060,7 +43158,7 @@ const up$16 = async (db) => {
|
|
|
43060
43158
|
where exists (select 1 from granted)
|
|
43061
43159
|
`.execute(db);
|
|
43062
43160
|
};
|
|
43063
|
-
const down$
|
|
43161
|
+
const down$19 = async (db) => {
|
|
43064
43162
|
await sql`
|
|
43065
43163
|
with revoked as (
|
|
43066
43164
|
delete from admin_role_permissions where action = 'changes.ship' returning 1
|
|
@@ -43072,8 +43170,8 @@ const down$16 = async (db) => {
|
|
|
43072
43170
|
//#endregion
|
|
43073
43171
|
//#region src/db/migrations/20261003130000_create_sites.ts
|
|
43074
43172
|
var _20261003130000_create_sites_exports = /* @__PURE__ */ __exportAll({
|
|
43075
|
-
down: () => down$
|
|
43076
|
-
up: () => up$
|
|
43173
|
+
down: () => down$18,
|
|
43174
|
+
up: () => up$18
|
|
43077
43175
|
});
|
|
43078
43176
|
/**
|
|
43079
43177
|
* Sites (plan agentic-ecosystem §H, ADR 0011): one instance hosts many sites with one schema and one login.
|
|
@@ -43261,7 +43359,7 @@ const siteRoleAssignments = async (db) => {
|
|
|
43261
43359
|
`.execute(db);
|
|
43262
43360
|
await sql`alter table admin_invitations drop column role_ids`.execute(db);
|
|
43263
43361
|
};
|
|
43264
|
-
const up$
|
|
43362
|
+
const up$18 = async (db) => {
|
|
43265
43363
|
await createSites(db);
|
|
43266
43364
|
await addSiteColumns(db);
|
|
43267
43365
|
await tieCopiesToEntries(db);
|
|
@@ -43274,7 +43372,7 @@ const assertSingleSite = async (db) => {
|
|
|
43274
43372
|
const { rows } = await sql`select count(*)::text as count from sites`.execute(db);
|
|
43275
43373
|
if (Number(rows[0]?.count ?? 0) > 1) throw new Error("Cannot roll back sites while more than one site exists: merging them would merge their snapshot ledgers and uniqueness and widen role assignments. Delete the other sites first.");
|
|
43276
43374
|
};
|
|
43277
|
-
const down$
|
|
43375
|
+
const down$18 = async (db) => {
|
|
43278
43376
|
await assertSingleSite(db);
|
|
43279
43377
|
await sql`alter table admin_invitations add column role_ids uuid[] not null default '{}'::uuid[]`.execute(db);
|
|
43280
43378
|
await sql`
|
|
@@ -43345,8 +43443,8 @@ const down$15 = async (db) => {
|
|
|
43345
43443
|
//#endregion
|
|
43346
43444
|
//#region src/db/migrations/20261003140200_enqueue_field_index_layout.ts
|
|
43347
43445
|
var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __exportAll({
|
|
43348
|
-
down: () => down$
|
|
43349
|
-
up: () => up$
|
|
43446
|
+
down: () => down$17,
|
|
43447
|
+
up: () => up$17
|
|
43350
43448
|
});
|
|
43351
43449
|
/**
|
|
43352
43450
|
* Field index layout v2 (sites plan §H): per-field expression indexes on `entry_heads` lead with `site_id`,
|
|
@@ -43356,12 +43454,12 @@ var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __expor
|
|
|
43356
43454
|
* old ones while the instance keeps serving. Nothing to rebuild (no field index yet, e.g. a new install):
|
|
43357
43455
|
* no job.
|
|
43358
43456
|
*/
|
|
43359
|
-
const JOB_TYPE = "schema.fieldIndexLayout";
|
|
43360
|
-
const IDEMPOTENCY_KEY = "schema.fieldIndexLayout:v2";
|
|
43361
|
-
const up$
|
|
43457
|
+
const JOB_TYPE$1 = "schema.fieldIndexLayout";
|
|
43458
|
+
const IDEMPOTENCY_KEY$1 = "schema.fieldIndexLayout:v2";
|
|
43459
|
+
const up$17 = async (db) => {
|
|
43362
43460
|
await sql`
|
|
43363
43461
|
insert into jobs (type, payload, max_attempts, idempotency_key)
|
|
43364
|
-
select ${JOB_TYPE}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY}
|
|
43462
|
+
select ${JOB_TYPE$1}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY$1}
|
|
43365
43463
|
where exists (
|
|
43366
43464
|
select 1 from pg_catalog.pg_index i
|
|
43367
43465
|
join pg_catalog.pg_class c on c.oid = i.indexrelid
|
|
@@ -43371,14 +43469,14 @@ const up$14 = async (db) => {
|
|
|
43371
43469
|
`.execute(db);
|
|
43372
43470
|
};
|
|
43373
43471
|
/** The layout job is idempotent; rolling back only forgets it if it has not run yet. */
|
|
43374
|
-
const down$
|
|
43375
|
-
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
|
|
43472
|
+
const down$17 = async (db) => {
|
|
43473
|
+
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY$1} and status = 'pending'`.execute(db);
|
|
43376
43474
|
};
|
|
43377
43475
|
//#endregion
|
|
43378
43476
|
//#region src/db/migrations/20261003140500_change_set_items_site_and_preview_entry.ts
|
|
43379
43477
|
var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE__ */ __exportAll({
|
|
43380
|
-
down: () => down$
|
|
43381
|
-
up: () => up$
|
|
43478
|
+
down: () => down$16,
|
|
43479
|
+
up: () => up$16
|
|
43382
43480
|
});
|
|
43383
43481
|
/**
|
|
43384
43482
|
* Sites, publishing (plan agentic-ecosystem §H, package G5).
|
|
@@ -43389,7 +43487,7 @@ var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE
|
|
|
43389
43487
|
* - `preview_tokens.entry_id` becomes required: a preview token previews one entry of one site. Model-wide
|
|
43390
43488
|
* tokens (short-lived by design) are deleted; whoever needs one opens the entry's preview again.
|
|
43391
43489
|
*/
|
|
43392
|
-
const up$
|
|
43490
|
+
const up$16 = async (db) => {
|
|
43393
43491
|
await sql`alter table change_sets add constraint change_sets_id_site_uq unique (id, site_id)`.execute(db);
|
|
43394
43492
|
await sql`alter table change_set_items add column site_id uuid`.execute(db);
|
|
43395
43493
|
await sql`
|
|
@@ -43405,7 +43503,7 @@ const up$13 = async (db) => {
|
|
|
43405
43503
|
await sql`delete from preview_tokens where entry_id is null`.execute(db);
|
|
43406
43504
|
await sql`alter table preview_tokens alter column entry_id set not null`.execute(db);
|
|
43407
43505
|
};
|
|
43408
|
-
const down$
|
|
43506
|
+
const down$16 = async (db) => {
|
|
43409
43507
|
await sql`alter table preview_tokens alter column entry_id drop not null`.execute(db);
|
|
43410
43508
|
await sql`alter table change_set_items drop constraint change_set_items_entry_site_fk`.execute(db);
|
|
43411
43509
|
await sql`alter table change_set_items drop constraint change_set_items_set_site_fk`.execute(db);
|
|
@@ -43415,8 +43513,8 @@ const down$13 = async (db) => {
|
|
|
43415
43513
|
//#endregion
|
|
43416
43514
|
//#region src/db/migrations/20261003150000_widen_deployment_providers.ts
|
|
43417
43515
|
var _20261003150000_widen_deployment_providers_exports = /* @__PURE__ */ __exportAll({
|
|
43418
|
-
down: () => down$
|
|
43419
|
-
up: () => up$
|
|
43516
|
+
down: () => down$15,
|
|
43517
|
+
up: () => up$15
|
|
43420
43518
|
});
|
|
43421
43519
|
/**
|
|
43422
43520
|
* Deploy adapters (plan agentic-ecosystem §B2): deployment connections may use the Vercel and Netlify
|
|
@@ -43431,11 +43529,11 @@ const replaceCheck = async (db, check) => {
|
|
|
43431
43529
|
check (${check}) not valid`.execute(db);
|
|
43432
43530
|
await sql`alter table deployment_connections validate constraint deployment_connections_provider_check`.execute(db);
|
|
43433
43531
|
};
|
|
43434
|
-
const up$
|
|
43532
|
+
const up$15 = async (db) => {
|
|
43435
43533
|
await replaceCheck(db, WIDENED);
|
|
43436
43534
|
};
|
|
43437
43535
|
/** Refuses while Vercel or Netlify connections exist: a down migration never deletes user data. */
|
|
43438
|
-
const down$
|
|
43536
|
+
const down$15 = async (db) => {
|
|
43439
43537
|
const { rows } = await sql`
|
|
43440
43538
|
select count(*)::text as count from deployment_connections where provider in ('vercel', 'netlify')
|
|
43441
43539
|
`.execute(db);
|
|
@@ -43446,8 +43544,8 @@ const down$12 = async (db) => {
|
|
|
43446
43544
|
//#endregion
|
|
43447
43545
|
//#region src/db/migrations/20261003160000_create_assist_runs.ts
|
|
43448
43546
|
var _20261003160000_create_assist_runs_exports = /* @__PURE__ */ __exportAll({
|
|
43449
|
-
down: () => down$
|
|
43450
|
-
up: () => up$
|
|
43547
|
+
down: () => down$14,
|
|
43548
|
+
up: () => up$14
|
|
43451
43549
|
});
|
|
43452
43550
|
/**
|
|
43453
43551
|
* Editor assists (plan agentic-ecosystem §A0, §I): one `assist_runs` row per assist request (who, which
|
|
@@ -43463,14 +43561,14 @@ const replaceSourceCheck = async (db, check) => {
|
|
|
43463
43561
|
await sql`alter table change_sets add constraint change_sets_source_check check (${check}) not valid`.execute(db);
|
|
43464
43562
|
await sql`alter table change_sets validate constraint change_sets_source_check`.execute(db);
|
|
43465
43563
|
};
|
|
43466
|
-
const up$
|
|
43564
|
+
const up$14 = async (db) => {
|
|
43467
43565
|
await db.schema.createTable("assist_runs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("site_id", "uuid", (col) => col.notNull().references("sites.id").onDelete("cascade")).addColumn("actor_type", "text", (col) => col.notNull()).addColumn("actor_id", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(ACTIONS)).addColumn("rule", "text").addColumn("provider", "text", (col) => col.notNull()).addColumn("model", "text", (col) => col.notNull()).addColumn("input_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("output_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("duration_ms", "integer", (col) => col.notNull().defaultTo(0)).addColumn("status", "text", (col) => col.notNull().check(sql`status in ('queued', 'running', 'succeeded', 'failed')`)).addColumn("error_code", "text").addColumn("job_id", "uuid", (col) => col.references("jobs.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
43468
43566
|
await db.schema.createIndex("assist_runs_site_created_idx").on("assist_runs").columns(["site_id", "created_at"]).execute();
|
|
43469
43567
|
await db.schema.createIndex("assist_runs_created_idx").on("assist_runs").column("created_at").execute();
|
|
43470
43568
|
await replaceSourceCheck(db, SOURCES_WIDENED);
|
|
43471
43569
|
};
|
|
43472
43570
|
/** Refuses while change sets opened by assist exist: a down migration never deletes user data. */
|
|
43473
|
-
const down$
|
|
43571
|
+
const down$14 = async (db) => {
|
|
43474
43572
|
const { rows } = await sql`
|
|
43475
43573
|
select count(*)::text as count from change_sets where source = 'assist'
|
|
43476
43574
|
`.execute(db);
|
|
@@ -43482,8 +43580,8 @@ const down$11 = async (db) => {
|
|
|
43482
43580
|
//#endregion
|
|
43483
43581
|
//#region src/db/migrations/20261003170000_audit_events_seq.ts
|
|
43484
43582
|
var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43485
|
-
down: () => down$
|
|
43486
|
-
up: () => up$
|
|
43583
|
+
down: () => down$13,
|
|
43584
|
+
up: () => up$13
|
|
43487
43585
|
});
|
|
43488
43586
|
/**
|
|
43489
43587
|
* `audit_events.seq`: a monotonic tiebreaker after `occurred_at`, so events written in the same instant (one
|
|
@@ -43492,17 +43590,17 @@ var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43492
43590
|
* are numbered in the order the rewrite reads them. No index: the audit queries filter and sort by
|
|
43493
43591
|
* `occurred_at` first, and `seq` only breaks ties.
|
|
43494
43592
|
*/
|
|
43495
|
-
const up$
|
|
43593
|
+
const up$13 = async (db) => {
|
|
43496
43594
|
await db.schema.alterTable("audit_events").addColumn("seq", "bigint", (col) => col.notNull().generatedAlwaysAsIdentity()).execute();
|
|
43497
43595
|
};
|
|
43498
|
-
const down$
|
|
43596
|
+
const down$13 = async (db) => {
|
|
43499
43597
|
await db.schema.alterTable("audit_events").dropColumn("seq").execute();
|
|
43500
43598
|
};
|
|
43501
43599
|
//#endregion
|
|
43502
43600
|
//#region src/db/migrations/20261004120000_models_site_scope.ts
|
|
43503
43601
|
var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43504
|
-
down: () => down$
|
|
43505
|
-
up: () => up$
|
|
43602
|
+
down: () => down$12,
|
|
43603
|
+
up: () => up$12
|
|
43506
43604
|
});
|
|
43507
43605
|
/**
|
|
43508
43606
|
* Per-site schema (plan site-schema): a definition belongs to one site (`models.site_id`) or is shared by every
|
|
@@ -43515,7 +43613,7 @@ var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43515
43613
|
* over `lower(api_key)` cannot hold. Share or delete them first; then `down` is lossless.
|
|
43516
43614
|
*/
|
|
43517
43615
|
const NIL_UUID$1 = "00000000-0000-0000-0000-000000000000";
|
|
43518
|
-
const up$
|
|
43616
|
+
const up$12 = async (db) => {
|
|
43519
43617
|
await sql`alter table models add column site_id uuid references sites (id)`.execute(db);
|
|
43520
43618
|
await sql`create index models_site_idx on models (site_id)`.execute(db);
|
|
43521
43619
|
await sql`drop index models_api_key_uq`.execute(db);
|
|
@@ -43525,7 +43623,7 @@ const up$9 = async (db) => {
|
|
|
43525
43623
|
`.execute(db);
|
|
43526
43624
|
await sql`alter table schema_drafts add column shared boolean not null default false`.execute(db);
|
|
43527
43625
|
};
|
|
43528
|
-
const down$
|
|
43626
|
+
const down$12 = async (db) => {
|
|
43529
43627
|
const { rows } = await sql`select count(*)::text as count from models where site_id is not null`.execute(db);
|
|
43530
43628
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
43531
43629
|
await sql`alter table schema_drafts drop column shared`.execute(db);
|
|
@@ -43537,8 +43635,8 @@ const down$9 = async (db) => {
|
|
|
43537
43635
|
//#endregion
|
|
43538
43636
|
//#region src/db/migrations/20261004130000_site_seo_settings.ts
|
|
43539
43637
|
var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43540
|
-
down: () => down$
|
|
43541
|
-
up: () => up$
|
|
43638
|
+
down: () => down$11,
|
|
43639
|
+
up: () => up$11
|
|
43542
43640
|
});
|
|
43543
43641
|
/**
|
|
43544
43642
|
* SEO defaults per site (plan seo-fields): `sites.seo_defaults` holds the site's default texts per locale, its
|
|
@@ -43549,7 +43647,7 @@ var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43549
43647
|
* about what a site publishes) gets it; the built-in owner and admin roles get it from `ensureSystemRoles`.
|
|
43550
43648
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43551
43649
|
*/
|
|
43552
|
-
const up$
|
|
43650
|
+
const up$11 = async (db) => {
|
|
43553
43651
|
await sql`alter table sites add column seo_defaults jsonb`.execute(db);
|
|
43554
43652
|
await sql`
|
|
43555
43653
|
with granted as (
|
|
@@ -43564,7 +43662,7 @@ const up$8 = async (db) => {
|
|
|
43564
43662
|
where exists (select 1 from granted)
|
|
43565
43663
|
`.execute(db);
|
|
43566
43664
|
};
|
|
43567
|
-
const down$
|
|
43665
|
+
const down$11 = async (db) => {
|
|
43568
43666
|
await sql`
|
|
43569
43667
|
with revoked as (
|
|
43570
43668
|
delete from admin_role_permissions where action = 'site.settings' returning 1
|
|
@@ -43575,10 +43673,61 @@ const down$8 = async (db) => {
|
|
|
43575
43673
|
await sql`alter table sites drop column seo_defaults`.execute(db);
|
|
43576
43674
|
};
|
|
43577
43675
|
//#endregion
|
|
43676
|
+
//#region src/db/migrations/20261005120000_entry_heads_entry_created_at.ts
|
|
43677
|
+
var _20261005120000_entry_heads_entry_created_at_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43678
|
+
down: () => down$10,
|
|
43679
|
+
up: () => up$10
|
|
43680
|
+
});
|
|
43681
|
+
/**
|
|
43682
|
+
* Delivery perf 2 (ADR 0001 amendment): the entry's `created_at` on each head (`entry_created_at`), so a list
|
|
43683
|
+
* in the default newest-first order walks `entry_heads_entry_order_idx` and stops at the page instead of
|
|
43684
|
+
* joining every matching head to `entries` and sorting them. `entries.created_at` never changes, and every head
|
|
43685
|
+
* insert copies it (repositories `entryHeads.insert`, `transferImport.insertHead`).
|
|
43686
|
+
*
|
|
43687
|
+
* The column is added nullable (a catalog-only change) and backfilled here in batches. The index is built
|
|
43688
|
+
* here on an empty table (a new install). With heads present it is built `CONCURRENTLY`, which cannot run
|
|
43689
|
+
* in a migration's transaction, by the `schema.entryOrderIndex` job this migration enqueues
|
|
43690
|
+
* (schema/planner/indexes.ts), so writes continue while it builds; queries are correct without it.
|
|
43691
|
+
*/
|
|
43692
|
+
const INDEX$2 = "entry_heads_entry_order_idx";
|
|
43693
|
+
const JOB_TYPE = "schema.entryOrderIndex";
|
|
43694
|
+
const IDEMPOTENCY_KEY = "schema.entryOrderIndex:v1";
|
|
43695
|
+
const BATCH$1 = 5e3;
|
|
43696
|
+
const up$10 = async (db) => {
|
|
43697
|
+
await sql`alter table entry_heads add column entry_created_at timestamptz`.execute(db);
|
|
43698
|
+
for (;;) {
|
|
43699
|
+
const result = await sql`
|
|
43700
|
+
update entry_heads h set entry_created_at = e.created_at
|
|
43701
|
+
from entries e
|
|
43702
|
+
where e.id = h.entry_id and (h.entry_id, h.locale, h.state) in (
|
|
43703
|
+
select entry_id, locale, state from entry_heads where entry_created_at is null limit ${BATCH$1}
|
|
43704
|
+
)
|
|
43705
|
+
`.execute(db);
|
|
43706
|
+
if (Number(result.numAffectedRows ?? 0n) < BATCH$1) break;
|
|
43707
|
+
}
|
|
43708
|
+
const { rows } = await sql`select exists (select 1 from entry_heads) as present`.execute(db);
|
|
43709
|
+
if (rows[0]?.present) {
|
|
43710
|
+
await sql`
|
|
43711
|
+
insert into jobs (type, payload, max_attempts, idempotency_key)
|
|
43712
|
+
values (${JOB_TYPE}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY})
|
|
43713
|
+
on conflict do nothing
|
|
43714
|
+
`.execute(db);
|
|
43715
|
+
return;
|
|
43716
|
+
}
|
|
43717
|
+
await sql`
|
|
43718
|
+
create index ${sql.id(INDEX$2)} on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)
|
|
43719
|
+
`.execute(db);
|
|
43720
|
+
};
|
|
43721
|
+
const down$10 = async (db) => {
|
|
43722
|
+
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
|
|
43723
|
+
await sql`drop index if exists ${sql.id(INDEX$2)}`.execute(db);
|
|
43724
|
+
await sql`alter table entry_heads drop column entry_created_at`.execute(db);
|
|
43725
|
+
};
|
|
43726
|
+
//#endregion
|
|
43578
43727
|
//#region src/db/migrations/mysql/0001_baseline.ts
|
|
43579
43728
|
var _0001_baseline_exports$1 = /* @__PURE__ */ __exportAll({
|
|
43580
|
-
down: () => down$
|
|
43581
|
-
up: () => up$
|
|
43729
|
+
down: () => down$9,
|
|
43730
|
+
up: () => up$9
|
|
43582
43731
|
});
|
|
43583
43732
|
/**
|
|
43584
43733
|
* MySQL baseline: Shapio's whole system schema as of PostgreSQL migration
|
|
@@ -44761,13 +44910,13 @@ const withoutForeignKeyChecks = async (db, statements) => db.connection().execut
|
|
|
44761
44910
|
await sql`set foreign_key_checks = 1`.execute(connection);
|
|
44762
44911
|
}
|
|
44763
44912
|
});
|
|
44764
|
-
const up$
|
|
44765
|
-
const down$
|
|
44913
|
+
const up$9 = async (db) => withoutForeignKeyChecks(db, STATEMENTS$1);
|
|
44914
|
+
const down$9 = async (db) => withoutForeignKeyChecks(db, [...TABLES$1].reverse().map((table) => `drop table if exists \`${table}\``));
|
|
44766
44915
|
//#endregion
|
|
44767
44916
|
//#region src/db/migrations/mysql/20261003170000_audit_events_seq.ts
|
|
44768
44917
|
var _20261003170000_audit_events_seq_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44769
|
-
down: () => down$
|
|
44770
|
-
up: () => up$
|
|
44918
|
+
down: () => down$8,
|
|
44919
|
+
up: () => up$8
|
|
44771
44920
|
});
|
|
44772
44921
|
/**
|
|
44773
44922
|
* MySQL twin of `../20261003170000_audit_events_seq.ts`: `seq` is `AUTO_INCREMENT`, which InnoDB only allows
|
|
@@ -44775,26 +44924,26 @@ var _20261003170000_audit_events_seq_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
44775
44924
|
* tiebreaker). Existing rows are numbered in primary-key order. `db/mysql/tables.ts` lists `seq` as the
|
|
44776
44925
|
* table's identity, so an insert's emulated RETURNING finds its row by `LAST_INSERT_ID()`.
|
|
44777
44926
|
*/
|
|
44778
|
-
const up$
|
|
44927
|
+
const up$8 = async (db) => {
|
|
44779
44928
|
await sql`alter table \`audit_events\`
|
|
44780
44929
|
add column \`seq\` bigint not null auto_increment,
|
|
44781
44930
|
add unique key \`audit_events_seq_key\` (\`seq\`)`.execute(db);
|
|
44782
44931
|
};
|
|
44783
|
-
const down$
|
|
44932
|
+
const down$8 = async (db) => {
|
|
44784
44933
|
await sql`alter table \`audit_events\` drop column \`seq\``.execute(db);
|
|
44785
44934
|
};
|
|
44786
44935
|
//#endregion
|
|
44787
44936
|
//#region src/db/migrations/mysql/20261004120000_models_site_scope.ts
|
|
44788
44937
|
var _20261004120000_models_site_scope_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44789
|
-
down: () => down$
|
|
44790
|
-
up: () => up$
|
|
44938
|
+
down: () => down$7,
|
|
44939
|
+
up: () => up$7
|
|
44791
44940
|
});
|
|
44792
44941
|
/**
|
|
44793
44942
|
* MySQL twin of `../20261004120000_models_site_scope.ts`. The per-scope API ID key uses functional key parts,
|
|
44794
44943
|
* like the baseline: `coalesce(site_id, '')` for the scope (MySQL has no `NULLS NOT DISTINCT`) and the
|
|
44795
44944
|
* case-folded API ID of live rows only (a deleted row's part is NULL, and NULLs never collide).
|
|
44796
44945
|
*/
|
|
44797
|
-
const up$
|
|
44946
|
+
const up$7 = async (db) => {
|
|
44798
44947
|
await sql`alter table \`models\` add column \`site_id\` char(36) character set ascii collate ascii_general_ci`.execute(db);
|
|
44799
44948
|
await sql`create index \`models_site_idx\` on \`models\` (\`site_id\`)`.execute(db);
|
|
44800
44949
|
await sql`alter table \`models\` add constraint \`models_site_id_fkey\` foreign key (\`site_id\`) references \`sites\` (\`id\`)`.execute(db);
|
|
@@ -44803,7 +44952,7 @@ const up$5 = async (db) => {
|
|
|
44803
44952
|
((coalesce(\`site_id\`, '')), (case when (\`deleted_at\` IS NULL) then lower(\`api_key\`) end))`.execute(db);
|
|
44804
44953
|
await sql`alter table \`schema_drafts\` add column \`shared\` tinyint(1) not null default 0`.execute(db);
|
|
44805
44954
|
};
|
|
44806
|
-
const down$
|
|
44955
|
+
const down$7 = async (db) => {
|
|
44807
44956
|
const { rows } = await sql`select count(*) as count from \`models\` where \`site_id\` is not null`.execute(db);
|
|
44808
44957
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
44809
44958
|
await sql`alter table \`schema_drafts\` drop column \`shared\``.execute(db);
|
|
@@ -44817,8 +44966,8 @@ const down$5 = async (db) => {
|
|
|
44817
44966
|
//#endregion
|
|
44818
44967
|
//#region src/db/migrations/mysql/20261004130000_site_seo_settings.ts
|
|
44819
44968
|
var _20261004130000_site_seo_settings_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44820
|
-
down: () => down$
|
|
44821
|
-
up: () => up$
|
|
44969
|
+
down: () => down$6,
|
|
44970
|
+
up: () => up$6
|
|
44822
44971
|
});
|
|
44823
44972
|
/**
|
|
44824
44973
|
* MySQL twin of `../20261004130000_site_seo_settings.ts`. `insert ignore` stands in for `on conflict do
|
|
@@ -44826,7 +44975,7 @@ var _20261004130000_site_seo_settings_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
44826
44975
|
*/
|
|
44827
44976
|
const bumpPermissionsVersion$1 = (db) => sql`update \`system_versions\` set \`permissions_version\` = \`permissions_version\` + 1,
|
|
44828
44977
|
\`updated_at\` = current_timestamp(6)`.execute(db);
|
|
44829
|
-
const up$
|
|
44978
|
+
const up$6 = async (db) => {
|
|
44830
44979
|
await sql`alter table \`sites\` add column \`seo_defaults\` json`.execute(db);
|
|
44831
44980
|
const granted = await sql`
|
|
44832
44981
|
insert ignore into \`admin_role_permissions\` (\`id\`, \`role_id\`, \`action\`, \`model_id\`, \`condition\`, \`field_ids\`)
|
|
@@ -44836,24 +44985,53 @@ const up$4 = async (db) => {
|
|
|
44836
44985
|
`.execute(db);
|
|
44837
44986
|
if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
|
|
44838
44987
|
};
|
|
44839
|
-
const down$
|
|
44988
|
+
const down$6 = async (db) => {
|
|
44840
44989
|
const revoked = await sql`delete from \`admin_role_permissions\` where \`action\` = 'site.settings'`.execute(db);
|
|
44841
44990
|
if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
|
|
44842
44991
|
await sql`alter table \`sites\` drop column \`seo_defaults\``.execute(db);
|
|
44843
44992
|
};
|
|
44844
44993
|
//#endregion
|
|
44994
|
+
//#region src/db/migrations/mysql/20261005120000_entry_heads_entry_created_at.ts
|
|
44995
|
+
var _20261005120000_entry_heads_entry_created_at_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44996
|
+
down: () => down$5,
|
|
44997
|
+
up: () => up$5
|
|
44998
|
+
});
|
|
44999
|
+
/**
|
|
45000
|
+
* MySQL twin of `../20261005120000_entry_heads_entry_created_at.ts`. The column is added `ALGORITHM=INSTANT`
|
|
45001
|
+
* and backfilled in batches; the index is built online (`ALGORITHM=INPLACE, LOCK=NONE`), so writes continue.
|
|
45002
|
+
* It is one more index of `entry_heads`, which is why `MYSQL_MAX_FIELD_INDEXES` (db/limits.ts) went down by one.
|
|
45003
|
+
*/
|
|
45004
|
+
const INDEX$1 = "entry_heads_entry_order_idx";
|
|
45005
|
+
const BATCH = 5e3;
|
|
45006
|
+
const up$5 = async (db) => {
|
|
45007
|
+
await sql`alter table \`entry_heads\` add column \`entry_created_at\` datetime(6), algorithm=instant`.execute(db);
|
|
45008
|
+
for (;;) {
|
|
45009
|
+
const result = await sql`update \`entry_heads\` set \`entry_created_at\` = (
|
|
45010
|
+
select e.\`created_at\` from \`entries\` e where e.\`id\` = \`entry_heads\`.\`entry_id\`
|
|
45011
|
+
) where \`entry_created_at\` is null limit ${sql.lit(BATCH)}`.execute(db);
|
|
45012
|
+
if (Number(result.numAffectedRows ?? 0n) < BATCH) break;
|
|
45013
|
+
}
|
|
45014
|
+
await sql`create index ${sql.id(INDEX$1)} on \`entry_heads\`
|
|
45015
|
+
(\`site_id\`, \`model_id\`, \`state\`, \`entry_created_at\` desc, \`entry_id\`) algorithm=inplace lock=none`.execute(db);
|
|
45016
|
+
};
|
|
45017
|
+
const down$5 = async (db) => {
|
|
45018
|
+
await sql`drop index ${sql.id(INDEX$1)} on \`entry_heads\``.execute(db);
|
|
45019
|
+
await sql`alter table \`entry_heads\` drop column \`entry_created_at\``.execute(db);
|
|
45020
|
+
};
|
|
45021
|
+
//#endregion
|
|
44845
45022
|
//#region src/db/migrations/mysql/index.ts
|
|
44846
45023
|
const MYSQL_MIGRATIONS = {
|
|
44847
45024
|
"0001_baseline": _0001_baseline_exports$1,
|
|
44848
45025
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$1,
|
|
44849
45026
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$1,
|
|
44850
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1
|
|
45027
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1,
|
|
45028
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$1
|
|
44851
45029
|
};
|
|
44852
45030
|
//#endregion
|
|
44853
45031
|
//#region src/db/migrations/sqlite/0001_baseline.ts
|
|
44854
45032
|
var _0001_baseline_exports = /* @__PURE__ */ __exportAll({
|
|
44855
|
-
down: () => down$
|
|
44856
|
-
up: () => up$
|
|
45033
|
+
down: () => down$4,
|
|
45034
|
+
up: () => up$4
|
|
44857
45035
|
});
|
|
44858
45036
|
/**
|
|
44859
45037
|
* SQLite baseline: Shapio's whole system schema as of PostgreSQL migration
|
|
@@ -45998,18 +46176,18 @@ const TABLES = [
|
|
|
45998
46176
|
"assist_runs",
|
|
45999
46177
|
"sequences"
|
|
46000
46178
|
];
|
|
46001
|
-
const up$
|
|
46179
|
+
const up$4 = async (db) => {
|
|
46002
46180
|
for (const statement of STATEMENTS) await sql.raw(statement).execute(db);
|
|
46003
46181
|
};
|
|
46004
|
-
const down$
|
|
46182
|
+
const down$4 = async (db) => {
|
|
46005
46183
|
await sql`pragma defer_foreign_keys = on`.execute(db);
|
|
46006
46184
|
for (const table of [...TABLES].reverse()) await sql.raw(`drop table if exists "${table}"`).execute(db);
|
|
46007
46185
|
};
|
|
46008
46186
|
//#endregion
|
|
46009
46187
|
//#region src/db/migrations/sqlite/20261003170000_audit_events_seq.ts
|
|
46010
46188
|
var _20261003170000_audit_events_seq_exports = /* @__PURE__ */ __exportAll({
|
|
46011
|
-
down: () => down$
|
|
46012
|
-
up: () => up$
|
|
46189
|
+
down: () => down$3,
|
|
46190
|
+
up: () => up$3
|
|
46013
46191
|
});
|
|
46014
46192
|
/**
|
|
46015
46193
|
* SQLite twin of `../20261003170000_audit_events_seq.ts`. SQLite cannot add a column whose default calls a
|
|
@@ -46036,7 +46214,7 @@ const INDEXES = [
|
|
|
46036
46214
|
`create index "audit_events_occurred_at_idx" on "audit_events" (occurred_at DESC)`,
|
|
46037
46215
|
`create index "audit_events_target_idx" on "audit_events" (target_type, target_id, occurred_at DESC)`
|
|
46038
46216
|
];
|
|
46039
|
-
const up$
|
|
46217
|
+
const up$3 = async (db) => {
|
|
46040
46218
|
await sql`insert into "sequences" (name, value) values ('audit_events_seq', 0)`.execute(db);
|
|
46041
46219
|
await sql`alter table "audit_events" rename to "audit_events_before_seq"`.execute(db);
|
|
46042
46220
|
await sql`create table "audit_events" (
|
|
@@ -46064,15 +46242,15 @@ const up$2 = async (db) => {
|
|
|
46064
46242
|
await sql`drop table "audit_events_before_seq"`.execute(db);
|
|
46065
46243
|
for (const statement of INDEXES) await sql.raw(statement).execute(db);
|
|
46066
46244
|
};
|
|
46067
|
-
const down$
|
|
46245
|
+
const down$3 = async (db) => {
|
|
46068
46246
|
await sql`alter table "audit_events" drop column "seq"`.execute(db);
|
|
46069
46247
|
await sql`delete from "sequences" where name = 'audit_events_seq'`.execute(db);
|
|
46070
46248
|
};
|
|
46071
46249
|
//#endregion
|
|
46072
46250
|
//#region src/db/migrations/sqlite/20261004120000_models_site_scope.ts
|
|
46073
46251
|
var _20261004120000_models_site_scope_exports = /* @__PURE__ */ __exportAll({
|
|
46074
|
-
down: () => down$
|
|
46075
|
-
up: () => up$
|
|
46252
|
+
down: () => down$2,
|
|
46253
|
+
up: () => up$2
|
|
46076
46254
|
});
|
|
46077
46255
|
/**
|
|
46078
46256
|
* SQLite twin of `../20261004120000_models_site_scope.ts`. `down` rebuilds `models` without `site_id`
|
|
@@ -46082,7 +46260,7 @@ var _20261004120000_models_site_scope_exports = /* @__PURE__ */ __exportAll({
|
|
|
46082
46260
|
* deletes nothing they reference.
|
|
46083
46261
|
*/
|
|
46084
46262
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
46085
|
-
const up$
|
|
46263
|
+
const up$2 = async (db) => {
|
|
46086
46264
|
await sql`alter table "models" add column "site_id" text_uuid references sites (id)`.execute(db);
|
|
46087
46265
|
await sql`create index "models_site_idx" on "models" (site_id)`.execute(db);
|
|
46088
46266
|
await sql`drop index "models_api_key_uq"`.execute(db);
|
|
@@ -46092,7 +46270,7 @@ const up$1 = async (db) => {
|
|
|
46092
46270
|
`.execute(db);
|
|
46093
46271
|
await sql`alter table "schema_drafts" add column "shared" integer_boolean not null default 0`.execute(db);
|
|
46094
46272
|
};
|
|
46095
|
-
const down$
|
|
46273
|
+
const down$2 = async (db) => {
|
|
46096
46274
|
const { rows } = await sql`select count(*) as count from "models" where site_id is not null`.execute(db);
|
|
46097
46275
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
46098
46276
|
await sql`alter table "schema_drafts" drop column "shared"`.execute(db);
|
|
@@ -46117,15 +46295,15 @@ const down$1 = async (db) => {
|
|
|
46117
46295
|
//#endregion
|
|
46118
46296
|
//#region src/db/migrations/sqlite/20261004130000_site_seo_settings.ts
|
|
46119
46297
|
var _20261004130000_site_seo_settings_exports = /* @__PURE__ */ __exportAll({
|
|
46120
|
-
down: () => down,
|
|
46121
|
-
up: () => up
|
|
46298
|
+
down: () => down$1,
|
|
46299
|
+
up: () => up$1
|
|
46122
46300
|
});
|
|
46123
46301
|
/**
|
|
46124
46302
|
* SQLite twin of `../20261004130000_site_seo_settings.ts`. `insert or ignore` stands in for `on conflict do
|
|
46125
46303
|
* nothing` (the grant's unique index); the permissions version moves only when a grant was added or removed.
|
|
46126
46304
|
*/
|
|
46127
46305
|
const bumpPermissionsVersion = (db) => sql`update "system_versions" set permissions_version = permissions_version + 1, updated_at = shapio_now()`.execute(db);
|
|
46128
|
-
const up = async (db) => {
|
|
46306
|
+
const up$1 = async (db) => {
|
|
46129
46307
|
await sql`alter table "sites" add column "seo_defaults" text_jsonb`.execute(db);
|
|
46130
46308
|
const granted = await sql`
|
|
46131
46309
|
insert or ignore into "admin_role_permissions" (role_id, action, model_id, condition, field_ids)
|
|
@@ -46135,18 +46313,42 @@ const up = async (db) => {
|
|
|
46135
46313
|
`.execute(db);
|
|
46136
46314
|
if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
|
|
46137
46315
|
};
|
|
46138
|
-
const down = async (db) => {
|
|
46316
|
+
const down$1 = async (db) => {
|
|
46139
46317
|
const revoked = await sql`delete from "admin_role_permissions" where action = 'site.settings'`.execute(db);
|
|
46140
46318
|
if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
|
|
46141
46319
|
await sql`alter table "sites" drop column "seo_defaults"`.execute(db);
|
|
46142
46320
|
};
|
|
46143
46321
|
//#endregion
|
|
46322
|
+
//#region src/db/migrations/sqlite/20261005120000_entry_heads_entry_created_at.ts
|
|
46323
|
+
var _20261005120000_entry_heads_entry_created_at_exports = /* @__PURE__ */ __exportAll({
|
|
46324
|
+
down: () => down,
|
|
46325
|
+
up: () => up
|
|
46326
|
+
});
|
|
46327
|
+
/**
|
|
46328
|
+
* SQLite twin of `../20261005120000_entry_heads_entry_created_at.ts`. One process holds the write lock, so
|
|
46329
|
+
* the backfill is one statement and the index is built here (a plain `CREATE INDEX`; writers wait for it, the
|
|
46330
|
+
* documented single-process limitation of ADR 0001, "D2: as built").
|
|
46331
|
+
*/
|
|
46332
|
+
const INDEX = "entry_heads_entry_order_idx";
|
|
46333
|
+
const up = async (db) => {
|
|
46334
|
+
await sql`alter table "entry_heads" add column "entry_created_at" text_timestamptz`.execute(db);
|
|
46335
|
+
await sql`update "entry_heads" set entry_created_at = (
|
|
46336
|
+
select e.created_at from "entries" e where e.id = "entry_heads".entry_id
|
|
46337
|
+
) where entry_created_at is null`.execute(db);
|
|
46338
|
+
await sql`create index ${sql.id(INDEX)} on "entry_heads" (site_id, model_id, state, entry_created_at desc, entry_id)`.execute(db);
|
|
46339
|
+
};
|
|
46340
|
+
const down = async (db) => {
|
|
46341
|
+
await sql`drop index if exists ${sql.id(INDEX)}`.execute(db);
|
|
46342
|
+
await sql`alter table "entry_heads" drop column "entry_created_at"`.execute(db);
|
|
46343
|
+
};
|
|
46344
|
+
//#endregion
|
|
46144
46345
|
//#region src/db/migrations/sqlite/index.ts
|
|
46145
46346
|
const SQLITE_MIGRATIONS = {
|
|
46146
46347
|
"0001_baseline": _0001_baseline_exports,
|
|
46147
46348
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports,
|
|
46148
46349
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports,
|
|
46149
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports
|
|
46350
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports,
|
|
46351
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports
|
|
46150
46352
|
};
|
|
46151
46353
|
//#endregion
|
|
46152
46354
|
//#region src/db/migrations/index.ts
|
|
@@ -46178,7 +46380,8 @@ const MIGRATIONS = {
|
|
|
46178
46380
|
"20261003160000_create_assist_runs": _20261003160000_create_assist_runs_exports,
|
|
46179
46381
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$2,
|
|
46180
46382
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$2,
|
|
46181
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2
|
|
46383
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2,
|
|
46384
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$2
|
|
46182
46385
|
};
|
|
46183
46386
|
const PROVIDERS = {
|
|
46184
46387
|
postgres: { getMigrations: () => Promise.resolve({ ...MIGRATIONS }) },
|
|
@@ -49277,4 +49480,4 @@ const startDedicatedWorker = async (config) => {
|
|
|
49277
49480
|
//#endregion
|
|
49278
49481
|
export { toTypeName as $, update$8 as A, checkThemeContrast as B, BEFORE_EVENTS as C, listForAssets as D, buildEditorManifest as E, RESERVED_FIELD_API_KEYS as F, mergePulledLock as G, hashDefinition as H, RESERVED_TYPE_NAMES as I, schemaFilePath as J, parseDefinition as K, SCHEMA_KIND_DIRECTORIES as L, describeError as M, LOCK_FILE_PATH as N, listLiveByDriver as O, RESERVED_DEFINITION_API_KEYS as P, suggestPlural as Q, SITES_DIRECTORY as R, AFTER_EVENTS as S, EXTENSION_ROUTE_ROOT as T, lockCoversSite as U, foldApiKey as V, lockEntriesForSite as W, serializeDefinition as X, scopeDirectory as Y, serializeLockFile as Z, createLogger as _, loadConfig as a, createStableId as at, SITE_KEY_PATTERN as b, getPendingMigrations as c, sqliteDriverOf as ct, createMediaStorage as d, upgradeLockFile as et, createSite$1 as f, generateToken as g, SYSTEM_CLI_ACTOR as h, assertDatabaseReachable as i, SAFE_LINK_PATTERN as it, STORAGE_DRIVERS$1 as j, lockById$5 as k, migrateToLatest as l, createAdminDirect as m, startServer as n, withSiteCovered as nt, loadEnvFileIfPresent as o, validateRichText as ot, listAllSites as p, parseLockFile as q, copyObjectVerified as r, RICHTEXT_FORMAT as rt, buildApp as s, createDb as st, startDedicatedWorker as t, validateSchema as tt, SHAPIO_VERSION as u, createUrlBuilder as v, EXTENSION_JOB_PREFIX as w, loadProjectConfig as x, loopbackUrl as y, THEME_VARIANTS as z };
|
|
49279
49482
|
|
|
49280
|
-
//# sourceMappingURL=worker-
|
|
49483
|
+
//# sourceMappingURL=worker-BDYDbaea.js.map
|