@aglyn/aglyn 1.0.0-beta.277 → 1.0.0-beta.283

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 (95) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-preferences.d.ts +146 -11
  3. package/src/lib/app-utils/account-preferences.js +266 -18
  4. package/src/lib/app-utils/account-preferences.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.js +2 -26
  6. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  7. package/src/lib/app-utils/activity-type-labels.d.ts +18 -0
  8. package/src/lib/app-utils/activity-type-labels.js +46 -0
  9. package/src/lib/app-utils/activity-type-labels.js.map +1 -0
  10. package/src/lib/app-utils/artifact-provenance.d.ts +3 -1
  11. package/src/lib/app-utils/artifact-provenance.js.map +1 -1
  12. package/src/lib/app-utils/bundle-intent.d.ts +37 -0
  13. package/src/lib/app-utils/bundle-intent.js +47 -3
  14. package/src/lib/app-utils/bundle-intent.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +17 -1
  16. package/src/lib/app-utils/compose-reusable-components.js +80 -5
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/docs-help-sections.generated.js +13 -1
  19. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  20. package/src/lib/app-utils/docs-help.generated.d.ts +23 -11
  21. package/src/lib/app-utils/docs-help.generated.js +64 -7
  22. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  23. package/src/lib/app-utils/docs-index.generated.js +359 -51
  24. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  25. package/src/lib/app-utils/error-beacon.d.ts +19 -0
  26. package/src/lib/app-utils/error-beacon.js +28 -0
  27. package/src/lib/app-utils/error-beacon.js.map +1 -1
  28. package/src/lib/app-utils/health-report.d.ts +3 -2
  29. package/src/lib/app-utils/health-report.js +22 -2
  30. package/src/lib/app-utils/health-report.js.map +1 -1
  31. package/src/lib/app-utils/native-nav.d.ts +39 -4
  32. package/src/lib/app-utils/native-nav.js +69 -3
  33. package/src/lib/app-utils/native-nav.js.map +1 -1
  34. package/src/lib/app-utils/native-themes.d.ts +28 -17
  35. package/src/lib/app-utils/native-themes.js +123 -45
  36. package/src/lib/app-utils/native-themes.js.map +1 -1
  37. package/src/lib/app-utils/operator-alerts.js +4 -2
  38. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  39. package/src/lib/app-utils/org-permission-editor.d.ts +75 -0
  40. package/src/lib/app-utils/org-permission-editor.js +124 -0
  41. package/src/lib/app-utils/org-permission-editor.js.map +1 -0
  42. package/src/lib/app-utils/org-permissions.d.ts +36 -0
  43. package/src/lib/app-utils/org-permissions.js +78 -26
  44. package/src/lib/app-utils/org-permissions.js.map +1 -1
  45. package/src/lib/app-utils/org-role-grants.d.ts +59 -9
  46. package/src/lib/app-utils/org-role-grants.js +97 -10
  47. package/src/lib/app-utils/org-role-grants.js.map +1 -1
  48. package/src/lib/app-utils/plan-entitlements.d.ts +13 -0
  49. package/src/lib/app-utils/plan-entitlements.js +22 -0
  50. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  51. package/src/lib/app-utils/release-flags.d.ts +52 -1
  52. package/src/lib/app-utils/release-flags.js +68 -2
  53. package/src/lib/app-utils/release-flags.js.map +1 -1
  54. package/src/lib/app-utils/reusable-component-keys.d.ts +13 -0
  55. package/src/lib/app-utils/reusable-component-keys.js +12 -0
  56. package/src/lib/app-utils/reusable-component-keys.js.map +1 -1
  57. package/src/lib/app-utils/screen-analytics-aggregate.d.ts +34 -0
  58. package/src/lib/app-utils/screen-analytics-aggregate.js +37 -0
  59. package/src/lib/app-utils/screen-analytics-aggregate.js.map +1 -1
  60. package/src/lib/app-utils/site-care.d.ts +35 -0
  61. package/src/lib/app-utils/site-care.js +48 -0
  62. package/src/lib/app-utils/site-care.js.map +1 -1
  63. package/src/lib/app-utils/site-font-pairings.d.ts +59 -0
  64. package/src/lib/app-utils/site-font-pairings.js +514 -0
  65. package/src/lib/app-utils/site-font-pairings.js.map +1 -0
  66. package/src/lib/app-utils/site-starter.d.ts +136 -0
  67. package/src/lib/app-utils/site-starter.js +116 -0
  68. package/src/lib/app-utils/site-starter.js.map +1 -0
  69. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.d.ts +95 -0
  70. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js +333 -0
  71. package/src/lib/app-utils/stock-photos/stock-photo-picker-server.js.map +1 -0
  72. package/src/lib/app-utils/stock-photos/stock-photo-picker.d.ts +188 -0
  73. package/src/lib/app-utils/stock-photos/stock-photo-picker.js +193 -0
  74. package/src/lib/app-utils/stock-photos/stock-photo-picker.js.map +1 -0
  75. package/src/lib/plugin-manager/feature-plugins.d.ts +32 -0
  76. package/src/lib/plugin-manager/feature-plugins.js +12 -0
  77. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  78. package/src/lib/plugin-manager/first-party-plugins.generated.js +27 -1
  79. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  80. package/src/lib/plugin-manager/plugin-entitlements.d.ts +37 -0
  81. package/src/lib/plugin-manager/plugin-entitlements.js +84 -2
  82. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  83. package/src/lib/plugin-manager/plugin-media-ingest.d.ts +15 -0
  84. package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
  85. package/src/lib/plugin-manager/plugin-permissions.d.ts +7 -0
  86. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-product-catalog.d.ts +12 -0
  88. package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -1
  89. package/src/lib/plugin-manager/plugin-site-starters.d.ts +73 -0
  90. package/src/lib/plugin-manager/plugin-site-starters.js +30 -0
  91. package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -0
  92. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +8 -1
  93. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -1
  94. package/src/lib/plugin-manager/stock-photo-provider.d.ts +12 -0
  95. package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -1
