@getquick/content 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -2
- package/consent-migrations/0001_consent_records.sql +34 -0
- package/dist/bearer-DMOCoSci.js +16 -0
- package/dist/client-config-B3LNSOxl.js +4 -0
- package/dist/configuration-C_JGygQI.d.ts +259 -0
- package/dist/consent-astro.d.ts +36 -0
- package/dist/consent-astro.js +94 -0
- package/dist/consent-client.d.ts +37 -0
- package/dist/consent-client.js +321 -0
- package/dist/consent-migrations.d.ts +8 -0
- package/dist/consent-migrations.js +10 -0
- package/dist/consent-server.d.ts +77 -0
- package/dist/consent-server.js +292 -0
- package/dist/consent-testing.d.ts +96 -0
- package/dist/consent-testing.js +188 -0
- package/dist/consent.d.ts +114 -0
- package/dist/consent.js +5 -0
- package/dist/embeds-p9SyGNZI.js +344 -0
- package/dist/formatting-wOk2jz6J.js +276 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +39 -0
- package/dist/legal-C5Xiq59W.d.ts +48 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.js +6 -0
- package/dist/paths-Cmzzof3v.js +5 -0
- package/dist/records-CACA2O0Q.d.ts +56 -0
- package/dist/records-Dumrz759.js +74 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +6465 -0
- package/dist/state-BYWc5TUB.js +559 -0
- package/dist/state-Dzd52F3c.d.ts +77 -0
- package/dist/version-DyyNhsuU.js +6 -0
- package/dist/wordpress-CUFb6YZZ.d.ts +384 -0
- package/migrations/0001_publications.sql +31 -0
- package/migrations/0002_entries.sql +14 -0
- package/migrations/0003_publication_events.sql +36 -0
- package/migrations/0004_withdrawals.sql +30 -0
- package/migrations/0005_reconciliation.sql +45 -0
- package/package.json +56 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { r as ConsentConfiguration, t as AnalyticsSettings } from "./configuration-C_JGygQI.js";
|
|
2
|
+
import { r as LegalLanguage } from "./legal-C5Xiq59W.js";
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region frontend/src/site-language.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The `<html lang>` for a WordPress locale: its language and region as a
|
|
7
|
+
* BCP 47 tag (pt_PT_ao90 → pt-PT). WordPress's variants (ao90, formal) aren't
|
|
8
|
+
* BCP 47 subtags, so they are left out; no locale means English.
|
|
9
|
+
*/
|
|
10
|
+
declare function htmlLang(locale: string | undefined): string;
|
|
11
|
+
/** A language the site serves. */
|
|
12
|
+
interface SiteLanguage {
|
|
13
|
+
/** Its WordPress locale (pt_PT_ao90); empty when configuration names none. */
|
|
14
|
+
locale: string;
|
|
15
|
+
/** Polylang's slug, its URL directory: the default language's is its language code. */
|
|
16
|
+
slug: string;
|
|
17
|
+
/** Its GraphQL LanguageCodeEnum value: the slug in upper case (pt-br → PT_BR). */
|
|
18
|
+
code: string;
|
|
19
|
+
/** Its `<html lang>`. */
|
|
20
|
+
lang: string;
|
|
21
|
+
isDefault: boolean;
|
|
22
|
+
/** Its home route: / for the default language, /<slug>/ for the others. */
|
|
23
|
+
home: string;
|
|
24
|
+
}
|
|
25
|
+
interface WordPressLanguages {
|
|
26
|
+
locale?: string;
|
|
27
|
+
languages?: readonly {
|
|
28
|
+
locale: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* configuration's languages: wordpress.locale, the default, served at /, then
|
|
34
|
+
* each of wordpress.languages under its slug. A monolingual Site has one.
|
|
35
|
+
*/
|
|
36
|
+
declare function siteLanguages(wordpress: WordPressLanguages | undefined): SiteLanguage[];
|
|
37
|
+
/**
|
|
38
|
+
* A route's language: the one whose slug is its first segment, else the
|
|
39
|
+
* default language, which has no directory.
|
|
40
|
+
*/
|
|
41
|
+
declare function routeLanguage(route: string, among: readonly SiteLanguage[]): SiteLanguage;
|
|
42
|
+
/**
|
|
43
|
+
* How logs and reports name something a language has: "the front page" for
|
|
44
|
+
* the default language's, "the en front page" for another's.
|
|
45
|
+
*/
|
|
46
|
+
declare function languageSubject(language: SiteLanguage, what: string): string;
|
|
47
|
+
/** Whether a route is a language's home (/, /en/): served as a home, never as an entry. */
|
|
48
|
+
declare function isLanguageHome(route: string, among: readonly SiteLanguage[]): boolean;
|
|
49
|
+
declare namespace content_types_d_exports {
|
|
50
|
+
export { CmsFailure, CmsFailureReason, Delivery, DesignPresets, EntryContent, Extended, Found, HomeContent, Missing, PublishedEntry, SiteChrome, Translation, Unavailable, WordPressImage, WordPressMenuItem, WordPressPost, WordPressSettings };
|
|
51
|
+
}
|
|
52
|
+
interface WordPressImage {
|
|
53
|
+
sourceUrl: string;
|
|
54
|
+
altText: string;
|
|
55
|
+
}
|
|
56
|
+
interface WordPressPost {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
excerpt: string;
|
|
60
|
+
content: string;
|
|
61
|
+
uri: string;
|
|
62
|
+
date: string;
|
|
63
|
+
featuredImage: WordPressImage | null;
|
|
64
|
+
hasVideoHero: boolean;
|
|
65
|
+
}
|
|
66
|
+
interface WordPressMenuItem {
|
|
67
|
+
id: string;
|
|
68
|
+
label: string;
|
|
69
|
+
href: string;
|
|
70
|
+
target: string | null;
|
|
71
|
+
children: WordPressMenuItem[];
|
|
72
|
+
}
|
|
73
|
+
interface WordPressSettings {
|
|
74
|
+
title: string;
|
|
75
|
+
description: string;
|
|
76
|
+
}
|
|
77
|
+
interface DesignPresets {
|
|
78
|
+
spacingSizes: Array<{
|
|
79
|
+
slug: string;
|
|
80
|
+
size: string;
|
|
81
|
+
}>;
|
|
82
|
+
colors: Array<{
|
|
83
|
+
slug: string;
|
|
84
|
+
color: string;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A page or post's own content as the Site's extension read it
|
|
89
|
+
* (ContentConfig's `extension`): present only when the Site declares one and
|
|
90
|
+
* its read returned a value for that page or post.
|
|
91
|
+
*/
|
|
92
|
+
interface Extended<X = unknown> {
|
|
93
|
+
extension?: X;
|
|
94
|
+
}
|
|
95
|
+
interface HomeContent<X = unknown> extends DesignPresets {
|
|
96
|
+
/** The front page's WordPress entry (WPGraphQL's global id), if WordPress gave it. */
|
|
97
|
+
nodeId: string | null;
|
|
98
|
+
settings: WordPressSettings;
|
|
99
|
+
page: {
|
|
100
|
+
title: string;
|
|
101
|
+
content: string;
|
|
102
|
+
hasVideoHero: boolean;
|
|
103
|
+
} & Extended<X>;
|
|
104
|
+
/** WordPress failed on the blocks, so this is the page without them. */
|
|
105
|
+
blocksOmitted: boolean;
|
|
106
|
+
}
|
|
107
|
+
/** A published translation of an entry: its URI and its language's slug. */
|
|
108
|
+
interface Translation {
|
|
109
|
+
uri: string;
|
|
110
|
+
language: string;
|
|
111
|
+
}
|
|
112
|
+
interface EntryContent<X = unknown> extends WordPressPost, DesignPresets, Extended<X> {
|
|
113
|
+
/** Its language's slug, on a multilingual Site. */
|
|
114
|
+
language?: string;
|
|
115
|
+
/** Its published translations into the site's other languages, on a multilingual Site. */
|
|
116
|
+
translations?: Translation[];
|
|
117
|
+
/** WordPress failed on the blocks, so this is the entry without them. */
|
|
118
|
+
blocksOmitted: boolean;
|
|
119
|
+
/** When WordPress last modified it (ms, the CMS's clock), if it said. */
|
|
120
|
+
modifiedAt: number | null;
|
|
121
|
+
}
|
|
122
|
+
/** A published page or post as WordPress lists it: its id, URI and last modification. */
|
|
123
|
+
interface PublishedEntry {
|
|
124
|
+
/** WPGraphQL's global id, when WordPress gives it. */
|
|
125
|
+
id: string | null;
|
|
126
|
+
uri: string;
|
|
127
|
+
/** When WordPress last modified it (ms, the CMS's clock), if it said. */
|
|
128
|
+
modifiedAt: number | null;
|
|
129
|
+
}
|
|
130
|
+
interface SiteChrome {
|
|
131
|
+
menuItems: WordPressMenuItem[];
|
|
132
|
+
siteIcon: WordPressImage | null;
|
|
133
|
+
siteLogo: WordPressImage | null;
|
|
134
|
+
/**
|
|
135
|
+
* This language's LEGAL-1 Contract from gq-config, on a Site with consent
|
|
136
|
+
* CMS settings enabled (null when gq-config has none for it).
|
|
137
|
+
*/
|
|
138
|
+
legal?: LegalLanguage | null;
|
|
139
|
+
/** gq-config's GA4 settings, on a Site with consent CMS settings enabled. */
|
|
140
|
+
analytics?: AnalyticsSettings;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Why WordPress couldn't deliver: it didn't answer in time (`timeout`), couldn't
|
|
144
|
+
* be reached (`network`), answered with an HTTP error (`http`) or GraphQL errors
|
|
145
|
+
* (`graphql`), or answered with data missing the fields the Frontend requires
|
|
146
|
+
* (`schema`).
|
|
147
|
+
*/
|
|
148
|
+
type CmsFailureReason = "timeout" | "network" | "http" | "graphql" | "schema";
|
|
149
|
+
interface CmsFailure {
|
|
150
|
+
reason: CmsFailureReason;
|
|
151
|
+
message: string;
|
|
152
|
+
httpStatus?: number;
|
|
153
|
+
}
|
|
154
|
+
interface Found<T> {
|
|
155
|
+
kind: "found";
|
|
156
|
+
content: T;
|
|
157
|
+
}
|
|
158
|
+
/** WordPress answered with valid data, and has nothing published there. */
|
|
159
|
+
interface Missing {
|
|
160
|
+
kind: "missing";
|
|
161
|
+
}
|
|
162
|
+
/** The content couldn't be read. This says nothing about whether it exists. */
|
|
163
|
+
interface Unavailable {
|
|
164
|
+
kind: "unavailable";
|
|
165
|
+
failure: CmsFailure;
|
|
166
|
+
}
|
|
167
|
+
type Delivery<T> = Found<T> | Missing | Unavailable;
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region frontend/src/bindings.d.ts
|
|
170
|
+
interface SqlStatement {
|
|
171
|
+
bind(...values: unknown[]): SqlStatement;
|
|
172
|
+
first<T = Record<string, unknown>>(): Promise<T | null>;
|
|
173
|
+
all<T = Record<string, unknown>>(): Promise<{
|
|
174
|
+
results: T[];
|
|
175
|
+
}>;
|
|
176
|
+
run(): Promise<{
|
|
177
|
+
meta: {
|
|
178
|
+
changes: number;
|
|
179
|
+
};
|
|
180
|
+
}>;
|
|
181
|
+
}
|
|
182
|
+
interface SqlDatabase {
|
|
183
|
+
prepare(query: string): SqlStatement;
|
|
184
|
+
/** Runs the statements in order as one transaction: all of them apply, or none. */
|
|
185
|
+
batch<T = Record<string, unknown>>(statements: SqlStatement[]): Promise<Array<{
|
|
186
|
+
results: T[];
|
|
187
|
+
}>>;
|
|
188
|
+
}
|
|
189
|
+
interface ContentBindings {
|
|
190
|
+
/** The publication store (D1). */
|
|
191
|
+
PUBLICATION_DB?: SqlDatabase;
|
|
192
|
+
/** The trusted refresh's bearer token (a Worker secret). */
|
|
193
|
+
FRONTEND_REFRESH_TOKEN?: string;
|
|
194
|
+
/** The key the CMS signs its publication events with (a Worker secret). */
|
|
195
|
+
PUBLICATION_EVENT_SECRET?: string;
|
|
196
|
+
/** Read-only CMS edge identity; never the automation service or public env. */
|
|
197
|
+
GQ_AUTH_GRAPHQL_CLIENT_ID?: string;
|
|
198
|
+
GQ_AUTH_GRAPHQL_CLIENT_SECRET?: string;
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region frontend/src/page-types.d.ts
|
|
202
|
+
/** The front page as stored: what WordPress delivered, without read details. */
|
|
203
|
+
type HomePublication<X = unknown> = Omit<HomeContent<X>, "blocksOmitted" | "nodeId">;
|
|
204
|
+
/** A published page or post as stored, with the design presets it was read with. */
|
|
205
|
+
type EntryPublication<X = unknown> = Omit<EntryContent<X>, "blocksOmitted" | "modifiedAt">;
|
|
206
|
+
/**
|
|
207
|
+
* A page in another language that a visitor of this one may switch to: its
|
|
208
|
+
* published translation (`translation`), or else that language's home.
|
|
209
|
+
*/
|
|
210
|
+
interface Alternate {
|
|
211
|
+
language: SiteLanguage;
|
|
212
|
+
route: string;
|
|
213
|
+
translation: boolean;
|
|
214
|
+
}
|
|
215
|
+
interface HomePage<X = unknown> {
|
|
216
|
+
home: HomePublication<X>;
|
|
217
|
+
chrome: SiteChrome;
|
|
218
|
+
/** The other languages' homes that are served; none on a monolingual Site. */
|
|
219
|
+
alternates: Alternate[];
|
|
220
|
+
}
|
|
221
|
+
interface EntryPage<X = unknown> {
|
|
222
|
+
entry: EntryPublication<X>;
|
|
223
|
+
chrome: SiteChrome;
|
|
224
|
+
/** Where its readers may switch language; none on a monolingual Site. */
|
|
225
|
+
alternates: Alternate[];
|
|
226
|
+
}
|
|
227
|
+
/** The entry a route served is published at another URI now: the route redirects there. */
|
|
228
|
+
interface Moved {
|
|
229
|
+
kind: "moved";
|
|
230
|
+
uri: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Why there is nothing to serve: the CMS failed (live reads, in explicit development),
|
|
234
|
+
* the store couldn't be read (`store`), or the Site has never been refreshed,
|
|
235
|
+
* or holds only state this Frontend can't read (`not-ready`).
|
|
236
|
+
*/
|
|
237
|
+
type DeliveryFailureReason = CmsFailureReason | "store" | "not-ready";
|
|
238
|
+
interface NotServed {
|
|
239
|
+
kind: "unavailable";
|
|
240
|
+
failure: {
|
|
241
|
+
reason: DeliveryFailureReason;
|
|
242
|
+
message: string;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
type HomeDelivery<X = unknown> = Found<HomePage<X>> | (Missing & {
|
|
246
|
+
chrome: SiteChrome | null;
|
|
247
|
+
}) | NotServed;
|
|
248
|
+
type EntryDelivery<X = unknown> = Found<EntryPage<X>> | (Missing & {
|
|
249
|
+
chrome: SiteChrome | null;
|
|
250
|
+
}) | Moved | NotServed;
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region frontend/src/server.d.ts
|
|
253
|
+
interface ContentConfig<X = never> {
|
|
254
|
+
site: string;
|
|
255
|
+
graphqlEndpoint: string;
|
|
256
|
+
cmsOrigin: string;
|
|
257
|
+
wordpress?: WordPressLanguages;
|
|
258
|
+
/** Live delivery without a publication store is opt-in; false by default. */
|
|
259
|
+
development?: boolean;
|
|
260
|
+
isServer: () => boolean;
|
|
261
|
+
bindings: () => ContentBindings | Promise<ContentBindings>;
|
|
262
|
+
fetch?: typeof globalThis.fetch;
|
|
263
|
+
/**
|
|
264
|
+
* CONSENT-1 for this Site. Present, every published body's third-party
|
|
265
|
+
* embeds are rewritten into consent placeholders (unknown hosts blocked) on
|
|
266
|
+
* delivery; absent, bodies are delivered as published.
|
|
267
|
+
*/
|
|
268
|
+
consent?: ContentConsentConfig;
|
|
269
|
+
/**
|
|
270
|
+
* The Site's own content on its pages and posts: GraphQL fields the runtime
|
|
271
|
+
* reads with each of them, and the value the Site makes of them, stored with
|
|
272
|
+
* the publication and served as its `extension`.
|
|
273
|
+
*/
|
|
274
|
+
extension?: ContentExtension<X>;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A Site's own content on its pages and posts, beyond what the runtime reads:
|
|
278
|
+
* fields a plugin adds to WPGraphQL (a block tree, a page setting), read with
|
|
279
|
+
* every front page, page and post, stored with it, and served with it through
|
|
280
|
+
* outages, as the rest of it is.
|
|
281
|
+
*/
|
|
282
|
+
interface ContentExtension<X> {
|
|
283
|
+
/**
|
|
284
|
+
* GraphQL selections added to each kind of node the runtime reads: `page`
|
|
285
|
+
* to front pages and pages, `post` to posts (e.g. `wpBlocksJson`). They are
|
|
286
|
+
* read with the blocks: a read WordPress can only complete without its
|
|
287
|
+
* blocks leaves them out too, and isn't stored. A kind without fields has
|
|
288
|
+
* no extension.
|
|
289
|
+
*/
|
|
290
|
+
fields: {
|
|
291
|
+
page?: string;
|
|
292
|
+
post?: string;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* The Site's value for a node WordPress returned (its fields, the runtime's
|
|
296
|
+
* among them), or undefined when it has none (`extension` is then left
|
|
297
|
+
* out). It is stored as JSON, so it must survive JSON. Throwing fails the
|
|
298
|
+
* read as WordPress answering with the wrong schema: what is stored stays
|
|
299
|
+
* served.
|
|
300
|
+
*/
|
|
301
|
+
read(node: ExtensionNode): X | undefined;
|
|
302
|
+
/**
|
|
303
|
+
* The value read back from the store, or from a read about to be stored
|
|
304
|
+
* (JSON), or undefined when the Site can't serve it. A stored row whose
|
|
305
|
+
* extension doesn't parse is unusable, as any body that doesn't; one stored
|
|
306
|
+
* without an extension (before the Site declared it) still serves, without.
|
|
307
|
+
* Return a valid value as it is: reconciliation compares it with new reads.
|
|
308
|
+
*/
|
|
309
|
+
parse(stored: unknown): X | undefined;
|
|
310
|
+
}
|
|
311
|
+
/** A front page, page or post as WordPress returned it, for a Site's extension. */
|
|
312
|
+
interface ExtensionNode {
|
|
313
|
+
kind: "page" | "post";
|
|
314
|
+
fields: Readonly<Record<string, unknown>>;
|
|
315
|
+
}
|
|
316
|
+
interface ContentConsentConfig {
|
|
317
|
+
/** The catalog services the Site declares in code (e.g. "youtube", "google_maps"). */
|
|
318
|
+
services?: readonly string[];
|
|
319
|
+
/**
|
|
320
|
+
* Hosts that are the Site itself besides relative URLs: its Frontend and its
|
|
321
|
+
* CMS/media origin. The CMS origin is included by default.
|
|
322
|
+
*/
|
|
323
|
+
firstPartyHosts?: readonly string[];
|
|
324
|
+
/**
|
|
325
|
+
* Read gq-config's Legal Contract and GA4 settings into each language's
|
|
326
|
+
* chrome (requires gq-config with the `getquickLegal` field). Their changes
|
|
327
|
+
* arrive as `legal` and `analytics` settings events.
|
|
328
|
+
*/
|
|
329
|
+
cmsSettings?: boolean;
|
|
330
|
+
}
|
|
331
|
+
declare function createContentRuntime<X = never>(input: ContentConfig<X>): {
|
|
332
|
+
languages: SiteLanguage[];
|
|
333
|
+
defaultLanguage: SiteLanguage;
|
|
334
|
+
multilingual: boolean;
|
|
335
|
+
routeLanguage: (route: string) => SiteLanguage;
|
|
336
|
+
isLanguageHome: (route: string) => boolean;
|
|
337
|
+
wordpress: WordPressClient<X>;
|
|
338
|
+
handleEvent: (request: Request, context?: {
|
|
339
|
+
siteOrigin?: string;
|
|
340
|
+
}) => Promise<Response>;
|
|
341
|
+
handleRefresh: (request: Request, context?: {
|
|
342
|
+
siteOrigin?: string;
|
|
343
|
+
}) => Promise<Response>;
|
|
344
|
+
publishedHome: (siteOrigin?: string, language?: SiteLanguage) => Promise<HomeDelivery<X>>;
|
|
345
|
+
publishedEntry: (path: string, siteOrigin?: string) => Promise<EntryDelivery<X>>;
|
|
346
|
+
pageChrome: (siteOrigin?: string) => Promise<SiteChrome | null>;
|
|
347
|
+
/**
|
|
348
|
+
* The design presets every page shares (GQ Design's spacing sizes and
|
|
349
|
+
* colors), for a page that isn't a publication: the stored ones when there
|
|
350
|
+
* is a store, else (in explicit development) a live read. Null when
|
|
351
|
+
* neither has them.
|
|
352
|
+
*/
|
|
353
|
+
designPresets: () => Promise<DesignPresets | null>;
|
|
354
|
+
/**
|
|
355
|
+
* The origins a link into this Site may use: WordPress's, and the
|
|
356
|
+
* Frontend's when given. `menuItemHref` turns links at them into paths.
|
|
357
|
+
*/
|
|
358
|
+
internalOrigins: (siteOrigin?: string) => string[];
|
|
359
|
+
/**
|
|
360
|
+
* The Site's consent configuration for a page served with this chrome:
|
|
361
|
+
* declared services, GA4 when gq-config's toggle is on, and the
|
|
362
|
+
* controller from the Legal Contract. Null when consent isn't configured.
|
|
363
|
+
*/
|
|
364
|
+
consentConfiguration: (chrome?: SiteChrome | null) => ConsentConfiguration | null;
|
|
365
|
+
};
|
|
366
|
+
type ContentRuntime<X = never> = ReturnType<typeof createContentRuntime<X>>;
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region frontend/src/wordpress.d.ts
|
|
369
|
+
interface WordPressClient<X = unknown> {
|
|
370
|
+
getHomeContent(this: void, language?: SiteLanguage): Promise<Delivery<HomeContent<X>>>;
|
|
371
|
+
getEntryByUri(this: void, uri: string): Promise<Delivery<EntryContent<X>>>;
|
|
372
|
+
getDesignPresets(this: void): Promise<Found<DesignPresets> | Unavailable>;
|
|
373
|
+
getPublishedEntries(this: void): Promise<Found<{
|
|
374
|
+
entries: PublishedEntry[];
|
|
375
|
+
requests: number;
|
|
376
|
+
}> | Unavailable>;
|
|
377
|
+
getPublishedRoutes(this: void): Promise<Found<string[]> | Unavailable>;
|
|
378
|
+
getSiteChrome(this: void, siteOrigin?: string, options?: {
|
|
379
|
+
location?: string;
|
|
380
|
+
language?: SiteLanguage;
|
|
381
|
+
}): Promise<Found<SiteChrome> | Unavailable>;
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
export { PublishedEntry as A, htmlLang as B, Delivery as C, Found as D, Extended as E, WordPressMenuItem as F, languageSubject as H, WordPressPost as I, WordPressSettings as L, Translation as M, Unavailable as N, HomeContent as O, WordPressImage as P, SiteLanguage as R, CmsFailureReason as S, EntryContent as T, routeLanguage as U, isLanguageHome as V, siteLanguages as W, NotServed as _, ContentRuntime as a, SqlStatement as b, Alternate as c, EntryPage as d, EntryPublication as f, Moved as g, HomePublication as h, ContentExtension as i, SiteChrome as j, Missing as k, DeliveryFailureReason as l, HomePage as m, ContentConfig as n, ExtensionNode as o, HomeDelivery as p, ContentConsentConfig as r, createContentRuntime as s, WordPressClient as t, EntryDelivery as u, ContentBindings as v, DesignPresets as w, CmsFailure as x, SqlDatabase as y, WordPressLanguages as z };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- The Frontend's durable published content: the last-known-good public
|
|
2
|
+
-- representation of each published thing ("home", "chrome"), and the outcome
|
|
3
|
+
-- of the last refresh of each. Alchemy applies these files in order on deploy
|
|
4
|
+
-- (infra/frontend.run.ts), before the Worker is updated, so a migration must
|
|
5
|
+
-- keep working with the Worker version it replaces: add, never rewrite.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE publications (
|
|
8
|
+
-- What the row is: "home" (the front page), "chrome" (menu, logo, icon).
|
|
9
|
+
key TEXT PRIMARY KEY,
|
|
10
|
+
-- "published" (body holds it) or "missing" (the CMS confirmed nothing is
|
|
11
|
+
-- published there).
|
|
12
|
+
state TEXT NOT NULL,
|
|
13
|
+
-- The version of body's shape. A Worker only serves the formats it knows.
|
|
14
|
+
format INTEGER NOT NULL,
|
|
15
|
+
body TEXT,
|
|
16
|
+
-- When the CMS read behind this row started (ms since the epoch). A
|
|
17
|
+
-- promotion only replaces a row whose read started earlier, so a slow or
|
|
18
|
+
-- delayed refresh can't overwrite a newer one.
|
|
19
|
+
read_started_at INTEGER NOT NULL,
|
|
20
|
+
promoted_at INTEGER NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE refresh_attempts (
|
|
24
|
+
key TEXT PRIMARY KEY,
|
|
25
|
+
attempted_at INTEGER NOT NULL,
|
|
26
|
+
-- "promoted", "superseded" (a newer read was already promoted) or "kept"
|
|
27
|
+
-- (the read failed, so the stored row was left as it was).
|
|
28
|
+
outcome TEXT NOT NULL,
|
|
29
|
+
reason TEXT,
|
|
30
|
+
message TEXT
|
|
31
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Published entries (pages and posts), one row each in publications, keyed
|
|
2
|
+
-- "entry:<uri>". Added for them, and harmless to the Worker version before:
|
|
3
|
+
-- it never names node_id, so its writes leave it NULL.
|
|
4
|
+
|
|
5
|
+
-- Which WordPress post or page a row holds (WPGraphQL's global id), so a
|
|
6
|
+
-- refresh that finds it at another URI can reconcile the route it left, and
|
|
7
|
+
-- a later withdrawal can find every route that served it.
|
|
8
|
+
ALTER TABLE publications ADD COLUMN node_id TEXT;
|
|
9
|
+
|
|
10
|
+
CREATE INDEX publications_node_id ON publications (node_id);
|
|
11
|
+
|
|
12
|
+
-- publications.state gains "moved": the entry the row held is published at
|
|
13
|
+
-- another URI now (body: {"uri": "<its URI>"}), so the route redirects there
|
|
14
|
+
-- instead of presenting its superseded copy as current.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- The CMS's publication events (POST /gq/events): one row per event the
|
|
2
|
+
-- Frontend accepted, so a duplicate is recognised, a delayed one can't undo a
|
|
3
|
+
-- newer one, and one whose refresh failed stays on record for a retry. Added
|
|
4
|
+
-- for them, and harmless to the Worker version before: it never reads it.
|
|
5
|
+
|
|
6
|
+
CREATE TABLE publication_events (
|
|
7
|
+
-- The event's identity, chosen by the CMS (a UUID). A delivery of an id
|
|
8
|
+
-- already recorded is a duplicate, or a retry of one that failed.
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
-- What happened: "publish" (a page or post was published or updated).
|
|
11
|
+
action TEXT NOT NULL,
|
|
12
|
+
-- The WordPress entry it is about (WPGraphQL's global id), as in
|
|
13
|
+
-- publications.node_id.
|
|
14
|
+
node_id TEXT NOT NULL,
|
|
15
|
+
-- The entry's URI when the event happened, and the one it had before, if it
|
|
16
|
+
-- moved.
|
|
17
|
+
uri TEXT NOT NULL,
|
|
18
|
+
previous_uri TEXT,
|
|
19
|
+
-- When it happened in the CMS (ms since the epoch): the order of one
|
|
20
|
+
-- entry's events. An event older than one already refreshed for the same
|
|
21
|
+
-- entry is superseded, never processed.
|
|
22
|
+
occurred_at INTEGER NOT NULL,
|
|
23
|
+
received_at INTEGER NOT NULL,
|
|
24
|
+
-- "received" (not processed yet, or interrupted), "refreshed", "failed"
|
|
25
|
+
-- (its refresh kept the stored versions; a retry may process it again) or
|
|
26
|
+
-- "superseded" (a newer event for the entry was refreshed).
|
|
27
|
+
status TEXT NOT NULL,
|
|
28
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
processed_at INTEGER,
|
|
30
|
+
-- Why it failed, when it did (a refresh failure reason and its message).
|
|
31
|
+
reason TEXT,
|
|
32
|
+
message TEXT
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX publication_events_node ON publication_events (node_id, occurred_at);
|
|
36
|
+
CREATE INDEX publication_events_status ON publication_events (status, received_at);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- The CMS's withdrawals (a page or post unpublished, made private or
|
|
2
|
+
-- password-protected, trashed or deleted): one row per WordPress entry whose
|
|
3
|
+
-- last accepted lifecycle event withdrew it. While a withdrawal is in force,
|
|
4
|
+
-- nothing promotes that entry's content again, whatever a read returns; only a
|
|
5
|
+
-- publication event that happened after it lifts it. Added for them, and
|
|
6
|
+
-- harmless to the Worker version before: it never reads this table, and a
|
|
7
|
+
-- "withdrawn" row is a state it doesn't know, so it serves it as a 503, never
|
|
8
|
+
-- as content.
|
|
9
|
+
|
|
10
|
+
CREATE TABLE withdrawals (
|
|
11
|
+
-- The WordPress entry (WPGraphQL's global id), as in publications.node_id.
|
|
12
|
+
node_id TEXT PRIMARY KEY,
|
|
13
|
+
-- The withdraw event in force (publication_events.id) and the URI the entry
|
|
14
|
+
-- had while it was published.
|
|
15
|
+
event_id TEXT NOT NULL,
|
|
16
|
+
uri TEXT NOT NULL,
|
|
17
|
+
-- When it happened in the CMS (ms since the epoch), and when the Frontend
|
|
18
|
+
-- accepted it (ms, the Frontend's clock).
|
|
19
|
+
withdrawn_at INTEGER NOT NULL,
|
|
20
|
+
accepted_at INTEGER NOT NULL,
|
|
21
|
+
-- The newer publication event that lifted it, and when that happened in the
|
|
22
|
+
-- CMS. NULL while the withdrawal is in force.
|
|
23
|
+
republished_by TEXT,
|
|
24
|
+
republished_at INTEGER
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- publications.state gains "withdrawn": the entry the row held was withdrawn
|
|
28
|
+
-- (body NULL). It is a 404, and nothing older than the withdrawal replaces it.
|
|
29
|
+
-- publication_events.action gains "withdraw"; one that was applied is
|
|
30
|
+
-- recorded "refreshed", like a publication: the store is up to date with it.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- Reconciliation (ADR 0009): the CMS's scheduler asks the Frontend, every
|
|
2
|
+
-- minute, to compare its publication store with what WordPress publishes and
|
|
3
|
+
-- to refresh what differs, so a change whose event never arrived still
|
|
4
|
+
-- reaches visitors. Added for it, and harmless to the Worker version before:
|
|
5
|
+
-- it never names these columns or this table.
|
|
6
|
+
|
|
7
|
+
-- When WordPress last modified the entry a row holds (ms since the epoch, the
|
|
8
|
+
-- CMS's clock: WPGraphQL's modifiedGmt), as the read that promoted it saw it.
|
|
9
|
+
-- NULL for rows promoted before this column, or by a read without it: a
|
|
10
|
+
-- reconciliation reads those again.
|
|
11
|
+
ALTER TABLE publications ADD COLUMN modified_at INTEGER;
|
|
12
|
+
|
|
13
|
+
-- The Site's reconciliation: one row, holding the lease that keeps two runs
|
|
14
|
+
-- from overlapping and the outcome of the last run, for diagnostics.
|
|
15
|
+
CREATE TABLE reconciliation (
|
|
16
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
17
|
+
-- The run holding the lease (the reconcile event's id), and until when (ms).
|
|
18
|
+
-- A run that was interrupted leaves it to expire.
|
|
19
|
+
run_id TEXT,
|
|
20
|
+
lease_until INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
started_at INTEGER,
|
|
22
|
+
finished_at INTEGER,
|
|
23
|
+
-- "reconciled" (nothing differs any more), "behind" (changes left for the
|
|
24
|
+
-- next run) or "failed" (a read or a refresh failed; what was stored is
|
|
25
|
+
-- kept).
|
|
26
|
+
outcome TEXT,
|
|
27
|
+
-- What the last run found: the published entries it compared, the changes it
|
|
28
|
+
-- refreshed, those it left for the next run, and those whose refresh failed.
|
|
29
|
+
checked INTEGER,
|
|
30
|
+
changed INTEGER,
|
|
31
|
+
pending INTEGER,
|
|
32
|
+
failed INTEGER,
|
|
33
|
+
-- The first failure's reason and message (no content, no secret).
|
|
34
|
+
reason TEXT,
|
|
35
|
+
message TEXT,
|
|
36
|
+
-- When the last run that left nothing behind started: the store matched
|
|
37
|
+
-- WordPress as of then.
|
|
38
|
+
reconciled_at INTEGER
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
-- Reconciliation records the change it found in an entry, when WordPress
|
|
42
|
+
-- gives its id and modification time, as a publication event in
|
|
43
|
+
-- publication_events (an id starting "reconcile-", the same for the same
|
|
44
|
+
-- change, and occurred_at its modification time), so it is processed, ordered
|
|
45
|
+
-- and retried by the same rules as one the CMS sent.
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getquick/content",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"migrations",
|
|
8
|
+
"consent-migrations"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"types": "./dist/server.d.ts",
|
|
19
|
+
"import": "./dist/server.js"
|
|
20
|
+
},
|
|
21
|
+
"./migrations": {
|
|
22
|
+
"types": "./dist/migrations.d.ts",
|
|
23
|
+
"import": "./dist/migrations.js"
|
|
24
|
+
},
|
|
25
|
+
"./consent": {
|
|
26
|
+
"types": "./dist/consent.d.ts",
|
|
27
|
+
"import": "./dist/consent.js"
|
|
28
|
+
},
|
|
29
|
+
"./consent/client": {
|
|
30
|
+
"types": "./dist/consent-client.d.ts",
|
|
31
|
+
"import": "./dist/consent-client.js"
|
|
32
|
+
},
|
|
33
|
+
"./consent/server": {
|
|
34
|
+
"types": "./dist/consent-server.d.ts",
|
|
35
|
+
"import": "./dist/consent-server.js"
|
|
36
|
+
},
|
|
37
|
+
"./consent/migrations": {
|
|
38
|
+
"types": "./dist/consent-migrations.d.ts",
|
|
39
|
+
"import": "./dist/consent-migrations.js"
|
|
40
|
+
},
|
|
41
|
+
"./consent/astro": {
|
|
42
|
+
"types": "./dist/consent-astro.d.ts",
|
|
43
|
+
"import": "./dist/consent-astro.js"
|
|
44
|
+
},
|
|
45
|
+
"./consent/testing": {
|
|
46
|
+
"types": "./dist/consent-testing.d.ts",
|
|
47
|
+
"import": "./dist/consent-testing.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"zod": "3.25.76"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22"
|
|
58
|
+
}
|
|
6
59
|
}
|