@voltro/cms 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +1215 -0
  2. package/THIRD-PARTY-NOTICES.md +118 -600
  3. package/dist/ContentForm.js +282 -0
  4. package/dist/RichDocumentEditor.js +550 -0
  5. package/dist/RichDocumentView.js +132 -0
  6. package/dist/RichTextSurface.js +135 -0
  7. package/dist/aiCandidates.js +199 -0
  8. package/dist/aiOperations.js +152 -0
  9. package/dist/audiences.js +31 -0
  10. package/dist/availability.js +121 -0
  11. package/dist/blueprint.js +166 -0
  12. package/dist/bulk.js +281 -0
  13. package/dist/cacheIdentity.js +152 -0
  14. package/dist/changes.js +90 -0
  15. package/dist/cmsContext.js +229 -0
  16. package/dist/collections.js +224 -0
  17. package/dist/consent.js +29 -0
  18. package/dist/contentType.js +191 -0
  19. package/dist/contentfulImport.js +162 -0
  20. package/dist/copies.js +89 -0
  21. package/dist/cutover.js +202 -0
  22. package/dist/deliveryChecks.js +138 -0
  23. package/dist/domains.js +145 -0
  24. package/dist/draftTexts.js +164 -0
  25. package/dist/duplicates.js +78 -0
  26. package/dist/edgePurge.js +69 -0
  27. package/dist/editHistory.js +113 -0
  28. package/dist/effectForm.js +11 -0
  29. package/dist/environments.js +109 -0
  30. package/dist/errors.js +67 -0
  31. package/dist/experiments.js +528 -0
  32. package/dist/exposure.js +28 -0
  33. package/dist/extensionManifest.js +81 -0
  34. package/dist/extensions.js +314 -0
  35. package/dist/fieldSchema.js +199 -0
  36. package/dist/formStrings.js +74 -0
  37. package/dist/glossary.js +73 -0
  38. package/dist/history.js +54 -0
  39. package/dist/htmlImport.js +297 -0
  40. package/dist/index.d.ts +2266 -170
  41. package/dist/index.js +73 -3947
  42. package/dist/media.js +29 -0
  43. package/dist/metrics.js +46 -0
  44. package/dist/migration.js +193 -0
  45. package/dist/migrationStages.js +13 -0
  46. package/dist/presenceAnchors.js +68 -0
  47. package/dist/preview.js +31 -0
  48. package/dist/promotion.js +107 -0
  49. package/dist/quality.js +188 -0
  50. package/dist/recurrence.js +78 -0
  51. package/dist/redirects.js +99 -0
  52. package/dist/referenceGraph.js +223 -0
  53. package/dist/references.js +173 -0
  54. package/dist/release.js +205 -0
  55. package/dist/rest.js +52 -0
  56. package/dist/review.js +160 -0
  57. package/dist/richDocument.js +491 -0
  58. package/dist/richInline.js +100 -0
  59. package/dist/schedule.js +80 -0
  60. package/dist/search.js +148 -0
  61. package/dist/selectionIntent.js +164 -0
  62. package/dist/seo.js +31 -0
  63. package/dist/series.js +105 -0
  64. package/dist/share.js +112 -0
  65. package/dist/stewardship.js +146 -0
  66. package/dist/taxonomy.js +36 -0
  67. package/dist/team.js +103 -0
  68. package/dist/templates.js +106 -0
  69. package/dist/trackChanges.js +197 -0
  70. package/dist/translation.js +64 -0
  71. package/dist/tree.js +87 -0
  72. package/dist/usage.js +50 -0
  73. package/dist/validation.js +192 -0
  74. package/dist/visualEditing.js +60 -0
  75. package/dist/web.d.ts +927 -131
  76. package/dist/web.js +33 -2
  77. package/dist/workflow.js +322 -0
  78. package/dist/write.js +394 -0
  79. package/dist/xliff.js +325 -0
  80. package/package.json +6 -6
  81. package/dist/extensionManifest-BK3ZcL2P.js +0 -2782
