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.
Files changed (136) hide show
  1. package/README.md +42 -42
  2. package/bin/astroid.mjs +35 -29
  3. package/dist/analytics/index.d.ts +3 -3
  4. package/dist/analytics/index.js +11 -11
  5. package/dist/astro/csp.d.ts +5 -5
  6. package/dist/astro/csp.js +6 -6
  7. package/dist/astro/index.js +1 -1
  8. package/dist/auth/index.d.ts +2 -2
  9. package/dist/auth/index.js +5 -5
  10. package/dist/commerce/adapters.d.ts +6 -6
  11. package/dist/commerce/adapters.js +9 -9
  12. package/dist/commerce/checkout-scaffold.d.ts +5 -5
  13. package/dist/commerce/checkout-scaffold.js +27 -27
  14. package/dist/commerce/checkout.d.ts +12 -12
  15. package/dist/commerce/checkout.js +7 -7
  16. package/dist/commerce/loader.d.ts +2 -2
  17. package/dist/commerce/loader.js +3 -3
  18. package/dist/commerce/mirror.d.ts +4 -4
  19. package/dist/commerce/mirror.js +12 -12
  20. package/dist/commerce/roles.d.ts +10 -10
  21. package/dist/commerce/roles.js +13 -13
  22. package/dist/commerce/secrets.d.ts +9 -9
  23. package/dist/commerce/secrets.js +9 -9
  24. package/dist/commerce/sync.d.ts +7 -7
  25. package/dist/commerce/sync.js +5 -5
  26. package/dist/components/sections.d.ts +9 -9
  27. package/dist/components/sections.js +12 -12
  28. package/dist/config.d.ts +89 -62
  29. package/dist/config.js +18 -18
  30. package/dist/email/inquiry.d.ts +2 -2
  31. package/dist/email/inquiry.js +1 -1
  32. package/dist/email/send.d.ts +4 -4
  33. package/dist/email/send.js +7 -7
  34. package/dist/email/templates.js +3 -3
  35. package/dist/email/theme.d.ts +1 -1
  36. package/dist/email/theme.js +4 -4
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.js +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/map/pmtiles.d.ts +5 -5
  41. package/dist/map/pmtiles.js +5 -5
  42. package/dist/map/scaffold.d.ts +2 -2
  43. package/dist/map/scaffold.js +8 -8
  44. package/dist/map/style.d.ts +4 -4
  45. package/dist/map/style.js +1 -1
  46. package/dist/portal/config.d.ts +3 -3
  47. package/dist/portal/config.js +11 -10
  48. package/dist/portal/guard.d.ts +4 -4
  49. package/dist/portal/guard.js +4 -4
  50. package/dist/portal/nav.js +2 -2
  51. package/dist/portal/scaffold.d.ts +4 -4
  52. package/dist/portal/scaffold.js +13 -13
  53. package/dist/portal/session.d.ts +11 -5
  54. package/dist/portal/session.js +10 -5
  55. package/dist/portfolio/scaffold.d.ts +1 -1
  56. package/dist/portfolio/scaffold.js +8 -8
  57. package/dist/project/actions.d.ts +1 -1
  58. package/dist/project/actions.js +18 -12
  59. package/dist/project/generate.d.ts +4 -4
  60. package/dist/project/generate.js +26 -26
  61. package/dist/project/index.d.ts +1 -0
  62. package/dist/project/index.js +2 -1
  63. package/dist/project/scaffold.d.ts +2 -2
  64. package/dist/project/scaffold.js +69 -13
  65. package/dist/project/seed.d.ts +22 -0
  66. package/dist/project/seed.js +214 -0
  67. package/dist/pwa/generate.d.ts +11 -11
  68. package/dist/pwa/generate.js +19 -19
  69. package/dist/queues/consumer.d.ts +3 -3
  70. package/dist/queues/consumer.js +2 -2
  71. package/dist/queues/messages.d.ts +4 -4
  72. package/dist/queues/messages.js +2 -2
  73. package/dist/queues/scaffold.d.ts +4 -4
  74. package/dist/queues/scaffold.js +17 -17
  75. package/dist/queues/webhook.d.ts +5 -5
  76. package/dist/queues/webhook.js +3 -3
  77. package/dist/realtime/scaffold.d.ts +4 -4
  78. package/dist/realtime/scaffold.js +12 -12
  79. package/dist/schema/collections.d.ts +42 -11
  80. package/dist/schema/collections.js +43 -42
  81. package/dist/schema/framework.d.ts +1 -1
  82. package/dist/schema/framework.js +2 -2
  83. package/dist/schema/generate.js +6 -6
  84. package/dist/schema/index.js +1 -1
  85. package/dist/secrets.d.ts +6 -6
  86. package/dist/secrets.js +6 -6
  87. package/dist/security/csp-origins.d.ts +1 -1
  88. package/dist/security/csp-origins.js +3 -3
  89. package/dist/security/rate-rules.d.ts +2 -2
  90. package/dist/security/rate-rules.js +8 -8
  91. package/dist/seo/resolve.d.ts +5 -5
  92. package/dist/seo/resolve.js +2 -2
  93. package/dist/seo/routes.d.ts +5 -5
  94. package/dist/seo/routes.js +3 -3
  95. package/dist/seo/structured-data.d.ts +6 -6
  96. package/dist/seo/structured-data.js +7 -7
  97. package/dist/status.d.ts +5 -5
  98. package/dist/status.js +7 -7
  99. package/dist/tenancy/index.d.ts +3 -3
  100. package/dist/tenancy/index.js +11 -11
  101. package/dist/worker/generate.d.ts +2 -2
  102. package/dist/worker/generate.js +91 -57
  103. package/dist/worker/index.js +1 -1
  104. package/dist/worker/routes.js +11 -11
  105. package/dist/workflow/advance.d.ts +3 -3
  106. package/dist/workflow/advance.js +6 -6
  107. package/dist/workflow/config.d.ts +4 -4
  108. package/dist/workflow/config.js +4 -4
  109. package/dist/workflow/generate.d.ts +2 -2
  110. package/dist/workflow/generate.js +11 -11
  111. package/package.json +3 -4
  112. package/src/components/Collection.tsx +5 -5
  113. package/src/components/Editable.astro +9 -9
  114. package/src/components/JustifiedGallery.astro +8 -8
  115. package/src/components/MediaSlot.astro +12 -12
  116. package/src/components/PortalShell.astro +4 -4
  117. package/src/components/RegisterSW.astro +3 -3
  118. package/src/components/Section.astro +8 -8
  119. package/src/components/Sections.astro +6 -6
  120. package/src/components/Seo.astro +3 -3
  121. package/src/components/StageBar.astro +3 -3
  122. package/src/components/StructuredData.astro +2 -2
  123. package/src/components/justify.ts +9 -9
  124. package/src/components/media-meta.ts +10 -10
  125. package/src/components/sections/AboutIntro.astro +1 -1
  126. package/src/components/sections/Contact.astro +1 -1
  127. package/src/components/sections/Cta.astro +1 -1
  128. package/src/components/sections/Faq.astro +1 -1
  129. package/src/components/sections/FeatureGrid.astro +2 -2
  130. package/src/components/sections/Hero.astro +1 -1
  131. package/src/components/sections/PricingTiers.astro +1 -1
  132. package/src/components/sections/ProductGrid.astro +1 -1
  133. package/src/components/sections/SplitImage.astro +1 -1
  134. package/src/components/sections/Steps.astro +1 -1
  135. package/src/components/sections/Testimonial.astro +1 -1
  136. package/src/components/sections.ts +17 -17