@@ -42,7 +42,8 @@
42
42
  *
43
43
  * Only what a sitemap needs and a query can express: one equality filter, the
44
44
  * setting that must be set for the pages to render at all, the address with
45
- * `{slug}` in it, a field that drops a row (a soft delete), and the date
45
+ * `{slug}` in it, a field that drops a row (a soft delete), a switch whose
46
+ * `false` drops one (a page turned off), and the date
46
47
  * fields to try in order. A row without a slug addresses nothing and is left
47
48
  * out. Anything richer belongs to the plugin's own page, not to its sitemap.
48
49
  *
@@ -73,6 +74,12 @@ export interface PluginSitemapSectionDeclaration {
73
74
  slugField?: string;
74
75
  /** A field whose truthy value leaves the row out, for a soft delete the filter cannot see. */
75
76
  skipWhen?: string;
77
+ /**
78
+ * A field whose `false` leaves the row out: a switch that is on unless it
79
+ * was turned off, so an absent value keeps the row — a product sold in
80
+ * person only (AGL-4060). A query cannot ask for "absent or true".
81
+ */
82
+ skipWhenFalse?: string;
76
83
  /** The fields a row's `lastmod` is read from, first present wins. */
77
84
  lastmod?: readonly string[];
78
85
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-sitemap-sections.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\nimport { PLUGIN_SITEMAP_SECTIONS_DECLARED } from './first-party-plugins.generated'\n\n/**\n * A child sitemap a plugin's documents fill, declared by that plugin\n * (AGL-3080).\n *\n * The tenant's `/sitemap.xml` is an index over one child per section (AGL-2520):\n * the site's pages, its content collections and its authors are the platform's\n * and are built by the route itself. A plugin that serves pages of its own — a\n * product at `/products/{slug}` — had its section written into that route by\n * hand: the collection, the filter, the store setting that switches it on, the\n * address and the date field were all core's knowledge of one plugin's model.\n *\n * So the plugin declares the section, and the route builds every declared one\n * the same way: counted for the index, paged by document id for the child.\n *\n * ## Compiled, because the reader is a published page's\n *\n * The sitemap is what a search engine is told the site holds. A section\n * registered at runtime would be missing from any process that had not loaded\n * the plugin, and a crawler reads a missing section as pages that no longer\n * exist — the products drop out of the index silently, on a live site. So\n * sections are compiled from `plugins.config.json` by\n * `tools/scripts/generate-plugin-manifests.mjs`, present the moment this\n * module is imported, and there is no registrar.\n *\n * ## What a declaration can say\n *\n * Only what a sitemap needs and a query can express: one equality filter, the\n * setting that must be set for the pages to render at all, the address with\n * `{slug}` in it, a field that drops a row (a soft delete), and the date\n * fields to try in order. A row without a slug addresses nothing and is left\n * out. Anything richer belongs to the plugin's own page, not to its sitemap.\n *\n * Reached by its own subpath, never the barrel.\n */\nexport interface PluginSitemapSectionDeclaration {\n /** The section's path segment: `/sitemaps/{section}/{page}.xml`. */\n section: string\n /** The subcollection under `hosts/{hostId}` whose documents are the pages. */\n collection: string\n /** The one equality a document must meet to be a page. */\n where?: { field: string; equals: string | number | boolean }\n /**\n * A `collection/doc` under the host and a field on it that must be set for\n * the section to exist: the template the pages render through. Unset, the\n * section is left out rather than listing addresses that 404.\n */\n enabledBy?: { doc: string; field: string }\n /** The page's site path, with `{slug}` standing for the document's slug. */\n path: string\n /** The field holding the slug; `slug` when absent. */\n slugField?: string\n /** A field whose truthy value leaves the row out, for a soft delete the filter cannot see. */\n skipWhen?: string\n /** The fields a row's `lastmod` is read from, first present wins. */\n lastmod?: readonly string[]\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginSitemapSection = PluginSitemapSectionDeclaration & {\n pluginId: string\n}\n\n/** Every declared section, in config order — the order the index lists them. */\nexport function listPluginSitemapSections(): readonly ResolvedPluginSitemapSection[] {\n return PLUGIN_SITEMAP_SECTIONS_DECLARED\n}\n\n/** One declared section by its name, or `null`. */\nexport function pluginSitemapSection(\n section: string,\n): ResolvedPluginSitemapSection | null {\n return PLUGIN_SITEMAP_SECTIONS_DECLARED.find((one) => one.section === section) ?? null\n}\n\n/** The site path of one row, or `null` for a row with no slug. */\nexport function pluginSitemapSectionPath(\n declaration: PluginSitemapSectionDeclaration,\n slug: unknown,\n): string | null {\n if (typeof slug !== 'string' || !slug) return null\n return declaration.path.replace('{slug}', slug)\n}\n"],"names":["PLUGIN_SITEMAP_SECTIONS_DECLARED","listPluginSitemapSections","pluginSitemapSection","section","find","one","pluginSitemapSectionPath","declaration","slug","path","replace"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gCAAgC,QAAQ,qCAAiC;AAgElF,8EAA8E,GAC9E,OAAO,SAASC;IACd,OAAOD;AACT;AAEA,iDAAiD,GACjD,OAAO,SAASE,qBACdC,OAAe;QAERH;IAAP,QAAOA,yCAAAA,iCAAiCI,IAAI,CAAC,CAACC,MAAQA,IAAIF,OAAO,KAAKA,oBAA/DH,yCAA2E;AACpF;AAEA,gEAAgE,GAChE,OAAO,SAASM,yBACdC,WAA4C,EAC5CC,IAAa;IAEb,IAAI,OAAOA,SAAS,YAAY,CAACA,MAAM,OAAO;IAC9C,OAAOD,YAAYE,IAAI,CAACC,OAAO,CAAC,UAAUF;AAC5C"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-sitemap-sections.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\nimport { PLUGIN_SITEMAP_SECTIONS_DECLARED } from './first-party-plugins.generated'\n\n/**\n * A child sitemap a plugin's documents fill, declared by that plugin\n * (AGL-3080).\n *\n * The tenant's `/sitemap.xml` is an index over one child per section (AGL-2520):\n * the site's pages, its content collections and its authors are the platform's\n * and are built by the route itself. A plugin that serves pages of its own — a\n * product at `/products/{slug}` — had its section written into that route by\n * hand: the collection, the filter, the store setting that switches it on, the\n * address and the date field were all core's knowledge of one plugin's model.\n *\n * So the plugin declares the section, and the route builds every declared one\n * the same way: counted for the index, paged by document id for the child.\n *\n * ## Compiled, because the reader is a published page's\n *\n * The sitemap is what a search engine is told the site holds. A section\n * registered at runtime would be missing from any process that had not loaded\n * the plugin, and a crawler reads a missing section as pages that no longer\n * exist — the products drop out of the index silently, on a live site. So\n * sections are compiled from `plugins.config.json` by\n * `tools/scripts/generate-plugin-manifests.mjs`, present the moment this\n * module is imported, and there is no registrar.\n *\n * ## What a declaration can say\n *\n * Only what a sitemap needs and a query can express: one equality filter, the\n * setting that must be set for the pages to render at all, the address with\n * `{slug}` in it, a field that drops a row (a soft delete), a switch whose\n * `false` drops one (a page turned off), and the date\n * fields to try in order. A row without a slug addresses nothing and is left\n * out. Anything richer belongs to the plugin's own page, not to its sitemap.\n *\n * Reached by its own subpath, never the barrel.\n */\nexport interface PluginSitemapSectionDeclaration {\n /** The section's path segment: `/sitemaps/{section}/{page}.xml`. */\n section: string\n /** The subcollection under `hosts/{hostId}` whose documents are the pages. */\n collection: string\n /** The one equality a document must meet to be a page. */\n where?: { field: string; equals: string | number | boolean }\n /**\n * A `collection/doc` under the host and a field on it that must be set for\n * the section to exist: the template the pages render through. Unset, the\n * section is left out rather than listing addresses that 404.\n */\n enabledBy?: { doc: string; field: string }\n /** The page's site path, with `{slug}` standing for the document's slug. */\n path: string\n /** The field holding the slug; `slug` when absent. */\n slugField?: string\n /** A field whose truthy value leaves the row out, for a soft delete the filter cannot see. */\n skipWhen?: string\n /**\n * A field whose `false` leaves the row out: a switch that is on unless it\n * was turned off, so an absent value keeps the row — a product sold in\n * person only (AGL-4060). A query cannot ask for \"absent or true\".\n */\n skipWhenFalse?: string\n /** The fields a row's `lastmod` is read from, first present wins. */\n lastmod?: readonly string[]\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginSitemapSection = PluginSitemapSectionDeclaration & {\n pluginId: string\n}\n\n/** Every declared section, in config order — the order the index lists them. */\nexport function listPluginSitemapSections(): readonly ResolvedPluginSitemapSection[] {\n return PLUGIN_SITEMAP_SECTIONS_DECLARED\n}\n\n/** One declared section by its name, or `null`. */\nexport function pluginSitemapSection(\n section: string,\n): ResolvedPluginSitemapSection | null {\n return PLUGIN_SITEMAP_SECTIONS_DECLARED.find((one) => one.section === section) ?? null\n}\n\n/** The site path of one row, or `null` for a row with no slug. */\nexport function pluginSitemapSectionPath(\n declaration: PluginSitemapSectionDeclaration,\n slug: unknown,\n): string | null {\n if (typeof slug !== 'string' || !slug) return null\n return declaration.path.replace('{slug}', slug)\n}\n"],"names":["PLUGIN_SITEMAP_SECTIONS_DECLARED","listPluginSitemapSections","pluginSitemapSection","section","find","one","pluginSitemapSectionPath","declaration","slug","path","replace"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gCAAgC,QAAQ,qCAAiC;AAuElF,8EAA8E,GAC9E,OAAO,SAASC;IACd,OAAOD;AACT;AAEA,iDAAiD,GACjD,OAAO,SAASE,qBACdC,OAAe;QAERH;IAAP,QAAOA,yCAAAA,iCAAiCI,IAAI,CAAC,CAACC,MAAQA,IAAIF,OAAO,KAAKA,oBAA/DH,yCAA2E;AACpF;AAEA,gEAAgE,GAChE,OAAO,SAASM,yBACdC,WAA4C,EAC5CC,IAAa;IAEb,IAAI,OAAOA,SAAS,YAAY,CAACA,MAAM,OAAO;IAC9C,OAAOD,YAAYE,IAAI,CAACC,OAAO,CAAC,UAAUF;AAC5C"}
@@ -120,6 +120,18 @@ export interface StockPhotoProvider {
120
120
  }): Promise<StockPhotoDownload | null>;
121
121
  /** How an asset copied from `photo` credits it. */
122
122
  credit(photo: StockPhoto): StockPhotoCredit;
123
+ /**
124
+ * A small copy of the photo for showing it as a search result (AGL-4066),
125
+ * fetched only from the library's own image hosts, or `null` when none can
126
+ * be had within `maxBytes` and the signal. A server process asks for it and
127
+ * hands the bytes to the member choosing, so a browser never loads an
128
+ * address at the library. Optional: a provider without it shows no
129
+ * thumbnails, and a placed photo is always the copy {@link download} fetched.
130
+ */
131
+ preview?(photo: StockPhoto, options: {
132
+ maxBytes: number;
133
+ signal?: AbortSignal;
134
+ }): Promise<StockPhotoDownload | null>;
123
135
  }
