@aglyn/aglyn 1.0.0-beta.292 → 1.0.0-beta.296
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/lib/app-utils/analytics-events.d.ts +12 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/business-profile-values.d.ts +3 -2
- package/src/lib/app-utils/business-profile-values.js +4 -3
- package/src/lib/app-utils/business-profile-values.js.map +1 -1
- package/src/lib/app-utils/crm-kinds.d.ts +7 -1
- package/src/lib/app-utils/crm-kinds.js +11 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +5 -1
- package/src/lib/app-utils/crm.js +51 -4
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +8 -7
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +9 -3
- package/src/lib/app-utils/docs-help.generated.js +20 -1
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +124 -40
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +31 -2
- package/src/lib/app-utils/notifications.js +18 -2
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/screen-version-publish.d.ts +71 -0
- package/src/lib/app-utils/screen-version-publish.js +82 -0
- package/src/lib/app-utils/screen-version-publish.js.map +1 -0
- package/src/lib/app-utils/server.d.ts +1 -0
- package/src/lib/app-utils/server.js +1 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-create-return.d.ts +63 -0
- package/src/lib/app-utils/site-create-return.js +72 -0
- package/src/lib/app-utils/site-create-return.js.map +1 -0
- package/src/lib/app-utils/site-starter-pick.d.ts +51 -0
- package/src/lib/app-utils/site-starter-pick.js +47 -0
- package/src/lib/app-utils/site-starter-pick.js.map +1 -0
- package/src/lib/app-utils/site-styles.d.ts +110 -0
- package/src/lib/app-utils/site-styles.js +126 -0
- package/src/lib/app-utils/site-styles.js.map +1 -0
- package/src/lib/app-utils/site-unavailable.d.ts +96 -0
- package/src/lib/app-utils/site-unavailable.js +80 -0
- package/src/lib/app-utils/site-unavailable.js.map +1 -0
- package/src/lib/app-utils/version-draft.d.ts +31 -0
- package/src/lib/app-utils/version-draft.js +29 -0
- package/src/lib/app-utils/version-draft.js.map +1 -0
- package/src/lib/app-utils/visitor-upload.d.ts +19 -0
- package/src/lib/app-utils/visitor-upload.js +29 -1
- package/src/lib/app-utils/visitor-upload.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +9 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +68 -13
- package/src/lib/plugin-manager/feature-plugins.js +16 -9
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +5 -2
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-starters.d.ts +30 -0
- package/src/lib/plugin-manager/plugin-site-starters.js +19 -0
- package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-style-looks.d.ts +43 -0
- package/src/lib/plugin-manager/plugin-site-style-looks.js +30 -0
- package/src/lib/plugin-manager/plugin-site-style-looks.js.map +1 -0
- package/src/lib/plugin-manager/plugin-upload-storage-gate.d.ts +60 -0
- package/src/lib/plugin-manager/plugin-upload-storage-gate.js +32 -0
- package/src/lib/plugin-manager/plugin-upload-storage-gate.js.map +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The styles a new site can start in (AGL-4049), and the record of which one
|
|
18
|
+
* a site was created with.
|
|
19
|
+
*
|
|
20
|
+
* Nothing in "Create site" is chosen for a person: a site is created only
|
|
21
|
+
* once they have picked a style. A style is a kind of site's look — the same
|
|
22
|
+
* 21 the AI start's Style step offers, each with an example home page as its
|
|
23
|
+
* picture — so the starter site and the AI start speak one vocabulary.
|
|
24
|
+
*
|
|
25
|
+
* This module is the LIST: what a person may choose on any surface, the
|
|
26
|
+
* native apps included (the derived-values generator emits it). The LOOK a
|
|
27
|
+
* style gives a site is the AI plugin's, behind the `core.site-style-looks`
|
|
28
|
+
* seam (`plugin-manager/plugin-site-style-looks.ts`); with no plugin
|
|
29
|
+
* answering, a site still starts in the default theme and the choice is
|
|
30
|
+
* still recorded. The AI plugin's own kinds are held to this list, id for id
|
|
31
|
+
* and in order, by `ai-site-kinds.spec.ts`.
|
|
32
|
+
*
|
|
33
|
+
* Pure data and readers, safe for any surface to import.
|
|
34
|
+
*/ /** Where the console serves each style's picture: `<dir>/<id>.webp`. */ export const SITE_STYLE_PREVIEW_DIR = '/_static/starter/styles';
|
|
35
|
+
/** The width and height every style picture is drawn at. */ export const SITE_STYLE_PREVIEW_SIZE = {
|
|
36
|
+
width: 800,
|
|
37
|
+
height: 800
|
|
38
|
+
};
|
|
39
|
+
const style = (id, name, blurb, alt)=>({
|
|
40
|
+
id,
|
|
41
|
+
name,
|
|
42
|
+
blurb,
|
|
43
|
+
preview: `${SITE_STYLE_PREVIEW_DIR}/${id}.webp`,
|
|
44
|
+
alt
|
|
45
|
+
});
|
|
46
|
+
/** Every style, in the order a person is shown them. */ export const SITE_STYLES = [
|
|
47
|
+
style('business', 'Business & services', 'Say what you do, why choose you, and how to reach you', "A cleaning company's home page: its heading beside a photo of gloved hands wiping a desk, then its services as cards"),
|
|
48
|
+
style('trades', 'Home services & trades', 'Roofers, plumbers, towing, landscaping: bold, direct, built on trust', "A roofer's home page: a bold heading over a full-width photo of a roofer carrying shingles, then roofing services"),
|
|
49
|
+
style('professional', 'Law, finance & consulting', 'Law firms, accountants, advisers: calm, established, credible', "A consulting firm's home page: its heading on a white card over a full-width photo of a team at a planning board"),
|
|
50
|
+
style('wellness', 'Health & wellness', 'Therapists, clinics, dentists, coaches: warm, reassuring, clear', "A therapy practice's home page: half the page a photo of a calm, sunlit room, its heading in the other half, then its sessions"),
|
|
51
|
+
style('restaurant', 'Restaurant & café', 'Warm, photo-led pages for food, drink and a place to visit', "A café's home page: its heading centered over a photo of the owner at the counter, then menu highlights"),
|
|
52
|
+
style('store', 'Online store', 'Product-first pages for things you sell', "A handmade goods shop's home page: its heading beside two overlapping photos of wax seals, then featured products"),
|
|
53
|
+
style('portfolio', 'Portfolio', 'Minimal, image-led pages that put the work first', "A prop stylist's home page: a large heading over a grid of three styled sets"),
|
|
54
|
+
style('studio', 'Design studio & agency', 'Bold type and case studies for creative teams', "A design studio's home page: an oversized heading on black over a wide photo of the team at a brand board"),
|
|
55
|
+
style('photography', 'Photography', 'Galleries first, words second', "A photographer's home page: its heading on black over a gallery of a wedding, a portrait and a maker"),
|
|
56
|
+
style('blog', 'Blog & writing', 'Reading-first pages with a calm serif voice', "A food journal's home page: its heading centered in a narrow column over a wide photo of vegetables on a cutting board"),
|
|
57
|
+
style('events', 'Events & weddings', 'Dates, places and the details guests need', "A festival's home page: two overlapping photos of a lantern-lit night market beside its dates, then the program"),
|
|
58
|
+
style('fitness', 'Fitness & sports', 'Gyms, trainers and studios with energy', "A gym's home page: a bold heading on black beside a photo of rows of dumbbells, then this week's classes"),
|
|
59
|
+
style('yoga', 'Yoga & mindfulness', 'Calm, spacious pages for classes and retreats', "A yoga studio's home page: a quiet centered heading over a wide photo of a class stretching on mats"),
|
|
60
|
+
style('beauty', 'Beauty & salon', 'Salons, barbers, spas and studios with style', "A hair salon's home page: its heading beside two overlapping photos of a stylist at work and the salon's chairs"),
|
|
61
|
+
style('realestate', 'Real estate', 'Agents, brokers and property: polished and local', "A real estate agent's home page: its heading on a white card over a photo of a white house, then new listings"),
|
|
62
|
+
style('education', 'Courses & education', 'Tutors, schools and courses, clear and encouraging', "A tutoring center's home page: a photo of a tutor at the whiteboard beside its heading, then its courses"),
|
|
63
|
+
style('nonprofit', 'Nonprofit & community', 'A cause, its impact and how to help', "A food bank's home page: its heading over a full-width photo of volunteers sorting donations, then the ways to help"),
|
|
64
|
+
style('music', 'Music & bands', 'Artists, bands and DJs, loud and visual', "A band's home page: its name in large type over a photo of a band under stage lights, then its tour dates"),
|
|
65
|
+
style('personal', 'Personal & resume', 'A simple page about you and your work', "A product manager's personal page: a round portrait over her centered introduction, then her experience"),
|
|
66
|
+
style('landing', 'Landing page', 'One page that leads to one action', "A budgeting app's landing page: its pitch centered on a colored band over a photo of a couple planning on a phone"),
|
|
67
|
+
style('coming-soon', 'Coming soon', 'A placeholder that collects interest', "A café's coming soon page: its heading and notify button centered over a photo of the café's coffee shelves")
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* "Pick for me": the AI start's style card that lets the answers decide. It
|
|
71
|
+
* is a card a person CLICKS, never a default, and it exists only where
|
|
72
|
+
* something can pick for them (the AI start); the starter site has no such
|
|
73
|
+
* card, so a site created with the starter always names a real style.
|
|
74
|
+
*/ export const SITE_STYLE_AUTO = 'auto';
|
|
75
|
+
/** Whether a value names one of the {@link SITE_STYLES}. */ export function isSiteStyleId(value) {
|
|
76
|
+
return typeof value === 'string' && SITE_STYLES.some((entry)=>entry.id === value);
|
|
77
|
+
}
|
|
78
|
+
/** The style an id names, or `null`. */ export function siteStyleOf(id) {
|
|
79
|
+
var _SITE_STYLES_find;
|
|
80
|
+
return (_SITE_STYLES_find = SITE_STYLES.find((entry)=>entry.id === id)) != null ? _SITE_STYLES_find : null;
|
|
81
|
+
}
|
|
82
|
+
export const HOST_STARTER_STARTS = [
|
|
83
|
+
'starter',
|
|
84
|
+
'ai',
|
|
85
|
+
'import',
|
|
86
|
+
'api'
|
|
87
|
+
];
|
|
88
|
+
/** A choice with its unset parts filled: no listing, no style, `base`. */ export function hostStarterChoice(input) {
|
|
89
|
+
var _input_listingId, _input_kind;
|
|
90
|
+
return {
|
|
91
|
+
listingId: (_input_listingId = input.listingId) != null ? _input_listingId : null,
|
|
92
|
+
version: input.version == null ? null : String(input.version),
|
|
93
|
+
kind: (_input_kind = input.kind) != null ? _input_kind : null,
|
|
94
|
+
mode: input.mode === 'ai' ? 'ai' : 'base',
|
|
95
|
+
start: input.start
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const textOrNull = (value)=>typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
99
|
+
/** A stored or answered choice, read defensively; `null` when it is not one. */ export function readHostStarterChoice(value) {
|
|
100
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
101
|
+
const record = value;
|
|
102
|
+
const start = record['start'];
|
|
103
|
+
if (!HOST_STARTER_STARTS.includes(start)) return null;
|
|
104
|
+
return hostStarterChoice({
|
|
105
|
+
listingId: textOrNull(record['listingId']),
|
|
106
|
+
version: textOrNull(record['version']),
|
|
107
|
+
kind: textOrNull(record['kind']),
|
|
108
|
+
mode: record['mode'] === 'ai' ? 'ai' : 'base',
|
|
109
|
+
start: start
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The `host_created` event's parameters for a site's choice: how it was
|
|
114
|
+
* started (`unknown` when the create's answer did not say), its style
|
|
115
|
+
* (`auto` for "Pick for me", `none` for no style) and the starter listing
|
|
116
|
+
* (`none` for none). Ids only, never a name a person typed.
|
|
117
|
+
*/ export function hostCreatedEventParams(choice) {
|
|
118
|
+
var _ref, _ref1, _ref2;
|
|
119
|
+
return {
|
|
120
|
+
start: (_ref = choice == null ? void 0 : choice.start) != null ? _ref : 'unknown',
|
|
121
|
+
starter_kind: (_ref1 = choice == null ? void 0 : choice.kind) != null ? _ref1 : 'none',
|
|
122
|
+
starter_listing: (_ref2 = choice == null ? void 0 : choice.listingId) != null ? _ref2 : 'none'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=site-styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-styles.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The styles a new site can start in (AGL-4049), and the record of which one\n * a site was created with.\n *\n * Nothing in \"Create site\" is chosen for a person: a site is created only\n * once they have picked a style. A style is a kind of site's look — the same\n * 21 the AI start's Style step offers, each with an example home page as its\n * picture — so the starter site and the AI start speak one vocabulary.\n *\n * This module is the LIST: what a person may choose on any surface, the\n * native apps included (the derived-values generator emits it). The LOOK a\n * style gives a site is the AI plugin's, behind the `core.site-style-looks`\n * seam (`plugin-manager/plugin-site-style-looks.ts`); with no plugin\n * answering, a site still starts in the default theme and the choice is\n * still recorded. The AI plugin's own kinds are held to this list, id for id\n * and in order, by `ai-site-kinds.spec.ts`.\n *\n * Pure data and readers, safe for any surface to import.\n */\n\n/** Where the console serves each style's picture: `<dir>/<id>.webp`. */\nexport const SITE_STYLE_PREVIEW_DIR = '/_static/starter/styles'\n\n/** The width and height every style picture is drawn at. */\nexport const SITE_STYLE_PREVIEW_SIZE = { width: 800, height: 800 } as const\n\n/** One style a site can start in. */\nexport interface SiteStyle {\n /** A persisted identifier: the AI start's kind id, stored on the site. */\n id: string\n /** The card's title. */\n name: string\n /** One line under it. */\n blurb: string\n /** The example home page's picture, as a path on the console's origin. */\n preview: string\n /** What the picture shows, for a reader who cannot see it. */\n alt: string\n}\n\nconst style = (id: string, name: string, blurb: string, alt: string): SiteStyle => ({\n id,\n name,\n blurb,\n preview: `${SITE_STYLE_PREVIEW_DIR}/${id}.webp`,\n alt,\n})\n\n/** Every style, in the order a person is shown them. */\nexport const SITE_STYLES: readonly SiteStyle[] = [\n style('business', 'Business & services', 'Say what you do, why choose you, and how to reach you', \"A cleaning company's home page: its heading beside a photo of gloved hands wiping a desk, then its services as cards\"),\n style('trades', 'Home services & trades', 'Roofers, plumbers, towing, landscaping: bold, direct, built on trust', \"A roofer's home page: a bold heading over a full-width photo of a roofer carrying shingles, then roofing services\"),\n style('professional', 'Law, finance & consulting', 'Law firms, accountants, advisers: calm, established, credible', \"A consulting firm's home page: its heading on a white card over a full-width photo of a team at a planning board\"),\n style('wellness', 'Health & wellness', 'Therapists, clinics, dentists, coaches: warm, reassuring, clear', \"A therapy practice's home page: half the page a photo of a calm, sunlit room, its heading in the other half, then its sessions\"),\n style('restaurant', 'Restaurant & café', 'Warm, photo-led pages for food, drink and a place to visit', \"A café's home page: its heading centered over a photo of the owner at the counter, then menu highlights\"),\n style('store', 'Online store', 'Product-first pages for things you sell', \"A handmade goods shop's home page: its heading beside two overlapping photos of wax seals, then featured products\"),\n style('portfolio', 'Portfolio', 'Minimal, image-led pages that put the work first', \"A prop stylist's home page: a large heading over a grid of three styled sets\"),\n style('studio', 'Design studio & agency', 'Bold type and case studies for creative teams', \"A design studio's home page: an oversized heading on black over a wide photo of the team at a brand board\"),\n style('photography', 'Photography', 'Galleries first, words second', \"A photographer's home page: its heading on black over a gallery of a wedding, a portrait and a maker\"),\n style('blog', 'Blog & writing', 'Reading-first pages with a calm serif voice', \"A food journal's home page: its heading centered in a narrow column over a wide photo of vegetables on a cutting board\"),\n style('events', 'Events & weddings', 'Dates, places and the details guests need', \"A festival's home page: two overlapping photos of a lantern-lit night market beside its dates, then the program\"),\n style('fitness', 'Fitness & sports', 'Gyms, trainers and studios with energy', \"A gym's home page: a bold heading on black beside a photo of rows of dumbbells, then this week's classes\"),\n style('yoga', 'Yoga & mindfulness', 'Calm, spacious pages for classes and retreats', \"A yoga studio's home page: a quiet centered heading over a wide photo of a class stretching on mats\"),\n style('beauty', 'Beauty & salon', 'Salons, barbers, spas and studios with style', \"A hair salon's home page: its heading beside two overlapping photos of a stylist at work and the salon's chairs\"),\n style('realestate', 'Real estate', 'Agents, brokers and property: polished and local', \"A real estate agent's home page: its heading on a white card over a photo of a white house, then new listings\"),\n style('education', 'Courses & education', 'Tutors, schools and courses, clear and encouraging', \"A tutoring center's home page: a photo of a tutor at the whiteboard beside its heading, then its courses\"),\n style('nonprofit', 'Nonprofit & community', 'A cause, its impact and how to help', \"A food bank's home page: its heading over a full-width photo of volunteers sorting donations, then the ways to help\"),\n style('music', 'Music & bands', 'Artists, bands and DJs, loud and visual', \"A band's home page: its name in large type over a photo of a band under stage lights, then its tour dates\"),\n style('personal', 'Personal & resume', 'A simple page about you and your work', \"A product manager's personal page: a round portrait over her centered introduction, then her experience\"),\n style('landing', 'Landing page', 'One page that leads to one action', \"A budgeting app's landing page: its pitch centered on a colored band over a photo of a couple planning on a phone\"),\n style('coming-soon', 'Coming soon', 'A placeholder that collects interest', \"A café's coming soon page: its heading and notify button centered over a photo of the café's coffee shelves\"),\n]\n\n/**\n * \"Pick for me\": the AI start's style card that lets the answers decide. It\n * is a card a person CLICKS, never a default, and it exists only where\n * something can pick for them (the AI start); the starter site has no such\n * card, so a site created with the starter always names a real style.\n */\nexport const SITE_STYLE_AUTO = 'auto'\n\n/** Whether a value names one of the {@link SITE_STYLES}. */\nexport function isSiteStyleId(value: unknown): value is string {\n return typeof value === 'string' && SITE_STYLES.some((entry) => entry.id === value)\n}\n\n/** The style an id names, or `null`. */\nexport function siteStyleOf(id: unknown): SiteStyle | null {\n return SITE_STYLES.find((entry) => entry.id === id) ?? null\n}\n\n// ── What a site was created with ────────────────────────────────────────────\n\n/**\n * How a site was started: the starter site (`starter`), the AI start (`ai`),\n * a site brought in from somewhere else (`import`), or a program through the\n * public API (`api`), which keeps the default and chooses no style.\n */\nexport type HostStarterStart = 'starter' | 'ai' | 'import' | 'api'\n\nexport const HOST_STARTER_STARTS: readonly HostStarterStart[] = ['starter', 'ai', 'import', 'api']\n\n/**\n * `hosts/{id}.starterChoice` (AGL-4049): what a site was created with, written\n * by the server in the create's own transaction and never by a client, so\n * which styles people choose can be counted, and a site created and then left\n * alone can be told apart from one that was built on.\n *\n * - `kind`: the style id; {@link SITE_STYLE_AUTO} when the AI start's \"Pick\n * for me\" was chosen; `null` when none was (the API, a seed);\n * - `listingId` / `version`: the marketplace starter picked, or `null`;\n * - `mode`: `base` copies a starter's pages, `ai` writes them for the business.\n */\nexport interface HostStarterChoice {\n listingId: string | null\n version: string | null\n kind: string | null\n mode: 'base' | 'ai'\n start: HostStarterStart\n}\n\n/** A choice with its unset parts filled: no listing, no style, `base`. */\nexport function hostStarterChoice(input: Partial<HostStarterChoice> & { start: HostStarterStart }): HostStarterChoice {\n return {\n listingId: input.listingId ?? null,\n version: input.version == null ? null : String(input.version),\n kind: input.kind ?? null,\n mode: input.mode === 'ai' ? 'ai' : 'base',\n start: input.start,\n }\n}\n\nconst textOrNull = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\n\n/** A stored or answered choice, read defensively; `null` when it is not one. */\nexport function readHostStarterChoice(value: unknown): HostStarterChoice | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const record = value as Record<string, unknown>\n const start = record['start']\n if (!(HOST_STARTER_STARTS as readonly unknown[]).includes(start)) return null\n return hostStarterChoice({\n listingId: textOrNull(record['listingId']),\n version: textOrNull(record['version']),\n kind: textOrNull(record['kind']),\n mode: record['mode'] === 'ai' ? 'ai' : 'base',\n start: start as HostStarterStart,\n })\n}\n\n/**\n * The `host_created` event's parameters for a site's choice: how it was\n * started (`unknown` when the create's answer did not say), its style\n * (`auto` for \"Pick for me\", `none` for no style) and the starter listing\n * (`none` for none). Ids only, never a name a person typed.\n */\nexport function hostCreatedEventParams(choice: HostStarterChoice | null): {\n start: string\n starter_kind: string\n starter_listing: string\n} {\n return {\n start: choice?.start ?? 'unknown',\n starter_kind: choice?.kind ?? 'none',\n starter_listing: choice?.listingId ?? 'none',\n }\n}\n"],"names":["SITE_STYLE_PREVIEW_DIR","SITE_STYLE_PREVIEW_SIZE","width","height","style","id","name","blurb","alt","preview","SITE_STYLES","SITE_STYLE_AUTO","isSiteStyleId","value","some","entry","siteStyleOf","find","HOST_STARTER_STARTS","hostStarterChoice","input","listingId","version","String","kind","mode","start","textOrNull","trim","readHostStarterChoice","Array","isArray","record","includes","hostCreatedEventParams","choice","starter_kind","starter_listing"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The site's 503 answer, sent as a 503 (AGL-4110).
|
|
19
|
+
*
|
|
20
|
+
* Maintenance mode (AGL-131) and preview (AGL-4086) answer a visitor who is
|
|
21
|
+
* not let through with the site's designed 503 page, or the built-in notice,
|
|
22
|
+
* at every address. The body was right and the status was not: every address
|
|
23
|
+
* said `200 OK`, which tells a crawler the notice IS the page. A live site
|
|
24
|
+
* that spent a weekend in maintenance could be re-indexed as "Temporarily
|
|
25
|
+
* unavailable" and drop out of search. `503` with `Retry-After` is the answer
|
|
26
|
+
* that means "this site is briefly down, come back".
|
|
27
|
+
*
|
|
28
|
+
* The body still comes from the ISR-cached catch-all, exactly as before; only
|
|
29
|
+
* the tenant middleware can set the status, because an App Router page has no
|
|
30
|
+
* status lever and the middleware is the one layer that runs ahead of the
|
|
31
|
+
* cache on every request. It asks {@link siteUnavailableForRequest} and
|
|
32
|
+
* rewrites with the status, so a cached page body never decides it.
|
|
33
|
+
*
|
|
34
|
+
* DEPENDENCY-FREE ON PURPOSE, like `site-preview.ts`: the middleware runs on
|
|
35
|
+
* the edge and deep-imports this leaf, so it imports nothing.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* How long a crawler or uptime check is told to wait before asking again, in
|
|
39
|
+
* seconds: an hour.
|
|
40
|
+
*
|
|
41
|
+
* The bandwidth cap's figure (`BANDWIDTH_CAP_RETRY_AFTER_SECONDS`), for the
|
|
42
|
+
* same reason. Both states end the moment the owner flips a switch, and the
|
|
43
|
+
* site is back within about a minute, so no longer value is ever "correct".
|
|
44
|
+
* Too long is wrong in the direction that costs the site: a crawler staying
|
|
45
|
+
* away after the site came back. An hour is short enough to be wrong in the
|
|
46
|
+
* recoverable direction, and long enough that a crawler does not spend its
|
|
47
|
+
* visits re-reading "not yet".
|
|
48
|
+
*/
|
|
49
|
+
export declare const SITE_UNAVAILABLE_RETRY_AFTER_SECONDS = 3600;
|
|
50
|
+
/**
|
|
51
|
+
* Is the site in maintenance mode (AGL-131)?
|
|
52
|
+
*
|
|
53
|
+
* The loader's own test, shared with the verdict the middleware reads, so the
|
|
54
|
+
* status and the body are decided by one predicate: a field the loader reads as
|
|
55
|
+
* "in maintenance" is a field the middleware reads the same way.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isHostInMaintenance(host: {
|
|
58
|
+
maintenance?: unknown;
|
|
59
|
+
} | null | undefined): boolean;
|
|
60
|
+
/** What the middleware knows about a site, from its verdict. */
|
|
61
|
+
export interface SiteUnavailableVerdict {
|
|
62
|
+
/** Maintenance mode is on. */
|
|
63
|
+
maintenance?: boolean;
|
|
64
|
+
/** The WHOLE site is in preview. One page in preview is not this. */
|
|
65
|
+
preview?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** How the middleware is routing this request. */
|
|
68
|
+
export interface SiteUnavailableRouting {
|
|
69
|
+
/** An entry preview link (`?aglyn_preview=`) is being opened. */
|
|
70
|
+
entryPreview: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* A team cookie sent this request around the cache to the uncached preview
|
|
73
|
+
* route, which shows the team the site.
|
|
74
|
+
*/
|
|
75
|
+
teamPreview: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Does this page request get the site's 503 answer as an HTTP 503?
|
|
79
|
+
*
|
|
80
|
+
* - **Maintenance: yes, for everyone**, the site's team included. It is the
|
|
81
|
+
* switch that means "nobody", and the loader serves the 503 page to every
|
|
82
|
+
* request, the team's preview route included, so every status says so.
|
|
83
|
+
* - **A site in preview: yes, for the public**, the requests answered from the
|
|
84
|
+
* shared cache. A request carrying a team cookie goes to the uncached
|
|
85
|
+
* preview route instead, which shows the team the whole site.
|
|
86
|
+
* - **An entry preview link: no.** When the site is unavailable its route
|
|
87
|
+
* redirects to the plain address, which answers 503 by the rules above; a
|
|
88
|
+
* 503 set here would only argue with that redirect.
|
|
89
|
+
* - **One page in preview: no.** The public gets the site's 404 there, and the
|
|
90
|
+
* render's own 404 decides that status.
|
|
91
|
+
*
|
|
92
|
+
* Nothing here is a permission. The body is the loader's decision, and a
|
|
93
|
+
* request this answers `false` for is one whose body is already the site, or
|
|
94
|
+
* an answer with a status of its own.
|
|
95
|
+
*/
|
|
96
|
+
export declare function siteUnavailableForRequest(verdict: SiteUnavailableVerdict | null | undefined, routing: SiteUnavailableRouting): boolean;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The site's 503 answer, sent as a 503 (AGL-4110).
|
|
18
|
+
*
|
|
19
|
+
* Maintenance mode (AGL-131) and preview (AGL-4086) answer a visitor who is
|
|
20
|
+
* not let through with the site's designed 503 page, or the built-in notice,
|
|
21
|
+
* at every address. The body was right and the status was not: every address
|
|
22
|
+
* said `200 OK`, which tells a crawler the notice IS the page. A live site
|
|
23
|
+
* that spent a weekend in maintenance could be re-indexed as "Temporarily
|
|
24
|
+
* unavailable" and drop out of search. `503` with `Retry-After` is the answer
|
|
25
|
+
* that means "this site is briefly down, come back".
|
|
26
|
+
*
|
|
27
|
+
* The body still comes from the ISR-cached catch-all, exactly as before; only
|
|
28
|
+
* the tenant middleware can set the status, because an App Router page has no
|
|
29
|
+
* status lever and the middleware is the one layer that runs ahead of the
|
|
30
|
+
* cache on every request. It asks {@link siteUnavailableForRequest} and
|
|
31
|
+
* rewrites with the status, so a cached page body never decides it.
|
|
32
|
+
*
|
|
33
|
+
* DEPENDENCY-FREE ON PURPOSE, like `site-preview.ts`: the middleware runs on
|
|
34
|
+
* the edge and deep-imports this leaf, so it imports nothing.
|
|
35
|
+
*/ /**
|
|
36
|
+
* How long a crawler or uptime check is told to wait before asking again, in
|
|
37
|
+
* seconds: an hour.
|
|
38
|
+
*
|
|
39
|
+
* The bandwidth cap's figure (`BANDWIDTH_CAP_RETRY_AFTER_SECONDS`), for the
|
|
40
|
+
* same reason. Both states end the moment the owner flips a switch, and the
|
|
41
|
+
* site is back within about a minute, so no longer value is ever "correct".
|
|
42
|
+
* Too long is wrong in the direction that costs the site: a crawler staying
|
|
43
|
+
* away after the site came back. An hour is short enough to be wrong in the
|
|
44
|
+
* recoverable direction, and long enough that a crawler does not spend its
|
|
45
|
+
* visits re-reading "not yet".
|
|
46
|
+
*/ export const SITE_UNAVAILABLE_RETRY_AFTER_SECONDS = 3600;
|
|
47
|
+
/**
|
|
48
|
+
* Is the site in maintenance mode (AGL-131)?
|
|
49
|
+
*
|
|
50
|
+
* The loader's own test, shared with the verdict the middleware reads, so the
|
|
51
|
+
* status and the body are decided by one predicate: a field the loader reads as
|
|
52
|
+
* "in maintenance" is a field the middleware reads the same way.
|
|
53
|
+
*/ export function isHostInMaintenance(host) {
|
|
54
|
+
return Boolean(host == null ? void 0 : host.maintenance);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Does this page request get the site's 503 answer as an HTTP 503?
|
|
58
|
+
*
|
|
59
|
+
* - **Maintenance: yes, for everyone**, the site's team included. It is the
|
|
60
|
+
* switch that means "nobody", and the loader serves the 503 page to every
|
|
61
|
+
* request, the team's preview route included, so every status says so.
|
|
62
|
+
* - **A site in preview: yes, for the public**, the requests answered from the
|
|
63
|
+
* shared cache. A request carrying a team cookie goes to the uncached
|
|
64
|
+
* preview route instead, which shows the team the whole site.
|
|
65
|
+
* - **An entry preview link: no.** When the site is unavailable its route
|
|
66
|
+
* redirects to the plain address, which answers 503 by the rules above; a
|
|
67
|
+
* 503 set here would only argue with that redirect.
|
|
68
|
+
* - **One page in preview: no.** The public gets the site's 404 there, and the
|
|
69
|
+
* render's own 404 decides that status.
|
|
70
|
+
*
|
|
71
|
+
* Nothing here is a permission. The body is the loader's decision, and a
|
|
72
|
+
* request this answers `false` for is one whose body is already the site, or
|
|
73
|
+
* an answer with a status of its own.
|
|
74
|
+
*/ export function siteUnavailableForRequest(verdict, routing) {
|
|
75
|
+
if (!verdict || routing.entryPreview) return false;
|
|
76
|
+
if (verdict.maintenance === true) return true;
|
|
77
|
+
return verdict.preview === true && !routing.teamPreview;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=site-unavailable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-unavailable.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The site's 503 answer, sent as a 503 (AGL-4110).\n *\n * Maintenance mode (AGL-131) and preview (AGL-4086) answer a visitor who is\n * not let through with the site's designed 503 page, or the built-in notice,\n * at every address. The body was right and the status was not: every address\n * said `200 OK`, which tells a crawler the notice IS the page. A live site\n * that spent a weekend in maintenance could be re-indexed as \"Temporarily\n * unavailable\" and drop out of search. `503` with `Retry-After` is the answer\n * that means \"this site is briefly down, come back\".\n *\n * The body still comes from the ISR-cached catch-all, exactly as before; only\n * the tenant middleware can set the status, because an App Router page has no\n * status lever and the middleware is the one layer that runs ahead of the\n * cache on every request. It asks {@link siteUnavailableForRequest} and\n * rewrites with the status, so a cached page body never decides it.\n *\n * DEPENDENCY-FREE ON PURPOSE, like `site-preview.ts`: the middleware runs on\n * the edge and deep-imports this leaf, so it imports nothing.\n */\n\n/**\n * How long a crawler or uptime check is told to wait before asking again, in\n * seconds: an hour.\n *\n * The bandwidth cap's figure (`BANDWIDTH_CAP_RETRY_AFTER_SECONDS`), for the\n * same reason. Both states end the moment the owner flips a switch, and the\n * site is back within about a minute, so no longer value is ever \"correct\".\n * Too long is wrong in the direction that costs the site: a crawler staying\n * away after the site came back. An hour is short enough to be wrong in the\n * recoverable direction, and long enough that a crawler does not spend its\n * visits re-reading \"not yet\".\n */\nexport const SITE_UNAVAILABLE_RETRY_AFTER_SECONDS = 3600\n\n/**\n * Is the site in maintenance mode (AGL-131)?\n *\n * The loader's own test, shared with the verdict the middleware reads, so the\n * status and the body are decided by one predicate: a field the loader reads as\n * \"in maintenance\" is a field the middleware reads the same way.\n */\nexport function isHostInMaintenance(\n host: { maintenance?: unknown } | null | undefined,\n): boolean {\n return Boolean(host?.maintenance)\n}\n\n/** What the middleware knows about a site, from its verdict. */\nexport interface SiteUnavailableVerdict {\n /** Maintenance mode is on. */\n maintenance?: boolean\n /** The WHOLE site is in preview. One page in preview is not this. */\n preview?: boolean\n}\n\n/** How the middleware is routing this request. */\nexport interface SiteUnavailableRouting {\n /** An entry preview link (`?aglyn_preview=`) is being opened. */\n entryPreview: boolean\n /**\n * A team cookie sent this request around the cache to the uncached preview\n * route, which shows the team the site.\n */\n teamPreview: boolean\n}\n\n/**\n * Does this page request get the site's 503 answer as an HTTP 503?\n *\n * - **Maintenance: yes, for everyone**, the site's team included. It is the\n * switch that means \"nobody\", and the loader serves the 503 page to every\n * request, the team's preview route included, so every status says so.\n * - **A site in preview: yes, for the public**, the requests answered from the\n * shared cache. A request carrying a team cookie goes to the uncached\n * preview route instead, which shows the team the whole site.\n * - **An entry preview link: no.** When the site is unavailable its route\n * redirects to the plain address, which answers 503 by the rules above; a\n * 503 set here would only argue with that redirect.\n * - **One page in preview: no.** The public gets the site's 404 there, and the\n * render's own 404 decides that status.\n *\n * Nothing here is a permission. The body is the loader's decision, and a\n * request this answers `false` for is one whose body is already the site, or\n * an answer with a status of its own.\n */\nexport function siteUnavailableForRequest(\n verdict: SiteUnavailableVerdict | null | undefined,\n routing: SiteUnavailableRouting,\n): boolean {\n if (!verdict || routing.entryPreview) return false\n if (verdict.maintenance === true) return true\n return verdict.preview === true && !routing.teamPreview\n}\n"],"names":["SITE_UNAVAILABLE_RETRY_AFTER_SECONDS","isHostInMaintenance","host","Boolean","maintenance","siteUnavailableForRequest","verdict","routing","entryPreview","preview","teamPreview"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;CAmBC,GAED;;;;;;;;;;;CAWC,GACD,OAAO,MAAMA,uCAAuC,KAAI;AAExD;;;;;;CAMC,GACD,OAAO,SAASC,oBACdC,IAAkD;IAElD,OAAOC,QAAQD,wBAAAA,KAAME,WAAW;AAClC;AAqBA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,0BACdC,OAAkD,EAClDC,OAA+B;IAE/B,IAAI,CAACD,WAAWC,QAAQC,YAAY,EAAE,OAAO;IAC7C,IAAIF,QAAQF,WAAW,KAAK,MAAM,OAAO;IACzC,OAAOE,QAAQG,OAAO,KAAK,QAAQ,CAACF,QAAQG,WAAW;AACzD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Where a version's WORKING DRAFT lives (AGL-1152): one document per version,
|
|
19
|
+
* `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves
|
|
20
|
+
* to it as an author works and offers it with Open draft when the page is
|
|
21
|
+
* opened; publishing moves it into the version and clears it.
|
|
22
|
+
*
|
|
23
|
+
* Held here, in the core, because two writers must agree on it to the
|
|
24
|
+
* letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which
|
|
25
|
+
* saves its change into the same draft so the author reviews and publishes
|
|
26
|
+
* it in the Besigner exactly as they would their own (AGL-4116).
|
|
27
|
+
*/
|
|
28
|
+
/** The subcollection under a version that holds its draft. */
|
|
29
|
+
export declare const VERSION_DRAFT_COLLECTION = "draft";
|
|
30
|
+
/** The only id a draft may have. One per version, by construction. */
|
|
31
|
+
export declare const VERSION_DRAFT_DOC_ID = "current";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Where a version's WORKING DRAFT lives (AGL-1152): one document per version,
|
|
18
|
+
* `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves
|
|
19
|
+
* to it as an author works and offers it with Open draft when the page is
|
|
20
|
+
* opened; publishing moves it into the version and clears it.
|
|
21
|
+
*
|
|
22
|
+
* Held here, in the core, because two writers must agree on it to the
|
|
23
|
+
* letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which
|
|
24
|
+
* saves its change into the same draft so the author reviews and publishes
|
|
25
|
+
* it in the Besigner exactly as they would their own (AGL-4116).
|
|
26
|
+
*/ /** The subcollection under a version that holds its draft. */ export const VERSION_DRAFT_COLLECTION = 'draft';
|
|
27
|
+
/** The only id a draft may have. One per version, by construction. */ export const VERSION_DRAFT_DOC_ID = 'current';
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=version-draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/version-draft.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Where a version's WORKING DRAFT lives (AGL-1152): one document per version,\n * `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves\n * to it as an author works and offers it with Open draft when the page is\n * opened; publishing moves it into the version and clears it.\n *\n * Held here, in the core, because two writers must agree on it to the\n * letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which\n * saves its change into the same draft so the author reviews and publishes\n * it in the Besigner exactly as they would their own (AGL-4116).\n */\n\n/** The subcollection under a version that holds its draft. */\nexport const VERSION_DRAFT_COLLECTION = 'draft'\n\n/** The only id a draft may have. One per version, by construction. */\nexport const VERSION_DRAFT_DOC_ID = 'current'\n"],"names":["VERSION_DRAFT_COLLECTION","VERSION_DRAFT_DOC_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED,4DAA4D,GAC5D,OAAO,MAAMA,2BAA2B,QAAO;AAE/C,oEAAoE,GACpE,OAAO,MAAMC,uBAAuB,UAAS"}
|
|
@@ -145,6 +145,25 @@ export declare function visitorUploadPendingPath(scope: VisitorUploadScope, uplo
|
|
|
145
145
|
export declare function visitorUploadFolder(scope: VisitorUploadScope): string;
|
|
146
146
|
/** Where a checked file is kept: private to the site, with no download token. */
|
|
147
147
|
export declare function visitorUploadFinalPath(scope: VisitorUploadScope, uploadId: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* A kept file counts toward its workspace's storage the way a media library
|
|
150
|
+
* file does. Its bytes are added to the site's storage counter,
|
|
151
|
+
* `hosts/{hostId}/counters/media`, whose `bytes` is the one figure the
|
|
152
|
+
* storage band, the Billing meter, the usage alerts and the invoice all read.
|
|
153
|
+
* So a proof, a customer's file on a quote and a form's file are measured,
|
|
154
|
+
* warned about and billed exactly as a library upload is, and no reader of
|
|
155
|
+
* storage can leave them out.
|
|
156
|
+
*
|
|
157
|
+
* The same counter says how much of `bytes` is these files, in the two fields
|
|
158
|
+
* below. That keeps `count` the number of library files and lets the media
|
|
159
|
+
* library show its own share ({@link mediaLibraryBytes}).
|
|
160
|
+
*/
|
|
161
|
+
export declare const VISITOR_UPLOAD_COUNTER_BYTES = "uploadBytes";
|
|
162
|
+
export declare const VISITOR_UPLOAD_COUNTER_FILES = "uploadCount";
|
|
163
|
+
/** The bytes of kept files a site's storage counter holds, never more than its `bytes`. */
|
|
164
|
+
export declare function visitorUploadCounterBytes(counter: Record<string, unknown> | null | undefined): number;
|
|
165
|
+
/** The media library's own files: the counter's `bytes` less the kept files counted beside them. */
|
|
166
|
+
export declare function mediaLibraryBytes(counter: Record<string, unknown> | null | undefined): number;
|
|
148
167
|
/**
|
|
149
168
|
* The files a browser picked, checked before any is sent: how many, and each
|
|
150
169
|
* one's type and size. `already` is how many the submission holds.
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
* labelled `text/html` is nothing;
|
|
30
30
|
* - where its bytes live ({@link visitorUploadPendingPath} while it is on its
|
|
31
31
|
* way, {@link visitorUploadFinalPath} once it is checked);
|
|
32
|
-
* - the record a finished upload is ({@link VisitorUploadFile})
|
|
32
|
+
* - the record a finished upload is ({@link VisitorUploadFile});
|
|
33
|
+
* - how a kept file counts toward the workspace's storage
|
|
34
|
+
* ({@link VISITOR_UPLOAD_COUNTER_BYTES}).
|
|
33
35
|
*
|
|
34
36
|
* The server half (`@aglyn/tenant-data-admin/server/visitor-upload`) signs the
|
|
35
37
|
* upload, inspects the bytes and moves them; the browser half
|
|
@@ -341,6 +343,32 @@ export function isVisitorUploadSegment(value) {
|
|
|
341
343
|
/** Where a checked file is kept: private to the site, with no download token. */ export function visitorUploadFinalPath(scope, uploadId) {
|
|
342
344
|
return `${visitorUploadFolder(scope)}${uploadId}`;
|
|
343
345
|
}
|
|
346
|
+
/*------------------------------------------
|
|
347
|
+
* Storage
|
|
348
|
+
*-----------------------------------------*/ /**
|
|
349
|
+
* A kept file counts toward its workspace's storage the way a media library
|
|
350
|
+
* file does. Its bytes are added to the site's storage counter,
|
|
351
|
+
* `hosts/{hostId}/counters/media`, whose `bytes` is the one figure the
|
|
352
|
+
* storage band, the Billing meter, the usage alerts and the invoice all read.
|
|
353
|
+
* So a proof, a customer's file on a quote and a form's file are measured,
|
|
354
|
+
* warned about and billed exactly as a library upload is, and no reader of
|
|
355
|
+
* storage can leave them out.
|
|
356
|
+
*
|
|
357
|
+
* The same counter says how much of `bytes` is these files, in the two fields
|
|
358
|
+
* below. That keeps `count` the number of library files and lets the media
|
|
359
|
+
* library show its own share ({@link mediaLibraryBytes}).
|
|
360
|
+
*/ export const VISITOR_UPLOAD_COUNTER_BYTES = 'uploadBytes';
|
|
361
|
+
export const VISITOR_UPLOAD_COUNTER_FILES = 'uploadCount';
|
|
362
|
+
const counterBytes = (value)=>{
|
|
363
|
+
const bytes = Number(value);
|
|
364
|
+
return Number.isFinite(bytes) && bytes > 0 ? bytes : 0;
|
|
365
|
+
};
|
|
366
|
+
/** The bytes of kept files a site's storage counter holds, never more than its `bytes`. */ export function visitorUploadCounterBytes(counter) {
|
|
367
|
+
return Math.min(counterBytes(counter == null ? void 0 : counter['bytes']), counterBytes(counter == null ? void 0 : counter[VISITOR_UPLOAD_COUNTER_BYTES]));
|
|
368
|
+
}
|
|
369
|
+
/** The media library's own files: the counter's `bytes` less the kept files counted beside them. */ export function mediaLibraryBytes(counter) {
|
|
370
|
+
return counterBytes(counter == null ? void 0 : counter['bytes']) - visitorUploadCounterBytes(counter);
|
|
371
|
+
}
|
|
344
372
|
/**
|
|
345
373
|
* The files a browser picked, checked before any is sent: how many, and each
|
|
346
374
|
* one's type and size. `already` is how many the submission holds.
|