astroidjs 0.12.1 → 0.14.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 +42 -42
- package/bin/astroid.mjs +35 -29
- package/dist/analytics/index.d.ts +3 -3
- package/dist/analytics/index.js +11 -11
- package/dist/astro/csp.d.ts +5 -5
- package/dist/astro/csp.js +6 -6
- package/dist/astro/index.js +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +5 -5
- package/dist/commerce/adapters.d.ts +6 -6
- package/dist/commerce/adapters.js +9 -9
- package/dist/commerce/checkout-scaffold.d.ts +5 -5
- package/dist/commerce/checkout-scaffold.js +27 -27
- package/dist/commerce/checkout.d.ts +12 -12
- package/dist/commerce/checkout.js +7 -7
- package/dist/commerce/loader.d.ts +2 -2
- package/dist/commerce/loader.js +3 -3
- package/dist/commerce/mirror.d.ts +4 -4
- package/dist/commerce/mirror.js +12 -12
- package/dist/commerce/roles.d.ts +10 -10
- package/dist/commerce/roles.js +13 -13
- package/dist/commerce/secrets.d.ts +9 -9
- package/dist/commerce/secrets.js +9 -9
- package/dist/commerce/sync.d.ts +7 -7
- package/dist/commerce/sync.js +5 -5
- package/dist/components/sections.d.ts +9 -9
- package/dist/components/sections.js +12 -12
- package/dist/config.d.ts +89 -62
- package/dist/config.js +18 -18
- package/dist/email/inquiry.d.ts +2 -2
- package/dist/email/inquiry.js +1 -1
- package/dist/email/send.d.ts +4 -4
- package/dist/email/send.js +7 -7
- package/dist/email/templates.js +3 -3
- package/dist/email/theme.d.ts +1 -1
- package/dist/email/theme.js +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.js +1 -1
- package/dist/map/pmtiles.d.ts +5 -5
- package/dist/map/pmtiles.js +5 -5
- package/dist/map/scaffold.d.ts +2 -2
- package/dist/map/scaffold.js +8 -8
- package/dist/map/style.d.ts +4 -4
- package/dist/map/style.js +1 -1
- package/dist/portal/config.d.ts +3 -3
- package/dist/portal/config.js +11 -10
- package/dist/portal/guard.d.ts +4 -4
- package/dist/portal/guard.js +4 -4
- package/dist/portal/nav.js +2 -2
- package/dist/portal/scaffold.d.ts +4 -4
- package/dist/portal/scaffold.js +13 -13
- package/dist/portal/session.d.ts +11 -5
- package/dist/portal/session.js +10 -5
- package/dist/portfolio/scaffold.d.ts +1 -1
- package/dist/portfolio/scaffold.js +8 -8
- package/dist/project/actions.d.ts +1 -1
- package/dist/project/actions.js +18 -12
- package/dist/project/generate.d.ts +4 -4
- package/dist/project/generate.js +26 -26
- package/dist/project/index.d.ts +1 -0
- package/dist/project/index.js +2 -1
- package/dist/project/scaffold.d.ts +2 -2
- package/dist/project/scaffold.js +69 -13
- package/dist/project/seed.d.ts +22 -0
- package/dist/project/seed.js +214 -0
- package/dist/pwa/generate.d.ts +11 -11
- package/dist/pwa/generate.js +19 -19
- package/dist/queues/consumer.d.ts +3 -3
- package/dist/queues/consumer.js +2 -2
- package/dist/queues/messages.d.ts +4 -4
- package/dist/queues/messages.js +2 -2
- package/dist/queues/scaffold.d.ts +4 -4
- package/dist/queues/scaffold.js +17 -17
- package/dist/queues/webhook.d.ts +5 -5
- package/dist/queues/webhook.js +3 -3
- package/dist/realtime/scaffold.d.ts +4 -4
- package/dist/realtime/scaffold.js +12 -12
- package/dist/schema/collections.d.ts +42 -11
- package/dist/schema/collections.js +43 -42
- package/dist/schema/framework.d.ts +1 -1
- package/dist/schema/framework.js +2 -2
- package/dist/schema/generate.js +6 -6
- package/dist/schema/index.js +1 -1
- package/dist/secrets.d.ts +6 -6
- package/dist/secrets.js +6 -6
- package/dist/security/csp-origins.d.ts +1 -1
- package/dist/security/csp-origins.js +3 -3
- package/dist/security/rate-rules.d.ts +2 -2
- package/dist/security/rate-rules.js +8 -8
- package/dist/seo/resolve.d.ts +5 -5
- package/dist/seo/resolve.js +2 -2
- package/dist/seo/routes.d.ts +5 -5
- package/dist/seo/routes.js +3 -3
- package/dist/seo/structured-data.d.ts +6 -6
- package/dist/seo/structured-data.js +7 -7
- package/dist/status.d.ts +5 -5
- package/dist/status.js +7 -7
- package/dist/tenancy/index.d.ts +3 -3
- package/dist/tenancy/index.js +11 -11
- package/dist/worker/generate.d.ts +2 -2
- package/dist/worker/generate.js +91 -57
- package/dist/worker/index.js +1 -1
- package/dist/worker/routes.js +11 -11
- package/dist/workflow/advance.d.ts +3 -3
- package/dist/workflow/advance.js +6 -6
- package/dist/workflow/config.d.ts +4 -4
- package/dist/workflow/config.js +4 -4
- package/dist/workflow/generate.d.ts +2 -2
- package/dist/workflow/generate.js +11 -11
- package/package.json +3 -4
- package/src/components/Collection.tsx +5 -5
- package/src/components/Editable.astro +9 -9
- package/src/components/JustifiedGallery.astro +8 -8
- package/src/components/MediaSlot.astro +12 -12
- package/src/components/PortalShell.astro +4 -4
- package/src/components/RegisterSW.astro +3 -3
- package/src/components/Section.astro +8 -8
- package/src/components/Sections.astro +6 -6
- package/src/components/Seo.astro +3 -3
- package/src/components/StageBar.astro +3 -3
- package/src/components/StructuredData.astro +2 -2
- package/src/components/justify.ts +9 -9
- package/src/components/media-meta.ts +10 -10
- package/src/components/sections/AboutIntro.astro +1 -1
- package/src/components/sections/Contact.astro +1 -1
- package/src/components/sections/Cta.astro +1 -1
- package/src/components/sections/Faq.astro +1 -1
- package/src/components/sections/FeatureGrid.astro +2 -2
- package/src/components/sections/Hero.astro +1 -1
- package/src/components/sections/PricingTiers.astro +1 -1
- package/src/components/sections/ProductGrid.astro +1 -1
- package/src/components/sections/SplitImage.astro +1 -1
- package/src/components/sections/Steps.astro +1 -1
- package/src/components/sections/Testimonial.astro +1 -1
- package/src/components/sections.ts +17 -17
|
@@ -14,7 +14,7 @@ export declare const ASTROID_DEFAULT_CRON = "0 * * * *";
|
|
|
14
14
|
export declare function astroidCron(config: AstroidConfig): string | null;
|
|
15
15
|
/**
|
|
16
16
|
* Daily, at an off-peak-ish minute. The health scan crawls the site's own pages,
|
|
17
|
-
* so it is deliberately NOT on the hourly catalog cron
|
|
17
|
+
* so it is deliberately NOT on the hourly catalog cron—hourly would be a
|
|
18
18
|
* self-inflicted crawl 24× a day to recompute counts that move slowly.
|
|
19
19
|
*/
|
|
20
20
|
export declare const ASTROID_HEALTH_CRON = "17 4 * * *";
|
|
@@ -31,7 +31,7 @@ export declare const ASTROID_HEALTH_CRON = "17 4 * * *";
|
|
|
31
31
|
export declare function astroidCrons(config: AstroidConfig): string[];
|
|
32
32
|
/** Binding name for the project's queue producer. */
|
|
33
33
|
export declare const ASTROID_QUEUE_BINDING = "COMMERCE_QUEUE";
|
|
34
|
-
/** Queue names derived from the project key
|
|
34
|
+
/** Queue names derived from the project key—the main queue and its DLQ. */
|
|
35
35
|
export declare function astroidQueueNames(config: AstroidConfig): {
|
|
36
36
|
queue: string;
|
|
37
37
|
dlq: string;
|
|
@@ -44,9 +44,9 @@ export declare function astroidQueueNames(config: AstroidConfig): {
|
|
|
44
44
|
*/
|
|
45
45
|
export interface WebhookMessage {
|
|
46
46
|
kind: "webhook";
|
|
47
|
-
/** Which integration sent it
|
|
47
|
+
/** Which integration sent it—`"square"`, `"stripe"`, `"fourthwall"`. */
|
|
48
48
|
provider: string;
|
|
49
|
-
/** The provider's event type,
|
|
49
|
+
/** The provider's event type, for example, `"catalog.version.updated"`. */
|
|
50
50
|
type: string;
|
|
51
51
|
payload: unknown;
|
|
52
52
|
}
|
package/dist/queues/messages.js
CHANGED
|
@@ -25,7 +25,7 @@ export function astroidCron(config) {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Daily, at an off-peak-ish minute. The health scan crawls the site's own pages,
|
|
28
|
-
* so it is deliberately NOT on the hourly catalog cron
|
|
28
|
+
* so it is deliberately NOT on the hourly catalog cron—hourly would be a
|
|
29
29
|
* self-inflicted crawl 24× a day to recompute counts that move slowly.
|
|
30
30
|
*/
|
|
31
31
|
export const ASTROID_HEALTH_CRON = "17 4 * * *";
|
|
@@ -52,7 +52,7 @@ export function astroidCrons(config) {
|
|
|
52
52
|
}
|
|
53
53
|
/** Binding name for the project's queue producer. */
|
|
54
54
|
export const ASTROID_QUEUE_BINDING = "COMMERCE_QUEUE";
|
|
55
|
-
/** Queue names derived from the project key
|
|
55
|
+
/** Queue names derived from the project key—the main queue and its DLQ. */
|
|
56
56
|
export function astroidQueueNames(config) {
|
|
57
57
|
return { queue: `${config.key}-commerce`, dlq: `${config.key}-commerce-dlq` };
|
|
58
58
|
}
|
|
@@ -11,7 +11,7 @@ import type { AstroidConfig, CommerceProvider } from "../config.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function generateAstroidEnvBindings(config: AstroidConfig): string;
|
|
13
13
|
/**
|
|
14
|
-
* `src/queue.ts
|
|
14
|
+
* `src/queue.ts`—the consumer seam the generated worker imports.
|
|
15
15
|
*
|
|
16
16
|
* `astroidQueueHandler` already owns the dispatch every site wrote (periodic
|
|
17
17
|
* refresh, catalog-affecting webhook, no-op for everything else); what's left
|
|
@@ -20,14 +20,14 @@ export declare function generateAstroidEnvBindings(config: AstroidConfig): strin
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function generateAstroidQueueSeam(config: AstroidConfig): string;
|
|
22
22
|
/**
|
|
23
|
-
* The provider webhook receiver
|
|
23
|
+
* The provider webhook receiver—`src/pages/api/webhooks/<provider>.ts`.
|
|
24
24
|
*
|
|
25
25
|
* Thin on purpose: `handleWebhook` owns the ordering (verify the raw body before
|
|
26
26
|
* parsing) and the status-code contract (which codes ask the provider to retry
|
|
27
27
|
* and which tell it to stop). What's here is the provider's own header and
|
|
28
28
|
* verifier, plus the secret read.
|
|
29
29
|
*
|
|
30
|
-
* Returns null when the project has no commerce provider
|
|
30
|
+
* Returns null when the project has no commerce provider—nothing to receive.
|
|
31
31
|
*/
|
|
32
32
|
export declare function generateAstroidWebhookRoute(config: AstroidConfig, forProvider?: CommerceProvider): string | null;
|
|
33
33
|
/**
|
|
@@ -35,7 +35,7 @@ export declare function generateAstroidWebhookRoute(config: AstroidConfig, forPr
|
|
|
35
35
|
*
|
|
36
36
|
* Plural because roles are: a site running Stripe for invoicing beside
|
|
37
37
|
* Fourthwall for the storefront receives from both, each with its own signing
|
|
38
|
-
* secret and header. One route per provider, not per role
|
|
38
|
+
* secret and header. One route per provider, not per role—a provider filling
|
|
39
39
|
* two roles still has one endpoint and one secret.
|
|
40
40
|
*/
|
|
41
41
|
export declare function generateAstroidWebhookRoutes(config: AstroidConfig): {
|
package/dist/queues/scaffold.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The two SCAFFOLD-ONCE files the queue pipeline needs: the consumer seam
|
|
4
4
|
// (`src/queue.ts`) and the provider webhook receiver.
|
|
5
5
|
//
|
|
6
|
-
// Deliberately not part of the regenerated trio. Both exist to be edited
|
|
6
|
+
// Deliberately not part of the regenerated trio. Both exist to be edited—the
|
|
7
7
|
// consumer is where a project says what a catalog refresh actually does, and the
|
|
8
8
|
// webhook route is where it narrows which events it cares about. Regenerating
|
|
9
9
|
// over them would erase exactly the work they're for. The same boundary
|
|
@@ -15,7 +15,7 @@ import { ASTROID_QUEUE_BINDING } from "./messages.js";
|
|
|
15
15
|
/**
|
|
16
16
|
* Per-provider webhook facts: the header, the verifier, and how it's called.
|
|
17
17
|
*
|
|
18
|
-
* The signing-secret NAME is deliberately not here
|
|
18
|
+
* The signing-secret NAME is deliberately not here—it's one field of a
|
|
19
19
|
* provider's secret set, which `commerce/secrets.ts` owns so the wrangler
|
|
20
20
|
* generator, the status report, and this scaffold all read the same list.
|
|
21
21
|
*/
|
|
@@ -36,7 +36,7 @@ const PROVIDERS = {
|
|
|
36
36
|
// Stripe's header carries a timestamp; the verifier rejects replays outside
|
|
37
37
|
// its tolerance, so it needs the current time.
|
|
38
38
|
call: 'verifyStripeSignature(raw, headers.get(HEADER) ?? "", secret, Math.floor(Date.now() / 1000))',
|
|
39
|
-
note: "Stripe's signature is timestamped
|
|
39
|
+
note: "Stripe's signature is timestamped—the verifier rejects replays outside a 5-minute tolerance.",
|
|
40
40
|
},
|
|
41
41
|
fourthwall: {
|
|
42
42
|
module: "louise-toolkit/commerce/fourthwall",
|
|
@@ -61,12 +61,12 @@ export function generateAstroidEnvBindings(config) {
|
|
|
61
61
|
if (providers.length === 0)
|
|
62
62
|
return "";
|
|
63
63
|
return [
|
|
64
|
-
" /** Queue producer
|
|
64
|
+
" /** Queue producer—verified webhooks + the cron re-sync (src/queue.ts). */",
|
|
65
65
|
' COMMERCE_QUEUE: Queue<import("astroidjs").AstroidQueueMessage>;',
|
|
66
66
|
// One secret SET per provider: a site running Stripe for invoicing and
|
|
67
67
|
// Fourthwall for the storefront talks to both, credentialed and signed
|
|
68
68
|
// independently. Every one is optional, because every one is allowed to be
|
|
69
|
-
// absent
|
|
69
|
+
// absent—that's what leaves the module dormant rather than broken.
|
|
70
70
|
...providers.flatMap((provider) => [
|
|
71
71
|
` /** ${provider} API credentials. Absent or still holding the`,
|
|
72
72
|
" * DUMMY_REPLACE_ME sentinel reads as unconfigured, which leaves commerce",
|
|
@@ -80,7 +80,7 @@ export function generateAstroidEnvBindings(config) {
|
|
|
80
80
|
].join("\n");
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* `src/queue.ts
|
|
83
|
+
* `src/queue.ts`—the consumer seam the generated worker imports.
|
|
84
84
|
*
|
|
85
85
|
* `astroidQueueHandler` already owns the dispatch every site wrote (periodic
|
|
86
86
|
* refresh, catalog-affecting webhook, no-op for everything else); what's left
|
|
@@ -88,7 +88,7 @@ export function generateAstroidEnvBindings(config) {
|
|
|
88
88
|
* a generated constant.
|
|
89
89
|
*/
|
|
90
90
|
export function generateAstroidQueueSeam(config) {
|
|
91
|
-
// The STOREFRONT provider
|
|
91
|
+
// The STOREFRONT provider—it's the one with a catalog to re-sync. An
|
|
92
92
|
// invoicing-only provider has nothing for this hook to do.
|
|
93
93
|
const provider = astroidCommerceRoles(config.commerce).storefront;
|
|
94
94
|
const table = astroidCatalogMirror(config).table;
|
|
@@ -97,15 +97,15 @@ export function generateAstroidQueueSeam(config) {
|
|
|
97
97
|
// in rather than leaving it to be discovered on a busy day (#294).
|
|
98
98
|
const multiProvider = astroidCommerceProviders(config.commerce).length > 1;
|
|
99
99
|
return [
|
|
100
|
-
"// The queue consumer
|
|
100
|
+
"// The queue consumer—what each message actually does.",
|
|
101
101
|
"//",
|
|
102
102
|
"// Scaffolded once; yours to edit. `astroidQueueHandler` owns the dispatch",
|
|
103
103
|
"// (a periodic refresh and any catalog-affecting webhook trigger a re-sync;",
|
|
104
104
|
"// everything else acks as a no-op), so what's left here is what a refresh",
|
|
105
105
|
"// MEANS for this project.",
|
|
106
106
|
"//",
|
|
107
|
-
"// Throwing marks the message for retry. That's usually right
|
|
108
|
-
"// refresh means the site is serving stale data
|
|
107
|
+
"// Throwing marks the message for retry. That's usually right—a failed",
|
|
108
|
+
"// refresh means the site is serving stale data—and Cloudflare routes it to",
|
|
109
109
|
"// the DLQ once it exceeds max_retries (wrangler.jsonc).",
|
|
110
110
|
'import { astroidQueueHandler, type AstroidQueueMessage } from "astroidjs";',
|
|
111
111
|
"",
|
|
@@ -118,7 +118,7 @@ export function generateAstroidQueueSeam(config) {
|
|
|
118
118
|
? [
|
|
119
119
|
` // This project runs more than one commerce provider, and the catalog is`,
|
|
120
120
|
` // ${provider}'s. Scoping the refresh keeps the OTHER provider's webhooks from`,
|
|
121
|
-
` // triggering it
|
|
121
|
+
` // triggering it—Square alone emits an inventory event on every sale, which`,
|
|
122
122
|
` // unscoped would re-sync ${provider} once per transaction.`,
|
|
123
123
|
` catalogProvider: ${JSON.stringify(provider)},`,
|
|
124
124
|
]
|
|
@@ -143,7 +143,7 @@ export function generateAstroidQueueSeam(config) {
|
|
|
143
143
|
" // takes `retry: { attempts: 3 }`, backing off on 429/5xx inside every",
|
|
144
144
|
" // verb. It is off by default because a checkout route has a customer",
|
|
145
145
|
" // watching a spinner, and there a fast failure beats a slow one. Here",
|
|
146
|
-
" // the opposite holds
|
|
146
|
+
" // the opposite holds—a catalog push that gives up halfway is worse.",
|
|
147
147
|
]
|
|
148
148
|
: [
|
|
149
149
|
" // Nobody is watching this run, so ask for backoff on 429/5xx wherever",
|
|
@@ -158,7 +158,7 @@ export function generateAstroidQueueSeam(config) {
|
|
|
158
158
|
" // letting that escape is correct: an uncaught throw here marks the",
|
|
159
159
|
" // message for retry. Swallow it and the queue acks, the cron acks too,",
|
|
160
160
|
" // and the site serves a frozen catalog with nothing in `wrangler tail`.",
|
|
161
|
-
" // Partial failures don't throw
|
|
161
|
+
" // Partial failures don't throw—that's what `r.failed` above is for.",
|
|
162
162
|
" void env;",
|
|
163
163
|
" },",
|
|
164
164
|
" })(message);",
|
|
@@ -169,14 +169,14 @@ export function generateAstroidQueueSeam(config) {
|
|
|
169
169
|
.join("\n");
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
|
-
* The provider webhook receiver
|
|
172
|
+
* The provider webhook receiver—`src/pages/api/webhooks/<provider>.ts`.
|
|
173
173
|
*
|
|
174
174
|
* Thin on purpose: `handleWebhook` owns the ordering (verify the raw body before
|
|
175
175
|
* parsing) and the status-code contract (which codes ask the provider to retry
|
|
176
176
|
* and which tell it to stop). What's here is the provider's own header and
|
|
177
177
|
* verifier, plus the secret read.
|
|
178
178
|
*
|
|
179
|
-
* Returns null when the project has no commerce provider
|
|
179
|
+
* Returns null when the project has no commerce provider—nothing to receive.
|
|
180
180
|
*/
|
|
181
181
|
export function generateAstroidWebhookRoute(config, forProvider) {
|
|
182
182
|
const provider = forProvider ?? astroidCommerceProviders(config.commerce)[0];
|
|
@@ -194,7 +194,7 @@ export function generateAstroidWebhookRoute(config, forProvider) {
|
|
|
194
194
|
`// ${p.note}`,
|
|
195
195
|
"//",
|
|
196
196
|
"// Unprovisioned (the secret is absent or still the placeholder) answers 503,",
|
|
197
|
-
"// which keeps the provider retrying
|
|
197
|
+
"// which keeps the provider retrying—so events delivered before you set the",
|
|
198
198
|
"// secret land afterwards instead of being lost.",
|
|
199
199
|
'import type { APIRoute } from "astro";',
|
|
200
200
|
'import { handleWebhook, readModuleSecret } from "astroidjs";',
|
|
@@ -222,7 +222,7 @@ export function generateAstroidWebhookRoute(config, forProvider) {
|
|
|
222
222
|
*
|
|
223
223
|
* Plural because roles are: a site running Stripe for invoicing beside
|
|
224
224
|
* Fourthwall for the storefront receives from both, each with its own signing
|
|
225
|
-
* secret and header. One route per provider, not per role
|
|
225
|
+
* secret and header. One route per provider, not per role—a provider filling
|
|
226
226
|
* two roles still has one endpoint and one secret.
|
|
227
227
|
*/
|
|
228
228
|
export function generateAstroidWebhookRoutes(config) {
|
package/dist/queues/webhook.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AstroidQueueMessage } from "./messages.js";
|
|
2
2
|
/**
|
|
3
|
-
* The queue producer surface used here
|
|
3
|
+
* The queue producer surface used here—structural, so a real `Queue<T>`
|
|
4
4
|
* binding satisfies it without astroid depending on the Workers types.
|
|
5
5
|
*
|
|
6
6
|
* `Promise<unknown>` rather than `Promise<void>`: Cloudflare's `Queue.send`
|
|
@@ -15,18 +15,18 @@ export interface WebhookVerifyInput {
|
|
|
15
15
|
raw: string;
|
|
16
16
|
headers: Headers;
|
|
17
17
|
url: URL;
|
|
18
|
-
/** The signing secret
|
|
18
|
+
/** The signing secret—already checked to be real by the caller. */
|
|
19
19
|
secret: string;
|
|
20
20
|
}
|
|
21
21
|
export interface WebhookRouteOptions {
|
|
22
|
-
/** Which integration this endpoint serves
|
|
22
|
+
/** Which integration this endpoint serves—carried into the message. */
|
|
23
23
|
provider: string;
|
|
24
24
|
/**
|
|
25
25
|
* The signing secret, or `null` when unprovisioned. Read it with
|
|
26
26
|
* `readModuleSecret` so a placeholder counts as absent.
|
|
27
27
|
*/
|
|
28
28
|
secret: string | null;
|
|
29
|
-
/** Signature check over the raw body
|
|
29
|
+
/** Signature check over the raw body—for example, `verifySquareSignature`. */
|
|
30
30
|
verify: (input: WebhookVerifyInput) => boolean | Promise<boolean>;
|
|
31
31
|
/** The queue binding, or null/undefined when Queues aren't provisioned. */
|
|
32
32
|
queue?: QueueProducer | null;
|
|
@@ -38,7 +38,7 @@ export interface WebhookRouteOptions {
|
|
|
38
38
|
eventType?: (payload: unknown) => string;
|
|
39
39
|
/**
|
|
40
40
|
* Decide whether an event is worth queueing at all. Returning false acks the
|
|
41
|
-
* delivery without enqueuing
|
|
41
|
+
* delivery without enqueuing—the provider is satisfied and the consumer
|
|
42
42
|
* isn't woken for an event nothing acts on.
|
|
43
43
|
*/
|
|
44
44
|
accept?: (type: string, payload: unknown) => boolean;
|
package/dist/queues/webhook.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// All three sites wrote this route the same way, and the ordering is the part
|
|
6
6
|
// worth encoding. **Verify the HMAC over the raw body before parsing anything.**
|
|
7
|
-
// Not for style
|
|
7
|
+
// Not for style—parsing first means an unauthenticated caller can reach the
|
|
8
8
|
// JSON parser and everything downstream of it, and re-serializing a parsed body
|
|
9
9
|
// to check the signature is how signature checks quietly stop checking anything.
|
|
10
10
|
// So the raw text is read once, verified, and only then parsed.
|
|
@@ -58,7 +58,7 @@ export async function handleWebhook(request, url, options) {
|
|
|
58
58
|
payload = JSON.parse(raw);
|
|
59
59
|
}
|
|
60
60
|
catch {
|
|
61
|
-
// Also terminal
|
|
61
|
+
// Also terminal—a body that isn't JSON now won't become JSON later.
|
|
62
62
|
return text("Invalid JSON", 400);
|
|
63
63
|
}
|
|
64
64
|
const type = (options.eventType ?? defaultEventType)(payload);
|
|
@@ -76,6 +76,6 @@ export async function handleWebhook(request, url, options) {
|
|
|
76
76
|
return text("Queue unavailable", 503);
|
|
77
77
|
}
|
|
78
78
|
// 202, not 200: the work hasn't happened yet, it's been accepted. That's the
|
|
79
|
-
// entire point of enqueuing
|
|
79
|
+
// entire point of enqueuing—the response returns before the consumer runs.
|
|
80
80
|
return text("Accepted", 202);
|
|
81
81
|
}
|
|
@@ -10,12 +10,12 @@ export declare const ASTROID_REALTIME_MIGRATION_TAG = "v1";
|
|
|
10
10
|
/** Is the realtime module switched on for this project? */
|
|
11
11
|
export declare function usesRealtime(config: AstroidConfig): boolean;
|
|
12
12
|
/**
|
|
13
|
-
* `src/edit-session.ts
|
|
13
|
+
* `src/edit-session.ts`—the site-owned Durable Object subclass.
|
|
14
14
|
*
|
|
15
15
|
* Scaffold-once: `persist` is where a project decides what a flush means, and
|
|
16
16
|
* the lock/field sets are tuning. What Astroid fixes is the delegation shape,
|
|
17
17
|
* because getting it wrong fails in ways that look like anything but a bug in
|
|
18
|
-
* this file
|
|
18
|
+
* this file—a missing `webSocketClose` leaks presence forever, a non-lazy
|
|
19
19
|
* session breaks after the first hibernation wake.
|
|
20
20
|
*
|
|
21
21
|
* Returns null when the project has no realtime module.
|
|
@@ -23,8 +23,8 @@ export declare function usesRealtime(config: AstroidConfig): boolean;
|
|
|
23
23
|
export declare function generateAstroidEditSession(config: AstroidConfig): string | null;
|
|
24
24
|
/**
|
|
25
25
|
* The `CloudflareEnv` member the realtime module adds, as a block
|
|
26
|
-
* `create-astroid` substitutes into `src/env.d.ts`. Empty without the module
|
|
27
|
-
*
|
|
26
|
+
* `create-astroid` substitutes into `src/env.d.ts`. Empty without the module—a
|
|
27
|
+
* project that types a binding its wrangler.jsonc never creates is making a
|
|
28
28
|
* promise it doesn't keep.
|
|
29
29
|
*/
|
|
30
30
|
export declare function generateAstroidRealtimeEnv(config: AstroidConfig): string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// The package description has claimed "multi-editor sites" since 0.1.0, and this
|
|
7
7
|
// is the half that makes it true for two people on the SAME page. (The other
|
|
8
|
-
// axis
|
|
8
|
+
// axis—multi-EDITOR, that is, an org of accounts—was always real.) Without it
|
|
9
9
|
// two editors on one page clobber each other; the server-side draft merge
|
|
10
10
|
// narrows the window but there is no live channel, no presence, and no signal
|
|
11
11
|
// that someone else is in the same field.
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
// What Astroid generates and what it deliberately does NOT:
|
|
14
14
|
//
|
|
15
15
|
// - The DO SUBCLASS is scaffold-once (`src/edit-session.ts`), because it must
|
|
16
|
-
// import `cloudflare:workers
|
|
17
|
-
// carry
|
|
16
|
+
// import `cloudflare:workers`—a runtime-only specifier the toolkit can't
|
|
17
|
+
// carry—and because its `persist` is the seam a project tunes. Louise
|
|
18
18
|
// ships the session LOGIC it delegates to; this is the boilerplate around it.
|
|
19
19
|
// - The wrangler `durable_objects` binding + `migrations` block, which is the
|
|
20
20
|
// part nobody gets right from memory: a DO class needs a migration tag, and
|
|
21
21
|
// a SQLite-backed one needs `new_sqlite_classes` rather than `new_classes`.
|
|
22
22
|
// - The `realtimeRoute` upgrade endpoint, in the generated worker.
|
|
23
23
|
//
|
|
24
|
-
// Persistence goes through `applySaveDraft
|
|
24
|
+
// Persistence goes through `applySaveDraft`—the SAME path the fetch auto-save
|
|
25
25
|
// uses. One write path, per the ADR: the DO is a new front end to it, not a
|
|
26
26
|
// parallel store, so drafts, version history, publish, and read-your-writes all
|
|
27
27
|
// stay intact.
|
|
@@ -38,12 +38,12 @@ export function usesRealtime(config) {
|
|
|
38
38
|
return (config.modules ?? []).includes("realtime");
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* `src/edit-session.ts
|
|
41
|
+
* `src/edit-session.ts`—the site-owned Durable Object subclass.
|
|
42
42
|
*
|
|
43
43
|
* Scaffold-once: `persist` is where a project decides what a flush means, and
|
|
44
44
|
* the lock/field sets are tuning. What Astroid fixes is the delegation shape,
|
|
45
45
|
* because getting it wrong fails in ways that look like anything but a bug in
|
|
46
|
-
* this file
|
|
46
|
+
* this file—a missing `webSocketClose` leaks presence forever, a non-lazy
|
|
47
47
|
* session breaks after the first hibernation wake.
|
|
48
48
|
*
|
|
49
49
|
* Returns null when the project has no realtime module.
|
|
@@ -54,7 +54,7 @@ export function generateAstroidEditSession(config) {
|
|
|
54
54
|
return [
|
|
55
55
|
"// The per-page live editing session Durable Object (ADR 0002 / #71).",
|
|
56
56
|
"//",
|
|
57
|
-
"// Scaffolded once and yours to edit
|
|
57
|
+
"// Scaffolded once and yours to edit—`persist` in particular. What should NOT",
|
|
58
58
|
"// change is the delegation: every handler forwards to the session object, and",
|
|
59
59
|
"// the session is built LAZILY. A Durable Object is re-instantiated after a",
|
|
60
60
|
"// hibernation wake, so a session captured in a field initializer would be",
|
|
@@ -91,7 +91,7 @@ export function generateAstroidEditSession(config) {
|
|
|
91
91
|
" // Guard the collection: only `pages` is realtime, and a stray target",
|
|
92
92
|
" // must never write into the wrong table.",
|
|
93
93
|
' if (target.slug !== "pages") return;',
|
|
94
|
-
" // The SAME merge-over-pending-draft path the fetch auto-save uses
|
|
94
|
+
" // The SAME merge-over-pending-draft path the fetch auto-save uses—",
|
|
95
95
|
" // one write path, so drafts/history/publish semantics are identical.",
|
|
96
96
|
" //",
|
|
97
97
|
" // No `bufferKv` here on purpose: the DO's alarm IS the coalescer for",
|
|
@@ -105,8 +105,8 @@ export function generateAstroidEditSession(config) {
|
|
|
105
105
|
" snapshot,",
|
|
106
106
|
" );",
|
|
107
107
|
" // A THROW (D1 down) propagates so the alarm keeps the snapshot dirty",
|
|
108
|
-
" // and retries. An `ok: false` is terminal
|
|
109
|
-
" // draft
|
|
108
|
+
" // and retries. An `ok: false` is terminal—a deleted row, an invalid",
|
|
109
|
+
" // draft—which a retry can't fix, so let the alarm clear it, but say",
|
|
110
110
|
" // so rather than dropping it silently.",
|
|
111
111
|
" if (!result.ok) {",
|
|
112
112
|
" console.warn(",
|
|
@@ -143,8 +143,8 @@ export function generateAstroidEditSession(config) {
|
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
145
|
* The `CloudflareEnv` member the realtime module adds, as a block
|
|
146
|
-
* `create-astroid` substitutes into `src/env.d.ts`. Empty without the module
|
|
147
|
-
*
|
|
146
|
+
* `create-astroid` substitutes into `src/env.d.ts`. Empty without the module—a
|
|
147
|
+
* project that types a binding its wrangler.jsonc never creates is making a
|
|
148
148
|
* promise it doesn't keep.
|
|
149
149
|
*/
|
|
150
150
|
export function generateAstroidRealtimeEnv(config) {
|
|
@@ -2,18 +2,18 @@ import { type CollectionConfig, type ContentConfig } from "louise-toolkit/conten
|
|
|
2
2
|
import type { AstroidConfig } from "../config.js";
|
|
3
3
|
/**
|
|
4
4
|
* Return a copy of a `pages` write payload with its `sections` rich-text fields
|
|
5
|
-
* sanitized against the project media base
|
|
5
|
+
* sanitized against the project media base—a no-op when the write carries no
|
|
6
6
|
* `sections`. Pure; leaves every other field (and a partial PATCH's absent ones)
|
|
7
7
|
* untouched.
|
|
8
8
|
*
|
|
9
9
|
* Exported because two write paths need it: the collection's `beforeChange` hook
|
|
10
|
-
* below, AND the raw `pagesRoute` (which does not run collection hooks
|
|
10
|
+
* below, AND the raw `pagesRoute` (which does not run collection hooks—see
|
|
11
11
|
* {@link astroidPagesWriteHooks}).
|
|
12
12
|
*/
|
|
13
13
|
export declare function sanitizeAstroidPageSections(config: AstroidConfig, data: Record<string, unknown>): Record<string, unknown>;
|
|
14
14
|
/**
|
|
15
15
|
* Validate the (already-sanitized) `sections` of a `pages` write against the
|
|
16
|
-
* catalog, throwing `LouiseValidationError
|
|
16
|
+
* catalog, throwing `LouiseValidationError`—an unknown `_type`, a field of the
|
|
17
17
|
* wrong shape, or a setting outside its declared options is rejected with a 422
|
|
18
18
|
* carrying the per-field violations. A no-op when the write carries no
|
|
19
19
|
* `sections`, so a partial PATCH of other fields isn't spuriously validated.
|
|
@@ -23,7 +23,7 @@ export declare function assertAstroidPageSections(config: AstroidConfig, data: R
|
|
|
23
23
|
* The write-time hooks the raw `pagesRoute` (louise-toolkit/editor) needs to
|
|
24
24
|
* enforce the same section contract as the draft path.
|
|
25
25
|
*
|
|
26
|
-
* `pagesRoute` writes straight to the table and
|
|
26
|
+
* `pagesRoute` writes straight to the table and—unlike `versionsRoute`—takes
|
|
27
27
|
* no collection config, so it never runs the `beforeChange` hook below. Left
|
|
28
28
|
* bare (as it was), a direct `POST` / `PATCH /api/louise/pages/:id` persists an
|
|
29
29
|
* unknown section `_type`, a setting outside its options, or unsanitized section
|
|
@@ -37,18 +37,49 @@ export declare function assertAstroidPageSections(config: AstroidConfig, data: R
|
|
|
37
37
|
*
|
|
38
38
|
* pagesRoute({ table: pages, resolveEditor, fields, ...astroidPagesWriteHooks(config) })
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
/** The write context `pagesRoute` passes to a transform or validator. */
|
|
41
|
+
export interface AstroidPagesWriteContext {
|
|
42
|
+
operation: "create" | "update";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A site's own `pagesRoute` hooks, exported as `pagesHooks` from the
|
|
46
|
+
* scaffold-once `src/pages-hooks.ts` when `pages.hooks` is on.
|
|
47
|
+
*/
|
|
48
|
+
export interface AstroidPagesHooks {
|
|
49
|
+
/**
|
|
50
|
+
* Clean a page write before Astroid's section sanitize and validate run, for
|
|
51
|
+
* example to normalize the slug, clamp a title, or fill a new page's defaults.
|
|
52
|
+
* It gets only the allowlisted fields of the write.
|
|
53
|
+
*/
|
|
54
|
+
transform?: (data: Record<string, unknown>, ctx: AstroidPagesWriteContext) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
55
|
+
/**
|
|
56
|
+
* Reject a write, after both transforms and before Astroid's own section
|
|
57
|
+
* validation. Throw a `LouiseValidationError` for a 422 with per-field
|
|
58
|
+
* violations, for example when a required field is empty.
|
|
59
|
+
*/
|
|
60
|
+
validate?: (data: Record<string, unknown>, ctx: AstroidPagesWriteContext) => void | Promise<void>;
|
|
61
|
+
/** Slugs to refuse on top of {@link ASTROID_RESERVED_SLUGS}, such as a path
|
|
62
|
+
* a site's own file route serves. */
|
|
63
|
+
reservedSlugs?: Iterable<string>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Slugs no page may take, on any site. Each one is a path Astro, Cloudflare,
|
|
67
|
+
* or Astroid serves before the catch-all page route, so a page saved under it
|
|
68
|
+
* would be unreachable, and nothing would say why. `pagesRoute` refuses them
|
|
69
|
+
* with a 422 instead.
|
|
70
|
+
*/
|
|
71
|
+
export declare const ASTROID_RESERVED_SLUGS: readonly string[];
|
|
72
|
+
export declare function astroidPagesWriteHooks(config: AstroidConfig, site?: AstroidPagesHooks): {
|
|
41
73
|
sanitize: (html: string) => string;
|
|
42
|
-
transform: (data: Record<string, unknown
|
|
43
|
-
validate: (data: Record<string, unknown>, ctx:
|
|
44
|
-
|
|
45
|
-
}) => Promise<void>;
|
|
74
|
+
transform: (data: Record<string, unknown>, ctx: AstroidPagesWriteContext) => Promise<Record<string, unknown>>;
|
|
75
|
+
validate: (data: Record<string, unknown>, ctx: AstroidPagesWriteContext) => Promise<void>;
|
|
76
|
+
reservedSlugs: string[];
|
|
46
77
|
};
|
|
47
78
|
export declare function astroidPagesCollection(config: AstroidConfig): CollectionConfig;
|
|
48
79
|
/**
|
|
49
80
|
* The Louise `ContentConfig` for an Astroid project. Today: the `pages`
|
|
50
|
-
* collection. Archetype- and module-specific collections (
|
|
51
|
-
* `gallery`) layer in here as they land
|
|
81
|
+
* collection. Archetype- and module-specific collections (for example, a portfolio
|
|
82
|
+
* `gallery`) layer in here as they land—this is the single place that maps
|
|
52
83
|
* brand config down to Louise content.
|
|
53
84
|
*/
|
|
54
85
|
export declare function astroidContentConfig(config: AstroidConfig): ContentConfig;
|