@suiteop/storefront 0.1.1 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +74 -0
  3. package/dist/astro-content-model.cjs +17 -0
  4. package/dist/astro-content-model.d.cts +4 -0
  5. package/dist/astro-content-model.d.ts +4 -0
  6. package/dist/astro-content-model.js +10 -0
  7. package/dist/astro.cjs +3 -1
  8. package/dist/astro.d.cts +38 -17
  9. package/dist/astro.d.ts +38 -17
  10. package/dist/astro.js +2 -2
  11. package/dist/checkout.cjs +3 -3
  12. package/dist/checkout.js +2 -2
  13. package/dist/chunk-A43EXKPY.js +3 -0
  14. package/dist/chunk-BFUMJBZV.js +9 -0
  15. package/dist/chunk-EVSAMV5K.cjs +8 -0
  16. package/dist/{chunk-YFTWGF3L.js → chunk-HMLBMNGA.js} +1 -1
  17. package/dist/chunk-IBIDIWTI.js +8 -0
  18. package/dist/chunk-PUY7GWID.cjs +19 -0
  19. package/dist/chunk-PYVUDQSQ.js +3 -0
  20. package/dist/chunk-USOISC2R.cjs +17 -0
  21. package/dist/{chunk-RDREYP27.cjs → chunk-VV2UNVQ2.cjs} +2 -2
  22. package/dist/chunk-W4VKQWXY.cjs +16 -0
  23. package/dist/chunk-WNCQ6YBT.cjs +18 -0
  24. package/dist/chunk-Y6INA5K7.js +5 -0
  25. package/dist/chunk-ZI2PG7LS.js +6 -0
  26. package/dist/chunk-ZLAOY6IF.cjs +9 -0
  27. package/dist/{errors-Bp_jmXbA.d.ts → client-CdsTfFv7.d.ts} +104 -147
  28. package/dist/{errors-y-BiaVAk.d.cts → client-CmoApA__.d.cts} +104 -147
  29. package/dist/components.cjs +2 -1
  30. package/dist/components.d.cts +5 -2
  31. package/dist/components.d.ts +5 -2
  32. package/dist/components.js +2 -1
  33. package/dist/config.cjs +6 -2
  34. package/dist/config.d.cts +33 -1
  35. package/dist/config.d.ts +33 -1
  36. package/dist/config.js +3 -2
  37. package/dist/content.cjs +37 -0
  38. package/dist/content.d.cts +254 -0
  39. package/dist/content.d.ts +254 -0
  40. package/dist/content.js +4 -0
  41. package/dist/errors-Ct7uJn5o.d.ts +151 -0
  42. package/dist/errors-LMVeW29z.d.cts +151 -0
  43. package/dist/index.cjs +38 -20
  44. package/dist/index.d.cts +14 -3
  45. package/dist/index.d.ts +14 -3
  46. package/dist/index.js +5 -3
  47. package/dist/react.cjs +6 -5
  48. package/dist/react.d.cts +2 -1
  49. package/dist/react.d.ts +2 -1
  50. package/dist/react.js +5 -4
  51. package/package.json +22 -2
  52. package/dist/chunk-AIQUVZEI.cjs +0 -8
  53. package/dist/chunk-GIIXCS6X.cjs +0 -18
  54. package/dist/chunk-LIBKUMJV.js +0 -8
  55. package/dist/chunk-SK5TD73J.js +0 -3
  56. package/dist/chunk-UACP7BZ7.js +0 -6
  57. package/dist/chunk-XIAAWHAY.cjs +0 -17
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  Hand-written per release. Tag `storefront-sdk@<version>` on `main` publishes it.
4
4
 