package/dist/media.js ADDED
@@ -0,0 +1,29 @@
1
+ import { asTyped as e } from "./effectForm.js";
2
+ //#region src/media.ts
3
+ var t = (e) => Object.entries(e.fields).filter(([, e]) => e.kind === "media").map(([e]) => e), n = async (e, t, r) => {
4
+ if (t == null) return t;
5
+ switch (e.kind) {
6
+ case "media": return typeof t == "string" ? await r(t) ?? t : t;
7
+ case "array": {
8
+ let i = e.element;
9
+ return i === void 0 || !Array.isArray(t) ? t : Promise.all(t.map((e) => n(i, e, r)));
10
+ }
11
+ case "struct": {
12
+ let i = e.struct;
13
+ if (i === void 0 || typeof t != "object" || Array.isArray(t)) return t;
14
+ let a = t, o = { ...a };
15
+ for (let [e, t] of Object.entries(i)) e in a && (o[e] = await n(t, a[e], r));
16
+ return o;
17
+ }
18
+ default: return t;
19
+ }
20
+ }, r = async (e, t, r) => {
21
+ let i = { ...t };
22
+ for (let [a, o] of Object.entries(e.fields)) {
23
+ let e = o;
24
+ (e.kind === "media" || e.kind === "array" || e.kind === "struct") && a in t && (i[a] = await n(e, t[a], r));
25
+ }
26
+ return i;
27
+ }, i = async (e, t, n) => Promise.all(t.map((t) => r(e, t, n))), a = (e) => t(e), o = (...t) => e(() => r(...t)), s = (...t) => e(() => i(...t));
28
+ //#endregion
29
+ export { a as mediaFields, r as resolveMedia, i as resolveMediaAll, s as resolveMediaAllEffect, o as resolveMediaEffect };
@@ -0,0 +1,46 @@
1
+ import { fnv1a as e } from "./fieldSchema.js";
2
+ //#region src/metrics.ts
3
+ var t = (e) => JSON.stringify([
4
+ e.id,
5
+ e.formula,
6
+ e.unit,
7
+ [...e.scope].sort(),
8
+ e.freshness,
9
+ [...e.quality].sort(),
10
+ [...e.drill].sort()
11
+ ]), n = (n) => {
12
+ if (!Number.isInteger(n.version) || n.version < 1) throw Error(`metric '${n.id}': version must be a positive integer`);
13
+ return {
14
+ ...n,
15
+ fingerprint: e(t(n))
16
+ };
17
+ }, r = (e, t) => {
18
+ let n = new Map(t.map((e) => [e.id, e]));
19
+ return e.filter((e) => {
20
+ let t = n.get(e.id);
21
+ return t !== void 0 && t.version === e.version && t.fingerprint !== e.fingerprint;
22
+ }).map((e) => e.id);
23
+ }, i = (e, t) => {
24
+ let n = /* @__PURE__ */ new Map(), r = 0;
25
+ for (let { day: i, value: a } of t) {
26
+ let t = Date.parse(`${i}T23:59:59.999Z`), o = e.find((e) => Date.parse(e.from) <= t && (e.to === null || Date.parse(e.to) > t));
27
+ if (o === void 0) {
28
+ r += a;
29
+ continue;
30
+ }
31
+ let s = n.get(o) ?? {
32
+ value: 0,
33
+ days: 0
34
+ };
35
+ s.value += a, s.days += 1, n.set(o, s);
36
+ }
37
+ return {
38
+ shares: [...n.entries()].map(([e, t]) => ({
39
+ ...e,
40
+ ...t
41
+ })).sort((e, t) => Date.parse(e.from) - Date.parse(t.from)),
42
+ unattributed: r
43
+ };
44
+ };
45
+ //#endregion
46
+ export { i as attributeToRevisions, n as defineMetric, r as metricVersionDrift };
@@ -0,0 +1,193 @@
1
+ import { asTyped as e } from "./effectForm.js";
2
+ import { MIGRATION_STAGES as t } from "./migrationStages.js";
3
+ import { and as n, eq as r, id as i, table as a, text as o, timestamp as s } from "@voltro/database";
4
+ import { tenant as c } from "@voltro/plugin-multitenancy/mixin";
5
+ //#region src/migration.ts
6
+ var l = "cms_migration_stages", u = "cms_migration_records", d = a(l, {
7
+ id: i({ prefix: "cmst" }),
8
+ source: o().maxLength(200),
9
+ stage: o().oneOf([...t]),
10
+ ownerId: o().nullable(),
11
+ status: o().oneOf(["open", "passed"]),
12
+ note: o().nullable(),
13
+ evidenceUri: o().nullable(),
14
+ passedBy: o().nullable(),
15
+ passedAt: s().nullable()
16
+ }).with(c()).unique("cms_migration_stages_once", [
17
+ "tenantId",
18
+ "source",
19
+ "stage"
20
+ ]), f = a(u, {
21
+ id: i({ prefix: "cmrc" }),
22
+ source: o().maxLength(200),
23
+ sourceKey: o().maxLength(200),
24
+ sourceType: o().maxLength(64),
25
+ result: o().oneOf([
26
+ "created",
27
+ "already",
28
+ "skipped",
29
+ "failed"
30
+ ]),
31
+ targetType: o().maxLength(64).nullable(),
32
+ targetDocumentId: o().maxLength(64).nullable(),
33
+ transformation: o().nullable(),
34
+ conflict: o().nullable(),
35
+ loss: o().nullable(),
36
+ recordedAt: s()
37
+ }).with(c()).unique("cms_migration_records_once", [
38
+ "tenantId",
39
+ "source",
40
+ "sourceKey"
41
+ ]), p = (e, t) => ({
42
+ stage: e,
43
+ ownerId: typeof t?.ownerId == "string" ? t.ownerId : null,
44
+ status: t?.status === "passed" ? "passed" : "open",
45
+ note: typeof t?.note == "string" ? t.note : null,
46
+ evidenceUri: typeof t?.evidenceUri == "string" ? t.evidenceUri : null,
47
+ passedBy: typeof t?.passedBy == "string" ? t.passedBy : null,
48
+ passedAt: t?.passedAt instanceof Date ? t.passedAt : t?.passedAt ? new Date(String(t.passedAt)) : null
49
+ }), m = async (e, n) => {
50
+ let i = await e.query({
51
+ table: l,
52
+ predicate: r("source", n),
53
+ order: [],
54
+ take: void 0,
55
+ skip: void 0,
56
+ projection: void 0
57
+ });
58
+ return t.map((e) => p(e, i.find((t) => t.stage === e)));
59
+ }, h = async (e, t, i) => (await e.query({
60
+ table: l,
61
+ predicate: n(r("source", t), r("stage", i)),
62
+ order: [],
63
+ take: 1,
64
+ skip: void 0,
65
+ projection: void 0
66
+ }))[0], g = async (e, t, n, r, i) => {
67
+ let a = await h(e, t, n);
68
+ return a?.status === "passed" ? {
69
+ ok: !1,
70
+ reason: "passed"
71
+ } : (a === void 0 ? await e.insert(l, {
72
+ source: t,
73
+ stage: n,
74
+ ownerId: r,
75
+ status: "open",
76
+ note: null,
77
+ evidenceUri: null,
78
+ passedBy: null,
79
+ passedAt: null,
80
+ ...i === null ? {} : { tenantId: i }
81
+ }) : await e.update(l, String(a.id), { ownerId: r }), { ok: !0 });
82
+ }, _ = async (e, n, r, i, a = /* @__PURE__ */ new Date()) => {
83
+ let o = await m(e, n), s = o.find((e) => e.stage === r);
84
+ if (s.status === "passed") return {
85
+ ok: !1,
86
+ reason: "passed"
87
+ };
88
+ if (s.ownerId === null) return {
89
+ ok: !1,
90
+ reason: "noOwner"
91
+ };
92
+ if (s.ownerId !== i.actorId) return {
93
+ ok: !1,
94
+ reason: "notOwner"
95
+ };
96
+ let c = i.note.trim();
97
+ if (c === "" || c.length > 1e3) return {
98
+ ok: !1,
99
+ reason: "noNote"
100
+ };
101
+ let u = o.slice(0, t.indexOf(r)).find((e) => e.status !== "passed");
102
+ if (u !== void 0) return {
103
+ ok: !1,
104
+ reason: "earlierOpen",
105
+ stage: u.stage
106
+ };
107
+ if (i.gate === !1) return {
108
+ ok: !1,
109
+ reason: "gateNotMet"
110
+ };
111
+ let d = await h(e, n, r);
112
+ return await e.update(l, String(d.id), {
113
+ status: "passed",
114
+ note: c,
115
+ evidenceUri: i.evidenceUri?.trim() ? i.evidenceUri.trim().slice(0, 500) : null,
116
+ passedBy: i.actorId,
117
+ passedAt: a
118
+ }), { ok: !0 };
119
+ }, v = async (e, n, r) => {
120
+ let i = t.slice(t.indexOf(r)), a = 0;
121
+ for (let t of i) {
122
+ let r = await h(e, n, t);
123
+ r?.status === "passed" && (await e.update(l, String(r.id), {
124
+ status: "open",
125
+ passedBy: null,
126
+ passedAt: null
127
+ }), a++);
128
+ }
129
+ return a;
130
+ }, y = async (e, t, i, a, o = /* @__PURE__ */ new Date()) => {
131
+ for (let s of i) {
132
+ let i = {
133
+ sourceType: s.sourceType,
134
+ result: s.result,
135
+ targetType: s.targetType ?? null,
136
+ targetDocumentId: s.targetDocumentId ?? null,
137
+ transformation: s.transformation ?? null,
138
+ conflict: s.conflict ?? null,
139
+ loss: s.loss ?? null,
140
+ recordedAt: o
141
+ }, [c] = await e.query({
142
+ table: u,
143
+ predicate: n(r("source", t), r("sourceKey", s.sourceKey)),
144
+ order: [],
145
+ take: 1,
146
+ skip: void 0,
147
+ projection: void 0
148
+ });
149
+ c === void 0 ? await e.insert(u, {
150
+ source: t,
151
+ sourceKey: s.sourceKey,
152
+ ...i,
153
+ ...a === null ? {} : { tenantId: a }
154
+ }) : await e.update(u, String(c.id), c.result === "created" && s.result === "already" ? { recordedAt: o } : i);
155
+ }
156
+ }, b = async (e, t, n = 200) => {
157
+ let i = await e.query({
158
+ table: u,
159
+ predicate: r("source", t),
160
+ order: [{
161
+ column: "sourceKey",
162
+ direction: "asc"
163
+ }],
164
+ take: void 0,
165
+ skip: void 0,
166
+ projection: void 0
167
+ }), a = (e) => typeof e == "string" && e !== "" ? e : null, o = i.map((e) => ({
168
+ sourceKey: String(e.sourceKey),
169
+ sourceType: String(e.sourceType),
170
+ result: String(e.result),
171
+ targetType: a(e.targetType),
172
+ targetDocumentId: a(e.targetDocumentId),
173
+ transformation: a(e.transformation),
174
+ conflict: a(e.conflict),
175
+ loss: a(e.loss),
176
+ recordedAt: e.recordedAt instanceof Date ? e.recordedAt : new Date(String(e.recordedAt))
177
+ })), s = {
178
+ created: 0,
179
+ already: 0,
180
+ skipped: 0,
181
+ failed: 0
182
+ };
183
+ for (let e of o) s[e.result]++;
184
+ let c = (e) => Number(e.conflict !== null || e.loss !== null);
185
+ return {
186
+ counts: s,
187
+ withConflict: o.filter((e) => e.conflict !== null).length,
188
+ withLoss: o.filter((e) => e.loss !== null).length,
189
+ records: [...o].sort((e, t) => c(t) - c(e)).slice(0, n)
190
+ };
191
+ }, x = (...t) => e(() => m(...t)), S = (...t) => e(() => g(...t)), C = (...t) => e(() => _(...t)), w = (...t) => e(() => v(...t)), T = (...t) => e(() => y(...t)), E = (...t) => e(() => b(...t));
192
+ //#endregion
193
+ export { u as CMS_MIGRATION_RECORDS_TABLE, l as CMS_MIGRATION_STAGES_TABLE, t as MIGRATION_STAGES, f as cmsMigrationRecordsTable, d as cmsMigrationStagesTable, b as migrationLedger, E as migrationLedgerEffect, m as migrationStages, x as migrationStagesEffect, _ as passStage, C as passStageEffect, y as recordMigrationRecords, T as recordMigrationRecordsEffect, v as reopenStage, w as reopenStageEffect, g as setStageOwner, S as setStageOwnerEffect };
@@ -0,0 +1,13 @@
1
+ //#region src/migrationStages.ts
2
+ var e = [
3
+ "inventory",
4
+ "mapping",
5
+ "dryRun",
6
+ "reconciliation",
7
+ "delta",
8
+ "cutover",
9
+ "fallback",
10
+ "uat"
11
+ ];
12
+ //#endregion
13
+ export { e as MIGRATION_STAGES };
@@ -0,0 +1,68 @@
1
+ //#region src/presenceAnchors.ts
2
+ var e = /^[A-Za-z0-9_.:/-]{1,120}$/, t = (t) => typeof t == "string" && e.test(t), n = (e) => Number.isInteger(e) && e >= 0 && e <= 1e6, r = (e) => typeof e == "number" && Number.isFinite(e) && e >= 0 && e <= 1, i = (e) => {
3
+ if (typeof e != "object" || !e) return null;
4
+ let i = e;
5
+ switch (i.kind) {
6
+ case "view": return t(i.view) ? {
7
+ kind: "view",
8
+ view: i.view
9
+ } : null;
10
+ case "field": return t(i.field) ? {
11
+ kind: "field",
12
+ field: i.field
13
+ } : null;
14
+ case "component": return t(i.field) && t(i.instanceId) && t(i.componentId) ? {
15
+ kind: "component",
16
+ field: i.field,
17
+ instanceId: i.instanceId,
18
+ componentId: i.componentId
19
+ } : null;
20
+ case "slot": return t(i.field) && t(i.instanceId) && t(i.componentId) && t(i.prop) ? {
21
+ kind: "slot",
22
+ field: i.field,
23
+ instanceId: i.instanceId,
24
+ componentId: i.componentId,
25
+ prop: i.prop
26
+ } : null;
27
+ case "selection": return t(i.field) && n(i.start) && n(i.end) && i.end >= i.start ? {
28
+ kind: "selection",
29
+ field: i.field,
30
+ start: i.start,
31
+ end: i.end
32
+ } : null;
33
+ case "assetRegion": return r(i.x) && r(i.y) && r(i.width) && r(i.height) && i.x + i.width <= 1 && i.y + i.height <= 1 ? {
34
+ kind: "assetRegion",
35
+ x: i.x,
36
+ y: i.y,
37
+ width: i.width,
38
+ height: i.height
39
+ } : null;
40
+ default: return null;
41
+ }
42
+ }, a = (e) => {
43
+ let t = e;
44
+ if (typeof t != "object" || !t || typeof t.closest != "function") return null;
45
+ let n = t.closest("[data-cms-field]"), r = t.closest("[data-cms-instance]");
46
+ if (r != null) {
47
+ let e = r.parentElement?.closest?.("[data-cms-field]") ?? null, t = e?.getAttribute("data-cms-field") ?? null, a = r.getAttribute("data-cms-instance"), o = r.getAttribute("data-cms-component"), s = n != null && n !== e ? n.getAttribute("data-cms-field") : null;
48
+ if (t !== null && a !== null && o !== null) return i(s === null ? {
49
+ kind: "component",
50
+ field: t,
51
+ instanceId: a,
52
+ componentId: o
53
+ } : {
54
+ kind: "slot",
55
+ field: t,
56
+ instanceId: a,
57
+ componentId: o,
58
+ prop: s
59
+ });
60
+ }
61
+ let a = n?.getAttribute("data-cms-field") ?? null;
62
+ return a === null ? null : i({
63
+ kind: "field",
64
+ field: a
65
+ });
66
+ };
67
+ //#endregion
68
+ export { i as parsePresenceAnchor, a as presenceAnchorOf };
@@ -0,0 +1,31 @@
1
+ import { createHmac as e, timingSafeEqual as t } from "node:crypto";
2
+ //#region src/preview.ts
3
+ var n = 3600, r = (e) => (Buffer.isBuffer(e) ? e : Buffer.from(e, "utf8")).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""), i = (e) => Buffer.from(e.replace(/-/g, "+").replace(/_/g, "/"), "base64"), a = (e) => e ?? process.env.VOLTRO_SESSION_SECRET ?? null, o = (t, i, o = {}) => {
4
+ let s = a(o.secret);
5
+ if (s === null) throw Error("cms previewToken requires a signing secret — set VOLTRO_SESSION_SECRET or pass { secret }. Refusing to sign with a hardcoded default (it would ship in the bundle and be forgeable).");
6
+ let c = {
7
+ t,
8
+ i,
9
+ exp: Math.floor(Date.now() / 1e3) + (o.ttlSeconds ?? n)
10
+ }, l = r(JSON.stringify(c));
11
+ return `${l}.${r(e("sha256", s).update(l).digest())}`;
12
+ }, s = (n, r = {}) => {
13
+ if (!n) return null;
14
+ let o = a(r.secret);
15
+ if (o === null) return null;
16
+ let s = n.indexOf(".");
17
+ if (s <= 0 || s === n.length - 1) return null;
18
+ let c = n.slice(0, s), l = n.slice(s + 1), u = e("sha256", o).update(c).digest(), d = i(l);
19
+ if (d.length !== u.length || !t(d, u)) return null;
20
+ try {
21
+ let e = JSON.parse(i(c).toString("utf8"));
22
+ return typeof e.t != "string" || typeof e.i != "string" || typeof e.exp != "number" || e.exp < Math.floor(Date.now() / 1e3) ? null : {
23
+ type: e.t,
24
+ id: e.i
25
+ };
26
+ } catch {
27
+ return null;
28
+ }
29
+ };
30
+ //#endregion
31
+ export { o as previewToken, s as verifyPreviewToken };
@@ -0,0 +1,107 @@
1
+ import { asTyped as e } from "./effectForm.js";
2
+ import { ContentIdConflict as t, ContentNotFound as n, ContentValidationFailed as r, ReferenceUnavailable as i, ReleaseNotEditable as a, ReviewRequired as o, RevisionMismatch as s } from "./errors.js";
3
+ import { pickDeclared as c, saveDraft as l } from "./write.js";
4
+ import { addToRelease as u, createRelease as d, publishRelease as f } from "./release.js";
5
+ import { and as p, eq as m, neq as h } from "@voltro/database";
6
+ //#region src/promotion.ts
7
+ var g = (e, t, n, r, i = []) => e.query({
8
+ table: t,
9
+ predicate: p(m("site", n), m("environment", r), h("status", "archived"), ...i),
10
+ order: [{
11
+ column: "id",
12
+ direction: "asc"
13
+ }],
14
+ take: void 0,
15
+ skip: void 0,
16
+ projection: void 0
17
+ }), _ = (e) => JSON.stringify(e, (e, t) => typeof t == "object" && t && !Array.isArray(t) && !(t instanceof Date) ? Object.fromEntries(Object.entries(t).sort(([e], [t]) => e < t ? -1 : 1)) : t), v = (e) => {
18
+ for (let t of [
19
+ "title",
20
+ "name",
21
+ "slug"
22
+ ]) if (typeof e[t] == "string" && e[t] !== "") return e[t];
23
+ return String(e.documentId);
24
+ }, y = (e) => `${String(e.documentId)}\u0000${String(e.locale)}`, b = async (e, t, n) => {
25
+ let r = [];
26
+ for (let i of t) {
27
+ let t = await g(e, `${i.name}_published`, n.site, n.from);
28
+ if (t.length === 0) continue;
29
+ let a = new Map((await g(e, `${i.name}_published`, n.site, n.to)).map((e) => [y(e), e])), o = new Map((await g(e, `${i.name}_drafts`, n.site, n.to)).map((e) => [y(e), e]));
30
+ for (let e of t) {
31
+ let t = a.get(y(e)), n = o.get(y(e)), s = {
32
+ contentType: i.name,
33
+ documentId: String(e.documentId),
34
+ locale: String(e.locale),
35
+ title: v(e)
36
+ };
37
+ n !== void 0 && n.status === "draft" ? r.push({
38
+ ...s,
39
+ change: "blocked"
40
+ }) : t === void 0 ? r.push({
41
+ ...s,
42
+ change: "new"
43
+ }) : _(c(i, e)) !== _(c(i, t)) && r.push({
44
+ ...s,
45
+ change: "changed"
46
+ });
47
+ }
48
+ }
49
+ return r;
50
+ }, x = async (e, t, n, r, i) => {
51
+ if (r.length === 0) return {
52
+ ok: !1,
53
+ reason: "nothingSelected",
54
+ items: []
55
+ };
56
+ let a = await b(e, t, n), o = new Map(a.map((e) => [`${e.contentType}\u0000${e.documentId}\u0000${e.locale}`, e])), s = r.filter((e) => !o.has(`${e.contentType}\u0000${e.documentId}\u0000${e.locale}`));
57
+ if (s.length > 0) return {
58
+ ok: !1,
59
+ reason: "notInPlan",
60
+ items: s
61
+ };
62
+ let p = r.filter((e) => o.get(`${e.contentType}\u0000${e.documentId}\u0000${e.locale}`).change === "blocked");
63
+ if (p.length > 0) return {
64
+ ok: !1,
65
+ reason: "blocked",
66
+ items: p
67
+ };
68
+ let h = new Map(t.map((e) => [e.name, e])), _ = await d(e, {
69
+ name: i.name,
70
+ site: n.site,
71
+ environment: n.to
72
+ }), v = String(_.id);
73
+ for (let t of r) {
74
+ let r = h.get(t.contentType), [i] = await g(e, `${r.name}_published`, n.site, n.from, [m("documentId", t.documentId), m("locale", t.locale)]), a = {
75
+ site: n.site,
76
+ environment: n.to,
77
+ locale: t.locale
78
+ }, [o] = await g(e, `${r.name}_drafts`, n.site, n.to, [m("documentId", t.documentId), m("locale", t.locale)]), s = c(r, i), d = await l(e, r, o === void 0 ? {
79
+ ...s,
80
+ documentId: t.documentId
81
+ } : {
82
+ ...s,
83
+ id: o.id
84
+ }, a);
85
+ await u(e, v, {
86
+ type: r,
87
+ id: String(d.id),
88
+ locale: t.locale,
89
+ revisionId: String(d.revisionId)
90
+ });
91
+ }
92
+ return await f(e, t, v), {
93
+ ok: !0,
94
+ releaseId: v,
95
+ promoted: r.length
96
+ };
97
+ }, S = [
98
+ r,
99
+ t,
100
+ n,
101
+ s,
102
+ a,
103
+ o,
104
+ i
105
+ ], C = (...t) => e(() => b(...t), ...S), w = (...t) => e(() => x(...t), ...S);
106
+ //#endregion
107
+ export { b as planPromotion, C as planPromotionEffect, x as promote, w as promoteEffect };
@@ -0,0 +1,188 @@
1
+ import { asTyped as e } from "./effectForm.js";
2
+ import { ContentNotFound as t } from "./errors.js";
3
+ import { and as n, eq as r, id as i, table as a, text as o, timestamp as s } from "@voltro/database";
4
+ import { tenant as c } from "@voltro/plugin-multitenancy/mixin";
5
+ //#region src/quality.ts
6
+ var l = "cms_quality_exceptions", u = 90, d = a(l, {
7
+ id: i({ prefix: "cqex" }),
8
+ site: o().maxLength(64),
9
+ environment: o().maxLength(64),
10
+ locale: o().maxLength(64),
11
+ contentType: o().maxLength(64),
12
+ variantId: o().maxLength(64),
13
+ ruleId: o().maxLength(64),
14
+ reason: o(),
15
+ status: o().oneOf([
16
+ "requested",
17
+ "approved",
18
+ "rejected",
19
+ "withdrawn"
20
+ ]),
21
+ requestedBy: o(),
22
+ requestedAt: s(),
23
+ decidedBy: o().nullable(),
24
+ decidedAt: s().nullable(),
25
+ note: o().nullable(),
26
+ expiresAt: s()
27
+ }).with(c()).index("cms_quality_exceptions_by_variant", [
28
+ "contentType",
29
+ "variantId",
30
+ "ruleId"
31
+ ]), f = (e, t) => t === null || (e.sites === void 0 || e.sites.includes(t.site)) && (e.locales === void 0 || e.locales.includes(t.locale)), p = (e, t, n = {
32
+ now: /* @__PURE__ */ new Date(),
33
+ scope: null
34
+ }) => (e.quality ?? []).filter((e) => f(e, n.scope) && e.check(t, n)).map((e) => ({
35
+ ruleId: e.id,
36
+ field: e.field,
37
+ message: e.message,
38
+ blocking: e.blocking !== !1,
39
+ pack: e.pack ?? null
40
+ })), m = (e) => e instanceof Date ? e : new Date(String(e)), h = (e, t) => {
41
+ let n = m(e.expiresAt), r = String(e.status);
42
+ return {
43
+ id: String(e.id),
44
+ ruleId: String(e.ruleId),
45
+ reason: String(e.reason),
46
+ state: (r === "approved" || r === "requested") && n.getTime() <= t.getTime() ? "expired" : r,
47
+ requestedBy: String(e.requestedBy),
48
+ requestedAt: m(e.requestedAt),
49
+ decidedBy: typeof e.decidedBy == "string" ? e.decidedBy : null,
50
+ note: typeof e.note == "string" ? e.note : null,
51
+ expiresAt: n
52
+ };
53
+ }, g = (e, t, n) => [
54
+ r("contentType", e.name),
55
+ r("variantId", t),
56
+ r("site", n.site),
57
+ r("environment", n.environment),
58
+ r("locale", n.locale)
59
+ ], _ = async (e, t, r, i, a = /* @__PURE__ */ new Date()) => (await e.query({
60
+ table: l,
61
+ predicate: n(...g(t, r, i)),
62
+ order: [{
63
+ column: "requestedAt",
64
+ direction: "desc"
65
+ }],
66
+ take: void 0,
67
+ skip: void 0,
68
+ projection: void 0
69
+ })).map((e) => h(e, a)), v = async (e, i, a, o, s, c = /* @__PURE__ */ new Date()) => {
70
+ let u = (i.quality ?? []).find((e) => e.id === s.ruleId && f(e, o));
71
+ if (u === void 0) return {
72
+ ok: !1,
73
+ reason: "unknownRule"
74
+ };
75
+ if (u.blocking === !1) return {
76
+ ok: !1,
77
+ reason: "notBlocking"
78
+ };
79
+ let d = s.reason.trim();
80
+ if (d === "" || d.length > 500) return {
81
+ ok: !1,
82
+ reason: "noReason"
83
+ };
84
+ let p = (s.expiresAt.getTime() - c.getTime()) / 864e5;
85
+ if (!(p > 0) || p > 90) return {
86
+ ok: !1,
87
+ reason: "badExpiry"
88
+ };
89
+ let [m] = await e.query({
90
+ table: `${i.name}_drafts`,
91
+ predicate: n(r("id", a), r("site", o.site), r("environment", o.environment), r("locale", o.locale)),
92
+ order: [],
93
+ take: 1,
94
+ skip: void 0,
95
+ projection: void 0
96
+ });
97
+ if (m === void 0) throw new t({
98
+ contentType: i.name,
99
+ id: a,
100
+ stage: "draft"
101
+ });
102
+ if ((await _(e, i, a, o, c)).some((e) => e.ruleId === u.id && (e.state === "requested" || e.state === "approved"))) return {
103
+ ok: !1,
104
+ reason: "alreadyOpen"
105
+ };
106
+ let h = m.tenantId, g = await e.insert(l, {
107
+ site: o.site,
108
+ environment: o.environment,
109
+ locale: o.locale,
110
+ contentType: i.name,
111
+ variantId: a,
112
+ ruleId: u.id,
113
+ reason: d,
114
+ status: "requested",
115
+ requestedBy: s.actorId,
116
+ requestedAt: c,
117
+ decidedBy: null,
118
+ decidedAt: null,
119
+ note: null,
120
+ expiresAt: s.expiresAt,
121
+ ...h == null ? {} : { tenantId: h }
122
+ });
123
+ return {
124
+ ok: !0,
125
+ id: String(g.id)
126
+ };
127
+ }, y = async (e, t, n, i = /* @__PURE__ */ new Date()) => {
128
+ let [a] = await e.query({
129
+ table: l,
130
+ predicate: r("id", t),
131
+ order: [],
132
+ take: 1,
133
+ skip: void 0,
134
+ projection: void 0
135
+ });
136
+ if (a === void 0) return {
137
+ ok: !1,
138
+ reason: "notFound"
139
+ };
140
+ let o = h(a, i), s = n.note !== void 0 && n.note.trim() !== "" ? n.note.trim().slice(0, 500) : null;
141
+ if (n.decision === "withdraw") return o.state === "requested" && o.requestedBy !== n.actorId ? {
142
+ ok: !1,
143
+ reason: "notRequester"
144
+ } : o.state !== "requested" && o.state !== "approved" ? {
145
+ ok: !1,
146
+ reason: "notOpen"
147
+ } : (await e.update(l, t, {
148
+ status: "withdrawn",
149
+ decidedBy: n.actorId,
150
+ decidedAt: i,
151
+ note: s
152
+ }), {
153
+ ok: !0,
154
+ state: "withdrawn"
155
+ });
156
+ if (o.state !== "requested") return {
157
+ ok: !1,
158
+ reason: "notOpen"
159
+ };
160
+ if (o.requestedBy === n.actorId) return {
161
+ ok: !1,
162
+ reason: "selfApproval"
163
+ };
164
+ let c = n.decision === "approve" ? "approved" : "rejected";
165
+ return await e.update(l, t, {
166
+ status: c,
167
+ decidedBy: n.actorId,
168
+ decidedAt: i,
169
+ note: s
170
+ }), {
171
+ ok: !0,
172
+ state: c
173
+ };
174
+ }, b = async (e, t, n, r, i, a = /* @__PURE__ */ new Date()) => {
175
+ let o = p(t, i, {
176
+ now: a,
177
+ scope: r
178
+ }).filter((e) => e.blocking);
179
+ if (o.length === 0) return [];
180
+ let s = new Set((await _(e, t, n, r, a)).filter((e) => e.state === "approved").map((e) => e.ruleId));
181
+ return o.filter((e) => !s.has(e.ruleId)).map((e) => ({
182
+ field: e.field,
183
+ rule: `quality.${e.ruleId}`,
184
+ message: e.message
185
+ }));
186
+ }, x = (...t) => e(() => _(...t)), S = (...n) => e(() => v(...n), t), C = (...t) => e(() => y(...t)), w = (...t) => e(() => b(...t));
187
+ //#endregion
188
+ export { l as CMS_QUALITY_EXCEPTIONS_TABLE, u as MAX_EXCEPTION_DAYS, d as cmsQualityExceptionsTable, y as decideQualityException, C as decideQualityExceptionEffect, _ as qualityExceptions, x as qualityExceptionsEffect, p as qualityFindings, b as qualityViolations, w as qualityViolationsEffect, v as requestQualityException, S as requestQualityExceptionEffect, f as ruleApplies };