astroidjs 0.12.1 → 0.13.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 +25 -25
- package/dist/analytics/index.d.ts +3 -3
- package/dist/analytics/index.js +9 -9
- 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 +9 -9
- 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 +9 -9
- 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 +62 -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 +4 -4
- package/dist/map/style.d.ts +4 -4
- package/dist/map/style.js +1 -1
- package/dist/portal/config.d.ts +2 -2
- package/dist/portal/config.js +3 -3
- 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 +6 -6
- package/dist/portal/session.d.ts +2 -2
- package/dist/portal/session.js +5 -5
- package/dist/portfolio/scaffold.d.ts +1 -1
- package/dist/portfolio/scaffold.js +4 -4
- package/dist/project/actions.d.ts +1 -1
- package/dist/project/actions.js +6 -6
- package/dist/project/generate.d.ts +4 -4
- package/dist/project/generate.js +15 -15
- package/dist/project/index.js +1 -1
- package/dist/project/scaffold.d.ts +2 -2
- package/dist/project/scaffold.js +11 -11
- package/dist/pwa/generate.d.ts +11 -11
- package/dist/pwa/generate.js +12 -12
- 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 +8 -8
- 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 +8 -8
- package/dist/schema/collections.d.ts +6 -6
- package/dist/schema/collections.js +23 -23
- package/dist/schema/framework.d.ts +1 -1
- package/dist/schema/framework.js +2 -2
- package/dist/schema/generate.js +2 -2
- 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 +6 -6
- package/dist/worker/generate.d.ts +2 -2
- package/dist/worker/generate.js +19 -19
- package/dist/worker/index.js +1 -1
- package/dist/worker/routes.js +1 -1
- 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 +4 -4
- 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
package/dist/seo/resolve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// SEO resolution
|
|
3
|
+
// SEO resolution—settings defaults + per-page overrides, collapsed into the
|
|
4
4
|
// exact set of values a `<head>` needs.
|
|
5
5
|
//
|
|
6
6
|
// Both sites that hand-built this layer converged on the same three-level
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Kept as a pure function rather than baked into the component so it can be
|
|
15
15
|
// unit-tested, and so a route that isn't rendering a page (an OG-image endpoint,
|
|
16
16
|
// a feed) can resolve the same values.
|
|
17
|
-
/** Trimmed value, or undefined
|
|
17
|
+
/** Trimmed value, or undefined—an empty/whitespace string counts as unset. */
|
|
18
18
|
const clean = (value) => {
|
|
19
19
|
const trimmed = value?.trim();
|
|
20
20
|
return trimmed ? trimmed : undefined;
|
package/dist/seo/routes.d.ts
CHANGED
|
@@ -4,14 +4,14 @@ import type { AstroidConfig } from "../config.js";
|
|
|
4
4
|
* API always, plus the portal's account + auth surfaces and checkout when those
|
|
5
5
|
* are enabled.
|
|
6
6
|
*
|
|
7
|
-
* These are *prefixes
|
|
7
|
+
* These are *prefixes*—`robots.txt` matches by prefix, so `/api/` covers every
|
|
8
8
|
* endpoint beneath it.
|
|
9
9
|
*/
|
|
10
10
|
export declare function astroidNoindexPaths(config: AstroidConfig): string[];
|
|
11
11
|
export interface RobotsOptions {
|
|
12
|
-
/** Serving origin,
|
|
12
|
+
/** Serving origin, for example, `new URL(request.url).origin`. */
|
|
13
13
|
origin: string;
|
|
14
|
-
/** Paths to disallow
|
|
14
|
+
/** Paths to disallow—defaults to {@link astroidNoindexPaths}. */
|
|
15
15
|
disallow?: string[];
|
|
16
16
|
/**
|
|
17
17
|
* Disallow the entire site. Pass `settings.disableIndexing` so the same
|
|
@@ -25,7 +25,7 @@ export declare function astroidRobotsTxt(config: AstroidConfig, options: RobotsO
|
|
|
25
25
|
export interface SitemapEntry {
|
|
26
26
|
/** Site-root-relative path (`"/shop/beans"`) or an absolute URL. */
|
|
27
27
|
path: string;
|
|
28
|
-
/** Last modified
|
|
28
|
+
/** Last modified—a Date or an ISO string. */
|
|
29
29
|
lastmod?: Date | string;
|
|
30
30
|
}
|
|
31
31
|
export interface SitemapOptions {
|
|
@@ -38,7 +38,7 @@ export interface SitemapOptions {
|
|
|
38
38
|
*
|
|
39
39
|
* Entries are de-duplicated and sorted (a stable document diffs cleanly and
|
|
40
40
|
* caches predictably), excluded paths are dropped by prefix, and every `loc` is
|
|
41
|
-
* XML-escaped
|
|
41
|
+
* XML-escaped—a slug containing `&` would otherwise produce a malformed
|
|
42
42
|
* document that search engines reject wholesale.
|
|
43
43
|
*/
|
|
44
44
|
export declare function astroidSitemapXml(config: AstroidConfig, entries: (SitemapEntry | string)[], options: SitemapOptions): string;
|
package/dist/seo/routes.js
CHANGED
|
@@ -18,7 +18,7 @@ import { ASTROID_PORTAL_BASE_PATH } from "../security/rate-rules.js";
|
|
|
18
18
|
* API always, plus the portal's account + auth surfaces and checkout when those
|
|
19
19
|
* are enabled.
|
|
20
20
|
*
|
|
21
|
-
* These are *prefixes
|
|
21
|
+
* These are *prefixes*—`robots.txt` matches by prefix, so `/api/` covers every
|
|
22
22
|
* endpoint beneath it.
|
|
23
23
|
*/
|
|
24
24
|
export function astroidNoindexPaths(config) {
|
|
@@ -32,7 +32,7 @@ export function astroidNoindexPaths(config) {
|
|
|
32
32
|
paths.push(ASTROID_PORTAL_BASE_PATH, "/account", "/login", "/register", "/reset-password");
|
|
33
33
|
}
|
|
34
34
|
if (config.commerce) {
|
|
35
|
-
// The checkout PAGE, not `ASTROID_CHECKOUT_PATH
|
|
35
|
+
// The checkout PAGE, not `ASTROID_CHECKOUT_PATH`—that's the POST endpoint,
|
|
36
36
|
// already covered by the `/api/` prefix. What a crawler would actually reach
|
|
37
37
|
// is the UI route.
|
|
38
38
|
paths.push("/checkout", "/cart");
|
|
@@ -68,7 +68,7 @@ const escapeXml = (value) => value.replace(/[&<>"']/g, (c) => XML_ESCAPES[c] ??
|
|
|
68
68
|
*
|
|
69
69
|
* Entries are de-duplicated and sorted (a stable document diffs cleanly and
|
|
70
70
|
* caches predictably), excluded paths are dropped by prefix, and every `loc` is
|
|
71
|
-
* XML-escaped
|
|
71
|
+
* XML-escaped—a slug containing `&` would otherwise produce a malformed
|
|
72
72
|
* document that search engines reject wholesale.
|
|
73
73
|
*/
|
|
74
74
|
export function astroidSitemapXml(config, entries, options) {
|
|
@@ -6,7 +6,7 @@ export type JsonLdNode = Record<string, unknown>;
|
|
|
6
6
|
* The schema.org `@type` each archetype describes its owner with. These are
|
|
7
7
|
* intentionally the broad parent types: pick a subtype (`CafeOrCoffeeShop`,
|
|
8
8
|
* `ArtGallery`, `HomeAndConstructionBusiness`) via `seo.businessType` when you
|
|
9
|
-
* know one
|
|
9
|
+
* know one—a more specific type is strictly better for rich results.
|
|
10
10
|
*/
|
|
11
11
|
export declare const ARCHETYPE_BUSINESS_TYPE: Record<Archetype, string>;
|
|
12
12
|
export interface StructuredDataInput {
|
|
@@ -22,7 +22,7 @@ export interface StructuredDataInput {
|
|
|
22
22
|
/** Absolute origin serving this page (the canonical host). */
|
|
23
23
|
siteUrl: string;
|
|
24
24
|
/**
|
|
25
|
-
* An extra node for the thing this page is *about
|
|
25
|
+
* An extra node for the thing this page is *about*—a Product, a
|
|
26
26
|
* VisualArtwork, an Article. Joined into the same `@graph` so crawlers see
|
|
27
27
|
* one connected description rather than three unrelated blobs.
|
|
28
28
|
*/
|
|
@@ -32,8 +32,8 @@ export interface StructuredDataInput {
|
|
|
32
32
|
* Build the JSON-LD `@graph` for a page: the business node, a `WebSite` node,
|
|
33
33
|
* and the page's own entity when there is one.
|
|
34
34
|
*
|
|
35
|
-
* The business gets a stable `@id` (`<origin>/#business`) so other nodes
|
|
36
|
-
* product's `seller`, a future `Article` author
|
|
35
|
+
* The business gets a stable `@id` (`<origin>/#business`) so other nodes—a
|
|
36
|
+
* product's `seller`, a future `Article` author—can reference it by id
|
|
37
37
|
* instead of restating it.
|
|
38
38
|
*/
|
|
39
39
|
export declare function astroidStructuredData(input: StructuredDataInput): JsonLdNode;
|
|
@@ -42,8 +42,8 @@ export declare function astroidStructuredData(input: StructuredDataInput): JsonL
|
|
|
42
42
|
*
|
|
43
43
|
* `application/ld+json` is data, not executable script, so `script-src` doesn't
|
|
44
44
|
* govern it and no CSP hash is needed. But `JSON.stringify` does **not** escape
|
|
45
|
-
* `<`, so any value folded into the graph that contains a literal `</script
|
|
46
|
-
*
|
|
45
|
+
* `<`, so any value folded into the graph that contains a literal `</script>`—a
|
|
46
|
+
* product description, an artist statement, anything editor-authored—would
|
|
47
47
|
* close the tag early and inject markup straight into `<head>`. Escaping the
|
|
48
48
|
* HTML-significant characters as `\uXXXX` keeps the payload valid JSON while
|
|
49
49
|
* making it impossible to break out of the element.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// JSON-LD structured data
|
|
3
|
+
// JSON-LD structured data—the `@graph` that describes the business to rich
|
|
4
4
|
// results and AI answer surfaces.
|
|
5
5
|
//
|
|
6
6
|
// Everything here is generic except the business `@type`, which is the one thing
|
|
7
|
-
// that genuinely differs per site
|
|
7
|
+
// that genuinely differs per site—so it comes from the archetype, with a
|
|
8
8
|
// config escape hatch for the many cases where schema.org has a more specific
|
|
9
9
|
// subtype (a coffee shop is a `CafeOrCoffeeShop`, not a bare `Store`).
|
|
10
10
|
/**
|
|
11
11
|
* The schema.org `@type` each archetype describes its owner with. These are
|
|
12
12
|
* intentionally the broad parent types: pick a subtype (`CafeOrCoffeeShop`,
|
|
13
13
|
* `ArtGallery`, `HomeAndConstructionBusiness`) via `seo.businessType` when you
|
|
14
|
-
* know one
|
|
14
|
+
* know one—a more specific type is strictly better for rich results.
|
|
15
15
|
*/
|
|
16
16
|
export const ARCHETYPE_BUSINESS_TYPE = {
|
|
17
17
|
marketing: "Organization",
|
|
@@ -49,8 +49,8 @@ function sameAs(links) {
|
|
|
49
49
|
* Build the JSON-LD `@graph` for a page: the business node, a `WebSite` node,
|
|
50
50
|
* and the page's own entity when there is one.
|
|
51
51
|
*
|
|
52
|
-
* The business gets a stable `@id` (`<origin>/#business`) so other nodes
|
|
53
|
-
* product's `seller`, a future `Article` author
|
|
52
|
+
* The business gets a stable `@id` (`<origin>/#business`) so other nodes—a
|
|
53
|
+
* product's `seller`, a future `Article` author—can reference it by id
|
|
54
54
|
* instead of restating it.
|
|
55
55
|
*/
|
|
56
56
|
export function astroidStructuredData(input) {
|
|
@@ -91,8 +91,8 @@ export function astroidStructuredData(input) {
|
|
|
91
91
|
*
|
|
92
92
|
* `application/ld+json` is data, not executable script, so `script-src` doesn't
|
|
93
93
|
* govern it and no CSP hash is needed. But `JSON.stringify` does **not** escape
|
|
94
|
-
* `<`, so any value folded into the graph that contains a literal `</script
|
|
95
|
-
*
|
|
94
|
+
* `<`, so any value folded into the graph that contains a literal `</script>`—a
|
|
95
|
+
* product description, an artist statement, anything editor-authored—would
|
|
96
96
|
* close the tag early and inject markup straight into `<head>`. Escaping the
|
|
97
97
|
* HTML-significant characters as `\uXXXX` keeps the payload valid JSON while
|
|
98
98
|
* making it impossible to break out of the element.
|
package/dist/status.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SecretSource } from "./secrets.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Secrets every Astroid site has, independent of which modules are on.
|
|
6
6
|
*
|
|
7
|
-
* `SESSION_SECRET` is here but is NOT a dormancy gate
|
|
7
|
+
* `SESSION_SECRET` is here but is NOT a dormancy gate—it fails closed off
|
|
8
8
|
* localhost (see `getSessionSecret`), because an unsigned session isn't a
|
|
9
9
|
* feature to switch off. It's listed so the scaffold seeds and types it.
|
|
10
10
|
*/
|
|
@@ -19,7 +19,7 @@ export interface AstroidModuleReport {
|
|
|
19
19
|
configured: boolean;
|
|
20
20
|
/** Unprovisioned secret/binding names, in declaration order. */
|
|
21
21
|
missing: string[];
|
|
22
|
-
/** What the module does in this state
|
|
22
|
+
/** What the module does in this state—the sentence a banner prints. */
|
|
23
23
|
detail: string;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
@@ -27,7 +27,7 @@ export interface AstroidModuleReport {
|
|
|
27
27
|
*
|
|
28
28
|
* The scaffold uses this twice: to seed `.dev.vars`/`.env.example` with the
|
|
29
29
|
* placeholder sentinel, and to type the matching `CloudflareEnv` members. A
|
|
30
|
-
* module that isn't enabled contributes nothing
|
|
30
|
+
* module that isn't enabled contributes nothing—a declaration is a promise,
|
|
31
31
|
* and a marketing site shouldn't be told to provision a Square token.
|
|
32
32
|
*/
|
|
33
33
|
export declare function astroidSecretNames(config: AstroidConfig): Record<string, string[]>;
|
|
@@ -45,7 +45,7 @@ export type AstroidStatusEnv = MailerEnv & Record<string, SecretSource | unknown
|
|
|
45
45
|
export declare function astroidModuleStatus(config: AstroidConfig, env: AstroidStatusEnv): Promise<AstroidModuleReport[]>;
|
|
46
46
|
/**
|
|
47
47
|
* The report as a printable block. One line per module, missing names spelled
|
|
48
|
-
* out
|
|
49
|
-
*
|
|
48
|
+
* out—"commerce is off" sends someone reading source; "commerce is dormant—set
|
|
49
|
+
* SQUARE_ACCESS_TOKEN" does not.
|
|
50
50
|
*/
|
|
51
51
|
export declare function describeAstroidStatus(reports: AstroidModuleReport[]): string;
|
package/dist/status.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// The module status report
|
|
3
|
+
// The module status report—"what is actually switched on right now".
|
|
4
4
|
//
|
|
5
5
|
// `secrets.ts` gives one module its gate. This composes every enabled module's
|
|
6
6
|
// gate into one answer, which is what the dormant-until-provisioned convention
|
|
7
7
|
// needs to be usable rather than merely available: a fresh scaffold boots with
|
|
8
8
|
// nothing provisioned, and the failure mode that convention exists to avoid is
|
|
9
|
-
// not a crash
|
|
9
|
+
// not a crash—it's a developer wondering for twenty minutes why the contact
|
|
10
10
|
// form "works" but no mail arrives.
|
|
11
11
|
//
|
|
12
12
|
// So the deal is: dormant is fine, dormant AND SILENT is not. Two consumers of
|
|
@@ -22,7 +22,7 @@ import { EMAIL_SECRET_NAMES, resolveMailerStatus } from "./email/send.js";
|
|
|
22
22
|
/**
|
|
23
23
|
* Secrets every Astroid site has, independent of which modules are on.
|
|
24
24
|
*
|
|
25
|
-
* `SESSION_SECRET` is here but is NOT a dormancy gate
|
|
25
|
+
* `SESSION_SECRET` is here but is NOT a dormancy gate—it fails closed off
|
|
26
26
|
* localhost (see `getSessionSecret`), because an unsigned session isn't a
|
|
27
27
|
* feature to switch off. It's listed so the scaffold seeds and types it.
|
|
28
28
|
*/
|
|
@@ -36,7 +36,7 @@ export const ASTROID_CORE_SECRET_NAMES = [
|
|
|
36
36
|
*
|
|
37
37
|
* The scaffold uses this twice: to seed `.dev.vars`/`.env.example` with the
|
|
38
38
|
* placeholder sentinel, and to type the matching `CloudflareEnv` members. A
|
|
39
|
-
* module that isn't enabled contributes nothing
|
|
39
|
+
* module that isn't enabled contributes nothing—a declaration is a promise,
|
|
40
40
|
* and a marketing site shouldn't be told to provision a Square token.
|
|
41
41
|
*/
|
|
42
42
|
export function astroidSecretNames(config) {
|
|
@@ -47,7 +47,7 @@ export function astroidSecretNames(config) {
|
|
|
47
47
|
const commerce = commerceSecretNames(config.commerce);
|
|
48
48
|
if (commerce.length > 0)
|
|
49
49
|
groups.commerce = commerce;
|
|
50
|
-
// The CWV read-back's API credentials. Collection needs none of this
|
|
50
|
+
// The CWV read-back's API credentials. Collection needs none of this—only
|
|
51
51
|
// querying the p75 back out does, because the Analytics Engine SQL API is
|
|
52
52
|
// account-scoped and has no binding.
|
|
53
53
|
groups.vitals = [...ASTROID_VITALS_SECRET_NAMES];
|
|
@@ -96,8 +96,8 @@ export async function astroidModuleStatus(config, env) {
|
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
98
|
* The report as a printable block. One line per module, missing names spelled
|
|
99
|
-
* out
|
|
100
|
-
*
|
|
99
|
+
* out—"commerce is off" sends someone reading source; "commerce is dormant—set
|
|
100
|
+
* SQUARE_ACCESS_TOKEN" does not.
|
|
101
101
|
*/
|
|
102
102
|
export function describeAstroidStatus(reports) {
|
|
103
103
|
if (reports.length === 0)
|
package/dist/tenancy/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare function tenancyZone(tenancy: TenancyConfig): string;
|
|
|
30
30
|
*
|
|
31
31
|
* `null` covers four distinct cases that all mean "not a tenant": the apex
|
|
32
32
|
* itself (a wildcard does not match its own apex), a host outside the pattern
|
|
33
|
-
* (a preview domain, `localhost`), a reserved label, and an app label
|
|
33
|
+
* (a preview domain, `localhost`), a reserved label, and an app label—which
|
|
34
34
|
* has its own static rewrite via {@link appPrefix} instead of a lookup.
|
|
35
35
|
*
|
|
36
36
|
* Exported and pure so a site can unit-test its own reserved list without
|
|
@@ -39,7 +39,7 @@ export declare function tenancyZone(tenancy: TenancyConfig): string;
|
|
|
39
39
|
export declare function tenantLabel(host: string, tenancy: TenancyConfig): string | null;
|
|
40
40
|
/**
|
|
41
41
|
* The internal path prefix an app host rewrites to, or `null` when the host is
|
|
42
|
-
* not an app host
|
|
42
|
+
* not an app host—`appPrefix("studio.example.com", …)` → `"/studio"` under
|
|
43
43
|
* `apps: { studio: "/studio" }`.
|
|
44
44
|
*
|
|
45
45
|
* Static by design: an app exists whether or not any tenant does, so there is
|
|
@@ -49,7 +49,7 @@ export declare function tenantLabel(host: string, tenancy: TenancyConfig): strin
|
|
|
49
49
|
*/
|
|
50
50
|
export declare function appPrefix(host: string, tenancy: TenancyConfig): string | null;
|
|
51
51
|
/**
|
|
52
|
-
* The scaffold-once `src/tenancy.ts
|
|
52
|
+
* The scaffold-once `src/tenancy.ts`—the seam holding every decision Astroid
|
|
53
53
|
* refuses to make for a site.
|
|
54
54
|
*
|
|
55
55
|
* Written once and then yours: what a label resolves to, whether the lookup is
|
package/dist/tenancy/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Wildcard host dispatch: the parts that are the same for every site, and
|
|
4
4
|
// nothing that decides anything.
|
|
5
5
|
//
|
|
6
|
-
// Astroid owns two things a site cannot own on its own
|
|
6
|
+
// Astroid owns two things a site cannot own on its own—the wildcard Worker
|
|
7
7
|
// route (`hosts` can only express custom domains) and the single middleware file
|
|
8
8
|
// Astro permits. What a subdomain MEANS, whether the lookup is cached, and what
|
|
9
9
|
// an unknown host should do are all site policy, and live in the scaffolded
|
|
@@ -38,7 +38,7 @@ export function tenancyZone(tenancy) {
|
|
|
38
38
|
return tenancy.zone ?? tenancy.hostPattern.replace(/^\*\./, "");
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* The single subdomain label under the wildcard, before any policy
|
|
41
|
+
* The single subdomain label under the wildcard, before any policy—or `null`
|
|
42
42
|
* for the apex, an off-pattern host, or a dotted label. Shared by
|
|
43
43
|
* {@link tenantLabel} and {@link appPrefix} so their host handling (port,
|
|
44
44
|
* case, one-level-only) cannot drift.
|
|
@@ -51,7 +51,7 @@ function hostLabel(host, tenancy) {
|
|
|
51
51
|
return null;
|
|
52
52
|
const label = hostname.slice(0, -(suffix.length + 1));
|
|
53
53
|
// Only a single label counts. `a.b.example.com` under `*.example.com` is
|
|
54
|
-
// not `a.b
|
|
54
|
+
// not `a.b`—Cloudflare's wildcard matches one level, and treating a dotted
|
|
55
55
|
// string as a slug would put a `/` in a rewrite path.
|
|
56
56
|
if (!label || label.includes("."))
|
|
57
57
|
return null;
|
|
@@ -63,7 +63,7 @@ function hostLabel(host, tenancy) {
|
|
|
63
63
|
*
|
|
64
64
|
* `null` covers four distinct cases that all mean "not a tenant": the apex
|
|
65
65
|
* itself (a wildcard does not match its own apex), a host outside the pattern
|
|
66
|
-
* (a preview domain, `localhost`), a reserved label, and an app label
|
|
66
|
+
* (a preview domain, `localhost`), a reserved label, and an app label—which
|
|
67
67
|
* has its own static rewrite via {@link appPrefix} instead of a lookup.
|
|
68
68
|
*
|
|
69
69
|
* Exported and pure so a site can unit-test its own reserved list without
|
|
@@ -79,7 +79,7 @@ export function tenantLabel(host, tenancy) {
|
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* The internal path prefix an app host rewrites to, or `null` when the host is
|
|
82
|
-
* not an app host
|
|
82
|
+
* not an app host—`appPrefix("studio.example.com", …)` → `"/studio"` under
|
|
83
83
|
* `apps: { studio: "/studio" }`.
|
|
84
84
|
*
|
|
85
85
|
* Static by design: an app exists whether or not any tenant does, so there is
|
|
@@ -95,7 +95,7 @@ export function appPrefix(host, tenancy) {
|
|
|
95
95
|
return label ? (apps[label] ?? null) : null;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* The scaffold-once `src/tenancy.ts
|
|
98
|
+
* The scaffold-once `src/tenancy.ts`—the seam holding every decision Astroid
|
|
99
99
|
* refuses to make for a site.
|
|
100
100
|
*
|
|
101
101
|
* Written once and then yours: what a label resolves to, whether the lookup is
|
|
@@ -14,7 +14,7 @@ export declare function generateAstroidWorker(config: AstroidConfig): string;
|
|
|
14
14
|
* session + sticky `?louise` edit mode → content-freshness + security headers) via
|
|
15
15
|
* `createLouiseMiddleware`.
|
|
16
16
|
*
|
|
17
|
-
* The rate rules are NOT emitted as literals here
|
|
17
|
+
* The rate rules are NOT emitted as literals here—the file calls
|
|
18
18
|
* `astroidRateRules(astroidConfig)`, so the set stays real data in the package
|
|
19
19
|
* (testable, and a `match` predicate survives, which a serialized literal could
|
|
20
20
|
* not). Enabling a portal or commerce in the config adds that surface's rules
|
|
@@ -24,7 +24,7 @@ export declare function generateAstroidWorker(config: AstroidConfig): string;
|
|
|
24
24
|
* Astro emits a hash-based `content-security-policy` response header on every SSR
|
|
25
25
|
* page and owns `script-src`. The `cspStyleSrc` below tells
|
|
26
26
|
* `createLouiseMiddleware` to rewrite that header's `style-src` to
|
|
27
|
-
* `'self' 'unsafe-inline'
|
|
27
|
+
* `'self' 'unsafe-inline'`—a hash-based `style-src` would, per spec, void the
|
|
28
28
|
* `'unsafe-inline'` that Louise's data-driven `style=""` carriers and the
|
|
29
29
|
* editor's runtime-injected `<style>` require. Script hashes are left verbatim,
|
|
30
30
|
* and the inlined `data:` brand font is auto-allowed.
|
package/dist/worker/generate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// generateAstroidWorker / generateAstroidMiddleware
|
|
3
|
+
// generateAstroidWorker / generateAstroidMiddleware—emit the Cloudflare Worker
|
|
4
4
|
// entrypoint and the Astro middleware a Louise site would otherwise hand-write.
|
|
5
5
|
// The worker's editor routes are composed in the fixed order from the route plan
|
|
6
6
|
// (routes.ts), so the "versionsRoute/searchRoute before pagesRoute" collision is
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// One seam is marked with TODO(astroid) and filled by the auth slice: the
|
|
10
10
|
// `resolveEditor` session resolver. The section-catalog validate + sanitize on
|
|
11
|
-
// the pages routes is wired here
|
|
11
|
+
// the pages routes is wired here—versionsRoute runs it through the collection's
|
|
12
12
|
// beforeChange hook, and pagesRoute (which takes no collection config) through
|
|
13
13
|
// the `astroidPagesWriteHooks` spread, so both write paths enforce one contract.
|
|
14
14
|
import { ASTROID_VITALS_BINDING, generateAstroidCwvQuery } from "../analytics/index.js";
|
|
@@ -20,7 +20,7 @@ import { capturesInquiries } from "../schema/framework.js";
|
|
|
20
20
|
import { astroidCspStyleSrc } from "../security/csp-origins.js";
|
|
21
21
|
import { ASTROID_REWRITE_EXCLUDE, ASTROID_TENANT_PREFIX } from "../tenancy/index.js";
|
|
22
22
|
import { astroidEditorRoutePlan } from "./routes.js";
|
|
23
|
-
// Astroid's default editable site_settings surface
|
|
23
|
+
// Astroid's default editable site_settings surface—the columns the Settings
|
|
24
24
|
// panel may write, and which of them hold a media-library image URL.
|
|
25
25
|
//
|
|
26
26
|
// EXPORTED because the generated worker is not the only consumer: the scaffolded
|
|
@@ -58,7 +58,7 @@ export function generateAstroidWorker(config) {
|
|
|
58
58
|
const mediaBase = config.deploy?.mediaBase ?? "/media";
|
|
59
59
|
const seedName = config.theme.name;
|
|
60
60
|
const plan = astroidEditorRoutePlan(config);
|
|
61
|
-
// `realtimeRoute` lives in `louise-toolkit/realtime`, not `/editor
|
|
61
|
+
// `realtimeRoute` lives in `louise-toolkit/realtime`, not `/editor`—it is the
|
|
62
62
|
// one factory in the plan that isn't an editor route. Importing it with the
|
|
63
63
|
// rest type-checks fine HERE (the plan is just strings) and fails only in the
|
|
64
64
|
// scaffold, which is exactly how it got caught.
|
|
@@ -80,7 +80,7 @@ export function generateAstroidWorker(config) {
|
|
|
80
80
|
const routeCall = (name) => {
|
|
81
81
|
switch (name) {
|
|
82
82
|
case "overview":
|
|
83
|
-
// `inbox` only when this project captures inquiries
|
|
83
|
+
// `inbox` only when this project captures inquiries—an absent slice
|
|
84
84
|
// hides its card, which is right for an archetype with no contact form.
|
|
85
85
|
return inquiries
|
|
86
86
|
? "overviewRoute({ resolveEditor, content: overviewContent, inbox: overviewInbox, health: overviewHealth })"
|
|
@@ -105,7 +105,7 @@ export function generateAstroidWorker(config) {
|
|
|
105
105
|
case "save":
|
|
106
106
|
// No `bufferKv` here, deliberately: `saveRoute` has no such option. It
|
|
107
107
|
// writes live field saves (title, SEO) straight through, and the draft
|
|
108
|
-
// buffer belongs to the versioned body
|
|
108
|
+
// buffer belongs to the versioned body—that is, to versionsRoute.
|
|
109
109
|
return 'saveRoute({ resolveEditor, collections: { pages: { table: pages, fields: ["title", "seoTitle", "seoDescription"] } } })';
|
|
110
110
|
case "settings": {
|
|
111
111
|
// Site-specific keys (config.settings.customKeys) are merged into
|
|
@@ -119,15 +119,15 @@ export function generateAstroidWorker(config) {
|
|
|
119
119
|
// `aiRunner` rather than `(env) => env.AI`: it reads the binding AND the
|
|
120
120
|
// LOUISE_AI kill switch, so all three assists share one definition of
|
|
121
121
|
// "is generation on?" instead of each re-deriving it. Embeddings keep
|
|
122
|
-
// binding-presence as their switch
|
|
122
|
+
// binding-presence as their switch—see the helper's comment.
|
|
123
123
|
case "ai":
|
|
124
124
|
return "aiRoute({ resolveEditor, ai: aiRunner })";
|
|
125
125
|
case "seoFix":
|
|
126
126
|
return "seoFixRoute({ table: pages, resolveEditor, ai: aiRunner })";
|
|
127
127
|
case "media": {
|
|
128
128
|
// `altText` fills a new upload's alt from the image itself. Best-effort
|
|
129
|
-
// by contract
|
|
130
|
-
// upload
|
|
129
|
+
// by contract—a model error or a missing binding never fails the
|
|
130
|
+
// upload—so it costs nothing on a project that doesn't want it.
|
|
131
131
|
//
|
|
132
132
|
// `maxBytes` is emitted only when the site raised it: omitted, the
|
|
133
133
|
// route keeps louise-toolkit's DEFAULT_MAX_BYTES, so the generated
|
|
@@ -138,7 +138,7 @@ export function generateAstroidWorker(config) {
|
|
|
138
138
|
}
|
|
139
139
|
case "editors":
|
|
140
140
|
// The editor instance's user table is `louise_`-prefixed (the editor
|
|
141
|
-
// convention
|
|
141
|
+
// convention—the unprefixed `user` table is left for a second/portal
|
|
142
142
|
// instance). This route takes the table NAME, matching the
|
|
143
143
|
// `tablePrefix` the scaffolded `src/auth.ts` passes to `getLouiseAuth`.
|
|
144
144
|
return `editorsRoute({ table: ${JSON.stringify(astroidEditorTable("user"))}, resolveEditor })`;
|
|
@@ -174,7 +174,7 @@ export function generateAstroidWorker(config) {
|
|
|
174
174
|
p('import { defineForm } from "louise-toolkit/forms";');
|
|
175
175
|
if (queues)
|
|
176
176
|
p('import { processBatch } from "louise-toolkit/queues";');
|
|
177
|
-
// Only when a route actually takes a runner
|
|
177
|
+
// Only when a route actually takes a runner—a project with no AI assists
|
|
178
178
|
// should not import one, and knip would flag it if it did.
|
|
179
179
|
if (plan.some((route) => AI_ROUTES.has(route.name))) {
|
|
180
180
|
p('import { aiRunner } from "louise-toolkit/ai";');
|
|
@@ -408,7 +408,7 @@ export function generateAstroidWorker(config) {
|
|
|
408
408
|
}
|
|
409
409
|
// ONE scheduled handler for every cron, dispatching on `controller.cron`.
|
|
410
410
|
// Cloudflare gives no other way to tell them apart, and the strings here have
|
|
411
|
-
// to match `astroidCrons` exactly
|
|
411
|
+
// to match `astroidCrons` exactly—which is why both read the same constants
|
|
412
412
|
// rather than repeating a literal.
|
|
413
413
|
p(" // Cron. Cloudflare fires this for EVERY trigger in wrangler.jsonc and");
|
|
414
414
|
p(" // identifies which by `controller.cron`, so dispatch on it.");
|
|
@@ -444,7 +444,7 @@ export function generateAstroidWorker(config) {
|
|
|
444
444
|
p();
|
|
445
445
|
if (usesRealtime(config)) {
|
|
446
446
|
// Re-exported from the ENTRY because wrangler resolves a Durable Object
|
|
447
|
-
// binding's `class_name` against the worker's exports
|
|
447
|
+
// binding's `class_name` against the worker's exports—the class living in
|
|
448
448
|
// src/edit-session.ts is not enough on its own, and the failure is a deploy
|
|
449
449
|
// error about an unresolvable class rather than anything pointing here.
|
|
450
450
|
p("// The realtime edit-session Durable Object. Re-exported so wrangler can");
|
|
@@ -460,7 +460,7 @@ export function generateAstroidWorker(config) {
|
|
|
460
460
|
* session + sticky `?louise` edit mode → content-freshness + security headers) via
|
|
461
461
|
* `createLouiseMiddleware`.
|
|
462
462
|
*
|
|
463
|
-
* The rate rules are NOT emitted as literals here
|
|
463
|
+
* The rate rules are NOT emitted as literals here—the file calls
|
|
464
464
|
* `astroidRateRules(astroidConfig)`, so the set stays real data in the package
|
|
465
465
|
* (testable, and a `match` predicate survives, which a serialized literal could
|
|
466
466
|
* not). Enabling a portal or commerce in the config adds that surface's rules
|
|
@@ -470,7 +470,7 @@ export function generateAstroidWorker(config) {
|
|
|
470
470
|
* Astro emits a hash-based `content-security-policy` response header on every SSR
|
|
471
471
|
* page and owns `script-src`. The `cspStyleSrc` below tells
|
|
472
472
|
* `createLouiseMiddleware` to rewrite that header's `style-src` to
|
|
473
|
-
* `'self' 'unsafe-inline'
|
|
473
|
+
* `'self' 'unsafe-inline'`—a hash-based `style-src` would, per spec, void the
|
|
474
474
|
* `'unsafe-inline'` that Louise's data-driven `style=""` carriers and the
|
|
475
475
|
* editor's runtime-injected `<style>` require. Script hashes are left verbatim,
|
|
476
476
|
* and the inlined `data:` brand font is auto-allowed.
|
|
@@ -495,8 +495,8 @@ export function generateAstroidMiddleware(config) {
|
|
|
495
495
|
"// styles + inlined data: brand font are allowed.",
|
|
496
496
|
'import { env } from "cloudflare:workers";',
|
|
497
497
|
'import { createLouiseMiddleware } from "@louise-toolkit/astro";',
|
|
498
|
-
// One `astroidjs` import, composed from what this config actually uses
|
|
499
|
-
//
|
|
498
|
+
// One `astroidjs` import, composed from what this config actually uses—two
|
|
499
|
+
// import statements for the same module is legal and reads as an
|
|
500
500
|
// oversight in a file nobody is supposed to hand-edit.
|
|
501
501
|
`import { ${[
|
|
502
502
|
...(tenancy && Object.keys(tenancy.apps ?? {}).length ? ["appPrefix"] : []),
|
|
@@ -515,7 +515,7 @@ export function generateAstroidMiddleware(config) {
|
|
|
515
515
|
"// TODO(astroid): your AUTH seam — same resolveEditor as the generated worker.ts.",
|
|
516
516
|
'import { resolveEditor } from "./auth.js";',
|
|
517
517
|
// The portal's resolver lives in its OWN module, not the editor's auth
|
|
518
|
-
// seam
|
|
518
|
+
// seam—they're separate Better Auth instances and must not share a file.
|
|
519
519
|
...(portal ? ['import { resolvePortalUser } from "./portal-auth.js";'] : []),
|
|
520
520
|
"",
|
|
521
521
|
"// Rate-limit the public, unauthenticated POST surface, keyed by client IP",
|
|
@@ -552,7 +552,7 @@ export function generateAstroidMiddleware(config) {
|
|
|
552
552
|
" // anything the worker's routes didn't answer. A second check behind the",
|
|
553
553
|
" // worker's gate, and free: the editor is resolved here on every request.",
|
|
554
554
|
" apiGate: true,",
|
|
555
|
-
// `extend` runs once and may need to populate BOTH
|
|
555
|
+
// `extend` runs once and may need to populate BOTH—a tenanted site with a
|
|
556
556
|
// portal resolves a tenant and a customer on the same request.
|
|
557
557
|
...(portal || tenancy
|
|
558
558
|
? [
|
package/dist/worker/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// Worker + middleware generation
|
|
3
|
+
// Worker + middleware generation—config → the collision-free editor route plan
|
|
4
4
|
// → the Worker entrypoint and Astro middleware a site would otherwise hand-write.
|
|
5
5
|
export * from "./routes.js";
|
|
6
6
|
export * from "./generate.js";
|
package/dist/worker/routes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// The editor route plan
|
|
3
|
+
// The editor route plan—which louise-toolkit/editor routes a project needs, in
|
|
4
4
|
// the ONE order that avoids matcher collisions. This is where the "versionsRoute
|
|
5
5
|
// and searchRoute MUST precede pagesRoute" tribal knowledge lives: encoded once,
|
|
6
6
|
// as data, instead of re-derived by hand (and mis-ordered) in every site's
|
|
@@ -34,7 +34,7 @@ export interface AdvanceOptions {
|
|
|
34
34
|
db: WorkflowDatabase;
|
|
35
35
|
/** Table holding the `stage` column. */
|
|
36
36
|
table: string;
|
|
37
|
-
/** Audit table
|
|
37
|
+
/** Audit table—one row per completed stage. */
|
|
38
38
|
auditTable: string;
|
|
39
39
|
/** Primary key column on `table`. Default `"id"`. */
|
|
40
40
|
idColumn?: string;
|
|
@@ -87,13 +87,13 @@ export interface OverrideOptions extends Omit<AdvanceOptions, "specs" | "expecte
|
|
|
87
87
|
action: OverrideAction;
|
|
88
88
|
/** Override log table. */
|
|
89
89
|
overrideTable: string;
|
|
90
|
-
/** Where the item is now, from the operator's page
|
|
90
|
+
/** Where the item is now, from the operator's page—the same staleness guard. */
|
|
91
91
|
expectedStage: number;
|
|
92
92
|
/** Optional station/context label recorded with the override. */
|
|
93
93
|
station?: string;
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* Move an item out of band
|
|
96
|
+
* Move an item out of band—back a stage, or skip one—and log it.
|
|
97
97
|
*
|
|
98
98
|
* Sending an item BACK deletes the audit row for the stage being reopened, so
|
|
99
99
|
* "a sign-off exists" keeps meaning "that stage is genuinely done". Leaving it
|
package/dist/workflow/advance.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// The guarded advance
|
|
3
|
+
// The guarded advance—the one piece of a staged pipeline that is genuinely
|
|
4
4
|
// hard to get right.
|
|
5
5
|
//
|
|
6
6
|
// Two operators standing at two stations both press "sign off" on the same job.
|
|
7
7
|
// A read-then-write advance runs the item forward two stages and writes two
|
|
8
8
|
// audit rows, and nobody notices until the numbers stop adding up. The fix is
|
|
9
|
-
// optimistic concurrency: make the write itself assert the stage it
|
|
10
|
-
//
|
|
9
|
+
// optimistic concurrency: make the write itself assert the stage it
|
|
10
|
+
// expected—`UPDATE … SET stage = ? WHERE id = ? AND stage = ?`—and treat "0 rows
|
|
11
11
|
// changed" as the conflict signal rather than checking first and hoping.
|
|
12
12
|
//
|
|
13
13
|
// ORDERING MATTERS, and the reference gets it wrong. ghostfire's floor route
|
|
14
14
|
// inserts the sign-off row and THEN runs the guarded update, so a double submit
|
|
15
15
|
// writes two audit rows even though only one advance lands. Here the guarded
|
|
16
16
|
// update goes first and the audit row is written only if it actually moved the
|
|
17
|
-
// item
|
|
17
|
+
// item—so the audit table can't record work that didn't happen. The unique
|
|
18
18
|
// index the schema generator emits on `(entity_id, stage)` is the belt to that
|
|
19
19
|
// braces.
|
|
20
20
|
/** Longest initials we store. Three is what a floor actually writes. */
|
|
@@ -66,7 +66,7 @@ export async function advanceWorkflowStage(options) {
|
|
|
66
66
|
}
|
|
67
67
|
const next = expectedStage + 1;
|
|
68
68
|
// The guard. `changes === 0` means the row is gone or someone else already
|
|
69
|
-
// moved it
|
|
69
|
+
// moved it—the two cases are told apart below, but only after the write,
|
|
70
70
|
// so there is no window between the check and the update.
|
|
71
71
|
const advanced = await db
|
|
72
72
|
.prepare(`UPDATE ${table} SET stage = ? WHERE ${idColumn} = ? AND stage = ?`)
|
|
@@ -97,7 +97,7 @@ export async function advanceWorkflowStage(options) {
|
|
|
97
97
|
return { ok: true, stage: next, complete: next >= stageCount };
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
|
-
* Move an item out of band
|
|
100
|
+
* Move an item out of band—back a stage, or skip one—and log it.
|
|
101
101
|
*
|
|
102
102
|
* Sending an item BACK deletes the audit row for the stage being reopened, so
|
|
103
103
|
* "a sign-off exists" keeps meaning "that stage is genuinely done". Leaving it
|
|
@@ -13,7 +13,7 @@ export interface WorkflowField {
|
|
|
13
13
|
}
|
|
14
14
|
export interface WorkflowConfig {
|
|
15
15
|
/**
|
|
16
|
-
* Base name for the generated tables and routes
|
|
16
|
+
* Base name for the generated tables and routes—`"orders"` gives an
|
|
17
17
|
* `orders.stage` column, an `orders_signoffs` audit table, and
|
|
18
18
|
* `/api/orders/advance`.
|
|
19
19
|
*/
|
|
@@ -24,15 +24,15 @@ export interface WorkflowConfig {
|
|
|
24
24
|
* Per-stage fields recorded on sign-off, keyed by stage key. A stage with no
|
|
25
25
|
* entry records only actor + timestamp.
|
|
26
26
|
*
|
|
27
|
-
* These are the "specs" in the reference
|
|
27
|
+
* These are the "specs" in the reference—brew ratio, water activity. They
|
|
28
28
|
* are stored as a JSON blob on the audit row rather than as columns, because
|
|
29
29
|
* they are documentation of what happened, not something the pipeline
|
|
30
30
|
* branches on, and every stage wants a different set.
|
|
31
31
|
*/
|
|
32
32
|
stationFields?: Record<string, WorkflowField[]>;
|
|
33
33
|
/**
|
|
34
|
-
* Emit an override log table. Every out-of-band move
|
|
35
|
-
* stage, skipping one
|
|
34
|
+
* Emit an override log table. Every out-of-band move—sending an item back a
|
|
35
|
+
* stage, skipping one—is recorded with the actor's initials. Default true:
|
|
36
36
|
* a pipeline you can override without a trace is one nobody trusts.
|
|
37
37
|
*/
|
|
38
38
|
overrides?: boolean;
|