5
+ ## 0.3.0
6
+
7
+ - Listings carry `images`: the photos as SuiteOp hosts them (its CDN once copied), in display
8
+ order, with `alt`, `width` and `height` when known. `pictures` (the PMS's own array) is
9
+ unchanged and deprecated.
10
+ - `imageUrl`, `imageSrcSet`, `imageProps` and `IMAGE_WIDTHS`: responsive URLs for SuiteOp-hosted
11
+ photos at 320, 640, 1080 and 1600 px wide, as AVIF or WebP when the browser takes them. Any
12
+ other URL passes through unchanged.
13
+ - `listingPhotos(listing)` reads `images` first, then `pictures`; passing `pictures` still works.
14
+ `Gallery` serves each SuiteOp-hosted slide with a `srcset`, and its new `sizes` prop describes
15
+ the slide's rendered width (default `100vw`). Slides with a known size now carry `width` and
16
+ `height` attributes so the browser reserves their space; a site whose CSS sets an image's
17
+ width but not its height should add `height: auto`, or the photo stretches to the attribute
18
+ height.
19
+
20
+ ## 0.2.0
21
+
22
+ - `@suiteop/storefront/content`: `defineContent` and `field.*` declare the site's content model
23
+ (collections such as a blog, page sections, custom fields on buildings and areas) in its own
24
+ code. `createContentReader(client, definition)` reads the values typed from that same object —
25
+ `collection(key).list()` / `.get(slugOrId)`, `section(key).get()`, `fieldsOf(key, record)` —
26
+ with no code generation; a stored value that no longer fits its field reads as `null` (`[]` for
27
+ a list). `toContentModel` / `parseContentModel` give the model as the API stores it.
28
+ - The client reads content: `contentModel.get()`, `collections.list(key, { limit, offset })`,
29
+ `collections.get(key, slugOrId)` (null when nothing published answers), `sections.get(key)`.
30
+ - `suiteop({ content: './src/suiteop.content.ts' })` emits the model at
31
+ `/_suiteop/content-model.json`; the SuiteOp deploy registers it, so the dashboard shows a form
32
+ per type. `suiteop({ routes: { '/blog/[slug]': 'collection:blog' } })` puts a collection's
33
+ pages in the sitemap; `/area/[…]` routes are inferred as the site's areas (`locations`).
34
+ - Sites without a content model build and deploy exactly as on 0.1.
35
+ - `defineSuiteOpConfig` accepts 14 more theme fonts (free Google fonts on Fontsource): Fraunces,
36
+ DM Sans, Cormorant Garamond, EB Garamond, Lora, Crimson Pro, Source Serif 4, IBM Plex Serif,
37
+ Manrope, Plus Jakarta Sans, Space Grotesk, IBM Plex Sans, Libre Franklin, Josefin Sans. Every
38
+ earlier font id is unchanged.
39
+ - `@suiteop/storefront/config` exports `STOREFRONT_FONT_FAMILIES`, each theme font's Fontsource
40
+ family name. The starter names its fonts from it, so a font added in a later release builds on
41
+ sites forked before it.
42
+
5
43
  ## 0.1.1
6
44
 
7
45
  - The published `package.json` no longer carries the repository's own build scripts. No API
@@ -31,4 +69,10 @@ Hand-written per release. Tag `storefront-sdk@<version>` on `main` publishes it.
31
69
  - `createAnalytics` / `analyticsFor` and `useTrack()` in `/react`: batched page events (with the
32
70
  publishable key) to the storefront edge's `/_suiteop/events`; `SuiteOpProvider` sends `page_view`.
33
71
  The earlier single-event `track(name, params)` is gone: the edge accepts only the batch.
72
+ - A checkout session carries `extras` (the upsell lines the API priced and the total held), and
73
+ `checkoutSessions.create` accepts `extras` (custom-field values, upsell ids and quantities).
74
+ `<suiteop-checkout>` collects and sends them itself. New error code `extras_invalid`.
75
+ `bookings.get` answers `totalMinor` as what was paid (the stay plus its extras) and lists the
76
+ booked `lines`. `suiteop-checkout:ready` on the payment step carries `totalMinor` and
77
+ `currency` of the session being paid for.
34
78
  - Speaks Storefront API version `2026-09-27`.
package/README.md CHANGED
@@ -58,11 +58,85 @@ charged, so a retry replays the dead session and your UI should show the failure
58
58
  `checkoutSessions.forget(quoteRef, ratePlanId)` releases the key explicitly — never call it after
59
59
  a `failed` result, or a retry can take a second payment. A failed create carries the key in `error.details.idempotencyKey`.
60
60
 
61
+ A site can add custom fields and upsells to its checkout. `<suiteop-checkout>` shows them and
62
+ sends the guest's choices itself, so a host page creates the session exactly as above. A session
63
+ carries `extras`: the upsell lines the API priced and `heldTotalMinor`, the total the hold is
64
+ for. Show that total, never one computed on the page. With extras the guest pays for a session
65
+ the element created, so a page that prints a total of its own takes it from the element's
66
+ `suiteop-checkout:ready` event (`detail.totalMinor`, `detail.currency`, present on the payment
67
+ step). A booking's `totalMinor` is what was paid, and `lines` lists the extras inside it.
68
+
61
69
  Requires Node 20+ outside browsers (global `fetch` and `crypto`).
62
70
 
63
71
  Money is an integer `…Minor` field (the amount × 100) with a lower-case ISO 4217 currency;
64
72
  upper-case it yourself for display.
65
73
 
74
+ ## Content
75
+
76
+ A site declares the content its property manager edits in the SuiteOp dashboard — collections
77
+ (a blog, press mentions), page sections (a home-page banner) and extra fields on buildings or
78
+ areas — in its own code, and reads the values per request:
79
+
80
+ ```ts
81
+ // src/suiteop.content.ts
82
+ import { defineContent, field } from '@suiteop/storefront/content'
83
+
84
+ export default defineContent({
85
+ blog: {
86
+ kind: 'collection',
87
+ label: 'Blog',
88
+ titleField: 'title',
89
+ routable: true, // each post gets a slug
90
+ drafts: true, // posts are published explicitly
91
+ order: 'newest',
92
+ fields: {
93
+ title: field.text({ label: 'Title', required: true, maxLength: 150 }),
94
+ cover: field.image({ label: 'Cover photo' }),
95
+ body: field.richText({ label: 'Article', required: true }),
96
+ },
97
+ },
98
+ home_hero: {
99
+ kind: 'section',
100
+ label: 'Home — top banner',
101
+ fields: {
102
+ title: field.text({ label: 'Headline' }),
103
+ photos: field.gallery({ label: 'Photos' }),
104
+ },
105
+ },
106
+ area_details: {
107
+ kind: 'fields',
108
+ attachesTo: 'area',
109
+ label: 'Area details',
110
+ fields: { best_for: field.text({ label: 'Best for' }) },
111
+ },
112
+ })
113
+ ```
114
+
115
+ ```ts
116
+ import { createContentReader } from '@suiteop/storefront/content'
117
+ import definition from './suiteop.content'
118
+
119
+ const content = createContentReader(storefront, definition)
120
+ const { items } = await content.collection('blog').list({ limit: 10 }) // newest first
121
+ const post = await content.collection('blog').get(slug) // null: draft, scheduled or unknown
122
+ const hero = await content.section('home_hero').get()
123
+ const [area] = await storefront.locations.list()
124
+ const bestFor = content.fieldsOf('area_details', area).best_for // string | null
125
+ ```
126
+
127
+ Field types: `text`, `richText` (Markdown — render it with raw HTML disabled), `image`,
128
+ `gallery`, `number`, `boolean`, `date` (`YYYY-MM-DD`), `option`, `link` (`{ url, label }`),
129
+ `reference` / `references` (`{ kind, id, slug }`, `to: 'listing' | 'building' | 'area' |
130
+ 'collection:<key>'`). Every value is typed from the declaration; any single value may be `null`
131
+ (not filled in yet, or no longer fitting its field after a code change) and lists may be empty.
132
+ Text is in the client's `locale` with the site's default language as fallback.
133
+
134
+ With `suiteop({ content: './src/suiteop.content.ts' })` in `astro.config.mjs`, the build emits the
135
+ model at `/_suiteop/content-model.json` and the SuiteOp deploy registers it: new types and fields
136
+ appear in the dashboard; ones the code stops declaring are hidden, and nothing a manager typed is
137
+ ever deleted. Add `routes: { '/blog/[slug]': 'collection:blog' }` for the sitemap to list each
138
+ published post.
139
+
66
140
  ## Errors
67
141
 
68
142
  Every failure is a `StorefrontError` with `status` (0 when nothing answered), `code`,
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var chunkW4VKQWXY_cjs = require('./chunk-W4VKQWXY.cjs');
4
+ require('./chunk-WNCQ6YBT.cjs');
5
+ require('./chunk-USOISC2R.cjs');
6
+ require('./chunk-LVFRVNT3.cjs');
7
+ var definition = require('virtual:suiteop/content');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var definition__default = /*#__PURE__*/_interopDefault(definition);
12
+
13
+ var prerender=true;function GET(){return new Response(`${JSON.stringify(chunkW4VKQWXY_cjs.parseContentModel(definition__default.default))}
14
+ `,{headers:{"content-type":"application/json"}})}
15
+
16
+ exports.GET = GET;
17
+ exports.prerender = prerender;
@@ -0,0 +1,4 @@
1
+ declare const prerender = true;
2
+ declare function GET(): Response;
3
+
4
+ export { GET, prerender };
@@ -0,0 +1,4 @@
1
+ declare const prerender = true;
2
+ declare function GET(): Response;
3
+
4
+ export { GET, prerender };
@@ -0,0 +1,10 @@
1
+ import { parseContentModel } from './chunk-IBIDIWTI.js';
2
+ import './chunk-Y6INA5K7.js';
3
+ import './chunk-ZI2PG7LS.js';
4
+ import './chunk-6NFURJXI.js';
5
+ import definition from 'virtual:suiteop/content';
6
+
7
+ var prerender=true;function GET(){return new Response(`${JSON.stringify(parseContentModel(definition))}
8
+ `,{headers:{"content-type":"application/json"}})}
9
+
10
+ export { GET, prerender };
package/dist/astro.cjs CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  var zod = require('zod');
4
4
 
5
- var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"]];var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=zod.z.string().regex(/^\/(?![/\\])/);var siteContextSchema=zod.z.object({siteId:zod.z.string().min(1),orgId:zod.z.string().min(1),region:zod.z.enum(["us","eu","apac"]),environment:zod.z.enum(["live","test"]),publishableKey:zod.z.string().regex(/^pk_(live|test)_/),country:zod.z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:zod.z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:zod.z.record(zod.z.string().regex(/^x-suiteop-[a-z0-9-]+$/),zod.z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function suiteop(){let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,updateConfig})=>{if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
5
+ var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"],[/^\/area\/\[[^\]/]+\]$/,"locations"]];var DECLARABLE_SOURCE=/^(listings|buildings|locations|collection:[a-z][a-z0-9_]{0,39})$/;var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=input.declaredRoutes?.[pattern]??SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=zod.z.string().regex(/^\/(?![/\\])/);var siteContextSchema=zod.z.object({siteId:zod.z.string().min(1),orgId:zod.z.string().min(1),region:zod.z.enum(["us","eu","apac"]),environment:zod.z.enum(["live","test"]),publishableKey:zod.z.string().regex(/^pk_(live|test)_/),country:zod.z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:zod.z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:zod.z.record(zod.z.string().regex(/^x-suiteop-[a-z0-9-]+$/),zod.z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";var CONTENT_MODEL_PATH="_suiteop/content-model.json";var CONTENT_MODULE_ID="virtual:suiteop/content";var RESOLVED_CONTENT_MODULE_ID=`\0${CONTENT_MODULE_ID}`;function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function contentModulePlugin(file){return {name:"@suiteop/storefront:content",resolveId:id=>id===CONTENT_MODULE_ID?RESOLVED_CONTENT_MODULE_ID:void 0,load:id=>id===RESOLVED_CONTENT_MODULE_ID?`export { default } from ${JSON.stringify(file)}`:void 0}}function suiteop(options={}){for(const[pattern,source]of Object.entries(options.routes??{})){if(!DECLARABLE_SOURCE.test(source)){throw new Error(`suiteop(): route ${pattern} has source "${source}"; use listings, buildings, locations or collection:<key>.`)}}let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,config,updateConfig,injectRoute})=>{if(options.content){const file=decodeURIComponent(new URL(options.content,config.root).pathname);updateConfig({vite:{plugins:[contentModulePlugin(file)]}});injectRoute({pattern:`/${CONTENT_MODEL_PATH}`,entrypoint:"@suiteop/storefront/astro/content-model",prerender:true});}if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,declaredRoutes:options.routes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
6
6
  `);logger.info(`suiteop manifest: ${manifest.pages.length} pages, ${manifest.dynamicRoutes.length} dynamic routes`);}}}}async function builtHtmlPaths(dir){const{readdir}=await import('node:fs/promises');const{fileURLToPath}=await import('node:url');const entries=await readdir(fileURLToPath(dir),{recursive:true,withFileTypes:true});const paths=[];for(const entry of entries){if(!entry.isFile()||!entry.name.endsWith(".html"))continue;const rel=`${entry.parentPath}/${entry.name}`.slice(fileURLToPath(dir).length).replace(/\\/g,"/").replace(/^\/+/,"");if(/^(_astro|_suiteop)\//.test(rel))continue;paths.push(`/${rel.replace(/(^|\/)index\.html$/,"$1").replace(/\.html$/,"")}`);}return paths}async function readBuiltHtml(dir,pathname,format){const{readFile}=await import('node:fs/promises');const base=pathname.replace(/^\/+/,"").replace(/\/+$/,"");const candidates=base===""?["index.html"]:format==="file"?[`${base}.html`,`${base}/index.html`]:[`${base}/index.html`,`${base}.html`];for(const candidate of candidates){try{return await readFile(new URL(candidate,dir),"utf8")}catch(err){if(err.code!=="ENOENT")throw err}}return null}
7
7
 
8
+ exports.CONTENT_MODEL_PATH = CONTENT_MODEL_PATH;
8
9
  exports.SITE_MANIFEST_PATH = SITE_MANIFEST_PATH;
9
10
  exports.buildSiteManifest = buildSiteManifest;
11
+ exports.contentModulePlugin = contentModulePlugin;
10
12
  exports.devProxy = devProxy;
11
13
  exports.getSiteContext = getSiteContext;
12
14
  exports.suiteop = suiteop;
package/dist/astro.d.cts CHANGED
@@ -1,17 +1,4 @@
1
- interface SiteContext {
2
- siteId: string;
3
- orgId: string;
4
- region: 'us' | 'eu' | 'apac';
5
- environment: 'live' | 'test';
6
- publishableKey: string;
7
- country: string | null;
8
- apiBasePath: string;
9
- apiBaseUrl: string | null;
10
- apiHeaders: Record<string, string>;
11
- }
12
- declare function getSiteContext(locals: object): SiteContext | null;
13
-
14
- type SiteManifestSource = 'listings' | 'buildings';
1
+ type SiteManifestSource = 'listings' | 'buildings' | 'locations' | `collection:${string}`;
15
2
  interface SiteManifestPage {
16
3
  path: string;
17
4
  locale: string;
@@ -30,6 +17,7 @@ interface SiteManifest {
30
17
  interface BuildSiteManifestInput {
31
18
  pages: string[];
32
19
  dynamicRoutes: string[];
20
+ declaredRoutes?: Record<string, SiteManifestSource>;
33
21
  readHtml(pathname: string): Promise<string | null>;
34
22
  now: Date;
35
23
  config: {
@@ -39,7 +27,21 @@ interface BuildSiteManifestInput {
39
27
  }
40
28
  declare function buildSiteManifest(input: BuildSiteManifestInput): Promise<SiteManifest>;
41
29
 
30
+ interface SiteContext {
31
+ siteId: string;
32
+ orgId: string;
33
+ region: 'us' | 'eu' | 'apac';
34
+ environment: 'live' | 'test';
35
+ publishableKey: string;
36
+ country: string | null;
37
+ apiBasePath: string;
38
+ apiBaseUrl: string | null;
39
+ apiHeaders: Record<string, string>;
40
+ }
41
+ declare function getSiteContext(locals: object): SiteContext | null;
42
+
42
43
  declare const SITE_MANIFEST_PATH = "_suiteop/manifest.json";
44
+ declare const CONTENT_MODEL_PATH = "_suiteop/content-model.json";
43
45
  interface ProxyTarget {
44
46
  target: string;
45
47
  changeOrigin: boolean;
@@ -49,16 +51,30 @@ interface ProxyTarget {
49
51
  interface I18nLocale {
50
52
  path: string;
51
53
  }
54
+ interface ContentModulePlugin {
55
+ name: string;
56
+ resolveId(id: string): string | undefined;
57
+ load(id: string): string | undefined;
58
+ }
52
59
  interface ConfigSetupOptions {
53
60
  command: 'dev' | 'build' | 'preview' | 'sync';
61
+ config: {
62
+ root: URL;
63
+ };
54
64
  updateConfig: (config: {
55
65
  vite: {
56
- server: {
66
+ server?: {
57
67
  proxy: Record<string, ProxyTarget>;
58
68
  strictPort: boolean;
59
69
  };
70
+ plugins?: ContentModulePlugin[];
60
71
  };
61
72
  }) => unknown;
73
+ injectRoute: (route: {
74
+ pattern: string;
75
+ entrypoint: string;
76
+ prerender?: boolean;
77
+ }) => void;
62
78
  }
63
79
  interface ConfigDoneOptions {
64
80
  config: {
@@ -99,6 +115,11 @@ interface SuiteOpIntegration {
99
115
  };
100
116
  }
101
117
  declare function devProxy(env: Record<string, string | undefined>): Record<string, ProxyTarget>;
102
- declare function suiteop(): SuiteOpIntegration;
118
+ interface SuiteOpIntegrationOptions {
119
+ content?: string;
120
+ routes?: Record<string, SiteManifestSource>;
121
+ }
122
+ declare function contentModulePlugin(file: string): ContentModulePlugin;
123
+ declare function suiteop(options?: SuiteOpIntegrationOptions): SuiteOpIntegration;
103
124
 
104
- export { type BuildSiteManifestInput, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, buildSiteManifest, devProxy, getSiteContext, suiteop };
125
+ export { type BuildSiteManifestInput, CONTENT_MODEL_PATH, type ContentModulePlugin, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, type SuiteOpIntegrationOptions, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
package/dist/astro.d.ts CHANGED
@@ -1,17 +1,4 @@
1
- interface SiteContext {
2
- siteId: string;
3
- orgId: string;
4
- region: 'us' | 'eu' | 'apac';
5
- environment: 'live' | 'test';
6
- publishableKey: string;
7
- country: string | null;
8
- apiBasePath: string;
9
- apiBaseUrl: string | null;
10
- apiHeaders: Record<string, string>;
11
- }
12
- declare function getSiteContext(locals: object): SiteContext | null;
13
-
14
- type SiteManifestSource = 'listings' | 'buildings';
1
+ type SiteManifestSource = 'listings' | 'buildings' | 'locations' | `collection:${string}`;
15
2
  interface SiteManifestPage {
16
3
  path: string;
17
4
  locale: string;
@@ -30,6 +17,7 @@ interface SiteManifest {
30
17
  interface BuildSiteManifestInput {
31
18
  pages: string[];
32
19
  dynamicRoutes: string[];
20
+ declaredRoutes?: Record<string, SiteManifestSource>;
33
21
  readHtml(pathname: string): Promise<string | null>;
34
22
  now: Date;
35
23
  config: {
@@ -39,7 +27,21 @@ interface BuildSiteManifestInput {
39
27
  }
40
28
  declare function buildSiteManifest(input: BuildSiteManifestInput): Promise<SiteManifest>;
41
29
 
30
+ interface SiteContext {
31
+ siteId: string;
32
+ orgId: string;
33
+ region: 'us' | 'eu' | 'apac';
34
+ environment: 'live' | 'test';
35
+ publishableKey: string;
36
+ country: string | null;
37
+ apiBasePath: string;
38
+ apiBaseUrl: string | null;
39
+ apiHeaders: Record<string, string>;
40
+ }
41
+ declare function getSiteContext(locals: object): SiteContext | null;
42
+
42
43
  declare const SITE_MANIFEST_PATH = "_suiteop/manifest.json";
44
+ declare const CONTENT_MODEL_PATH = "_suiteop/content-model.json";
43
45
  interface ProxyTarget {
44
46
  target: string;
45
47
  changeOrigin: boolean;
@@ -49,16 +51,30 @@ interface ProxyTarget {
49
51
  interface I18nLocale {
50
52
  path: string;
51
53
  }
54
+ interface ContentModulePlugin {
55
+ name: string;
56
+ resolveId(id: string): string | undefined;
57
+ load(id: string): string | undefined;
58
+ }
52
59
  interface ConfigSetupOptions {
53
60
  command: 'dev' | 'build' | 'preview' | 'sync';
61
+ config: {
62
+ root: URL;
63
+ };
54
64
  updateConfig: (config: {
55
65
  vite: {
56
- server: {
66
+ server?: {
57
67
  proxy: Record<string, ProxyTarget>;
58
68
  strictPort: boolean;
59
69
  };
70
+ plugins?: ContentModulePlugin[];
60
71
  };
61
72
  }) => unknown;
73
+ injectRoute: (route: {
74
+ pattern: string;
75
+ entrypoint: string;
76
+ prerender?: boolean;
77
+ }) => void;
62
78
  }
63
79
  interface ConfigDoneOptions {
64
80
  config: {
@@ -99,6 +115,11 @@ interface SuiteOpIntegration {
99
115
  };
100
116
  }
101
117
  declare function devProxy(env: Record<string, string | undefined>): Record<string, ProxyTarget>;
102
- declare function suiteop(): SuiteOpIntegration;
118
+ interface SuiteOpIntegrationOptions {
119
+ content?: string;
120
+ routes?: Record<string, SiteManifestSource>;
121
+ }
122
+ declare function contentModulePlugin(file: string): ContentModulePlugin;
123
+ declare function suiteop(options?: SuiteOpIntegrationOptions): SuiteOpIntegration;
103
124
 
104
- export { type BuildSiteManifestInput, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, buildSiteManifest, devProxy, getSiteContext, suiteop };
125
+ export { type BuildSiteManifestInput, CONTENT_MODEL_PATH, type ContentModulePlugin, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, type SuiteOpIntegrationOptions, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
package/dist/astro.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"]];var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=z.string().regex(/^\/(?![/\\])/);var siteContextSchema=z.object({siteId:z.string().min(1),orgId:z.string().min(1),region:z.enum(["us","eu","apac"]),environment:z.enum(["live","test"]),publishableKey:z.string().regex(/^pk_(live|test)_/),country:z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:z.record(z.string().regex(/^x-suiteop-[a-z0-9-]+$/),z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function suiteop(){let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,updateConfig})=>{if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
3
+ var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"],[/^\/area\/\[[^\]/]+\]$/,"locations"]];var DECLARABLE_SOURCE=/^(listings|buildings|locations|collection:[a-z][a-z0-9_]{0,39})$/;var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=input.declaredRoutes?.[pattern]??SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=z.string().regex(/^\/(?![/\\])/);var siteContextSchema=z.object({siteId:z.string().min(1),orgId:z.string().min(1),region:z.enum(["us","eu","apac"]),environment:z.enum(["live","test"]),publishableKey:z.string().regex(/^pk_(live|test)_/),country:z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:z.record(z.string().regex(/^x-suiteop-[a-z0-9-]+$/),z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";var CONTENT_MODEL_PATH="_suiteop/content-model.json";var CONTENT_MODULE_ID="virtual:suiteop/content";var RESOLVED_CONTENT_MODULE_ID=`\0${CONTENT_MODULE_ID}`;function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function contentModulePlugin(file){return {name:"@suiteop/storefront:content",resolveId:id=>id===CONTENT_MODULE_ID?RESOLVED_CONTENT_MODULE_ID:void 0,load:id=>id===RESOLVED_CONTENT_MODULE_ID?`export { default } from ${JSON.stringify(file)}`:void 0}}function suiteop(options={}){for(const[pattern,source]of Object.entries(options.routes??{})){if(!DECLARABLE_SOURCE.test(source)){throw new Error(`suiteop(): route ${pattern} has source "${source}"; use listings, buildings, locations or collection:<key>.`)}}let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,config,updateConfig,injectRoute})=>{if(options.content){const file=decodeURIComponent(new URL(options.content,config.root).pathname);updateConfig({vite:{plugins:[contentModulePlugin(file)]}});injectRoute({pattern:`/${CONTENT_MODEL_PATH}`,entrypoint:"@suiteop/storefront/astro/content-model",prerender:true});}if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,declaredRoutes:options.routes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
4
4
  `);logger.info(`suiteop manifest: ${manifest.pages.length} pages, ${manifest.dynamicRoutes.length} dynamic routes`);}}}}async function builtHtmlPaths(dir){const{readdir}=await import('node:fs/promises');const{fileURLToPath}=await import('node:url');const entries=await readdir(fileURLToPath(dir),{recursive:true,withFileTypes:true});const paths=[];for(const entry of entries){if(!entry.isFile()||!entry.name.endsWith(".html"))continue;const rel=`${entry.parentPath}/${entry.name}`.slice(fileURLToPath(dir).length).replace(/\\/g,"/").replace(/^\/+/,"");if(/^(_astro|_suiteop)\//.test(rel))continue;paths.push(`/${rel.replace(/(^|\/)index\.html$/,"$1").replace(/\.html$/,"")}`);}return paths}async function readBuiltHtml(dir,pathname,format){const{readFile}=await import('node:fs/promises');const base=pathname.replace(/^\/+/,"").replace(/\/+$/,"");const candidates=base===""?["index.html"]:format==="file"?[`${base}.html`,`${base}/index.html`]:[`${base}/index.html`,`${base}.html`];for(const candidate of candidates){try{return await readFile(new URL(candidate,dir),"utf8")}catch(err){if(err.code!=="ENOENT")throw err}}return null}
5
5
 
6
- export { SITE_MANIFEST_PATH, buildSiteManifest, devProxy, getSiteContext, suiteop };
6
+ export { CONTENT_MODEL_PATH, SITE_MANIFEST_PATH, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
package/dist/checkout.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkRDREYP27_cjs = require('./chunk-RDREYP27.cjs');
4
- require('./chunk-XIAAWHAY.cjs');
3
+ var chunkVV2UNVQ2_cjs = require('./chunk-VV2UNVQ2.cjs');
4
+ require('./chunk-USOISC2R.cjs');
5
5
  require('./chunk-LVFRVNT3.cjs');
6
6
 
7
7
 
8
8
 
9
9
  Object.defineProperty(exports, "loadCheckout", {
10
10
  enumerable: true,
11
- get: function () { return chunkRDREYP27_cjs.loadCheckout; }
11
+ get: function () { return chunkVV2UNVQ2_cjs.loadCheckout; }
12
12
  });
package/dist/checkout.js CHANGED
@@ -1,3 +1,3 @@
1
- export { loadCheckout } from './chunk-YFTWGF3L.js';
2
- import './chunk-UACP7BZ7.js';
1
+ export { loadCheckout } from './chunk-HMLBMNGA.js';
2
+ import './chunk-ZI2PG7LS.js';
3
3
  import './chunk-6NFURJXI.js';
@@ -0,0 +1,3 @@
1
+ var CDN_IMAGE_ORIGIN="https://cdn.suiteop.com";var CDN_IMAGE_HOST="cdn.suiteop.com";var TRANSFORM_SEGMENT="/cdn-cgi/image/";var ORG_SCOPED_KEY=/^(booking-engine\/)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\//;var CDN_IMAGE_WIDTHS=[320,640,1080,1600];function cdnObjectKey(url){if(url.includes(TRANSFORM_SEGMENT))return void 0;let parsed;try{parsed=new URL(url);}catch{return void 0}if(parsed.protocol!=="https:"||parsed.host!==CDN_IMAGE_HOST)return void 0;if(parsed.username||parsed.password)return void 0;const key=parsed.pathname.slice(1);if(!ORG_SCOPED_KEY.test(key)||parsed.search||parsed.hash)return void 0;return key}function cdnImageUrl(url,width){if(!url)return void 0;const key=cdnObjectKey(url);if(key===void 0)return url;return `${CDN_IMAGE_ORIGIN}${TRANSFORM_SEGMENT}width=${width},format=auto,quality=80/${key}`}function cdnImageSrcSet(url,widths=CDN_IMAGE_WIDTHS){if(!url||cdnObjectKey(url)===void 0)return void 0;return widths.map(w=>`${cdnImageUrl(url,w)} ${w}w`).join(", ")}function cdnImageProps(url,width,sizes){const srcSet=cdnImageSrcSet(url);if(!srcSet)return {src:url??void 0};return {src:cdnImageUrl(url,width),srcSet,sizes}}
2
+
3
+ export { CDN_IMAGE_WIDTHS, cdnImageProps, cdnImageSrcSet, cdnImageUrl };
@@ -0,0 +1,9 @@
1
+ import { BE_BOOKING_REQUEST_STATUSES, STOREFRONT_KEY_ENVIRONMENTS, REGIONS } from './chunk-PYVUDQSQ.js';
2
+ import { requiredTrimmedEmailField, storefrontReviewSchema, storefrontContentItemSchema, storefrontLocationSchema, storefrontBuildingSchema, storefrontListingSummarySchema, storefrontContactResultSchema, storefrontContentModelSchema, storefrontListingSchema, storefrontSiteSchema } from './chunk-Y6INA5K7.js';
3
+ import { StorefrontConfigError, StorefrontError, storefrontQuoteSchema, StorefrontTimeoutError, StorefrontNetworkError, errorFromResponse } from './chunk-ZI2PG7LS.js';
4
+ import { openEnum, storefrontDateSchema, storefrontCalendarSchema, storefrontAvailabilitySchema } from './chunk-6NFURJXI.js';
5
+ import { z } from 'zod';
6
+
7
+ var STOREFRONT_CHANNELS=["ai_assistant","paid_search","paid_social","organic_search","organic_social","email","referral","direct"];var AI_ASSISTANT_HOSTS={"chatgpt.com":"chatgpt","chat.openai.com":"chatgpt","perplexity.ai":"perplexity","gemini.google.com":"gemini","copilot.microsoft.com":"copilot","claude.ai":"claude"};var AI_ASSISTANT_SOURCES={"chatgpt.com":"chatgpt",perplexity:"perplexity","perplexity.ai":"perplexity",gemini:"gemini",copilot:"copilot","claude.ai":"claude"};var PAID_CLICKS=["google","microsoft","meta"];var PAID_MEDIUMS=new Set(["cpc","ppc","paid","paidsearch","sem"]);var EMAIL_RE=/newsletter|mailchimp|klaviyo/;var SEARCH_SUFFIXES=["bing.com","duckduckgo.com","search.yahoo.com","baidu.com","ecosia.org","search.brave.com"];var SEARCH_ANY_TLD_RE=/^(?:www\.)?(?:google|yandex)\.[a-z]{2,3}(?:\.[a-z]{2})?$/;var SOCIAL_SUFFIXES=["facebook.com","instagram.com","t.co","x.com","twitter.com","linkedin.com","tiktok.com","youtube.com","reddit.com"];var SOCIAL_ANY_TLD_RE=/(?:^|\.)pinterest\.[a-z]{2,3}(?:\.[a-z]{2})?$/;var SOCIAL_SOURCES=new Set(["facebook","fb","instagram","ig","meta","twitter","x","linkedin","pinterest","tiktok","youtube","reddit"]);var clean=value=>{const v=value?.trim().toLowerCase();return v?v:null};var hostOf=value=>clean(value)?.replace(/\.$/,"")??null;var hostMatches=(host,suffix)=>host===suffix||host.endsWith(`.${suffix}`);var bareHost=host=>host.replace(/^www\./,"");function aiLabel(host,source){if(host){const suffix=Object.keys(AI_ASSISTANT_HOSTS).find(s=>hostMatches(host,s));if(suffix)return AI_ASSISTANT_HOSTS[suffix]}return source&&Object.hasOwn(AI_ASSISTANT_SOURCES,source)?AI_ASSISTANT_SOURCES[source]:null}var isSearchHost=host=>SEARCH_ANY_TLD_RE.test(host)||SEARCH_SUFFIXES.some(s=>hostMatches(host,s));var isSocialHost=host=>SOCIAL_ANY_TLD_RE.test(host)||SOCIAL_SUFFIXES.some(s=>hostMatches(host,s));function paidClickOf(t){const medium=clean(t.utmMedium);if(t.fbclid&&medium!==null&&PAID_MEDIUMS.has(medium))return "meta";if(t.gclid||t.gbraid||t.wbraid)return "google";if(t.msclkid)return "microsoft";if(t.fbclid)return "meta";return t.paidClick??null}function classifyChannel(t){const source=clean(t.utmSource);const medium=clean(t.utmMedium);const site=bareHost(hostOf(t.siteHost)??"");const ref=hostOf(t.referrerHost);const external=ref&&bareHost(ref)!==site?ref:null;const result=(channel,label=null)=>({channel,source:label??source??external,medium});const socialSource=source!==null&&(SOCIAL_SOURCES.has(source)||isSocialHost(source))||!!(external&&isSocialHost(external));const paidMedium=medium!==null&&PAID_MEDIUMS.has(medium);const searchClick=t.gclid||t.gbraid||t.wbraid||t.msclkid||t.paidClick==="google"||t.paidClick==="microsoft";const metaClick=t.fbclid||t.paidClick==="meta";if(searchClick||paidMedium){return result(socialSource||metaClick&&paidMedium?"paid_social":"paid_search")}if(medium==="email"||EMAIL_RE.test(source??"")||EMAIL_RE.test(medium??"")){return result("email")}const ai=aiLabel(external,source);if(ai)return result("ai_assistant",ai);if(external&&isSearchHost(external))return result("organic_search");if(socialSource||metaClick)return result("organic_social");const anyUtm=[source,medium,t.utmCampaign,t.utmTerm,t.utmContent].some(v=>clean(v??null));if(!external&&!anyUtm)return result("direct");return result("referral")}var consentStateSchema=z.object({analytics:z.boolean(),ads:z.boolean(),source:z.enum(["default","user"]),region:z.enum(["restricted","other"]),decidedAt:z.iso.datetime().max(40).nullable()});function isSafePrivacyUrl(value){if([...value].some(ch=>/\s/.test(ch)||ch.charCodeAt(0)<32))return false;if(value.startsWith("/"))return !value.startsWith("//")&&!value.startsWith("/\\");if(!value.startsWith("https://")||!URL.canParse(value))return false;const url=new URL(value);return url.hostname!==""&&url.username===""&&url.password===""}var STOREFRONT_CONSENT_MODES=["regional","strict"];var modeField=z.enum(STOREFRONT_CONSENT_MODES);var privacyPolicyUrlField=z.string().trim().max(2048,"validation.storefront.privacy_policy_url_invalid").refine(isSafePrivacyUrl,"validation.storefront.privacy_policy_url_invalid").nullable();z.object({mode:modeField.default("regional"),privacyPolicyUrl:privacyPolicyUrlField.default(null)});z.object({siteId:z.uuid(),consentSettings:z.object({mode:modeField.optional(),privacyPolicyUrl:privacyPolicyUrlField.optional()})});var ACTIVE_RESERVATION_STATUSES=["confirmed","reserved","awaiting_payment"];new Set(ACTIVE_RESERVATION_STATUSES);var SCHEDULABLE_RESERVATION_STATUSES=[...ACTIVE_RESERVATION_STATUSES,"blocked"];new Set(SCHEDULABLE_RESERVATION_STATUSES);var VOLATILE_SYNC_STATUSES=["pending","pending_deletion","failed_deletion"];new Set(VOLATILE_SYNC_STATUSES);var CURRENCIES=["usd","eur","aud","gbp","cad","chf","cny","jpy","nzd","nok","mxn","thb","bgn","aed","inr","rub","brl","zar","sgd","hkd","xcd","xof","clp","dkk","try","bnd","xcg","shp","fkp","idr","ron","czk"];var ZERO_DECIMAL_CURRENCY_CODES=new Set(["bif","clp","djf","gnf","jpy","kmf","krw","mga","pyg","rwf","vnd","vuv","xaf","xof","xpf"]);new Set(CURRENCIES.filter(code=>ZERO_DECIMAL_CURRENCY_CODES.has(code)));var LIVE_PAYMENT_INTENT_STATUS_LIST=["succeeded","processing","requires_capture","refunded","disputed","reversed"];new Set(LIVE_PAYMENT_INTENT_STATUS_LIST);var BE_SITE_EXPERIMENT_VARIANTS=["a","b"];var ACTIVE_MIGRATION_RUN_STATUSES=["running","writing","finalizing","rolling_back"];new Set(ACTIVE_MIGRATION_RUN_STATUSES.filter(status=>status!=="rolling_back"));var experimentVariantSchema=z.enum(BE_SITE_EXPERIMENT_VARIANTS);var experimentAssignmentSchema=z.object({id:z.uuid(),variant:experimentVariantSchema});z.object({siteId:z.uuid()});z.object({siteId:z.uuid(),deploymentBId:z.uuid("validation.storefront.experiment_variant_required"),splitBPercent:z.number("validation.storefront.experiment_split_range").int("validation.storefront.experiment_split_range").min(1,"validation.storefront.experiment_split_range").max(99,"validation.storefront.experiment_split_range")});z.object({id:z.uuid(),winner:experimentVariantSchema.nullable()});z.object({id:z.uuid()});var clickId=z.string().min(1).max(512).regex(/^[\w.\-~]+$/);var storable=value=>!value.includes("\0")&&!/\p{Surrogate}/u.test(value);var utm=z.string().max(150).refine(storable).nullable();var attributionTouchSchema=z.object({ts:z.iso.datetime().max(40),landingPath:z.string().max(512).regex(/^\/[^?#]*$/).refine(storable),referrerHost:z.string().max(253).regex(/^[a-z0-9.-]+$/).nullable(),utmSource:utm,utmMedium:utm,utmCampaign:utm,utmTerm:utm,utmContent:utm,gclid:clickId.nullable(),gbraid:clickId.nullable(),wbraid:clickId.nullable(),fbclid:clickId.nullable(),msclkid:clickId.nullable(),paidClick:z.enum(PAID_CLICKS).nullable().default(null)});var attributionIdsSchema=z.object({gaClientId:z.string().max(64).regex(/^\d+\.\d+$/).nullable(),gaSessionId:z.string().max(32).regex(/^\d+$/).nullable(),fbp:z.string().max(128).regex(/^fb\.\d\.\d+\.\d+$/).nullable(),fbc:z.string().max(600).regex(/^fb\.\d\.\d+\.[\w-]+$/).nullable()});var storefrontAttributionInputSchema=z.object({first:attributionTouchSchema.nullable(),last:attributionTouchSchema.nullable(),ids:attributionIdsSchema,consent:consentStateSchema,experiment:experimentAssignmentSchema.optional()});var STORED_ATTRIBUTION_CHANNELS=[...STOREFRONT_CHANNELS,"ai_agent_mcp"];storefrontAttributionInputSchema.extend({firstChannel:z.enum(STORED_ATTRIBUTION_CHANNELS).nullable(),lastChannel:z.enum(STORED_ATTRIBUTION_CHANNELS),userAgent:z.string().max(512).nullable(),capturedAt:z.iso.datetime()});function withoutClickIds(touch){return {...touch,gclid:null,gbraid:null,wbraid:null,fbclid:null,msclkid:null,paidClick:paidClickOf(touch)}}var MAX_CHECKOUT_CUSTOM_FIELDS=10;var MAX_CHECKOUT_UPSELLS=12;var MAX_CHECKOUT_UPSELL_QUANTITY=50;var MAX_CHECKOUT_FIELD_LENGTH=2e3;var MAX_FIELD_KEY_LENGTH=40;var BOOKING_UPSELL_CHARGE_TYPES=["fixed_price","price_per_quantity","price_per_night","price_per_guest_per_night","free"];var localizedTextSchema=z.object({default:z.string().trim().min(1,"validation.required").max(200,"validation.max_length"),translations:z.record(z.string().min(2).max(10),z.string().max(200,"validation.max_length")).optional()});var fieldKeySchema=z.string().regex(new RegExp(`^[a-z][a-z0-9_]{0,${MAX_FIELD_KEY_LENGTH-1}}$`),"validation.storefront.extensions_invalid_key").refine(key=>!(key in Object.prototype),"validation.storefront.extensions_invalid_key");var boundSchema=z.number({error:"validation.number_required"}).int("validation.integer_required");var fieldBase={key:fieldKeySchema,label:localizedTextSchema,required:z.boolean()};var customFieldDefSchema=z.discriminatedUnion("type",[z.object({type:z.enum(["text","textarea"]),...fieldBase,maxLength:z.number({error:"validation.number_required"}).int("validation.integer_required").min(1,"validation.too_small").max(MAX_CHECKOUT_FIELD_LENGTH,"validation.too_large").default(500)}),z.object({type:z.literal("select"),...fieldBase,options:z.array(z.object({value:z.string().max(64).refine(value=>value.trim()!=="","validation.required"),label:localizedTextSchema})).min(1,"validation.storefront.extensions_select_needs_options").max(30,"validation.too_large").refine(options=>new Set(options.map(o=>o.value)).size===options.length,{message:"validation.storefront.extensions_duplicate_option"})}),z.object({type:z.literal("checkbox"),...fieldBase}),z.object({type:z.literal("number"),...fieldBase,min:boundSchema.optional(),max:boundSchema.optional()}),z.object({type:z.literal("date"),...fieldBase})]);z.object({customFields:z.array(customFieldDefSchema).max(MAX_CHECKOUT_CUSTOM_FIELDS,"validation.too_large").superRefine((fields,ctx)=>{const seen=new Set;fields.forEach((field,index)=>{if(seen.has(field.key)){ctx.addIssue({code:"custom",path:[index,"key"],message:"validation.storefront.extensions_duplicate_key"});}seen.add(field.key);if(field.type==="number"&&field.min!=null&&field.max!=null&&field.min>field.max){ctx.addIssue({code:"custom",path:[index,"max"],message:"validation.storefront.extensions_number_range_invalid"});}});}).default([]),upsells:z.object({portalUpsellIds:z.array(z.uuid()).max(MAX_CHECKOUT_UPSELLS,"validation.too_large").refine(ids=>new Set(ids).size===ids.length,{message:"validation.storefront.extensions_duplicate_upsell"}).default([])}).default({portalUpsellIds:[]})});var checkoutExtrasInputSchema=z.object({customFields:z.record(z.string().max(MAX_FIELD_KEY_LENGTH),z.union([z.string().max(MAX_CHECKOUT_FIELD_LENGTH),z.number(),z.boolean()])).refine(values=>Object.keys(values).length<=MAX_CHECKOUT_CUSTOM_FIELDS,{message:"validation.too_large"}).default({}),upsells:z.array(z.object({portalUpsellId:z.uuid(),quantity:z.number().int().min(1).max(MAX_CHECKOUT_UPSELL_QUANTITY)})).max(MAX_CHECKOUT_UPSELLS).default([])});var upsellOfferSchema=z.object({portalUpsellId:z.uuid(),title:z.string(),description:z.string().nullable(),imageUrl:z.string().nullable(),chargeType:z.enum(BOOKING_UPSELL_CHARGE_TYPES),unitMinor:z.number().int(),minQuantity:z.number().int(),maxQuantity:z.number().int(),quantifierLabel:z.string().nullable(),currency:z.string().length(3)});z.object({locale:z.string().max(35).optional().catch(void 0)});z.object({customFields:z.array(customFieldDefSchema),upsellOffers:z.array(upsellOfferSchema)});var checkoutSessionExtrasSchema=z.object({lines:z.array(z.object({portalUpsellId:z.uuid().nullable(),title:z.string(),quantity:z.number().int(),totalMinor:z.number().int()})),extrasTotalMinor:z.number().int(),heldTotalMinor:z.number().int()});var amountMinor=z.number().int().positive();var currency=z.string().length(3);var chargedByHost=z.literal("host");var checkoutMountDescriptorKnownSchema=z.discriminatedUnion("provider",[z.object({provider:z.literal("stripe"),publishableKey:z.string().min(1),stripeAccountId:z.string().nullable(),clientSecret:z.string().min(1),amountMinor,currency}),z.object({provider:z.literal("juspay"),publishableKey:z.string().min(1),providerBaseUrl:z.url(),clientSecret:z.string().min(1),profileId:z.string().min(1),amountMinor,currency}),z.object({provider:z.literal("guesty_pay"),guestyPartnerId:z.string().min(1),paymentProviderId:z.string().min(1),externalListingId:z.string().min(1),pmsQuoteId:z.string().nullable(),amountMinor,currency,chargedBy:chargedByHost.optional()}),z.object({provider:z.literal("pms_stripe"),publishableKey:z.string().min(1),stripeAccountId:z.string().min(1),clientSecret:z.string().min(1),amountMinor,currency,chargedBy:chargedByHost})]);var KNOWN_MOUNT_PROVIDERS=["stripe","juspay","guesty_pay","pms_stripe"];var checkoutMountDescriptorSchema=z.union([checkoutMountDescriptorKnownSchema,z.object({provider:z.string().refine(p=>!KNOWN_MOUNT_PROVIDERS.includes(p))}).loose()]);z.enum(BE_BOOKING_REQUEST_STATUSES);var bookingRequestStatusResponseSchema=openEnum(BE_BOOKING_REQUEST_STATUSES);var checkoutSessionQuoteSchema=z.object({listingId:z.uuid(),checkIn:z.iso.date(),checkOut:z.iso.date(),guests:z.number().int().positive(),ratePlanId:z.string(),totalMinor:z.number().int(),currency:z.string().length(3)});z.object({quoteRef:z.string().min(1).max(4096),ratePlanId:z.string().min(1).max(200).optional(),attribution:z.custom().optional(),extras:checkoutExtrasInputSchema.optional(),hostSessionId:z.uuid().optional()});var checkoutSessionSchema=z.object({id:z.uuid(),status:bookingRequestStatusResponseSchema,quote:checkoutSessionQuoteSchema,mount:checkoutMountDescriptorSchema.nullable(),chargedBy:chargedByHost.optional(),guestSaved:z.literal(true).optional(),extras:checkoutSessionExtrasSchema.optional(),extrasSessionId:z.uuid().optional(),expiresAt:z.iso.datetime()});var storefrontGuestInputSchema=z.object({firstName:z.string().trim().min(1,"validation.first_name_required").max(100),lastName:z.string().trim().min(1,"validation.last_name_required").max(100),email:requiredTrimmedEmailField,phone:z.string().trim().min(1,"validation.phone_required").max(40)});var storefrontConsentsSchema=z.object({emailMarketing:z.boolean().default(false),smsMarketing:z.boolean().default(false)}).default({emailMarketing:false,smsMarketing:false});var confirmCheckoutSessionInputSchema=z.object({quoteRef:z.string().min(1).max(4096),guest:storefrontGuestInputSchema,consents:storefrontConsentsSchema,paymentMethodId:z.string().min(1).max(255).optional(),language:z.string().max(35).optional().catch(void 0)});confirmCheckoutSessionInputSchema.pick({quoteRef:true,guest:true,consents:true,language:true});z.object({saved:z.boolean()});var bookingResultSchema=z.object({bookingRequestId:z.uuid(),confirmationToken:z.string(),status:bookingRequestStatusResponseSchema,confirmationCode:z.string().nullable()});var storefrontBookingSchema=z.object({id:z.uuid(),status:bookingRequestStatusResponseSchema,propertyId:z.uuid().nullable(),checkIn:z.iso.date().nullable(),checkOut:z.iso.date().nullable(),guestCount:z.number().int().nullable(),guestFirstName:z.string().nullable(),totalMinor:z.number().int().nullable(),currency:z.string().nullable(),confirmationCode:z.string().nullable(),lines:z.array(z.object({title:z.string(),quantity:z.number().int(),totalMinor:z.number().int()})).optional()});z.enum(STOREFRONT_KEY_ENVIRONMENTS);var STOREFRONT_KEY_RE=new RegExp(`^pk_(${STOREFRONT_KEY_ENVIRONMENTS.join("|")})_(${REGIONS.join("|")})_[0-9A-Za-z]{24}$`);function parseStorefrontKey(token){const match=STOREFRONT_KEY_RE.exec(token);if(!match)return null;return {environment:match[1],region:match[2]}}var ORIGIN_HOST_RE=/^(?:[a-z0-9-]+\.)*[a-z0-9-]+$|^\[[0-9a-f:.]+\]$/;function parseUrl(value){try{return new URL(value)}catch{return null}}z.string().refine(value=>{const url=parseUrl(value);if(!url)return false;return (url.protocol==="https:"||url.protocol==="http:")&&url.origin===value&&ORIGIN_HOST_RE.test(url.hostname)},"validation.storefront.origin_invalid");var CLIENT_CONVERSION_EVENT_NAMES=["begin_checkout","generate_lead","sign_up"];var STOREFRONT_EVENT_PATH_MAX=512;var base={eventId:z.uuid(),ts:z.number().int(),path:z.string().max(STOREFRONT_EVENT_PATH_MAX),referrerHost:z.string().max(253).nullable()};var currency2=z.string().length(3).regex(/^[a-z]{3}$/);var storefrontClientEventSchema=z.discriminatedUnion("name",[z.object({...base,name:z.literal("page_view")}),z.object({...base,name:z.literal("view_search_results"),checkIn:storefrontDateSchema.optional(),checkOut:storefrontDateSchema.optional(),guests:z.number().int().min(1).max(50).optional(),locationId:z.uuid().optional()}),z.object({...base,name:z.literal("view_item_list"),listName:z.string().max(64),itemCount:z.number().int().min(0).max(500)}),z.object({...base,name:z.literal("view_item"),listingId:z.uuid()}),z.object({...base,name:z.literal("view_quote"),listingId:z.uuid(),valueMinor:z.number().int().nonnegative(),currency:currency2,nights:z.number().int().min(1).max(365)}),z.object({...base,name:z.literal("begin_checkout"),checkoutSessionId:z.uuid()}),z.object({...base,name:z.literal("add_payment_info"),checkoutSessionId:z.uuid()}),z.object({...base,name:z.literal("generate_lead"),formId:z.string().max(64).optional()}),z.object({...base,name:z.literal("sign_up"),method:z.literal("newsletter")})]);var STOREFRONT_EVENT_BATCH_MAX=20;z.object({events:z.array(storefrontClientEventSchema).min(1).max(STOREFRONT_EVENT_BATCH_MAX)});z.object({events:z.array(z.object({eventId:z.uuid(),name:z.enum(CLIENT_CONVERSION_EVENT_NAMES),occurredAt:z.iso.datetime(),pageUrl:z.url({protocol:/^https?$/}).max(2048),checkoutSessionId:z.uuid().nullable(),userAgent:z.string().max(512).nullable(),attribution:storefrontAttributionInputSchema.nullable()})).min(1).max(STOREFRONT_EVENT_BATCH_MAX)});var EXPERIMENT_COOKIE="__so_exp";var COOKIE_RE=/(?:^|;\s*)__so_exp=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.(a|b)(?:\s*;|\s*$)/;function readExperimentCookie(cookies){const match=COOKIE_RE.exec(cookies??"");return match?{id:match[1],variant:match[2]}:null}var ATTRIBUTION_COOKIE="so_attr";var ATTRIBUTION_COOKIE_MAX_AGE_SECONDS=90*24*60*60;var COOKIE_SCOPE="Path=/; SameSite=Lax; Secure";var ATTRIBUTION_COOKIE_ATTRIBUTES=`Max-Age=${ATTRIBUTION_COOKIE_MAX_AGE_SECONDS}; ${COOKIE_SCOPE}`;var ATTRIBUTION_COOKIE_MAX_BYTES=3584;var attributionCookieSchema=z.object({first:attributionTouchSchema.nullable(),last:attributionTouchSchema.nullable()});function toBase64Url(text){let binary="";for(const byte of new TextEncoder().encode(text))binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function fromBase64Url(value){const binary=atob(value.replace(/-/g,"+").replace(/_/g,"/"));return new TextDecoder().decode(Uint8Array.from(binary,ch=>ch.charCodeAt(0)))}function encodeAttributionCookie(value){const encoded=toBase64Url(JSON.stringify(value));return encoded.length>ATTRIBUTION_COOKIE_MAX_BYTES?null:encoded}function serializeAttributionCookie(value){const encoded=encodeAttributionCookie(value);return encoded===null?null:`${ATTRIBUTION_COOKIE}=${encoded}; ${ATTRIBUTION_COOKIE_ATTRIBUTES}`}function decodeAttributionCookie(value){if(!value)return null;return attributionCookieSchema.parse(JSON.parse(fromBase64Url(value)))}function expiredAttributionCookies(cookies,consent){const revoked=[...consent.analytics?[]:[ATTRIBUTION_COOKIE,EXPERIMENT_COOKIE],...consent.ads?[]:["_fbc","_fbp"]];return revoked.filter(name=>readCookie(cookies,name)!==null).map(name=>`${name}=; Max-Age=0; ${COOKIE_SCOPE}`)}function strippedAttributionCookie(held){const clicked=t=>t!==null&&[t.gclid,t.gbraid,t.wbraid,t.fbclid,t.msclkid].some(id=>id!==null);if(!held||!(clicked(held.first)||clicked(held.last)))return null;return serializeAttributionCookie({first:held.first&&withoutClickIds(held.first),last:held.last&&withoutClickIds(held.last)})??`${ATTRIBUTION_COOKIE}=; Max-Age=0; ${COOKIE_SCOPE}`}function readCookie(header,name){if(!header)return null;for(const part of header.split(";")){const eq=part.indexOf("=");if(eq!==-1&&part.slice(0,eq).trim()===name)return part.slice(eq+1).trim()}return null}var MalformedCookieError=class extends Error{constructor(name){super(`${name} is malformed`);this.name="MalformedAttributionError";}};var NO_IDS={gaClientId:null,gaSessionId:null,fbp:null,fbc:null};function matchCookie(cookies,name,re,report){const value=name?readCookie(cookies,name):null;if(!value)return null;const match=re.exec(value);if(!match){report(new MalformedCookieError(name),name);return null}return match.slice(1).filter(Boolean).join(".")}function browserIdsFromCookies(cookies,consent,report){const sessionCookie=cookies.split(";").map(part=>part.split("=")[0].trim()).find(name=>name.startsWith("_ga_"))??null;const ga=consent.analytics;const ads=consent.ads;const ids={gaClientId:ga?matchCookie(cookies,"_ga",/^GA\d\.\d+\.(\d+)\.(\d+)$/,report):null,gaSessionId:ga?matchCookie(cookies,sessionCookie,/^(?:GS1\.\d+\.(\d+)\.|GS2\.\d+\.s(\d+)(?:\$|$))/,report):null,fbp:ads?matchCookie(cookies,"_fbp",/^(fb\.\d\.\d+\.\d+)$/,report):null,fbc:ads?matchCookie(cookies,"_fbc",/^(fb\.\d\.\d+\.[\w-]+)$/,report):null};const parsed=attributionIdsSchema.safeParse(ids);if(parsed.success)return parsed.data;report(parsed.error,"ids");return NO_IDS}var isDirect=touch=>classifyChannel({...touch,siteHost:globalThis.location?.hostname??""}).channel==="direct";var TOUCH_ELEMENT_ID="__suiteop_touch";var CONSENT_EVENT="suiteop:consent";var DENIED={analytics:false,ads:false,source:"default",region:"restricted",decidedAt:null};var defaultReport=(err,where)=>{console.warn(`[suiteop] ignored malformed attribution input (${where})`,err);};function pageConsent(){const read=globalThis.__suiteop?.consent?.get;const parsed=consentStateSchema.safeParse(typeof read==="function"?read():null);return parsed.success?parsed.data:DENIED}function pageTouch(report){const text=document.getElementById(TOUCH_ELEMENT_ID)?.textContent;if(!text)return null;try{return attributionTouchSchema.parse(JSON.parse(text))}catch(err){report(err,TOUCH_ELEMENT_ID);return null}}function storedCookie(cookies,report){try{return decodeAttributionCookie(readCookie(cookies,ATTRIBUTION_COOKIE))}catch(err){report(err,ATTRIBUTION_COOKIE);return null}}function getAttribution(opts={}){if(typeof document==="undefined")return null;const report=opts.report??defaultReport;const cookies=document.cookie;const consent=pageConsent();const stored=consent.analytics?storedCookie(cookies,report):null;const touch=pageTouch(report);const pageIsLast=touch!==null&&!(stored?.last&&(isDirect(touch)||Date.parse(stored.last.ts)>Date.parse(touch.ts)));const experiment=consent.analytics?readExperimentCookie(cookies):null;return {first:stored?.first??touch,last:pageIsLast?touch:stored?.last??null,ids:browserIdsFromCookies(cookies,consent,report),consent,...experiment?{experiment}:{}}}function watchAttributionConsent(opts={}){if(typeof window==="undefined")return ()=>{};const report=opts.report??defaultReport;const persist=consent=>{if(readCookie(document.cookie,ATTRIBUTION_COOKIE))return;const touch=pageTouch(report);if(!touch)return;const kept=consent.ads?touch:withoutClickIds(touch);const cookie=serializeAttributionCookie({first:kept,last:kept});if(cookie)document.cookie=cookie;};const withdraw=consent=>{for(const cookie of expiredAttributionCookies(document.cookie,consent)){document.cookie=cookie;}if(consent.analytics&&!consent.ads){const stripped=strippedAttributionCookie(storedCookie(document.cookie,report));if(stripped)document.cookie=stripped;}};const initial=pageConsent();let analytics=initial.analytics;if(initial.source==="user")withdraw(initial);if(analytics)persist(initial);const onConsent=event=>{const next=consentStateSchema.safeParse(event.detail);if(!next.success)return;const granted=!analytics&&next.data.analytics;analytics=next.data.analytics;withdraw(next.data);if(granted)persist(next.data);};window.addEventListener(CONSENT_EVENT,onConsent);return ()=>window.removeEventListener(CONSENT_EVENT,onConsent)}var EVENTS_ENDPOINT="/_suiteop/events";var FLUSH_INTERVAL_MS=2e3;var FLUSH_AT_EVENTS=10;var defaultReport2=err=>{console.warn("[suiteop] storefront events could not be sent",err);};function hostOf2(url){try{return new URL(url).hostname||null}catch{return null}}var META_EVENTS={begin_checkout:"InitiateCheckout",generate_lead:"Lead",sign_up:"CompleteRegistration",view_item:"ViewContent",view_search_results:"Search",add_payment_info:"AddPaymentInfo"};var GTAG_SKIP=new Set(["page_view","purchase"]);function mirrorToTags(event){const w=window;if(typeof w.gtag!=="function"&&typeof w.fbq!=="function")return;const consent=pageConsent();const{name,eventId,ts:_ts,path:_path,referrerHost:_ref,...params}=event;if(consent.analytics&&typeof w.gtag==="function"&&!GTAG_SKIP.has(name)){w.gtag("event",name,params);}const metaName=META_EVENTS[name];if(consent.ads&&typeof w.fbq==="function"&&metaName){w.fbq("track",metaName,{},{eventID:eventId});}}function trackPurchasePixel(booking){if(typeof window==="undefined")return;const fbq=window.fbq;if(typeof fbq!=="function"||!pageConsent().ads)return;const amount=typeof booking.totalMinor==="number"&&booking.currency?{value:booking.totalMinor/100,currency:booking.currency.toUpperCase()}:{};fbq("track","Purchase",amount,{eventID:booking.bookingRequestId});}var NOOP={track(){},pageView(){},flush(){},dispose(){}};function createAnalytics(opts){if(typeof window==="undefined")return NOOP;const report=opts.report??defaultReport2;const url=`${EVENTS_ENDPOINT}?key=${encodeURIComponent(opts.publishableKey)}`;let queue=[];let timer=null;let landing=true;let lastViewedHref=null;let disabled=false;let reported=false;const fail=err=>{if(reported)return;reported=true;report(err);};const send=(events,beacon)=>{const body=JSON.stringify({events});if(beacon&&typeof navigator.sendBeacon==="function"&&navigator.sendBeacon(url,body)){return}fetch(url,{method:"POST",body,keepalive:true,credentials:"same-origin",headers:{"content-type":"application/json"}}).then(response=>{if(response.status===404)disabled=true;else if(!response.ok)fail(new Error(`storefront events answered ${response.status}`));}).catch(fail);};const flush=({beacon=false}={})=>{if(timer)clearTimeout(timer);timer=null;if(disabled||queue.length===0)return;const pending=queue;queue=[];for(let i=0;i<pending.length;i+=STOREFRONT_EVENT_BATCH_MAX){send(pending.slice(i,i+STOREFRONT_EVENT_BATCH_MAX),beacon);}};const enqueue=(event,referrerHost)=>{const full={...event,eventId:crypto.randomUUID(),ts:Date.now(),path:location.pathname.slice(0,STOREFRONT_EVENT_PATH_MAX),referrerHost};try{mirrorToTags(full);}catch(err){fail(err);}if(disabled)return;queue.push(full);if(queue.length>=FLUSH_AT_EVENTS)flush();else timer??=setTimeout(()=>flush(),FLUSH_INTERVAL_MS);};const pageView=()=>{if(location.href===lastViewedHref)return;lastViewedHref=location.href;const referrerHost=landing?hostOf2(document.referrer):location.hostname;landing=false;enqueue({name:"page_view"},referrerHost);};const onPageHide=()=>flush({beacon:true});const onPageShow=event=>{if(!event.persisted)return;lastViewedHref=null;pageView();};window.addEventListener("pagehide",onPageHide);window.addEventListener("pageshow",onPageShow);return {track:event=>enqueue(event,location.hostname),pageView,flush,dispose:()=>{flush({beacon:true});window.removeEventListener("pagehide",onPageHide);window.removeEventListener("pageshow",onPageShow);}}}var shared=new Map;function analyticsFor(publishableKey){let instance=shared.get(publishableKey);if(!instance){instance=createAnalytics({publishableKey});if(instance!==NOOP)shared.set(publishableKey,instance);}return instance}function parsePublishableKey(key){if(typeof key==="string"&&key.trim().startsWith("sk_")){throw new StorefrontConfigError("A SuiteOp secret key (sk_\u2026) was passed as the publishable key. Secret keys must never be used in a browser or shipped in site code \u2014 revoke it now, then use a publishable key (pk_live_\u2026 / pk_test_\u2026) from the site settings.")}const parsed=typeof key==="string"?parseStorefrontKey(key.trim()):null;if(!parsed){throw new StorefrontConfigError("Invalid SuiteOp publishable key: expected pk_{live|test}_{us|eu|apac}_ followed by 24 letters or digits.")}return parsed}function defaultApiOrigin({environment,region}){return environment==="live"?`https://api-${region}.suiteop.com`:`https://api-${region}-staging.suiteop.com`}var SDK_VERSION="0.3.0";var STOREFRONT_API_VERSION="2026-09-27";var GET_RETRY_JITTER_MS=300;function buildUrl(base2,spec){const params=new URLSearchParams;for(const[key,value]of Object.entries(spec.query??{})){if(value!==void 0&&value!==null)params.set(key,String(value));}const search=params.toString();return `${base2}${spec.path}${search?`?${search}`:""}`}function sleep(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted)return reject(signal.reason);const onAbort=()=>{clearTimeout(timer);reject(signal?.reason);};const timer=setTimeout(()=>{signal?.removeEventListener("abort",onAbort);resolve();},ms);signal?.addEventListener("abort",onAbort,{once:true});})}function parseJson(text){if(!text)return void 0;try{return JSON.parse(text)}catch{return void 0}}function isRetryable(err){return err instanceof StorefrontNetworkError||err instanceof StorefrontError&&err.status>=500}function createTransport(opts){async function attempt(spec){const controller=new AbortController;let timedOut=false;const timer=setTimeout(()=>{timedOut=true;controller.abort(new DOMException("The request timed out","TimeoutError"));},spec.timeoutMs);const onCallerAbort=()=>controller.abort(spec.signal?.reason);if(spec.signal?.aborted)onCallerAbort();else spec.signal?.addEventListener("abort",onCallerAbort,{once:true});try{let response;let text;try{response=await opts.fetch(buildUrl(opts.baseUrl(),spec),{method:spec.method,headers:{...opts.headers,...spec.body===void 0?{}:{"Content-Type":"application/json"},...spec.headers},body:spec.body===void 0?void 0:JSON.stringify(spec.body),signal:controller.signal});text=await response.text();}catch(err){if(spec.signal?.aborted)throw spec.signal.reason;if(timedOut)throw new StorefrontTimeoutError(spec.timeoutMs);throw new StorefrontNetworkError(err)}const body=parseJson(text);if(!response.ok)throw errorFromResponse(response,body);const envelope=body;const meta=envelope?.meta??{};const requestId=typeof meta["requestId"]==="string"?meta["requestId"]:response.headers.get("x-request-id");if(!envelope||typeof envelope!=="object"||!("data"in envelope)){throw new StorefrontError({status:response.status,code:"invalid_response",message:"The Storefront API answered without a data envelope",requestId})}return {status:response.status,requestId,data:envelope.data,meta}}finally{clearTimeout(timer);spec.signal?.removeEventListener("abort",onCallerAbort);}}return {async send(spec){try{return await attempt(spec)}catch(err){if(spec.method!=="GET"||!isRetryable(err))throw err;await sleep(Math.random()*GET_RETRY_JITTER_MS,spec.signal);return attempt(spec)}}}}var STOREFRONT_API_PATH="/api/storefront/v1";var READ_TIMEOUT_MS=1e4;var WRITE_TIMEOUT_MS=2e4;function resolveBaseUrl(baseUrl,origin){if(baseUrl===void 0)return `${origin}${STOREFRONT_API_PATH}`;if(baseUrl==="same-origin"){const pageOrigin=globalThis.location?.origin;if(!pageOrigin||pageOrigin==="null"){throw new StorefrontConfigError("baseUrl 'same-origin' needs a page origin; pass an explicit baseUrl outside a browser.")}return `${pageOrigin}${STOREFRONT_API_PATH}`}let url;try{url=new URL(baseUrl);}catch{throw new StorefrontConfigError(`baseUrl must be an absolute URL or 'same-origin'.`)}const path=url.pathname.replace(/\/+$/,"");return `${url.origin}${path.endsWith(STOREFRONT_API_PATH)?path:`${path}${STOREFRONT_API_PATH}`}`}function uuidV4(){const c=globalThis.crypto;if(typeof c.randomUUID==="function")return c.randomUUID();const b=c.getRandomValues(new Uint8Array(16));b[6]=b[6]&15|64;b[8]=b[8]&63|128;const hex=Array.from(b,x=>x.toString(16).padStart(2,"0")).join("");return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`}function validate(schema,value,res){const parsed=schema.safeParse(value);if(parsed.success)return parsed.data;throw new StorefrontError({status:res.status,code:"invalid_response",message:"The Storefront API answered a shape this SDK version does not understand",requestId:res.requestId,details:{issues:parsed.error.issues.map(i=>({path:i.path,code:i.code}))}})}var paginationSchema=z.object({total:z.number().int(),limit:z.number().int(),offset:z.number().int()});var seg=value=>encodeURIComponent(value);var SLUG_OR_ID=/^[a-z0-9-]{1,120}$/;var slotOf=(quoteRef,ratePlanId)=>`${quoteRef}\0${ratePlanId??""}`;var RELEASED_SESSION_STATUSES=new Set(["expired","cancelled"]);var KEY_BOUND_ELSEWHERE="idempotency_key_reused";function endsSession(err){return err.code==="session_expired"||err.details["requiresNewSession"]===true}var attributionWatched=false;function createStorefrontClient(options){const key=parsePublishableKey(options.publishableKey);const baseUrl=resolveBaseUrl(options.baseUrl,defaultApiOrigin(key));const transport=createTransport({baseUrl:()=>baseUrl,fetch:(input,init)=>(options.fetch??globalThis.fetch)(input,init),headers:{Authorization:`Bearer ${options.publishableKey.trim()}`,Accept:"application/json","X-SuiteOp-SDK":`storefront-js/${SDK_VERSION}`,"X-SuiteOp-Api-Version":STOREFRONT_API_VERSION,...options.locale?{"Accept-Language":options.locale}:{}}});const keySlots=new Map;const sessionSlots=new Map;const release=(slot,key2)=>{if(keySlots.get(slot)===key2)keySlots.delete(slot);};const releaseSession=id=>{const entry=sessionSlots.get(id);if(!entry)return;sessionSlots.delete(id);release(...entry);};if(!attributionWatched&&typeof window!=="undefined"){attributionWatched=true;watchAttributionConsent();}const readTimeoutMs=options.timeoutMs??READ_TIMEOUT_MS;const writeTimeoutMs=options.timeoutMs??WRITE_TIMEOUT_MS;async function get(schema,path,opts,query){const res=await transport.send({method:"GET",path,query,timeoutMs:readTimeoutMs,signal:opts?.signal});return validate(schema,res.data,res)}async function post(schema,path,body,opts,headers){const res=await transport.send({method:"POST",path,body,headers,timeoutMs:writeTimeoutMs,signal:opts?.signal});return validate(schema,res.data,res)}return {publishableKey:options.publishableKey.trim(),baseUrl,site:{get:opts=>get(storefrontSiteSchema,"/site",opts)},listings:{async list(query,opts){const res=await transport.send({method:"GET",path:"/listings",query:{location:query?.location,building:query?.building,limit:query?.limit,offset:query?.offset},timeoutMs:readTimeoutMs,signal:opts?.signal});const items=validate(z.array(storefrontListingSummarySchema),res.data,res);const pagination=validate(paginationSchema,res.meta["pagination"],res);return {items,...pagination}},get:(id,opts)=>get(storefrontListingSchema,`/listings/${seg(id)}`,opts)},buildings:{list:opts=>get(z.array(storefrontBuildingSchema),"/buildings",opts),get:(id,opts)=>get(storefrontBuildingSchema,`/buildings/${seg(id)}`,opts)},locations:{list:opts=>get(z.array(storefrontLocationSchema),"/locations",opts)},contentModel:{get:opts=>get(storefrontContentModelSchema,"/content-model",opts)},collections:{async list(key2,query,opts){const res=await transport.send({method:"GET",path:`/collections/${seg(key2)}/items`,query:{limit:query?.limit,offset:query?.offset},timeoutMs:readTimeoutMs,signal:opts?.signal});const items=validate(z.array(storefrontContentItemSchema),res.data,res);const pagination=validate(paginationSchema,res.meta["pagination"],res);return {items,...pagination}},async get(key2,slugOrId,opts){if(!SLUG_OR_ID.test(slugOrId))return null;try{return await get(storefrontContentItemSchema,`/collections/${seg(key2)}/items/${seg(slugOrId)}`,opts)}catch(err){if(err instanceof StorefrontError&&err.status===404)return null;throw err}}},sections:{get:(key2,opts)=>get(storefrontContentItemSchema,`/sections/${seg(key2)}`,opts)},amenities:{list:opts=>get(z.array(z.string()),"/amenities",opts)},reviews:{list:(query,opts)=>get(z.array(storefrontReviewSchema),"/reviews",opts,{listingId:query?.listingId,limit:query?.limit,offset:query?.offset})},availability:{get:(listingId,stay,opts)=>get(storefrontAvailabilitySchema,`/listings/${seg(listingId)}/availability`,opts,{checkIn:stay.checkIn,checkOut:stay.checkOut,guests:stay.guests})},calendar:{get:(listingId,range,opts)=>get(storefrontCalendarSchema,`/listings/${seg(listingId)}/calendar`,opts,{from:range.from,to:range.to})},quotes:{create:(input,opts)=>post(storefrontQuoteSchema,"/quotes",input,opts)},checkoutSessions:{async create(input,opts){const slot=slotOf(input.quoteRef,input.ratePlanId);const idempotencyKey=opts?.idempotencyKey??keySlots.get(slot)??uuidV4();keySlots.set(slot,idempotencyKey);const attribution=input.attribution===void 0?getAttribution():input.attribution;const body=attribution?{...input,attribution}:input;try{const created=await post(checkoutSessionSchema,"/checkout-sessions",body,opts,{"Idempotency-Key":idempotencyKey});if(RELEASED_SESSION_STATUSES.has(created.status))release(slot,idempotencyKey);else sessionSlots.set(created.id,[slot,idempotencyKey]);return {...created,idempotencyKey}}catch(err){if(err instanceof StorefrontError){err.details["idempotencyKey"]=idempotencyKey;if(err.code===KEY_BOUND_ELSEWHERE)release(slot,idempotencyKey);}throw err}},get:(id,opts)=>get(checkoutSessionSchema,`/checkout-sessions/${seg(id)}`,opts),async confirm(id,input,opts){try{const result=await post(bookingResultSchema,`/checkout-sessions/${seg(id)}/confirm`,input,opts);return result}catch(err){if(err instanceof StorefrontError&&endsSession(err)){releaseSession(id);}throw err}},forget(quoteRef,ratePlanId){keySlots.delete(slotOf(quoteRef,ratePlanId));}},bookings:{get:(token,opts)=>get(storefrontBookingSchema,`/bookings/${seg(token)}`,opts)},contact:{async create(input,opts){await post(storefrontContactResultSchema,"/contact",input,opts);}}}}
8
+
9
+ export { SDK_VERSION, STOREFRONT_API_PATH, STOREFRONT_API_VERSION, analyticsFor, createAnalytics, createStorefrontClient, getAttribution, parsePublishableKey, trackPurchasePixel };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var CDN_IMAGE_ORIGIN="https://cdn.suiteop.com";var CDN_IMAGE_HOST="cdn.suiteop.com";var TRANSFORM_SEGMENT="/cdn-cgi/image/";var ORG_SCOPED_KEY=/^(booking-engine\/)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\//;var CDN_IMAGE_WIDTHS=[320,640,1080,1600];function cdnObjectKey(url){if(url.includes(TRANSFORM_SEGMENT))return void 0;let parsed;try{parsed=new URL(url);}catch{return void 0}if(parsed.protocol!=="https:"||parsed.host!==CDN_IMAGE_HOST)return void 0;if(parsed.username||parsed.password)return void 0;const key=parsed.pathname.slice(1);if(!ORG_SCOPED_KEY.test(key)||parsed.search||parsed.hash)return void 0;return key}function cdnImageUrl(url,width){if(!url)return void 0;const key=cdnObjectKey(url);if(key===void 0)return url;return `${CDN_IMAGE_ORIGIN}${TRANSFORM_SEGMENT}width=${width},format=auto,quality=80/${key}`}function cdnImageSrcSet(url,widths=CDN_IMAGE_WIDTHS){if(!url||cdnObjectKey(url)===void 0)return void 0;return widths.map(w=>`${cdnImageUrl(url,w)} ${w}w`).join(", ")}function cdnImageProps(url,width,sizes){const srcSet=cdnImageSrcSet(url);if(!srcSet)return {src:url??void 0};return {src:cdnImageUrl(url,width),srcSet,sizes}}
4
+
5
+ exports.CDN_IMAGE_WIDTHS = CDN_IMAGE_WIDTHS;
6
+ exports.cdnImageProps = cdnImageProps;
7
+ exports.cdnImageSrcSet = cdnImageSrcSet;
8
+ exports.cdnImageUrl = cdnImageUrl;
@@ -1,4 +1,4 @@
1
- import { StorefrontError } from './chunk-UACP7BZ7.js';
1
+ import { StorefrontError } from './chunk-ZI2PG7LS.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  var TAG="suiteop-checkout";var manifestSchema=z.object({v1:z.object({path:z.string().regex(/^\/checkout\/v1\.[0-9a-f]{12}\.js$/),integrity:z.string().regex(/^sha384-[A-Za-z0-9+/]+={0,2}$/)})});var loads=new Map;var requested=new Map;function scriptUrl(url){const n=requested.get(url)??0;requested.set(url,n+1);return n===0?url:`${url}?retry=${n}`}function unavailable(message,cause){return new StorefrontError({status:0,code:"checkout_unavailable",message,cause})}async function fetchEntry(origin,fresh){const res=await fetch(`${origin}/checkout/manifest.json`,{cache:fresh?"no-store":"default",credentials:"omit"});if(!res.ok)throw unavailable(`The checkout manifest answered ${res.status}`);const parsed=manifestSchema.safeParse(await res.json());if(!parsed.success)throw unavailable("The checkout manifest has no usable v1 entry");return parsed.data.v1}function inject(src,integrity){const script=document.createElement("script");script.type="module";script.src=src;script.integrity=integrity;script.crossOrigin="anonymous";const ready=new Promise((resolve,reject)=>{script.addEventListener("error",()=>reject(unavailable(`${src} failed to load`)),{once:true});script.addEventListener("load",()=>customElements.get(TAG)?resolve():reject(unavailable(`${src} defined no <${TAG}>`)),{once:true});void customElements.whenDefined(TAG).then(()=>resolve());});document.head.append(script);return [script,ready]}async function load(origin){if(customElements.get(TAG))return;for(const fresh of [false,true]){try{const entry=await fetchEntry(origin,fresh);const[script,ready]=inject(scriptUrl(`${origin}${entry.path}`),entry.integrity);try{return await ready}catch(err){script.remove();if(fresh)throw err}}catch(err){if(err instanceof StorefrontError)throw err;throw unavailable("The checkout bundle could not be loaded",err)}}}function loadCheckout(options={}){if(typeof window==="undefined"||typeof document==="undefined")return Promise.resolve();let origin;try{origin=new URL(options.origin??window.location.origin).origin;}catch(err){return Promise.reject(unavailable("loadCheckout: origin must be an absolute URL",err))}const key=`${origin}|v${options.major??1}`;const existing=loads.get(key);if(existing)return existing;const loading=load(origin);loads.set(key,loading);loading.catch(()=>{if(loads.get(key)===loading)loads.delete(key);});return loading}