package/dist/astro/csp.js CHANGED
@@ -9,14 +9,14 @@
9
9
  // - **Astro owns `script-src`.** Its `security.csp` hashes every script it
10
10
  // processes, so the policy can be `'self'` with no `'unsafe-inline'`. What it
11
11
  // does NOT hash is Solid's hydration bootstrap, which `@astrojs/solid-js`
12
- // injects on every page carrying an island — Astro only tracks its own inline
12
+ // injects on every page carrying an island—Astro only tracks its own inline
13
13
  // scripts. So we compute that hash from the very function the renderer calls,
14
14
  // which means it follows solid-js upgrades instead of going stale as a
15
15
  // copy-pasted literal.
16
16
  // - **The middleware owns `style-src`.** Louise's data-driven `style=""`
17
17
  // carriers and the editor's runtime-injected `<style>` need
18
18
  // `'unsafe-inline'`, and per spec a single hash in `style-src` VOIDS
19
- // `'unsafe-inline'` — so the two cannot coexist in one directive. The
19
+ // `'unsafe-inline'`—so the two cannot coexist in one directive. The
20
20
  // generated middleware rewrites that one directive after the fact
21
21
  // (`cspStyleSrc`), leaving Astro's script hashes verbatim.
22
22
  //
@@ -33,7 +33,7 @@ export { astroidCspOrigins } from "../security/csp-origins.js";
33
33
  * Hash of Solid's inline hydration bootstrap.
34
34
  *
35
35
  * `@astrojs/solid-js` injects this script on every page with an island, but
36
- * Astro's CSP tracker only hashes scripts it processed itself — so without this
36
+ * Astro's CSP tracker only hashes scripts it processed itself—so without this
37
37
  * the bootstrap is blocked under `script-src 'self'` and every island silently
38
38
  * fails to hydrate. Computed from `generateHydrationScript()` (the same call the
39
39
  * renderer makes), so a solid-js upgrade that changes the bootstrap updates the
@@ -45,7 +45,7 @@ export function solidHydrationHash() {
45
45
  }
46
46
  /**
47
47
  * Render one directive. The name is a literal from the union above, so the
48
- * concatenation is a valid `CspDirective` by construction — which is what the
48
+ * concatenation is a valid `CspDirective` by construction—which is what the
49
49
  * assertion is standing in for (TS widens template concatenation to `string`).
50
50
  */