124
136
  /**
125
137
  * Many plugins may each register a library; a caller is handed the first
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/stock-photo-provider.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\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * STOCK PHOTOS (AGL-3660): a library of licensed photos outside this\n * platform that a server process searches and copies from.\n *\n * Core defines the contract and never names a library. A plugin registers a\n * provider from its server declarations, and a caller — the AI site build is\n * the first — asks for {@link stockPhotoProvider} and gets the first one\n * this deployment configured, or `null`. Nothing here stores a photo: a\n * caller that keeps one copies its bytes into the site's own media library\n * (`plugin-media-ingest.ts`), because a page that names a library's own\n * address would hotlink it, and most libraries' terms refuse that.\n *\n * What a provider owes its library's terms — caching searches, its rate\n * limit, the hosts it may fetch from — it keeps inside itself, so a caller\n * cannot get them wrong. What it owes the caller is {@link\n * StockPhotoProvider.credit}: the words and links the asset records about\n * where it came from, and whether the library requires them shown.\n *\n * A provider that is registered and not configured (its key unset) answers\n * as absent, so a deployment without the key behaves as one without the\n * plugin.\n */\n\n/** The shape a caller wants; a provider maps it onto its own filter. */\nexport type StockPhotoOrientation = 'horizontal' | 'vertical' | 'any'\n\nexport interface StockPhotoSearchRequest {\n /** Plain search words, at most {@link STOCK_PHOTO_QUERY_MAX_CHARS}. */\n query: string\n orientation?: StockPhotoOrientation\n /** The smallest original a hit may have, in pixels. */\n minWidth?: number\n minHeight?: number\n /** A hint that the photo should show people, where the library can filter by it. */\n people?: boolean\n}\n\n/** The longest query a caller sends; libraries refuse or truncate longer ones. */\nexport const STOCK_PHOTO_QUERY_MAX_CHARS = 100\n\n/** One photo a search found. */\nexport interface StockPhoto {\n /** The registering provider's id. */\n provider: string\n /** The library's own id for the photo, stable across searches. */\n id: string\n /** The size of the copy {@link StockPhotoProvider.download} fetches. */\n width: number\n height: number\n /** The photo's page at the library, for the credit. */\n pageUrl: string\n /** The contributor's name as the library shows it. */\n photographer: string\n /** The contributor's page at the library, when it has one. */\n photographerUrl?: string\n /** The library's tags, lowercase. */\n tags: string[]\n /**\n * The library's own description of the photo, where it writes one (a\n * Pexels hit's `alt`). A caller ranks hits by it with the tags.\n */\n alt?: string\n}\n\n/** What an asset copied from a library records about where it came from. */\nexport interface StockPhotoCredit {\n /** The library's name, as its terms ask it written. */\n providerLabel: string\n /** The license the photo is used under. */\n license: string\n licenseUrl: string\n /** Whether the license requires the credit shown wherever the photo is. */\n attributionRequired: boolean\n /** A sentence crediting the photo, for the asset's description. */\n text: string\n}\n\nexport interface StockPhotoSearchResult {\n photos: StockPhoto[]\n /** Whether the answer came from the provider's own cache. */\n cached: boolean\n}\n\nexport interface StockPhotoDownload {\n bytes: Uint8Array\n /** `image/jpeg`, `image/png` or `image/webp`. */\n contentType: string\n}\n\nexport interface StockPhotoProvider {\n /** A stable id, the prefix of every source key: `pixabay`. */\n id: string\n /** The library's name, in customer copy. */\n label: string\n /** Whether this deployment holds what the provider needs to search. */\n isConfigured(): boolean\n /**\n * The photos matching `request`, best first. `null` when the provider\n * cannot ask right now — its rate limit is spent, or the library failed —\n * which a caller treats as \"no photo\", never as an error to retry.\n */\n search(\n request: StockPhotoSearchRequest,\n options?: { signal?: AbortSignal },\n ): Promise<StockPhotoSearchResult | null>\n /**\n * The photo's bytes, fetched only from the library's own image hosts, or\n * `null` when they cannot be had within `maxBytes` and the signal.\n */\n download(\n photo: StockPhoto,\n options: { maxBytes: number; signal?: AbortSignal },\n ): Promise<StockPhotoDownload | null>\n /** How an asset copied from `photo` credits it. */\n credit(photo: StockPhoto): StockPhotoCredit\n}\n\n/**\n * Many plugins may each register a library; a caller is handed the first\n * configured one, highest priority first.\n */\nexport const STOCK_PHOTO_PROVIDERS = definePluginServiceContract<StockPhotoProvider>(\n 'core.stock-photos',\n { multiple: true },\n)\n\n/** The key an asset copied from a library is found again by: `{provider}:{id}`. */\nexport function stockPhotoSourceKey(photo: Pick<StockPhoto, 'provider' | 'id'>): string {\n return `${photo.provider}:${photo.id}`\n}\n\n/**\n * Registers a library. The owner is the loader's marker when a register fn\n * is running, else `options.pluginId`; the provider's own id tells two\n * libraries of one plugin apart.\n */\nexport function registerStockPhotoProvider(\n provider: StockPhotoProvider,\n options?: { pluginId?: string; priority?: number },\n): void {\n if (!provider?.id?.trim()) throw new Error('a stock photo provider needs an id')\n registerPluginService(STOCK_PHOTO_PROVIDERS, provider, {\n key: provider.id,\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n })\n}\n\n/**\n * The first registered library this deployment configured, or `null`.\n * Synchronous and free of I/O. A provider whose configuration check throws\n * answers as unconfigured: a stock photo is decoration, and its failure\n * leaves the caller's own fallback standing.\n */\nexport function stockPhotoProvider(): StockPhotoProvider | null {\n for (const { impl } of resolvePluginServices(STOCK_PHOTO_PROVIDERS)) {\n try {\n if (impl.isConfigured()) return impl\n } catch {\n // Unconfigured, as the note says.\n }\n }\n return null\n}\n\n/**\n * A PHOTO FOR A SUBJECT, KEPT IN THE SITE'S LIBRARY (AGL-3755).\n *\n * The providers above answer searches; choosing well among the hits is\n * work of its own — subject nouns rather than whole sentences, hits scored\n * by how their tags meet the subject and the business's world, nothing\n * placed twice in one act, the business's own world when the subject finds\n * nothing, and a starter photo when the library is down — and it lives in\n * the plugin that learned it on live site starts (the AI plugin). A plugin's\n * draft writer that wants a picture (an event's cover) asks\n * {@link stockPhotoPicker} for the deployment's one picker and gets a photo\n * the site can serve, or `null` where no picker is registered. It never\n * imports the plugin that picks.\n *\n * The picker copies a stock photo into the site's own library as the member\n * named, through `core.media-ingest`, and only once: a photo the library\n * already holds (by its source key) is reused, never stored a second time.\n */\nexport interface StockPhotoPickRequest {\n hostId: string\n /** The member the photo is stored as; their access, the site's lockdown and the plan's storage are checked as for an upload of theirs. */\n uid: string\n /** What the photo shows, in plain words: an event's title, a product's name. The picker reads the subject's nouns out of it. */\n subject: string\n /** The business in its own words (\"a sourdough bakery\"), which holds the photo to its world and is searched when the subject finds nothing. */\n business?: string\n /** The asset's description in the library; the subject where absent. */\n alt?: string\n /** The frame the photo fills; `horizontal` where absent. */\n orientation?: StockPhotoOrientation\n /**\n * The act the photo is one of (an AI job's id): no photo is placed twice\n * within it. Absent, only the site library's own reuse applies.\n */\n scope?: string\n /** Turns the choice among equally good photos; the same seed picks the same photo again. */\n seed?: string\n /** Source keys or asset srcs the act already shows. */\n avoid?: readonly string[]\n signal?: AbortSignal\n}\n\nexport interface StockPhotoPick {\n /** What an `image` node's `src` holds: the asset's media reference, or a starter photo's path. */\n src: string\n /** An address an `<img src>` or an image-address field takes as it stands: site-relative. */\n url: string\n width: number\n height: number\n /**\n * `library`: a stock photo in the site's own media library, copied there\n * now or reused. `starter`: one of the platform's starter photos, the\n * picture of last resort when the library is down or found nothing.\n */\n origin: 'library' | 'starter'\n /**\n * What the photo shows, in words a page's `alt` takes as they stand\n * (AGL-3856): the photo's own where the subject's words do not describe it.\n */\n alt?: string\n}\n\nexport interface StockPhotoPicker {\n /** A photo for the subject, or `null` when none can be had. Never throws. */\n pick(request: StockPhotoPickRequest): Promise<StockPhotoPick | null>\n}\n\n/** One picker per deployment: the plugin whose choosing it is registers it. */\nexport const STOCK_PHOTO_PICKER = definePluginServiceContract<StockPhotoPicker>(\n 'core.stock-photo-picker',\n { multiple: false },\n)\n\n/** Registers the deployment's picker; registering again replaces it. */\nexport function registerStockPhotoPicker(\n picker: StockPhotoPicker,\n options?: { pluginId?: string },\n): void {\n if (typeof picker?.pick !== 'function') throw new Error('a stock photo picker needs pick')\n registerPluginService(STOCK_PHOTO_PICKER, picker, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered picker, or `null` in a process that registered none. */\nexport function stockPhotoPicker(): StockPhotoPicker | null {\n return resolvePluginService(STOCK_PHOTO_PICKER) ?? null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","resolvePluginServices","STOCK_PHOTO_QUERY_MAX_CHARS","STOCK_PHOTO_PROVIDERS","multiple","stockPhotoSourceKey","photo","provider","id","registerStockPhotoProvider","options","trim","Error","key","pluginId","priority","undefined","stockPhotoProvider","impl","isConfigured","STOCK_PHOTO_PICKER","registerStockPhotoPicker","picker","pick","stockPhotoPicker"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,EACpBC,qBAAqB,QAChB,uBAAmB;AAuC1B,gFAAgF,GAChF,OAAO,MAAMC,8BAA8B,IAAG;AA+E9C;;;CAGC,GACD,OAAO,MAAMC,wBAAwBL,4BACnC,qBACA;IAAEM,UAAU;AAAK,GAClB;AAED,iFAAiF,GACjF,OAAO,SAASC,oBAAoBC,KAA0C;IAC5E,OAAO,GAAGA,MAAMC,QAAQ,CAAC,CAAC,EAAED,MAAME,EAAE,EAAE;AACxC;AAEA;;;;CAIC,GACD,OAAO,SAASC,2BACdF,QAA4B,EAC5BG,OAAkD;QAE7CH;IAAL,IAAI,EAACA,6BAAAA,eAAAA,SAAUC,EAAE,qBAAZD,aAAcI,IAAI,KAAI,MAAM,IAAIC,MAAM;IAC3Cb,sBAAsBI,uBAAuBI,UAAU;QACrDM,KAAKN,SAASC,EAAE;OACZE,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC,GACtDJ,CAAAA,2BAAAA,QAASK,QAAQ,MAAKC,YAAY;QAAED,UAAUL,QAAQK,QAAQ;IAAC,IAAI,CAAC;AAE5E;AAEA;;;;;CAKC,GACD,OAAO,SAASE;IACd,KAAK,MAAM,EAAEC,IAAI,EAAE,IAAIjB,sBAAsBE,uBAAwB;QACnE,IAAI;YACF,IAAIe,KAAKC,YAAY,IAAI,OAAOD;QAClC,EAAE,eAAM;QACN,kCAAkC;QACpC;IACF;IACA,OAAO;AACT;AAqEA,6EAA6E,GAC7E,OAAO,MAAME,qBAAqBtB,4BAChC,2BACA;IAAEM,UAAU;AAAM,GACnB;AAED,sEAAsE,GACtE,OAAO,SAASiB,yBACdC,MAAwB,EACxBZ,OAA+B;IAE/B,IAAI,QAAOY,0BAAAA,OAAQC,IAAI,MAAK,YAAY,MAAM,IAAIX,MAAM;IACxDb,sBAAsBqB,oBAAoBE,QAAQ,aAC5CZ,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,wEAAwE,GACxE,OAAO,SAASU;QACPxB;IAAP,QAAOA,wBAAAA,qBAAqBoB,+BAArBpB,wBAA4C;AACrD"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/stock-photo-provider.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\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * STOCK PHOTOS (AGL-3660): a library of licensed photos outside this\n * platform that a server process searches and copies from.\n *\n * Core defines the contract and never names a library. A plugin registers a\n * provider from its server declarations, and a caller — the AI site build is\n * the first — asks for {@link stockPhotoProvider} and gets the first one\n * this deployment configured, or `null`. Nothing here stores a photo: a\n * caller that keeps one copies its bytes into the site's own media library\n * (`plugin-media-ingest.ts`), because a page that names a library's own\n * address would hotlink it, and most libraries' terms refuse that.\n *\n * What a provider owes its library's terms — caching searches, its rate\n * limit, the hosts it may fetch from — it keeps inside itself, so a caller\n * cannot get them wrong. What it owes the caller is {@link\n * StockPhotoProvider.credit}: the words and links the asset records about\n * where it came from, and whether the library requires them shown.\n *\n * A provider that is registered and not configured (its key unset) answers\n * as absent, so a deployment without the key behaves as one without the\n * plugin.\n */\n\n/** The shape a caller wants; a provider maps it onto its own filter. */\nexport type StockPhotoOrientation = 'horizontal' | 'vertical' | 'any'\n\nexport interface StockPhotoSearchRequest {\n /** Plain search words, at most {@link STOCK_PHOTO_QUERY_MAX_CHARS}. */\n query: string\n orientation?: StockPhotoOrientation\n /** The smallest original a hit may have, in pixels. */\n minWidth?: number\n minHeight?: number\n /** A hint that the photo should show people, where the library can filter by it. */\n people?: boolean\n}\n\n/** The longest query a caller sends; libraries refuse or truncate longer ones. */\nexport const STOCK_PHOTO_QUERY_MAX_CHARS = 100\n\n/** One photo a search found. */\nexport interface StockPhoto {\n /** The registering provider's id. */\n provider: string\n /** The library's own id for the photo, stable across searches. */\n id: string\n /** The size of the copy {@link StockPhotoProvider.download} fetches. */\n width: number\n height: number\n /** The photo's page at the library, for the credit. */\n pageUrl: string\n /** The contributor's name as the library shows it. */\n photographer: string\n /** The contributor's page at the library, when it has one. */\n photographerUrl?: string\n /** The library's tags, lowercase. */\n tags: string[]\n /**\n * The library's own description of the photo, where it writes one (a\n * Pexels hit's `alt`). A caller ranks hits by it with the tags.\n */\n alt?: string\n}\n\n/** What an asset copied from a library records about where it came from. */\nexport interface StockPhotoCredit {\n /** The library's name, as its terms ask it written. */\n providerLabel: string\n /** The license the photo is used under. */\n license: string\n licenseUrl: string\n /** Whether the license requires the credit shown wherever the photo is. */\n attributionRequired: boolean\n /** A sentence crediting the photo, for the asset's description. */\n text: string\n}\n\nexport interface StockPhotoSearchResult {\n photos: StockPhoto[]\n /** Whether the answer came from the provider's own cache. */\n cached: boolean\n}\n\nexport interface StockPhotoDownload {\n bytes: Uint8Array\n /** `image/jpeg`, `image/png` or `image/webp`. */\n contentType: string\n}\n\nexport interface StockPhotoProvider {\n /** A stable id, the prefix of every source key: `pixabay`. */\n id: string\n /** The library's name, in customer copy. */\n label: string\n /** Whether this deployment holds what the provider needs to search. */\n isConfigured(): boolean\n /**\n * The photos matching `request`, best first. `null` when the provider\n * cannot ask right now — its rate limit is spent, or the library failed —\n * which a caller treats as \"no photo\", never as an error to retry.\n */\n search(\n request: StockPhotoSearchRequest,\n options?: { signal?: AbortSignal },\n ): Promise<StockPhotoSearchResult | null>\n /**\n * The photo's bytes, fetched only from the library's own image hosts, or\n * `null` when they cannot be had within `maxBytes` and the signal.\n */\n download(\n photo: StockPhoto,\n options: { maxBytes: number; signal?: AbortSignal },\n ): Promise<StockPhotoDownload | null>\n /** How an asset copied from `photo` credits it. */\n credit(photo: StockPhoto): StockPhotoCredit\n /**\n * A small copy of the photo for showing it as a search result (AGL-4066),\n * fetched only from the library's own image hosts, or `null` when none can\n * be had within `maxBytes` and the signal. A server process asks for it and\n * hands the bytes to the member choosing, so a browser never loads an\n * address at the library. Optional: a provider without it shows no\n * thumbnails, and a placed photo is always the copy {@link download} fetched.\n */\n preview?(\n photo: StockPhoto,\n options: { maxBytes: number; signal?: AbortSignal },\n ): Promise<StockPhotoDownload | null>\n}\n\n/**\n * Many plugins may each register a library; a caller is handed the first\n * configured one, highest priority first.\n */\nexport const STOCK_PHOTO_PROVIDERS = definePluginServiceContract<StockPhotoProvider>(\n 'core.stock-photos',\n { multiple: true },\n)\n\n/** The key an asset copied from a library is found again by: `{provider}:{id}`. */\nexport function stockPhotoSourceKey(photo: Pick<StockPhoto, 'provider' | 'id'>): string {\n return `${photo.provider}:${photo.id}`\n}\n\n/**\n * Registers a library. The owner is the loader's marker when a register fn\n * is running, else `options.pluginId`; the provider's own id tells two\n * libraries of one plugin apart.\n */\nexport function registerStockPhotoProvider(\n provider: StockPhotoProvider,\n options?: { pluginId?: string; priority?: number },\n): void {\n if (!provider?.id?.trim()) throw new Error('a stock photo provider needs an id')\n registerPluginService(STOCK_PHOTO_PROVIDERS, provider, {\n key: provider.id,\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n })\n}\n\n/**\n * The first registered library this deployment configured, or `null`.\n * Synchronous and free of I/O. A provider whose configuration check throws\n * answers as unconfigured: a stock photo is decoration, and its failure\n * leaves the caller's own fallback standing.\n */\nexport function stockPhotoProvider(): StockPhotoProvider | null {\n for (const { impl } of resolvePluginServices(STOCK_PHOTO_PROVIDERS)) {\n try {\n if (impl.isConfigured()) return impl\n } catch {\n // Unconfigured, as the note says.\n }\n }\n return null\n}\n\n/**\n * A PHOTO FOR A SUBJECT, KEPT IN THE SITE'S LIBRARY (AGL-3755).\n *\n * The providers above answer searches; choosing well among the hits is\n * work of its own — subject nouns rather than whole sentences, hits scored\n * by how their tags meet the subject and the business's world, nothing\n * placed twice in one act, the business's own world when the subject finds\n * nothing, and a starter photo when the library is down — and it lives in\n * the plugin that learned it on live site starts (the AI plugin). A plugin's\n * draft writer that wants a picture (an event's cover) asks\n * {@link stockPhotoPicker} for the deployment's one picker and gets a photo\n * the site can serve, or `null` where no picker is registered. It never\n * imports the plugin that picks.\n *\n * The picker copies a stock photo into the site's own library as the member\n * named, through `core.media-ingest`, and only once: a photo the library\n * already holds (by its source key) is reused, never stored a second time.\n */\nexport interface StockPhotoPickRequest {\n hostId: string\n /** The member the photo is stored as; their access, the site's lockdown and the plan's storage are checked as for an upload of theirs. */\n uid: string\n /** What the photo shows, in plain words: an event's title, a product's name. The picker reads the subject's nouns out of it. */\n subject: string\n /** The business in its own words (\"a sourdough bakery\"), which holds the photo to its world and is searched when the subject finds nothing. */\n business?: string\n /** The asset's description in the library; the subject where absent. */\n alt?: string\n /** The frame the photo fills; `horizontal` where absent. */\n orientation?: StockPhotoOrientation\n /**\n * The act the photo is one of (an AI job's id): no photo is placed twice\n * within it. Absent, only the site library's own reuse applies.\n */\n scope?: string\n /** Turns the choice among equally good photos; the same seed picks the same photo again. */\n seed?: string\n /** Source keys or asset srcs the act already shows. */\n avoid?: readonly string[]\n signal?: AbortSignal\n}\n\nexport interface StockPhotoPick {\n /** What an `image` node's `src` holds: the asset's media reference, or a starter photo's path. */\n src: string\n /** An address an `<img src>` or an image-address field takes as it stands: site-relative. */\n url: string\n width: number\n height: number\n /**\n * `library`: a stock photo in the site's own media library, copied there\n * now or reused. `starter`: one of the platform's starter photos, the\n * picture of last resort when the library is down or found nothing.\n */\n origin: 'library' | 'starter'\n /**\n * What the photo shows, in words a page's `alt` takes as they stand\n * (AGL-3856): the photo's own where the subject's words do not describe it.\n */\n alt?: string\n}\n\nexport interface StockPhotoPicker {\n /** A photo for the subject, or `null` when none can be had. Never throws. */\n pick(request: StockPhotoPickRequest): Promise<StockPhotoPick | null>\n}\n\n/** One picker per deployment: the plugin whose choosing it is registers it. */\nexport const STOCK_PHOTO_PICKER = definePluginServiceContract<StockPhotoPicker>(\n 'core.stock-photo-picker',\n { multiple: false },\n)\n\n/** Registers the deployment's picker; registering again replaces it. */\nexport function registerStockPhotoPicker(\n picker: StockPhotoPicker,\n options?: { pluginId?: string },\n): void {\n if (typeof picker?.pick !== 'function') throw new Error('a stock photo picker needs pick')\n registerPluginService(STOCK_PHOTO_PICKER, picker, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered picker, or `null` in a process that registered none. */\nexport function stockPhotoPicker(): StockPhotoPicker | null {\n return resolvePluginService(STOCK_PHOTO_PICKER) ?? null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","resolvePluginServices","STOCK_PHOTO_QUERY_MAX_CHARS","STOCK_PHOTO_PROVIDERS","multiple","stockPhotoSourceKey","photo","provider","id","registerStockPhotoProvider","options","trim","Error","key","pluginId","priority","undefined","stockPhotoProvider","impl","isConfigured","STOCK_PHOTO_PICKER","registerStockPhotoPicker","picker","pick","stockPhotoPicker"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,EACpBC,qBAAqB,QAChB,uBAAmB;AAuC1B,gFAAgF,GAChF,OAAO,MAAMC,8BAA8B,IAAG;AA2F9C;;;CAGC,GACD,OAAO,MAAMC,wBAAwBL,4BACnC,qBACA;IAAEM,UAAU;AAAK,GAClB;AAED,iFAAiF,GACjF,OAAO,SAASC,oBAAoBC,KAA0C;IAC5E,OAAO,GAAGA,MAAMC,QAAQ,CAAC,CAAC,EAAED,MAAME,EAAE,EAAE;AACxC;AAEA;;;;CAIC,GACD,OAAO,SAASC,2BACdF,QAA4B,EAC5BG,OAAkD;QAE7CH;IAAL,IAAI,EAACA,6BAAAA,eAAAA,SAAUC,EAAE,qBAAZD,aAAcI,IAAI,KAAI,MAAM,IAAIC,MAAM;IAC3Cb,sBAAsBI,uBAAuBI,UAAU;QACrDM,KAAKN,SAASC,EAAE;OACZE,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC,GACtDJ,CAAAA,2BAAAA,QAASK,QAAQ,MAAKC,YAAY;QAAED,UAAUL,QAAQK,QAAQ;IAAC,IAAI,CAAC;AAE5E;AAEA;;;;;CAKC,GACD,OAAO,SAASE;IACd,KAAK,MAAM,EAAEC,IAAI,EAAE,IAAIjB,sBAAsBE,uBAAwB;QACnE,IAAI;YACF,IAAIe,KAAKC,YAAY,IAAI,OAAOD;QAClC,EAAE,eAAM;QACN,kCAAkC;QACpC;IACF;IACA,OAAO;AACT;AAqEA,6EAA6E,GAC7E,OAAO,MAAME,qBAAqBtB,4BAChC,2BACA;IAAEM,UAAU;AAAM,GACnB;AAED,sEAAsE,GACtE,OAAO,SAASiB,yBACdC,MAAwB,EACxBZ,OAA+B;IAE/B,IAAI,QAAOY,0BAAAA,OAAQC,IAAI,MAAK,YAAY,MAAM,IAAIX,MAAM;IACxDb,sBAAsBqB,oBAAoBE,QAAQ,aAC5CZ,CAAAA,2BAAAA,QAASI,QAAQ,IAAG;QAAEA,UAAUJ,QAAQI,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,wEAAwE,GACxE,OAAO,SAASU;QACPxB;IAAP,QAAOA,wBAAAA,qBAAqBoB,+BAArBpB,wBAA4C;AACrD"}