51
51
  function directive(name, ...sources) {
@@ -53,7 +53,7 @@ function directive(name, ...sources) {
53
53
  return (list ? `${name} ${list}` : name);
54
54
  }
55
55
  /**
56
- * The `security` block for `astro.config.mjs` — Astro's half of the split.
56
+ * The `security` block for `astro.config.mjs`—Astro's half of the split.
57
57
  *
58
58
  * `style-src` is deliberately absent: the generated middleware rewrites it per
59
59
  * response, and declaring it here would be the hash-vs-`'unsafe-inline'`
@@ -93,7 +93,7 @@ export function astroidSecurity(config) {
93
93
  }
94
94
  /**
95
95
  * Vite build options the CSP depends on. `assetsInlineLimit: 0` stops Vite from
96
- * inlining small assets as `data:` URLs — an inlined script would be inline, and
96
+ * inlining small assets as `data:` URLs—an inlined script would be inline, and
97
97
  * therefore unhashed, and therefore blocked by `script-src 'self'`. Spread this
98
98
  * into `vite.build` rather than remembering why the number is zero.
99
99
  */
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // `astroidjs/astro` — the BUILD-TIME surface, imported from `astro.config.mjs`.
3
+ // `astroidjs/astro`—the BUILD-TIME surface, imported from `astro.config.mjs`.
4
4
  // Kept off the main entry on purpose: it reaches for `node:crypto` and
5
5
  // `solid-js/web`, neither of which belongs in the Worker bundle the generated
6
6
  // `worker.ts` produces.
@@ -1,6 +1,6 @@
1
1
  import type { AstroidConfig } from "../config.js";
2
2
  /**
3
- * The editor Better Auth instance's table prefix — `louise_user`,
3
+ * The editor Better Auth instance's table prefix—`louise_user`,
4
4
  * `louise_session`, … The unprefixed names are left free for a second (portal)
5
5
  * instance. Consumed by the generated `editorsRoute` and mirrored by the
6
6
  * scaffolded `src/auth.ts` (`getLouiseAuth({ tablePrefix })`) + its migration.
@@ -12,7 +12,7 @@ export declare const ASTROID_EDITOR_TABLE_PREFIX = "louise_";
12
12
  * reject a portal that would collide with it.
13
13
  */
14
14
  export declare const ASTROID_EDITOR_COOKIE_PREFIX = "better-auth";
15
- /** The editor's Better Auth table name for a given model (e.g. `louise_user`). */
15
+ /** The editor's Better Auth table name for a given model (for example, `louise_user`). */
16
16
  export declare function astroidEditorTable(model: string): string;
17
17
  /**
18
18
  * Reject a portal whose isolation would collide with the editor instance on the
@@ -3,12 +3,12 @@
3
3
  // Editor-auth convention + the two-instance isolation guard.
4
4
  //
5
5
  // Astroid runs up to TWO Better Auth instances on one origin: the EDITOR (the
6
- // studio — magic-link + passkey, a DB-managed admin allowlist) and, optionally, a
7
- // second PORTAL instance (customers/members/a shop account — see `portal/`). The
6
+ // studio—magic-link + passkey, a DB-managed admin allowlist) and, optionally, a
7
+ // second PORTAL instance (customers/members/a shop account—see `portal/`). The
8
8
  // editor owns Better Auth's default mount (`/api/auth`) and cookie because the
9
9
  // Louise editor client hardcodes them; its tables are namespaced with the
10
10
  // `louise_` prefix so a second instance can take the unprefixed `user`/`session`
11
- // tables without collision. The portal is the one that MOVES — a distinct mount,
11
+ // tables without collision. The portal is the one that MOVES—a distinct mount,
12
12
  // cookie prefix, and (by default) table prefix.
13
13
  //
14
14
  // The failure this guards against is subtle and intermittent: two instances that
@@ -18,7 +18,7 @@
18
18
  import { AstroidConfigError } from "../errors.js";
19
19
  import { astroidPortal } from "../portal/config.js";
20
20
  /**
21
- * The editor Better Auth instance's table prefix — `louise_user`,
21
+ * The editor Better Auth instance's table prefix—`louise_user`,
22
22
  * `louise_session`, … The unprefixed names are left free for a second (portal)
23
23
  * instance. Consumed by the generated `editorsRoute` and mirrored by the
24
24
  * scaffolded `src/auth.ts` (`getLouiseAuth({ tablePrefix })`) + its migration.
@@ -30,7 +30,7 @@ export const ASTROID_EDITOR_TABLE_PREFIX = "louise_";
30
30
  * reject a portal that would collide with it.
31
31
  */
32
32
  export const ASTROID_EDITOR_COOKIE_PREFIX = "better-auth";
33
- /** The editor's Better Auth table name for a given model (e.g. `louise_user`). */
33
+ /** The editor's Better Auth table name for a given model (for example, `louise_user`). */
34
34
  export function astroidEditorTable(model) {
35
35
  return `${ASTROID_EDITOR_TABLE_PREFIX}${model}`;
36
36
  }
@@ -62,7 +62,7 @@ export interface FourthwallProductLike {
62
62
  * Is this item sold at `locationId` at all?
63
63
  *
64
64
  * Exported because a location-scoped sync needs to SKIP items the merchant
65
- * doesn't carry, and `squareToCatalogItem` can't do that for you — it returns
65
+ * doesn't carry, and `squareToCatalogItem` can't do that for you—it returns
66
66
  * one item, and "don't store this row" isn't a `CatalogItem`. Without the guard
67
67
  * an unstocked item mirrors as a $0 card with no variants, which looks like a
68
68
  * pricing bug rather than a catalog decision.
@@ -79,7 +79,7 @@ export declare function squareItemSoldAt(item: SquareItemLike, locationId: strin
79
79
  *
80
80
  * `price` is the LOWEST variation price. A Square item is a family ("Bag of
81
81
  * beans" with 12oz and 2lb variations), so a single headline number has to mean
82
- * "from" — taking the first variation's price instead would change with Square's
82
+ * "from"—taking the first variation's price instead would change with Square's
83
83
  * ordering and quietly misprice the card.
84
84
  *
85
85
  * ## Scoping to one merchant
@@ -90,22 +90,22 @@ export declare function squareItemSoldAt(item: SquareItemLike, locationId: strin
90
90
  *
91
91
  * The headline number has to be scoped for the same reason the variants are.
92
92
  * "From $8" computed over the whole catalog, on a page where the $8 size isn't
93
- * stocked, advertises a price this merchant will never honour — and because the
93
+ * stocked, advertises a price this merchant will never honour—and because the
94
94
  * dropped variation is usually the cheap one, the error runs in the direction a
95
95
  * customer notices at the till.
96
96
  *
97
97
  * Unscoped behaviour is unchanged: no `locationId` means base prices and every
98
98
  * variation, which is correct for a single-location account.
99
99
  *
100
- * An item sold nowhere at `locationId` yields no variants and a price of 0 —
101
- * filter with {@link squareItemSoldAt} before calling rather than storing that.
100
+ * An item sold nowhere at `locationId` yields no variants and a price of 0—filter
101
+ * with {@link squareItemSoldAt} before calling rather than storing that.
102
102
  */
103
103
  export declare function squareToCatalogItem(item: SquareItemLike, options?: SquareAdapterOptions): CatalogItem;
104
104
  /**
105
105
  * Fourthwall product → `CatalogItem`. Same "lowest variant wins" rule as Square,
106
106
  * for the same reason.
107
107
  *
108
- * Fourthwall already prices in major units, so there's no conversion — mirroring
108
+ * Fourthwall already prices in major units, so there's no conversion—mirroring
109
109
  * `lowestPrice` in `louise-toolkit/commerce/fourthwall`.
110
110
  */
111
111
  export declare function fourthwallToCatalogItem(product: FourthwallProductLike): CatalogItem;
@@ -4,20 +4,20 @@
4
4
  //
5
5
  // This is the file the whole module exists for. themidwestartist.com's loader
6
6
  // says it outright: coracle runs the same helper over Square, "only the
7
- // content/repo reads differ — issue: repo drift." Two sites, one intent, two
7
+ // content/repo reads differ—issue: repo drift." Two sites, one intent, two
8
8
  // hand-written translations that drifted apart. The translation is mechanical,
9
9
  // so it belongs here once.
10
10
  //
11
11
  // Each provider's client already returns a normalized-for-that-provider shape
12
12
  // (`SquareCatalogItem`, `FwProduct`); these functions take that last step to the
13
- // shape the mirror stores. Deliberately pure — they take the provider's objects,
13
+ // shape the mirror stores. Deliberately pure—they take the provider's objects,
14
14
  // not credentials or an `env`, so they're trivially testable and the caller
15
15
  // keeps control of how the fetch happens (cached, paged, rate-limited).
16
16
  /**
17
17
  * Is this object sold at `locationId`?
18
18
  *
19
19
  * Mirrors `presentAt` in `louise-toolkit/commerce/square`, but tolerant of the
20
- * fields being absent. The two lists are NOT symmetric — `presentAtLocationIds`
20
+ * fields being absent. The two lists are NOT symmetric—`presentAtLocationIds`
21
21
  * is a whitelist consulted when `presentAtAllLocations` is false,
22
22
  * `absentAtLocationIds` a blacklist consulted when it is true. Reading them the
23
23
  * other way round shows a merchant products they do not carry.
@@ -40,7 +40,7 @@ const toMajor = (cents) => Math.round(cents) / 100;
40
40
  * Is this item sold at `locationId` at all?
41
41
  *
42
42
  * Exported because a location-scoped sync needs to SKIP items the merchant
43
- * doesn't carry, and `squareToCatalogItem` can't do that for you — it returns
43
+ * doesn't carry, and `squareToCatalogItem` can't do that for you—it returns
44
44
  * one item, and "don't store this row" isn't a `CatalogItem`. Without the guard
45
45
  * an unstocked item mirrors as a $0 card with no variants, which looks like a
46
46
  * pricing bug rather than a catalog decision.
@@ -61,7 +61,7 @@ export function squareItemSoldAt(item, locationId) {
61
61
  *
62
62
  * `price` is the LOWEST variation price. A Square item is a family ("Bag of
63
63
  * beans" with 12oz and 2lb variations), so a single headline number has to mean
64
- * "from" — taking the first variation's price instead would change with Square's
64
+ * "from"—taking the first variation's price instead would change with Square's
65
65
  * ordering and quietly misprice the card.
66
66
  *
67
67
  * ## Scoping to one merchant
@@ -72,15 +72,15 @@ export function squareItemSoldAt(item, locationId) {
72
72
  *
73
73
  * The headline number has to be scoped for the same reason the variants are.
74
74
  * "From $8" computed over the whole catalog, on a page where the $8 size isn't
75
- * stocked, advertises a price this merchant will never honour — and because the
75
+ * stocked, advertises a price this merchant will never honour—and because the
76
76
  * dropped variation is usually the cheap one, the error runs in the direction a
77
77
  * customer notices at the till.
78
78
  *
79
79
  * Unscoped behaviour is unchanged: no `locationId` means base prices and every
80
80
  * variation, which is correct for a single-location account.
81
81
  *
82
- * An item sold nowhere at `locationId` yields no variants and a price of 0 —
83
- * filter with {@link squareItemSoldAt} before calling rather than storing that.
82
+ * An item sold nowhere at `locationId` yields no variants and a price of 0—filter
83
+ * with {@link squareItemSoldAt} before calling rather than storing that.
84
84
  */
85
85
  export function squareToCatalogItem(item, options = {}) {
86
86
  const locationId = options.locationId;
@@ -117,7 +117,7 @@ export function squareToCatalogItem(item, options = {}) {
117
117
  * Fourthwall product → `CatalogItem`. Same "lowest variant wins" rule as Square,
118
118
  * for the same reason.
119
119
  *
120
- * Fourthwall already prices in major units, so there's no conversion — mirroring
120
+ * Fourthwall already prices in major units, so there's no conversion—mirroring
121
121
  * `lowestPrice` in `louise-toolkit/commerce/fourthwall`.
122
122
  */
123
123
  export function fourthwallToCatalogItem(product) {
@@ -2,7 +2,7 @@ import type { AstroidConfig } from "../config.js";
2
2
  /** Does this project take card payments in-page? Square storefront only. */
3
3
  export declare function usesCardCheckout(config: AstroidConfig): boolean;
4
4
  /**
5
- * `src/pages/api/checkout.ts` — the server-authoritative payment route.
5
+ * `src/pages/api/checkout.ts`—the server-authoritative payment route.
6
6
  *
7
7
  * Scaffold-once: a real store adds shipping, tax, an order record, a receipt
8
8
  * email. What Astroid fixes is the sequence, because every step of it is a place
@@ -12,7 +12,7 @@ export declare function usesCardCheckout(config: AstroidConfig): boolean;
12
12
  */
13
13
  export declare function generateAstroidCheckoutRoute(config: AstroidConfig): string | null;
14
14
  /**
15
- * `src/components/SquareCard.astro` — the card input.
15
+ * `src/components/SquareCard.astro`—the card input.
16
16
  *
17
17
  * Square's Web Payments SDK renders the field in an iframe from their CDN and
18
18
  * hands back a single-use token, so the raw card number never touches the Worker
@@ -29,13 +29,13 @@ export declare function generateAstroidSquareCard(config: AstroidConfig): string
29
29
  * application id is shipped to the browser by design, and the environment is a
30
30
  * choice, not a credential. Putting them in `credentials` would also fold them
31
31
  * into the dormancy gate, which is about whether the module can safely CALL
32
- * Square — a different question from whether the card field can render.
32
+ * Square—a different question from whether the card field can render.
33
33
  *
34
34
  * The two vars are gated SEPARATELY, and that split is load-bearing.
35
35
  * `SQUARE_ENVIRONMENT` selects the API HOST for every Square call, so it belongs
36
36
  * to any project that talks to Square at all; `SQUARE_APP_ID` only mounts the
37
- * browser card field. Gating both on card checkout — as this did until
38
- * `invoicing: "square"` became expressible — left a site that runs Square for
37
+ * browser card field. Gating both on card checkout—as this did until
38
+ * `invoicing: "square"` became expressible—left a site that runs Square for
39
39
  * invoicing alone with no `SQUARE_ENVIRONMENT`, and `SquareConfig.environment`
40
40
  * defaults to "sandbox". Every production invoice would have been created
41
41
  * against the sandbox: no error, no warning, just money that never arrives.
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // The checkout SEAM — the piece that made `archetype: "storefront"` a storefront
3
+ // The checkout SEAM—the piece that made `archetype: "storefront"` a storefront
4
4
  // that couldn't take a card.
5
5
  //
6
6
  // Everything around this already existed: `verifyCheckout` re-prices server-side,
@@ -10,7 +10,7 @@
10
10
  // missing was the route in the middle, so none of it was reachable.
11
11
  //
12
12
  // SCOPE, deliberately narrow. This generates the server-authoritative PAYMENT
13
- // path and a card input — not a cart, not a checkout page, not shipping or tax.
13
+ // path and a card input—not a cart, not a checkout page, not shipping or tax.
14
14
  // Where a cart lives (localStorage, D1, a portal session), what it holds, and how
15
15
  // it renders are project decisions Astroid has no business making, and a
16
16
  // half-opinionated cart is worse than none. What is NOT a project decision is the
@@ -26,7 +26,7 @@ export function usesCardCheckout(config) {
26
26
  return astroidCommerceRoles(config.commerce).storefront === "square";
27
27
  }
28
28
  /**
29
- * `src/pages/api/checkout.ts` — the server-authoritative payment route.
29
+ * `src/pages/api/checkout.ts`—the server-authoritative payment route.
30
30
  *
31
31
  * Scaffold-once: a real store adds shipping, tax, an order record, a receipt
32
32
  * email. What Astroid fixes is the sequence, because every step of it is a place
@@ -42,12 +42,12 @@ export function generateAstroidCheckoutRoute(config) {
42
42
  return [
43
43
  "// Server-authoritative checkout (POST /api/checkout).",
44
44
  "//",
45
- "// Scaffolded once and yours to extend — shipping, tax, an order row, a",
45
+ "// Scaffolded once and yours to extend—shipping, tax, an order row, a",
46
46
  "// receipt email all belong here. What should NOT change is the ORDER of the",
47
47
  "// steps below; each one is load-bearing:",
48
48
  "//",
49
49
  multi
50
- ? "// 0. Resolve WHICH MERCHANT this sale belongs to, from the host — never\n" +
50
+ ? "// 0. Resolve WHICH MERCHANT this sale belongs to, from the host—never\n" +
51
51
  "// from the request body. See `resolveLocationId` below."
52
52
  : null,
53
53
  multi
@@ -65,7 +65,7 @@ export function generateAstroidCheckoutRoute(config) {
65
65
  "// a double-clicked Pay button charges once while two customers buying",
66
66
  "// the same thing stay two charges.",
67
67
  "// 4. Charge only when commerce is actually provisioned. With placeholder",
68
- "// secrets this simulates instead — it must never call Square with a",
68
+ "// secrets this simulates instead—it must never call Square with a",
69
69
  "// dummy credential.",
70
70
  multi
71
71
  ? "//\n" +
@@ -95,13 +95,13 @@ export function generateAstroidCheckoutRoute(config) {
95
95
  "// ── Which merchant is this? ────────────────────────────────────────────────",
96
96
  "//",
97
97
  '// `square.locations: "multi"` means the location is a property of the',
98
- "// REQUEST, not of the environment — which is why Astroid does not require a",
98
+ "// REQUEST, not of the environment—which is why Astroid does not require a",
99
99
  "// SQUARE_LOCATION_ID for this project. Fill this in and keep two rules:",
100
100
  "//",
101
101
  "// * Derive it from the HOST (or an authenticated session), never from the",
102
102
  "// request body. A body-supplied location lets a customer name the",
103
103
  "// cheapest merchant's id and pay that price at the dearest merchant's",
104
- "// shop — the same exploit as a client-supplied price, one level back.",
104
+ "// shop—the same exploit as a client-supplied price, one level back.",
105
105
  "// * Return null for anything unrecognised. Falling back to a default",
106
106
  "// rings one merchant's sale against another merchant's books, and looks",
107
107
  "// completely successful while doing it.",
@@ -125,7 +125,7 @@ export function generateAstroidCheckoutRoute(config) {
125
125
  " // mirror holds ONE price per item, so it structurally cannot answer",
126
126
  ' // "what does this cost at this location". `retrieveVariationPricesAt`',
127
127
  " // resolves `location_overrides`, and omits any variation the merchant",
128
- " // does not carry — so an unstocked id fails closed as `unavailable`",
128
+ " // does not carry—so an unstocked id fails closed as `unavailable`",
129
129
  " // instead of silently selling at the base price.",
130
130
  " const locationId = scope?.locationId;",
131
131
  " if (!locationId) return new Map();",
@@ -150,7 +150,7 @@ export function generateAstroidCheckoutRoute(config) {
150
150
  " const prices = new Map<string, number>();",
151
151
  " for (const item of items) {",
152
152
  " // The mirror stores MAJOR units (dollars); the charge is in minor units.",
153
- " // `Math.round` is not decoration — 19.99 * 100 is 1998.9999999999998, and",
153
+ " // `Math.round` is not decoration—19.99 * 100 is 1998.9999999999998, and",
154
154
  " // a float cent here fails the exact-equality staleness check on every",
155
155
  " // single checkout.",
156
156
  " if (variantIds.includes(item.externalId)) {",
@@ -174,7 +174,7 @@ export function generateAstroidCheckoutRoute(config) {
174
174
  " // or probe prices, even though the card token itself is single-use. Requires",
175
175
  " // an Origin/Referer matching the host; a non-browser caller (a stripped",
176
176
  " // header) is refused. If you deliberately serve checkout from another origin,",
177
- " // this is the line to relax — it's yours.",
177
+ " // this is the line to relax—it's yours.",
178
178
  ' if (!isSameOrigin(request)) return json({ error: "Forbidden" }, 403);',
179
179
  "",
180
180
  " const body = (await request.json().catch(() => null)) as {",
@@ -203,7 +203,7 @@ export function generateAstroidCheckoutRoute(config) {
203
203
  ' return json({ error: "This storefront is not open for orders." }, 409);',
204
204
  " }",
205
205
  "",
206
- " // 4, EARLY — and the ordering is the point. Re-pricing per location is",
206
+ " // 4, EARLY—and the ordering is the point. Re-pricing per location is",
207
207
  " // itself a live Square call, so the dormancy gate has to precede",
208
208
  " // verification here rather than follow it; running it after would call",
209
209
  " // Square with a placeholder token, which this route must never do.",
@@ -231,7 +231,7 @@ export function generateAstroidCheckoutRoute(config) {
231
231
  " const check = await verifyCheckout(body.lines, serverPrices, {",
232
232
  " scope: { locationId },",
233
233
  " });",
234
- " // Every stale line, with its live price — feed `issues` to `repairCart`",
234
+ " // Every stale line, with its live price—feed `issues` to `repairCart`",
235
235
  " // (louise-toolkit/commerce) to fix the whole cart in one step.",
236
236
  " if (!check.ok) {",
237
237
  " return json({ error: check.message, reason: check.reason, issues: check.issues }, 409);",
@@ -243,7 +243,7 @@ export function generateAstroidCheckoutRoute(config) {
243
243
  : [
244
244
  " // 1 + 2: re-price and refuse on mismatch.",
245
245
  " const check = await verifyCheckout(body.lines, serverPrices);",
246
- " // Every stale line, with its live price — feed `issues` to `repairCart`",
246
+ " // Every stale line, with its live price—feed `issues` to `repairCart`",
247
247
  " // (louise-toolkit/commerce) to fix the whole cart in one step.",
248
248
  " if (!check.ok) {",
249
249
  " return json({ error: check.message, reason: check.reason, issues: check.issues }, 409);",
@@ -253,7 +253,7 @@ export function generateAstroidCheckoutRoute(config) {
253
253
  ' const idempotencyKey = await checkoutIdempotencyKey(check, "order", cartId);',
254
254
  "",
255
255
  " // 4: dormant until provisioned. An unconfigured store still re-prices and",
256
- " // still refuses a stale cart — it just doesn't move money.",
256
+ " // still refuses a stale cart—it just doesn't move money.",
257
257
  " // Cast as the toolkit's own `astroidModuleStatus` does: `readSecret`",
258
258
  " // accepts a plain string OR a Secrets Store binding, which CloudflareEnv",
259
259
  " // types more narrowly than the resolver's `SecretSource` map.",
@@ -278,7 +278,7 @@ export function generateAstroidCheckoutRoute(config) {
278
278
  "",
279
279
  " // Both are guaranteed real by the dormancy gate above; the narrowing here",
280
280
  " // is for the type system, which can't know that. `environment` is a UNION",
281
- ' // ("sandbox" | "production"), not a free string — an unrecognised value',
281
+ ' // ("sandbox" | "production"), not a free string—an unrecognised value',
282
282
  " // would otherwise silently select the sandbox host in production.",
283
283
  ' const environment = env.SQUARE_ENVIRONMENT === "production" ? "production" : "sandbox";',
284
284
  " const payment = await createPayment(",
@@ -291,7 +291,7 @@ export function generateAstroidCheckoutRoute(config) {
291
291
  " // The SERVER's number, never the client's.",
292
292
  ' amountMoney: { amount: check.subtotalCents, currency: "USD" },',
293
293
  multi
294
- ? " // The location the cart was PRICED against — necessarily the same one,\n" +
294
+ ? " // The location the cart was PRICED against—necessarily the same one,\n" +
295
295
  " // or the sale rings against a merchant who never quoted this total."
296
296
  : null,
297
297
  multi ? " locationId," : ' locationId: env.SQUARE_LOCATION_ID ?? "",',
@@ -319,7 +319,7 @@ export function generateAstroidCheckoutRoute(config) {
319
319
  .join("\n");
320
320
  }
321
321
  /**
322
- * `src/components/SquareCard.astro` — the card input.
322
+ * `src/components/SquareCard.astro`—the card input.
323
323
  *
324
324
  * Square's Web Payments SDK renders the field in an iframe from their CDN and
325
325
  * hands back a single-use token, so the raw card number never touches the Worker
@@ -336,7 +336,7 @@ export function generateAstroidSquareCard(config) {
336
336
  "---",
337
337
  "// Square Web Payments card input.",
338
338
  "//",
339
- "// The card field is an IFRAME served by Square's CDN — the raw number never",
339
+ "// The card field is an IFRAME served by Square's CDN—the raw number never",
340
340
  "// enters this page's DOM and never reaches the Worker, which is what keeps the",
341
341
  "// site out of PCI scope. `tokenize()` returns a single-use token; POST it to",
342
342
  "// /api/checkout, which re-prices server-side before charging.",
@@ -348,7 +348,7 @@ export function generateAstroidSquareCard(config) {
348
348
  ...(multi
349
349
  ? [
350
350
  "// Multi-location: the merchant is a property of the PAGE, so the id comes",
351
- "// in as a prop. Only the card iframe is bound to it — the charge takes its",
351
+ "// in as a prop. Only the card iframe is bound to it—the charge takes its",
352
352
  "// location from the server, which resolves it independently in",
353
353
  "// /api/checkout. This one cannot pick the merchant, and shouldn't: it is",
354
354
  "// rendered from markup a customer can reach.",
@@ -359,7 +359,7 @@ export function generateAstroidSquareCard(config) {
359
359
  "",
360
360
  ]
361
361
  : []),
362
- "// The PUBLIC application id — safe in the browser, unlike the access token.",
362
+ "// The PUBLIC application id—safe in the browser, unlike the access token.",
363
363
  "// Absent (an unprovisioned store) → render nothing rather than a dead form.",
364
364
  "const appId = env.SQUARE_APP_ID;",
365
365
  multi ? null : "const locationId = env.SQUARE_LOCATION_ID;",
@@ -417,7 +417,7 @@ export function generateAstroidSquareCard(config) {
417
417
  .filter((line) => line !== null)
418
418
  .join("\n");
419
419
  }
420
- /** Does this project talk to Square in ANY role — storefront, invoicing, or
420
+ /** Does this project talk to Square in ANY role—storefront, invoicing, or
421
421
  * otherwise? Distinct from {@link usesCardCheckout}, which asks the narrower
422
422
  * question of whether the in-page card field renders. */
423
423
  function usesSquare(config) {
@@ -430,13 +430,13 @@ function usesSquare(config) {
430
430
  * application id is shipped to the browser by design, and the environment is a
431
431
  * choice, not a credential. Putting them in `credentials` would also fold them
432
432
  * into the dormancy gate, which is about whether the module can safely CALL
433
- * Square — a different question from whether the card field can render.
433
+ * Square—a different question from whether the card field can render.
434
434
  *
435
435
  * The two vars are gated SEPARATELY, and that split is load-bearing.
436
436
  * `SQUARE_ENVIRONMENT` selects the API HOST for every Square call, so it belongs
437
437
  * to any project that talks to Square at all; `SQUARE_APP_ID` only mounts the
438
- * browser card field. Gating both on card checkout — as this did until
439
- * `invoicing: "square"` became expressible — left a site that runs Square for
438
+ * browser card field. Gating both on card checkout—as this did until
439
+ * `invoicing: "square"` became expressible—left a site that runs Square for
440
440
  * invoicing alone with no `SQUARE_ENVIRONMENT`, and `SquareConfig.environment`
441
441
  * defaults to "sandbox". Every production invoice would have been created
442
442
  * against the sandbox: no error, no warning, just money that never arrives.
@@ -459,11 +459,11 @@ export function astroidCheckoutVars(config) {
459
459
  * substitutes into `src/env.d.ts`. Empty without card checkout.
460
460
  */
461
461
  export function generateAstroidCheckoutEnv(config) {
462
- // Mirrors the gating in `astroidCheckoutVars` — the app id is card-checkout
462
+ // Mirrors the gating in `astroidCheckoutVars`—the app id is card-checkout
463
463
  // only, the environment belongs to any project that calls Square at all.
464
464
  const lines = [];
465
465
  if (usesCardCheckout(config)) {
466
- lines.push(" /** Square's PUBLIC application id — shipped to the browser to mount the", " * Web Payments card field. Not a secret; see wrangler.jsonc `vars`. */", " SQUARE_APP_ID: string;");
466
+ lines.push(" /** Square's PUBLIC application id—shipped to the browser to mount the", " * Web Payments card field. Not a secret; see wrangler.jsonc `vars`. */", " SQUARE_APP_ID: string;");
467
467
  }
468
468
  if (usesSquare(config)) {
469
469
  lines.push(' /** Square API environment: "sandbox" or "production". Selects the API', " * host for EVERY Square call, so it is required for invoicing too. */", " SQUARE_ENVIRONMENT: string;");
@@ -28,13 +28,13 @@ export interface VerifiedLine {
28
28
  * worth a notify-me. Collapsing them tells someone to remove an item the shop
29
29
  * will restock on Tuesday.
30
30
  *
31
- * A lookup can only produce `"out-of-stock"` by saying so — see
32
- * {@link ScopedPriceLookup} — because a bare `Map` has no way to distinguish
31
+ * A lookup can only produce `"out-of-stock"` by saying so—see
32
+ * {@link ScopedPriceLookup}—because a bare `Map` has no way to distinguish
33
33
  * them and guessing would put the wrong sentence on the screen.
34
34
  */
35
35
  export type CheckoutRefusal = "empty" | "unavailable" | "out-of-stock" | "price-changed" | "invalid";
36
36
  /**
37
- * One way the cart disagrees with the live catalog — the toolkit's
37
+ * One way the cart disagrees with the live catalog—the toolkit's
38
38
  * `CartIssue`, less the add-on case (checkout lines carry no add-ons yet).
39
39
  * Hand the list to `repairCart` from `louise-toolkit/commerce` to fix the cart
40
40
  * in one step.
@@ -52,7 +52,7 @@ export type CheckoutVerification = {
52
52
  reason: CheckoutRefusal;
53
53
  message: string;
54
54
  /**
55
- * Every problem, in cart order, with what the catalog says now — empty
55
+ * Every problem, in cart order, with what the catalog says now—empty
56
56
  * for `"empty"` and `"invalid"`, which are about the request, not the
57
57
  * catalog.
58
58
  */
@@ -62,7 +62,7 @@ export type CheckoutVerification = {
62
62
  * map omits is treated as no longer purchasable. */
63
63
  export type PriceLookup = (variantIds: string[]) => Promise<Map<string, number>>;
64
64
  /** Where the sale is happening. Optional, and providers without a location
65
- * dimension ignore it — a single-merchant Square account or Fourthwall store
65
+ * dimension ignore it—a single-merchant Square account or Fourthwall store
66
66
  * passes nothing and behaves exactly as before. */
67
67
  export interface CheckoutScope {
68
68
  /** Provider location id (Square) or equivalent merchant key. */
@@ -86,13 +86,13 @@ export interface ScopedPrices {
86
86
  * A price lookup that knows WHERE the sale is happening.
87
87
  *
88
88
  * This is the multi-merchant checkout guard. One shared catalog sold through
89
- * several merchants carries a different price per location — each shop's
90
- * commission is absorbed in its own override — so re-pricing a cart against
89
+ * several merchants carries a different price per location—each shop's
90
+ * commission is absorbed in its own override—so re-pricing a cart against
91
91
  * base prices lets a customer pay the cheapest merchant's price at the dearest
92
92
  * merchant's storefront. That is not a rounding error; it is the same class of
93
93
  * bug as trusting the client's `unitPriceCents`, just one level further back.
94
94
  *
95
- * `scope` is optional so a {@link PriceLookup} is still assignable here — an
95
+ * `scope` is optional so a {@link PriceLookup} is still assignable here—an
96
96
  * existing single-location lookup simply ignores the extra argument, which is
97
97
  * exactly what a function of lower arity does in JavaScript.
98
98
  *
@@ -131,7 +131,7 @@ export declare function verifyCheckout(lines: unknown, lookup: ScopedPriceLookup
131
131
  /**
132
132
  * A deterministic idempotency key for one buyer's checkout attempt.
133
133
  *
134
- * Providers dedupe on this, so the same key must mean the same charge — which
134
+ * Providers dedupe on this, so the same key must mean the same charge—which
135
135
  * cuts both ways, and the second direction is the one that costs money. It is
136
136
  * derived from the verified cart *and* `identity`, not from a random value or a
137
137
  * timestamp: a customer double-clicking Pay sends the same key twice and is
@@ -141,18 +141,18 @@ export declare function verifyCheckout(lines: unknown, lookup: ScopedPriceLookup
141
141
  * **`identity` is required, and it is what makes the key safe.** Without it the
142
142
  * key was a pure function of the cart contents, so two DIFFERENT customers
143
143
  * buying the same thing for the same price produced byte-identical keys. Stripe
144
- * and Square scope idempotency keys per account and retain them for ~24h, so the
144
+ * and Square scope idempotency keys per account and retain them for about 24 hours, so the
145
145
  * provider replayed the first customer's PaymentIntent instead of creating the
146
146
  * second's: the second buyer was never charged, no second order existed, and the
147
147
  * site reported success. On a single-SKU storefront that is ordinary traffic,
148
148
  * not an edge case.
149
149
  *
150
150
  * Pass something stable across a retry of THIS attempt and distinct between
151
- * buyers — a cart id, a checkout-session id, or a portal user id. Do not pass a
151
+ * buyers—a cart id, a checkout-session id, or a portal user id. Do not pass a
152
152
  * value that varies per request (a fresh uuid defeats the dedupe and a
153
153
  * double-click charges twice), and do not pass a constant.
154
154
  *
155
- * `scope` remains the OPERATION — `"order"` vs `"refund"` — so the two can never
155
+ * `scope` remains the OPERATION—`"order"` vs `"refund"`—so the two can never
156
156
  * collide for one buyer. It is not an identity and never was.
157
157
  */
158
158
  export declare function checkoutIdempotencyKey(verified: {
@@ -3,7 +3,7 @@
3
3
  // Server-authoritative checkout.
4
4
  //
5
5
  // A cart arrives from the browser, so every number in it is a claim, not a fact.
6
- // The rule this encodes — taken from coracle.coffee's working checkout — is that
6
+ // The rule this encodes—taken from coracle.coffee's working checkout—is that
7
7
  // the client's price is a **staleness check**, never an input to the charge:
8
8
  // look the price up server-side, and if it disagrees with what the customer was
9
9
  // shown, refuse rather than silently charging a different amount. Refusing is
@@ -16,7 +16,7 @@
16
16
  // The comparison itself is the toolkit's `cartIssues`, which reports EVERY
17
17
  // stale line rather than the first: a refusal that names one problem at a time
18
18
  // is how a customer ends up fixing a line, retrying, and being refused over the
19
- // next. What this adds is the opinion — policing the untrusted body, and the
19
+ // next. What this adds is the opinion—policing the untrusted body, and the
20
20
  // sentence a customer sees.
21
21
  import { cartIssues } from "louise-toolkit/commerce";
22
22
  import { AstroidUsageError } from "../errors.js";
@@ -91,7 +91,7 @@ lookup, options = {}) {
91
91
  }
92
92
  const { prices, outOfStock } = normalizeLookup(await lookup([...new Set(parsed.map((l) => l.variantId))], options.scope));
93
93
  // No `liveModifierIds`, so the add-on check is skipped and every issue is a
94
- // variant one — which is what makes the narrowing to CheckoutIssue true.
94
+ // variant one—which is what makes the narrowing to CheckoutIssue true.
95
95
  const issues = cartIssues(parsed, { prices, outOfStock });
96
96
  const [first] = issues;
97
97
  if (first)
@@ -109,7 +109,7 @@ lookup, options = {}) {
109
109
  /**
110
110
  * A deterministic idempotency key for one buyer's checkout attempt.
111
111
  *
112
- * Providers dedupe on this, so the same key must mean the same charge — which
112
+ * Providers dedupe on this, so the same key must mean the same charge—which
113
113
  * cuts both ways, and the second direction is the one that costs money. It is
114
114
  * derived from the verified cart *and* `identity`, not from a random value or a
115
115
  * timestamp: a customer double-clicking Pay sends the same key twice and is
@@ -119,18 +119,18 @@ lookup, options = {}) {
119
119
  * **`identity` is required, and it is what makes the key safe.** Without it the
120
120
  * key was a pure function of the cart contents, so two DIFFERENT customers
121
121
  * buying the same thing for the same price produced byte-identical keys. Stripe
122
- * and Square scope idempotency keys per account and retain them for ~24h, so the
122
+ * and Square scope idempotency keys per account and retain them for about 24 hours, so the
123
123
  * provider replayed the first customer's PaymentIntent instead of creating the
124
124
  * second's: the second buyer was never charged, no second order existed, and the
125
125
  * site reported success. On a single-SKU storefront that is ordinary traffic,
126
126
  * not an edge case.
127
127
  *
128
128
  * Pass something stable across a retry of THIS attempt and distinct between
129
- * buyers — a cart id, a checkout-session id, or a portal user id. Do not pass a
129
+ * buyers—a cart id, a checkout-session id, or a portal user id. Do not pass a
130
130
  * value that varies per request (a fresh uuid defeats the dedupe and a
131
131
  * double-click charges twice), and do not pass a constant.
132
132
  *
133
- * `scope` remains the OPERATION — `"order"` vs `"refund"` — so the two can never
133
+ * `scope` remains the OPERATION—`"order"` vs `"refund"`—so the two can never
134
134
  * collide for one buyer. It is not an identity and never was.
135
135
  */
136
136
  export async function checkoutIdempotencyKey(verified, scope, identity) {