@aglyn/plugins-marketplace 1.15.0 → 1.15.2

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 (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
@@ -0,0 +1,46 @@
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
+ import type { EditorPluginConnections } from '@aglyn/aglyn/app-utils/plugin-connection-editor';
18
+ import type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records';
19
+ import { type PluginConnectionDeclaration } from '@aglyn/aglyn/app-utils/plugin-connections';
20
+ /** A realm install as far as the editor needs it: the listing and its namespace. */
21
+ export interface EditorRealmInstall {
22
+ listingId: string;
23
+ identity?: string;
24
+ }
25
+ /** One installed listing's declared connections that sync, from its pin. */
26
+ export interface EditorListingConnections {
27
+ listingId: string;
28
+ connections: PluginConnectionDeclaration[];
29
+ }
30
+ /** The connections a pin's manifest declares that run a sync: only those write a record. */
31
+ export declare function syncingConnectionsOf(manifest: unknown): PluginConnectionDeclaration[];
32
+ /** A declared field name as a person reads it: `reviewCount` → `Review count`. */
33
+ export declare function connectionFieldLabel(field: string): string;
34
+ /** The listing whose namespace `componentId` is in, longest identity first. */
35
+ export declare function listingForComponent(installs: readonly EditorRealmInstall[]): (componentId: string) => string | null;
36
+ /**
37
+ * Core's `EditorPluginConnections` for a site: which listing a component is
38
+ * whose, each listing's records, and one variable per declared summary
39
+ * field, named for a person and holding what the last sync wrote (or `null`
40
+ * before one, so an author can bind it before the first sync).
41
+ */
42
+ export declare function editorPluginConnections(input: {
43
+ installs: readonly EditorRealmInstall[];
44
+ listings: readonly EditorListingConnections[];
45
+ records: Readonly<Record<string, Readonly<Record<string, PluginConnectionSiteRecord>>>>;
46
+ }): EditorPluginConnections;
@@ -0,0 +1,70 @@
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
+ */ import { pluginConnectionRecordVariables, pluginConnectionVariableId, validatePluginConnections } from "@aglyn/aglyn/app-utils/plugin-connections";
17
+ /** The connections a pin's manifest declares that run a sync: only those write a record. */ export function syncingConnectionsOf(manifest) {
18
+ const verdict = validatePluginConnections(manifest == null ? void 0 : manifest.connections);
19
+ return verdict.ok ? verdict.value.filter((connection)=>Boolean(connection.sync)) : [];
20
+ }
21
+ /** A declared field name as a person reads it: `reviewCount` → `Review count`. */ export function connectionFieldLabel(field) {
22
+ const words = field.replace(/_/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim().toLowerCase();
23
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : field;
24
+ }
25
+ /** The listing whose namespace `componentId` is in, longest identity first. */ export function listingForComponent(installs) {
26
+ const namespaces = installs.filter((install)=>typeof install.identity === 'string' && install.identity).map((install)=>({
27
+ prefix: `${install.identity}.`,
28
+ listingId: install.listingId
29
+ })).sort((a, b)=>b.prefix.length - a.prefix.length);
30
+ return (componentId)=>{
31
+ var _ref;
32
+ var _namespaces_find;
33
+ return (_ref = (_namespaces_find = namespaces.find((one)=>componentId.startsWith(one.prefix))) == null ? void 0 : _namespaces_find.listingId) != null ? _ref : null;
34
+ };
35
+ }
36
+ /**
37
+ * Core's `EditorPluginConnections` for a site: which listing a component is
38
+ * whose, each listing's records, and one variable per declared summary
39
+ * field, named for a person and holding what the last sync wrote (or `null`
40
+ * before one, so an author can bind it before the first sync).
41
+ */ export function editorPluginConnections(input) {
42
+ const variables = [];
43
+ // `installed`, not `listing`: the listing-field coverage guard reads every `listing.<field>` here as a document read.
44
+ for (const installed of input.listings){
45
+ for (const connection of installed.connections){
46
+ var _ref;
47
+ var _input_records_installed_listingId, _connection_sync;
48
+ const record = (_input_records_installed_listingId = input.records[installed.listingId]) == null ? void 0 : _input_records_installed_listingId[connection.id];
49
+ const values = pluginConnectionRecordVariables(installed.listingId, connection, record == null ? void 0 : record.fields);
50
+ for (const field of Object.keys((_ref = (_connection_sync = connection.sync) == null ? void 0 : _connection_sync.record.fields) != null ? _ref : {})){
51
+ var _ref1;
52
+ var _values_id;
53
+ const id = pluginConnectionVariableId(installed.listingId, connection.id, field);
54
+ if (!id) continue;
55
+ variables.push({
56
+ id,
57
+ label: `${connection.label}: ${connectionFieldLabel(field)}`,
58
+ value: (_ref1 = (_values_id = values[id]) == null ? void 0 : _values_id.value) != null ? _ref1 : null
59
+ });
60
+ }
61
+ }
62
+ }
63
+ return {
64
+ listingFor: listingForComponent(input.installs),
65
+ records: input.records,
66
+ variables
67
+ };
68
+ }
69
+
70
+ //# sourceMappingURL=editor-connections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/editor-connections.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 type {\n EditorConnectionVariable,\n EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport {\n pluginConnectionRecordVariables,\n pluginConnectionVariableId,\n validatePluginConnections,\n type PluginConnectionDeclaration,\n} from '@aglyn/aglyn/app-utils/plugin-connections'\n\n/*\n * WHAT THE EDITOR IS HANDED OF A SITE'S DECLARED CONNECTIONS (AGL-4196).\n *\n * Pure: the provider reads the site's realm installs, their pins and the\n * records, and this turns them into core's `EditorPluginConnections`, the\n * same attribution and the same variables the published page gets.\n */\n\n/** A realm install as far as the editor needs it: the listing and its namespace. */\nexport interface EditorRealmInstall {\n listingId: string\n identity?: string\n}\n\n/** One installed listing's declared connections that sync, from its pin. */\nexport interface EditorListingConnections {\n listingId: string\n connections: PluginConnectionDeclaration[]\n}\n\n/** The connections a pin's manifest declares that run a sync: only those write a record. */\nexport function syncingConnectionsOf(manifest: unknown): PluginConnectionDeclaration[] {\n const verdict = validatePluginConnections((manifest as { connections?: unknown } | null | undefined)?.connections)\n return verdict.ok ? verdict.value.filter((connection) => Boolean(connection.sync)) : []\n}\n\n/** A declared field name as a person reads it: `reviewCount` → `Review count`. */\nexport function connectionFieldLabel(field: string): string {\n const words = field\n .replace(/_/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim()\n .toLowerCase()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : field\n}\n\n/** The listing whose namespace `componentId` is in, longest identity first. */\nexport function listingForComponent(installs: readonly EditorRealmInstall[]): (componentId: string) => string | null {\n const namespaces = installs\n .filter((install) => typeof install.identity === 'string' && install.identity)\n .map((install) => ({ prefix: `${install.identity}.`, listingId: install.listingId }))\n .sort((a, b) => b.prefix.length - a.prefix.length)\n return (componentId) => namespaces.find((one) => componentId.startsWith(one.prefix))?.listingId ?? null\n}\n\n/**\n * Core's `EditorPluginConnections` for a site: which listing a component is\n * whose, each listing's records, and one variable per declared summary\n * field, named for a person and holding what the last sync wrote (or `null`\n * before one, so an author can bind it before the first sync).\n */\nexport function editorPluginConnections(input: {\n installs: readonly EditorRealmInstall[]\n listings: readonly EditorListingConnections[]\n records: Readonly<Record<string, Readonly<Record<string, PluginConnectionSiteRecord>>>>\n}): EditorPluginConnections {\n const variables: EditorConnectionVariable[] = []\n // `installed`, not `listing`: the listing-field coverage guard reads every `listing.<field>` here as a document read.\n for (const installed of input.listings) {\n for (const connection of installed.connections) {\n const record = input.records[installed.listingId]?.[connection.id]\n const values = pluginConnectionRecordVariables(installed.listingId, connection, record?.fields)\n for (const field of Object.keys(connection.sync?.record.fields ?? {})) {\n const id = pluginConnectionVariableId(installed.listingId, connection.id, field)\n if (!id) continue\n variables.push({ id, label: `${connection.label}: ${connectionFieldLabel(field)}`, value: values[id]?.value ?? null })\n }\n }\n }\n return { listingFor: listingForComponent(input.installs), records: input.records, variables }\n}\n"],"names":["pluginConnectionRecordVariables","pluginConnectionVariableId","validatePluginConnections","syncingConnectionsOf","manifest","verdict","connections","ok","value","filter","connection","Boolean","sync","connectionFieldLabel","field","words","replace","trim","toLowerCase","charAt","toUpperCase","slice","listingForComponent","installs","namespaces","install","identity","map","prefix","listingId","sort","a","b","length","componentId","find","one","startsWith","editorPluginConnections","input","variables","installed","listings","record","records","id","values","fields","Object","keys","push","label","listingFor"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SACEA,+BAA+B,EAC/BC,0BAA0B,EAC1BC,yBAAyB,QAEpB,4CAA2C;AAsBlD,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,QAAiB;IACpD,MAAMC,UAAUH,0BAA2BE,4BAAD,AAACA,SAA2DE,WAAW;IACjH,OAAOD,QAAQE,EAAE,GAAGF,QAAQG,KAAK,CAACC,MAAM,CAAC,CAACC,aAAeC,QAAQD,WAAWE,IAAI,KAAK,EAAE;AACzF;AAEA,gFAAgF,GAChF,OAAO,SAASC,qBAAqBC,KAAa;IAChD,MAAMC,QAAQD,MACXE,OAAO,CAAC,MAAM,KACdA,OAAO,CAAC,sBAAsB,SAC9BC,IAAI,GACJC,WAAW;IACd,OAAOH,QAAQA,MAAMI,MAAM,CAAC,GAAGC,WAAW,KAAKL,MAAMM,KAAK,CAAC,KAAKP;AAClE;AAEA,6EAA6E,GAC7E,OAAO,SAASQ,oBAAoBC,QAAuC;IACzE,MAAMC,aAAaD,SAChBd,MAAM,CAAC,CAACgB,UAAY,OAAOA,QAAQC,QAAQ,KAAK,YAAYD,QAAQC,QAAQ,EAC5EC,GAAG,CAAC,CAACF,UAAa,CAAA;YAAEG,QAAQ,GAAGH,QAAQC,QAAQ,CAAC,CAAC,CAAC;YAAEG,WAAWJ,QAAQI,SAAS;QAAC,CAAA,GACjFC,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEJ,MAAM,CAACK,MAAM,GAAGF,EAAEH,MAAM,CAACK,MAAM;IACnD,OAAO,CAACC;;YAAgBV;wBAAAA,mBAAAA,WAAWW,IAAI,CAAC,CAACC,MAAQF,YAAYG,UAAU,CAACD,IAAIR,MAAM,uBAA1DJ,iBAA8DK,SAAS,mBAAI;;AACrG;AAEA;;;;;CAKC,GACD,OAAO,SAASS,wBAAwBC,KAIvC;IACC,MAAMC,YAAwC,EAAE;IAChD,sHAAsH;IACtH,KAAK,MAAMC,aAAaF,MAAMG,QAAQ,CAAE;QACtC,KAAK,MAAMhC,cAAc+B,UAAUnC,WAAW,CAAE;;gBAC/BiC,oCAEiB7B;YAFhC,MAAMiC,UAASJ,qCAAAA,MAAMK,OAAO,CAACH,UAAUZ,SAAS,CAAC,qBAAlCU,kCAAoC,CAAC7B,WAAWmC,EAAE,CAAC;YAClE,MAAMC,SAAS9C,gCAAgCyC,UAAUZ,SAAS,EAAEnB,YAAYiC,0BAAAA,OAAQI,MAAM;YAC9F,KAAK,MAAMjC,SAASkC,OAAOC,IAAI,UAACvC,mBAAAA,WAAWE,IAAI,qBAAfF,iBAAiBiC,MAAM,CAACI,MAAM,mBAAI,CAAC,GAAI;;oBAGqBD;gBAF1F,MAAMD,KAAK5C,2BAA2BwC,UAAUZ,SAAS,EAAEnB,WAAWmC,EAAE,EAAE/B;gBAC1E,IAAI,CAAC+B,IAAI;gBACTL,UAAUU,IAAI,CAAC;oBAAEL;oBAAIM,OAAO,GAAGzC,WAAWyC,KAAK,CAAC,EAAE,EAAEtC,qBAAqBC,QAAQ;oBAAEN,KAAK,YAAEsC,aAAAA,MAAM,CAACD,GAAG,qBAAVC,WAAYtC,KAAK,oBAAI;gBAAK;YACtH;QACF;IACF;IACA,OAAO;QAAE4C,YAAY9B,oBAAoBiB,MAAMhB,QAAQ;QAAGqB,SAASL,MAAMK,OAAO;QAAEJ;IAAU;AAC9F"}
@@ -29,10 +29,13 @@
29
29
  * seller is transferred $0.80, $0.08 is owed to the state, Aglyn keeps $0.20
30
30
  * and pays Stripe about $0.33. Net −$0.13.
31
31
  *
32
- * Break-even is computed from the dearest payment method the platform's
33
- * configuration enables (`BNPL_PROCESSING_PERCENT`), because a marketplace
34
- * session pins no `payment_method_types`. IF BNPL IS EVER TURNED OFF FOR
35
- * MARKETPLACE SESSIONS, the rate these default to is what has to move with it.
32
+ * Break-even is computed at the buy-now-pay-later rate
33
+ * (`BNPL_PROCESSING_PERCENT`, 6%). Since AGL-4198 a marketplace session
34
+ * excludes buy-now-pay-later, so the dearest a buyer can pay with is a card,
35
+ * at 2.9%, and this floor holds a margin above break-even rather than sitting
36
+ * on it ($3 at 6% against $2 at the card rate, at today's lowest take).
37
+ * Lowering the floor to the card rate is a pricing decision of its own, left
38
+ * to the account owner; passing `CARD_PROCESSING_PERCENT` is the whole change.
36
39
  *
37
40
  * Nothing is billed from these figures — the charged amounts are the plan's
38
41
  * take rate and Stripe's own invoicing — so changing one changes a sentence
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/listing-price-floor.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 BNPL_PROCESSING_PERCENT,\n bindingMarketplaceFeePct,\n PROCESSING_FIXED_CENTS,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\n\n/*\n * WHAT ONE PAID LISTING'S SALE COSTS THE PLATFORM, AND THE FLOOR IT SETS\n * (AGL-2343).\n *\n * Marketplace checkout is a DESTINATION charge with a fixed\n * `transfer_data[amount]` and deliberately no `application_fee_amount`, so\n * that the sales tax stays with the platform that owes it (AGL-1544). On a\n * destination charge Stripe debits its fee from the PLATFORM's balance, and it\n * is charged on `amount_total` — the listing price plus the tax added on top\n * of it.\n *\n * So a $1 listing at a 20% take rate is a loss: the buyer pays $1.08, the\n * seller is transferred $0.80, $0.08 is owed to the state, Aglyn keeps $0.20\n * and pays Stripe about $0.33. Net −$0.13.\n *\n * Break-even is computed from the dearest payment method the platform's\n * configuration enables (`BNPL_PROCESSING_PERCENT`), because a marketplace\n * session pins no `payment_method_types`. IF BNPL IS EVER TURNED OFF FOR\n * MARKETPLACE SESSIONS, the rate these default to is what has to move with it.\n *\n * Nothing is billed from these figures — the charged amounts are the plan's\n * take rate and Stripe's own invoicing — so changing one changes a sentence\n * and the floor, never a bill.\n */\n\n/**\n * The tax rate the break-even figure assumes. Tax is added ON TOP of the\n * listing price and enlarges the base Stripe charges its percentage against,\n * so it makes the platform's cost slightly worse; the real rate is whatever\n * `automatic_tax` computes for the buyer's address, and this is the\n * platform's own Texas rate as a representative figure.\n */\nexport const MARKETPLACE_ASSUMED_TAX_PERCENT = 8.25\n\n/** Where the money goes on one sale of a paid listing (AGL-2343). */\nexport interface MarketplaceSaleEconomics {\n /** The listing price. */\n priceCents: number\n /** Added on top, and owed to the state. */\n taxCents: number\n /** What the buyer is charged. */\n buyerPaysCents: number\n /** `transfer_data[amount]` — the seller's share of the pre-tax price. */\n sellerReceivesCents: number\n /** The platform's take. */\n platformFeeCents: number\n /** Stripe's fee, debited from the platform on a destination charge. */\n processingCents: number\n /** `platformFeeCents - processingCents`. Negative is a loss on the sale. */\n platformNetCents: number\n}\n\n/**\n * The funds flow for one sale, mirroring what `checkout.ts` actually builds\n * (AGL-2343): tax exclusive and on top, a fixed transfer of the seller's share\n * of the PRE-tax price, and Stripe's fee charged on the buyer's total.\n */\nexport function marketplaceSaleEconomics(\n priceUsd: number,\n feePercent: number,\n processingPercent: number = BNPL_PROCESSING_PERCENT,\n): MarketplaceSaleEconomics {\n const priceCents = Math.max(0, Math.round(priceUsd * 100))\n const taxCents = Math.round(\n (priceCents * MARKETPLACE_ASSUMED_TAX_PERCENT) / 100,\n )\n const buyerPaysCents = priceCents + taxCents\n const platformFeeCents = Math.round((priceCents * feePercent) / 100)\n const sellerReceivesCents = priceCents - platformFeeCents\n // A zero-price listing takes no payment at all, so there is no fee to pay.\n const processingCents =\n priceCents === 0\n ? 0\n : Math.round(\n (buyerPaysCents * processingPercent) / 100 +\n PROCESSING_FIXED_CENTS,\n )\n return {\n priceCents,\n taxCents,\n buyerPaysCents,\n sellerReceivesCents,\n platformFeeCents,\n processingCents,\n platformNetCents: platformFeeCents - processingCents,\n }\n}\n\n/**\n * The cheapest WHOLE-DOLLAR price at which the platform does not lose money on\n * a sale (AGL-2343).\n *\n * Whole dollars because every publish route rounds `priceUsd` to one, so a\n * fractional break-even is not a price anyone can actually set. Searched\n * rather than solved algebraically so that it can never disagree with\n * `marketplaceSaleEconomics` — the rounding in there is what decides the\n * answer at these amounts, and a closed form would drift from it silently.\n *\n * THIS IS THE ENFORCED FLOOR: `marketplaceMinPriceUsd` returns this figure\n * and every publish door refuses a paid listing under it. Recorded in the Pricing\n * Decision Log and on AGL-2343.\n */\nexport function marketplaceBreakEvenUsd(\n feePercent: number = bindingMarketplaceFeePct(),\n processingPercent: number = BNPL_PROCESSING_PERCENT,\n): number {\n // The listing price ceiling is `MARKETPLACE_MAX_PRICE_USD` in the model,\n // which takes its floor from this module, so reading it here would be a\n // cycle. The loop only needs SOME bound, and the answer is single digits at\n // every rate the table holds, so a generous local one costs nothing and\n // cannot drift into a wrong figure.\n const searchCeilingUsd = 1000\n for (let priceUsd = 1; priceUsd <= searchCeilingUsd; priceUsd += 1) {\n if (\n marketplaceSaleEconomics(priceUsd, feePercent, processingPercent)\n .platformNetCents >= 0\n ) {\n return priceUsd\n }\n }\n return searchCeilingUsd\n}\n\n/**\n * THE MINIMUM PRICE a paid marketplace listing may carry (AGL-2343).\n *\n * The floor is not a chosen round number — it is the break-even price itself,\n * the\n * cheapest whole dollar at which `marketplaceSaleEconomics` stops returning a\n * negative platform net, computed at the take rate that breaks even LATEST and\n * the dearest payment method the live configuration enables. Today that is $3.\n *\n * DERIVED, NEVER RESTATED. A second hand-written copy of this number in a\n * publish route or a form is the artifact that decays into a route refusing a\n * price a form invited, so both the server-side validator\n * (`MARKETPLACE_MIN_PRICE_USD` in the marketplace model) and every price field\n * read this. If the payment method set changes, or the plan table's lowest\n * take rate moves, the floor moves with them on the next call.\n *\n * ZERO IS NOT BELOW THE FLOOR. A free listing takes no payment at all, so it\n * costs nothing to process; the floor is a minimum on PAID listings only.\n */\nexport function marketplaceMinPriceUsd(): number {\n return marketplaceBreakEvenUsd()\n}\n\n/**\n * Whether a price is a PAID price that the floor refuses (AGL-2343) — the one\n * predicate a form uses to mark its price field in error and hold its publish\n * button, so that the button and the server agree about what is publishable.\n *\n * Rounds first because every publish route does, and a form's value is a\n * string mid-type: `'2.6'` is stored as $3 and must read as allowed here, or\n * the button would refuse a price the route accepts.\n */\nexport function isBelowMarketplacePriceFloor(priceUsd: unknown): boolean {\n const price = Math.round(Number(priceUsd) || 0)\n return price > 0 && price < marketplaceMinPriceUsd()\n}\n\n/**\n * The sentence a publish form shows under its price field when the price is\n * below the floor (AGL-2343), or `undefined` when there is nothing to say.\n *\n * NOT ADVISORY ANY MORE. The same figure is enforced by every publish route\n * through `publishPreconditionRefusal`, so this text has to read as a\n * requirement rather than a warning — a publisher who is told \"consider\n * raising it\" and then refused has been lied to by the form. It still explains\n * WHY, because a bare minimum with no arithmetic behind it reads as an\n * arbitrary tax on cheap listings.\n *\n * Shared by every publish form rather than written into each, because the\n * figure has to be the same one in all of them and the wording is the only\n * place a publisher ever sees it.\n *\n * Quoted at the take rate that breaks even LATEST, so the sentence holds for a\n * free-plan publisher too.\n */\nexport function marketplacePriceCostNote(\n priceUsd: unknown,\n): string | undefined {\n const price = Math.round(Number(priceUsd) || 0)\n const minimum = marketplaceMinPriceUsd()\n // A free listing takes no payment, so it costs nothing to process and has\n // nothing to warn about — silence there is the point, not an oversight.\n if (!isBelowMarketplacePriceFloor(price)) return undefined\n const { processingCents, platformFeeCents } = marketplaceSaleEconomics(\n price,\n bindingMarketplaceFeePct(),\n )\n const usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n return (\n `Too low to publish. Processing a $${price} payment costs about ` +\n `${usd(processingCents)}, more than the ${usd(platformFeeCents)} platform ` +\n `fee — the sale would lose money. The minimum paid price is ` +\n `$${minimum}, and a free listing ($0) takes no payment at all.`\n )\n}\n\n/**\n * The standing helper sentence a price field shows when there is nothing wrong\n * (AGL-2343): the minimum stated up front, so a publisher meets it while\n * typing instead of discovering it as a refusal.\n *\n * A capability that is not surfaced in the console does not count as shipped,\n * and that applies to a REFUSAL as much as to a feature: a floor only a route\n * knows about is a trap.\n *\n * @param suffix whatever else that particular form needs to say, appended.\n */\nexport function marketplacePriceFloorHint(suffix?: string): string {\n return (\n `0 for free, or $${marketplaceMinPriceUsd()} and up for a paid listing.` +\n (suffix ? ` ${suffix}` : '')\n )\n}\n"],"names":["BNPL_PROCESSING_PERCENT","bindingMarketplaceFeePct","PROCESSING_FIXED_CENTS","MARKETPLACE_ASSUMED_TAX_PERCENT","marketplaceSaleEconomics","priceUsd","feePercent","processingPercent","priceCents","Math","max","round","taxCents","buyerPaysCents","platformFeeCents","sellerReceivesCents","processingCents","platformNetCents","marketplaceBreakEvenUsd","searchCeilingUsd","marketplaceMinPriceUsd","isBelowMarketplacePriceFloor","price","Number","marketplacePriceCostNote","minimum","undefined","usd","cents","toFixed","marketplacePriceFloorHint","suffix"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,uBAAuB,EACvBC,wBAAwB,EACxBC,sBAAsB,QACjB,2CAA0C;AAEjD;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED;;;;;;CAMC,GACD,OAAO,MAAMC,kCAAkC,KAAI;AAoBnD;;;;CAIC,GACD,OAAO,SAASC,yBACdC,QAAgB,EAChBC,UAAkB,EAClBC,oBAA4BP,uBAAuB;IAEnD,MAAMQ,aAAaC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACN,WAAW;IACrD,MAAMO,WAAWH,KAAKE,KAAK,CACzB,AAACH,aAAaL,kCAAmC;IAEnD,MAAMU,iBAAiBL,aAAaI;IACpC,MAAME,mBAAmBL,KAAKE,KAAK,CAAC,AAACH,aAAaF,aAAc;IAChE,MAAMS,sBAAsBP,aAAaM;IACzC,2EAA2E;IAC3E,MAAME,kBACJR,eAAe,IACX,IACAC,KAAKE,KAAK,CACR,AAACE,iBAAiBN,oBAAqB,MACrCL;IAEV,OAAO;QACLM;QACAI;QACAC;QACAE;QACAD;QACAE;QACAC,kBAAkBH,mBAAmBE;IACvC;AACF;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASE,wBACdZ,aAAqBL,0BAA0B,EAC/CM,oBAA4BP,uBAAuB;IAEnD,yEAAyE;IACzE,wEAAwE;IACxE,4EAA4E;IAC5E,wEAAwE;IACxE,oCAAoC;IACpC,MAAMmB,mBAAmB;IACzB,IAAK,IAAId,WAAW,GAAGA,YAAYc,kBAAkBd,YAAY,EAAG;QAClE,IACED,yBAAyBC,UAAUC,YAAYC,mBAC5CU,gBAAgB,IAAI,GACvB;YACA,OAAOZ;QACT;IACF;IACA,OAAOc;AACT;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC;IACd,OAAOF;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,6BAA6BhB,QAAiB;IAC5D,MAAMiB,QAAQb,KAAKE,KAAK,CAACY,OAAOlB,aAAa;IAC7C,OAAOiB,QAAQ,KAAKA,QAAQF;AAC9B;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASI,yBACdnB,QAAiB;IAEjB,MAAMiB,QAAQb,KAAKE,KAAK,CAACY,OAAOlB,aAAa;IAC7C,MAAMoB,UAAUL;IAChB,0EAA0E;IAC1E,wEAAwE;IACxE,IAAI,CAACC,6BAA6BC,QAAQ,OAAOI;IACjD,MAAM,EAAEV,eAAe,EAAEF,gBAAgB,EAAE,GAAGV,yBAC5CkB,OACArB;IAEF,MAAM0B,MAAM,CAACC,QAAkB,CAAC,CAAC,EAAE,AAACA,CAAAA,QAAQ,GAAE,EAAGC,OAAO,CAAC,IAAI;IAC7D,OACE,CAAC,kCAAkC,EAAEP,MAAM,qBAAqB,CAAC,GACjE,GAAGK,IAAIX,iBAAiB,gBAAgB,EAAEW,IAAIb,kBAAkB,UAAU,CAAC,GAC3E,CAAC,2DAA2D,CAAC,GAC7D,CAAC,CAAC,EAAEW,QAAQ,kDAAkD,CAAC;AAEnE;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASK,0BAA0BC,MAAe;IACvD,OACE,CAAC,gBAAgB,EAAEX,yBAAyB,2BAA2B,CAAC,GACvEW,CAAAA,SAAS,CAAC,CAAC,EAAEA,QAAQ,GAAG,EAAC;AAE9B"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/listing-price-floor.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 BNPL_PROCESSING_PERCENT,\n bindingMarketplaceFeePct,\n PROCESSING_FIXED_CENTS,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\n\n/*\n * WHAT ONE PAID LISTING'S SALE COSTS THE PLATFORM, AND THE FLOOR IT SETS\n * (AGL-2343).\n *\n * Marketplace checkout is a DESTINATION charge with a fixed\n * `transfer_data[amount]` and deliberately no `application_fee_amount`, so\n * that the sales tax stays with the platform that owes it (AGL-1544). On a\n * destination charge Stripe debits its fee from the PLATFORM's balance, and it\n * is charged on `amount_total` — the listing price plus the tax added on top\n * of it.\n *\n * So a $1 listing at a 20% take rate is a loss: the buyer pays $1.08, the\n * seller is transferred $0.80, $0.08 is owed to the state, Aglyn keeps $0.20\n * and pays Stripe about $0.33. Net −$0.13.\n *\n * Break-even is computed at the buy-now-pay-later rate\n * (`BNPL_PROCESSING_PERCENT`, 6%). Since AGL-4198 a marketplace session\n * excludes buy-now-pay-later, so the dearest a buyer can pay with is a card,\n * at 2.9%, and this floor holds a margin above break-even rather than sitting\n * on it ($3 at 6% against $2 at the card rate, at today's lowest take).\n * Lowering the floor to the card rate is a pricing decision of its own, left\n * to the account owner; passing `CARD_PROCESSING_PERCENT` is the whole change.\n *\n * Nothing is billed from these figures — the charged amounts are the plan's\n * take rate and Stripe's own invoicing — so changing one changes a sentence\n * and the floor, never a bill.\n */\n\n/**\n * The tax rate the break-even figure assumes. Tax is added ON TOP of the\n * listing price and enlarges the base Stripe charges its percentage against,\n * so it makes the platform's cost slightly worse; the real rate is whatever\n * `automatic_tax` computes for the buyer's address, and this is the\n * platform's own Texas rate as a representative figure.\n */\nexport const MARKETPLACE_ASSUMED_TAX_PERCENT = 8.25\n\n/** Where the money goes on one sale of a paid listing (AGL-2343). */\nexport interface MarketplaceSaleEconomics {\n /** The listing price. */\n priceCents: number\n /** Added on top, and owed to the state. */\n taxCents: number\n /** What the buyer is charged. */\n buyerPaysCents: number\n /** `transfer_data[amount]` — the seller's share of the pre-tax price. */\n sellerReceivesCents: number\n /** The platform's take. */\n platformFeeCents: number\n /** Stripe's fee, debited from the platform on a destination charge. */\n processingCents: number\n /** `platformFeeCents - processingCents`. Negative is a loss on the sale. */\n platformNetCents: number\n}\n\n/**\n * The funds flow for one sale, mirroring what `checkout.ts` actually builds\n * (AGL-2343): tax exclusive and on top, a fixed transfer of the seller's share\n * of the PRE-tax price, and Stripe's fee charged on the buyer's total.\n */\nexport function marketplaceSaleEconomics(\n priceUsd: number,\n feePercent: number,\n processingPercent: number = BNPL_PROCESSING_PERCENT,\n): MarketplaceSaleEconomics {\n const priceCents = Math.max(0, Math.round(priceUsd * 100))\n const taxCents = Math.round(\n (priceCents * MARKETPLACE_ASSUMED_TAX_PERCENT) / 100,\n )\n const buyerPaysCents = priceCents + taxCents\n const platformFeeCents = Math.round((priceCents * feePercent) / 100)\n const sellerReceivesCents = priceCents - platformFeeCents\n // A zero-price listing takes no payment at all, so there is no fee to pay.\n const processingCents =\n priceCents === 0\n ? 0\n : Math.round(\n (buyerPaysCents * processingPercent) / 100 +\n PROCESSING_FIXED_CENTS,\n )\n return {\n priceCents,\n taxCents,\n buyerPaysCents,\n sellerReceivesCents,\n platformFeeCents,\n processingCents,\n platformNetCents: platformFeeCents - processingCents,\n }\n}\n\n/**\n * The cheapest WHOLE-DOLLAR price at which the platform does not lose money on\n * a sale (AGL-2343).\n *\n * Whole dollars because every publish route rounds `priceUsd` to one, so a\n * fractional break-even is not a price anyone can actually set. Searched\n * rather than solved algebraically so that it can never disagree with\n * `marketplaceSaleEconomics` — the rounding in there is what decides the\n * answer at these amounts, and a closed form would drift from it silently.\n *\n * THIS IS THE ENFORCED FLOOR: `marketplaceMinPriceUsd` returns this figure\n * and every publish door refuses a paid listing under it. Recorded in the Pricing\n * Decision Log and on AGL-2343.\n */\nexport function marketplaceBreakEvenUsd(\n feePercent: number = bindingMarketplaceFeePct(),\n processingPercent: number = BNPL_PROCESSING_PERCENT,\n): number {\n // The listing price ceiling is `MARKETPLACE_MAX_PRICE_USD` in the model,\n // which takes its floor from this module, so reading it here would be a\n // cycle. The loop only needs SOME bound, and the answer is single digits at\n // every rate the table holds, so a generous local one costs nothing and\n // cannot drift into a wrong figure.\n const searchCeilingUsd = 1000\n for (let priceUsd = 1; priceUsd <= searchCeilingUsd; priceUsd += 1) {\n if (\n marketplaceSaleEconomics(priceUsd, feePercent, processingPercent)\n .platformNetCents >= 0\n ) {\n return priceUsd\n }\n }\n return searchCeilingUsd\n}\n\n/**\n * THE MINIMUM PRICE a paid marketplace listing may carry (AGL-2343).\n *\n * The floor is not a chosen round number — it is the break-even price itself,\n * the\n * cheapest whole dollar at which `marketplaceSaleEconomics` stops returning a\n * negative platform net, computed at the take rate that breaks even LATEST and\n * the dearest payment method the live configuration enables. Today that is $3.\n *\n * DERIVED, NEVER RESTATED. A second hand-written copy of this number in a\n * publish route or a form is the artifact that decays into a route refusing a\n * price a form invited, so both the server-side validator\n * (`MARKETPLACE_MIN_PRICE_USD` in the marketplace model) and every price field\n * read this. If the payment method set changes, or the plan table's lowest\n * take rate moves, the floor moves with them on the next call.\n *\n * ZERO IS NOT BELOW THE FLOOR. A free listing takes no payment at all, so it\n * costs nothing to process; the floor is a minimum on PAID listings only.\n */\nexport function marketplaceMinPriceUsd(): number {\n return marketplaceBreakEvenUsd()\n}\n\n/**\n * Whether a price is a PAID price that the floor refuses (AGL-2343) — the one\n * predicate a form uses to mark its price field in error and hold its publish\n * button, so that the button and the server agree about what is publishable.\n *\n * Rounds first because every publish route does, and a form's value is a\n * string mid-type: `'2.6'` is stored as $3 and must read as allowed here, or\n * the button would refuse a price the route accepts.\n */\nexport function isBelowMarketplacePriceFloor(priceUsd: unknown): boolean {\n const price = Math.round(Number(priceUsd) || 0)\n return price > 0 && price < marketplaceMinPriceUsd()\n}\n\n/**\n * The sentence a publish form shows under its price field when the price is\n * below the floor (AGL-2343), or `undefined` when there is nothing to say.\n *\n * NOT ADVISORY ANY MORE. The same figure is enforced by every publish route\n * through `publishPreconditionRefusal`, so this text has to read as a\n * requirement rather than a warning — a publisher who is told \"consider\n * raising it\" and then refused has been lied to by the form. It still explains\n * WHY, because a bare minimum with no arithmetic behind it reads as an\n * arbitrary tax on cheap listings.\n *\n * Shared by every publish form rather than written into each, because the\n * figure has to be the same one in all of them and the wording is the only\n * place a publisher ever sees it.\n *\n * Quoted at the take rate that breaks even LATEST, so the sentence holds for a\n * free-plan publisher too.\n */\nexport function marketplacePriceCostNote(\n priceUsd: unknown,\n): string | undefined {\n const price = Math.round(Number(priceUsd) || 0)\n const minimum = marketplaceMinPriceUsd()\n // A free listing takes no payment, so it costs nothing to process and has\n // nothing to warn about — silence there is the point, not an oversight.\n if (!isBelowMarketplacePriceFloor(price)) return undefined\n const { processingCents, platformFeeCents } = marketplaceSaleEconomics(\n price,\n bindingMarketplaceFeePct(),\n )\n const usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n return (\n `Too low to publish. Processing a $${price} payment costs about ` +\n `${usd(processingCents)}, more than the ${usd(platformFeeCents)} platform ` +\n `fee — the sale would lose money. The minimum paid price is ` +\n `$${minimum}, and a free listing ($0) takes no payment at all.`\n )\n}\n\n/**\n * The standing helper sentence a price field shows when there is nothing wrong\n * (AGL-2343): the minimum stated up front, so a publisher meets it while\n * typing instead of discovering it as a refusal.\n *\n * A capability that is not surfaced in the console does not count as shipped,\n * and that applies to a REFUSAL as much as to a feature: a floor only a route\n * knows about is a trap.\n *\n * @param suffix whatever else that particular form needs to say, appended.\n */\nexport function marketplacePriceFloorHint(suffix?: string): string {\n return (\n `0 for free, or $${marketplaceMinPriceUsd()} and up for a paid listing.` +\n (suffix ? ` ${suffix}` : '')\n )\n}\n"],"names":["BNPL_PROCESSING_PERCENT","bindingMarketplaceFeePct","PROCESSING_FIXED_CENTS","MARKETPLACE_ASSUMED_TAX_PERCENT","marketplaceSaleEconomics","priceUsd","feePercent","processingPercent","priceCents","Math","max","round","taxCents","buyerPaysCents","platformFeeCents","sellerReceivesCents","processingCents","platformNetCents","marketplaceBreakEvenUsd","searchCeilingUsd","marketplaceMinPriceUsd","isBelowMarketplacePriceFloor","price","Number","marketplacePriceCostNote","minimum","undefined","usd","cents","toFixed","marketplacePriceFloorHint","suffix"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,uBAAuB,EACvBC,wBAAwB,EACxBC,sBAAsB,QACjB,2CAA0C;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GAED;;;;;;CAMC,GACD,OAAO,MAAMC,kCAAkC,KAAI;AAoBnD;;;;CAIC,GACD,OAAO,SAASC,yBACdC,QAAgB,EAChBC,UAAkB,EAClBC,oBAA4BP,uBAAuB;IAEnD,MAAMQ,aAAaC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACN,WAAW;IACrD,MAAMO,WAAWH,KAAKE,KAAK,CACzB,AAACH,aAAaL,kCAAmC;IAEnD,MAAMU,iBAAiBL,aAAaI;IACpC,MAAME,mBAAmBL,KAAKE,KAAK,CAAC,AAACH,aAAaF,aAAc;IAChE,MAAMS,sBAAsBP,aAAaM;IACzC,2EAA2E;IAC3E,MAAME,kBACJR,eAAe,IACX,IACAC,KAAKE,KAAK,CACR,AAACE,iBAAiBN,oBAAqB,MACrCL;IAEV,OAAO;QACLM;QACAI;QACAC;QACAE;QACAD;QACAE;QACAC,kBAAkBH,mBAAmBE;IACvC;AACF;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASE,wBACdZ,aAAqBL,0BAA0B,EAC/CM,oBAA4BP,uBAAuB;IAEnD,yEAAyE;IACzE,wEAAwE;IACxE,4EAA4E;IAC5E,wEAAwE;IACxE,oCAAoC;IACpC,MAAMmB,mBAAmB;IACzB,IAAK,IAAId,WAAW,GAAGA,YAAYc,kBAAkBd,YAAY,EAAG;QAClE,IACED,yBAAyBC,UAAUC,YAAYC,mBAC5CU,gBAAgB,IAAI,GACvB;YACA,OAAOZ;QACT;IACF;IACA,OAAOc;AACT;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC;IACd,OAAOF;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,6BAA6BhB,QAAiB;IAC5D,MAAMiB,QAAQb,KAAKE,KAAK,CAACY,OAAOlB,aAAa;IAC7C,OAAOiB,QAAQ,KAAKA,QAAQF;AAC9B;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASI,yBACdnB,QAAiB;IAEjB,MAAMiB,QAAQb,KAAKE,KAAK,CAACY,OAAOlB,aAAa;IAC7C,MAAMoB,UAAUL;IAChB,0EAA0E;IAC1E,wEAAwE;IACxE,IAAI,CAACC,6BAA6BC,QAAQ,OAAOI;IACjD,MAAM,EAAEV,eAAe,EAAEF,gBAAgB,EAAE,GAAGV,yBAC5CkB,OACArB;IAEF,MAAM0B,MAAM,CAACC,QAAkB,CAAC,CAAC,EAAE,AAACA,CAAAA,QAAQ,GAAE,EAAGC,OAAO,CAAC,IAAI;IAC7D,OACE,CAAC,kCAAkC,EAAEP,MAAM,qBAAqB,CAAC,GACjE,GAAGK,IAAIX,iBAAiB,gBAAgB,EAAEW,IAAIb,kBAAkB,UAAU,CAAC,GAC3E,CAAC,2DAA2D,CAAC,GAC7D,CAAC,CAAC,EAAEW,QAAQ,kDAAkD,CAAC;AAEnE;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASK,0BAA0BC,MAAe;IACvD,OACE,CAAC,gBAAgB,EAAEX,yBAAyB,2BAA2B,CAAC,GACvEW,CAAAA,SAAS,CAAC,CAAC,EAAEA,QAAQ,GAAG,EAAC;AAE9B"}
@@ -16,10 +16,12 @@
16
16
  */
17
17
  import type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions';
18
18
  /** The marketplace's publisher keys. */
19
- export type MarketplacePublisherPermission = 'marketplace.publisherProfile' | 'marketplace.manageListings' | 'marketplace.payouts';
19
+ export type MarketplacePublisherPermission = 'marketplace.publisherProfile' | 'marketplace.manageListings' | 'marketplace.payouts' | 'marketplace.connectionCredentials';
20
20
  export declare const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION: MarketplacePublisherPermission;
21
21
  export declare const MARKETPLACE_MANAGE_LISTINGS_PERMISSION: MarketplacePublisherPermission;
22
22
  export declare const MARKETPLACE_PAYOUTS_PERMISSION: MarketplacePublisherPermission;
23
+ /** Setting a listing's OAuth app credentials for its declared connections (AGL-4193). */
24
+ export declare const MARKETPLACE_CONNECTION_CREDENTIALS_PERMISSION: MarketplacePublisherPermission;
23
25
  /** The keys as the plugin declares them; owners and admins hold each by default. */
24
26
  export declare const MARKETPLACE_PUBLISHER_PERMISSIONS: readonly PluginPermission[];
25
27
  /** The refusal a door answers without one: names the permission, never a role. */
@@ -16,6 +16,7 @@
16
16
  */ export const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION = 'marketplace.publisherProfile';
17
17
  export const MARKETPLACE_MANAGE_LISTINGS_PERMISSION = 'marketplace.manageListings';
18
18
  export const MARKETPLACE_PAYOUTS_PERMISSION = 'marketplace.payouts';
19
+ /** Setting a listing's OAuth app credentials for its declared connections (AGL-4193). */ export const MARKETPLACE_CONNECTION_CREDENTIALS_PERMISSION = 'marketplace.connectionCredentials';
19
20
  /** The keys as the plugin declares them; owners and admins hold each by default. */ export const MARKETPLACE_PUBLISHER_PERMISSIONS = [
20
21
  {
21
22
  key: 'marketplace.publisherProfile',
@@ -52,6 +53,19 @@ export const MARKETPLACE_PAYOUTS_PERMISSION = 'marketplace.payouts';
52
53
  viewer: false
53
54
  },
54
55
  groupLabel: 'Marketplace'
56
+ },
57
+ {
58
+ // Declared connections (AGL-4193): the publisher's OAuth app per listing.
59
+ key: 'marketplace.connectionCredentials',
60
+ pluginId: 'marketplace',
61
+ label: 'Set a listing’s connection credentials',
62
+ description: 'Set or replace the OAuth app client id and secret a published plugin connects sites with. They are stored sealed and are never shown again.',
63
+ defaults: {
64
+ admin: true,
65
+ editor: false,
66
+ viewer: false
67
+ },
68
+ groupLabel: 'Marketplace'
55
69
  }
56
70
  ];
57
71
  /** The refusal a door answers without one: names the permission, never a role. */ export function marketplacePublisherPermissionRefusal(key) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-publisher-permissions.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 type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\n\n/*\n * THE WORKSPACE AS A MARKETPLACE PUBLISHER (AGL-4047).\n *\n * Saving the publisher profile, managing the listings the workspace has\n * published and setting up where its sales are paid asked whether the\n * member's workspace role was owner or admin: the routes through a role\n * helper, and the Firestore rules on `publisherProfiles` and\n * `marketplaceListings` through a role list of their own. Each is now a key\n * whose default is exactly those roles (the owner on the admin tier), so\n * nobody's access moved, and a custom role or a member's own permissions can\n * grant or refuse it.\n *\n * What is NOT a key: whether someone belongs to the workspace that published\n * a listing (`isPublisherMember`). Its members install it free on its own\n * sites (`ownsListingHere`; anywhere else they buy it like anyone), and\n * nobody in it may buy or review it. That is a fact about who published the\n * listing, so no grant can buy it and no refusal can take it away.\n *\n * Declared through `registerPluginPermissions`, as Sequences declares its own\n * workspace keys: a key of that registry rides the permission map the\n * console hands the marketplace's pages, which is what the seller sections\n * disable their controls by.\n */\n\n/** The marketplace's publisher keys. */\nexport type MarketplacePublisherPermission =\n | 'marketplace.publisherProfile'\n | 'marketplace.manageListings'\n | 'marketplace.payouts'\n\nexport const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION: MarketplacePublisherPermission = 'marketplace.publisherProfile'\nexport const MARKETPLACE_MANAGE_LISTINGS_PERMISSION: MarketplacePublisherPermission = 'marketplace.manageListings'\nexport const MARKETPLACE_PAYOUTS_PERMISSION: MarketplacePublisherPermission = 'marketplace.payouts'\n\n/** The keys as the plugin declares them; owners and admins hold each by default. */\nexport const MARKETPLACE_PUBLISHER_PERMISSIONS: readonly PluginPermission[] = [\n {\n key: 'marketplace.publisherProfile',\n pluginId: 'marketplace',\n label: 'Edit the publisher profile',\n description:\n 'Change the workspace’s public marketplace profile (its handle, name, bio, logo, support contact and links), and ask for the Verified publisher badge or withdraw the ask.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.manageListings',\n pluginId: 'marketplace',\n label: 'Manage published listings',\n description:\n 'Edit the listings the workspace has published (their words, pictures and category), make a plugin public or private, unpublish or republish a listing, and see each version’s review status.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.payouts',\n pluginId: 'marketplace',\n label: 'Manage marketplace payouts',\n description:\n 'Connect the payout account the workspace’s paid listings pay into, finish its setup, and open its payouts.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n]\n\n/** The refusal a door answers without one: names the permission, never a role. */\nexport function marketplacePublisherPermissionRefusal(key: MarketplacePublisherPermission): string {\n const label = MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["MARKETPLACE_PUBLISHER_PROFILE_PERMISSION","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","MARKETPLACE_PAYOUTS_PERMISSION","MARKETPLACE_PUBLISHER_PERMISSIONS","key","pluginId","label","description","defaults","admin","editor","viewer","groupLabel","marketplacePublisherPermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAkCD,OAAO,MAAMA,2CAA2E,+BAA8B;AACtH,OAAO,MAAMC,yCAAyE,6BAA4B;AAClH,OAAO,MAAMC,iCAAiE,sBAAqB;AAEnG,kFAAkF,GAClF,OAAO,MAAMC,oCAAiE;IAC5E;QACEC,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,SAASC,sCAAsCT,GAAmC;;QACzED;IAAd,MAAMG,iBAAQH,0CAAAA,kCAAkCW,IAAI,CAAC,CAACC,QAAUA,MAAMX,GAAG,KAAKA,yBAAhED,wCAAsEG,KAAK,mBAAIF;IAC7F,OAAO,CAAC,4BAA4B,EAAEE,MAAM,6BAA6B,CAAC;AAC5E"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-publisher-permissions.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 type { PluginPermission } from '@aglyn/aglyn/plugin-manager/plugin-permissions'\n\n/*\n * THE WORKSPACE AS A MARKETPLACE PUBLISHER (AGL-4047).\n *\n * Saving the publisher profile, managing the listings the workspace has\n * published and setting up where its sales are paid asked whether the\n * member's workspace role was owner or admin: the routes through a role\n * helper, and the Firestore rules on `publisherProfiles` and\n * `marketplaceListings` through a role list of their own. Each is now a key\n * whose default is exactly those roles (the owner on the admin tier), so\n * nobody's access moved, and a custom role or a member's own permissions can\n * grant or refuse it.\n *\n * What is NOT a key: whether someone belongs to the workspace that published\n * a listing (`isPublisherMember`). Its members install it free on its own\n * sites (`ownsListingHere`; anywhere else they buy it like anyone), and\n * nobody in it may buy or review it. That is a fact about who published the\n * listing, so no grant can buy it and no refusal can take it away.\n *\n * Declared through `registerPluginPermissions`, as Sequences declares its own\n * workspace keys: a key of that registry rides the permission map the\n * console hands the marketplace's pages, which is what the seller sections\n * disable their controls by.\n */\n\n/** The marketplace's publisher keys. */\nexport type MarketplacePublisherPermission =\n | 'marketplace.publisherProfile'\n | 'marketplace.manageListings'\n | 'marketplace.payouts'\n | 'marketplace.connectionCredentials'\n\nexport const MARKETPLACE_PUBLISHER_PROFILE_PERMISSION: MarketplacePublisherPermission = 'marketplace.publisherProfile'\nexport const MARKETPLACE_MANAGE_LISTINGS_PERMISSION: MarketplacePublisherPermission = 'marketplace.manageListings'\nexport const MARKETPLACE_PAYOUTS_PERMISSION: MarketplacePublisherPermission = 'marketplace.payouts'\n/** Setting a listing's OAuth app credentials for its declared connections (AGL-4193). */\nexport const MARKETPLACE_CONNECTION_CREDENTIALS_PERMISSION: MarketplacePublisherPermission =\n 'marketplace.connectionCredentials'\n\n/** The keys as the plugin declares them; owners and admins hold each by default. */\nexport const MARKETPLACE_PUBLISHER_PERMISSIONS: readonly PluginPermission[] = [\n {\n key: 'marketplace.publisherProfile',\n pluginId: 'marketplace',\n label: 'Edit the publisher profile',\n description:\n 'Change the workspace’s public marketplace profile (its handle, name, bio, logo, support contact and links), and ask for the Verified publisher badge or withdraw the ask.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.manageListings',\n pluginId: 'marketplace',\n label: 'Manage published listings',\n description:\n 'Edit the listings the workspace has published (their words, pictures and category), make a plugin public or private, unpublish or republish a listing, and see each version’s review status.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n key: 'marketplace.payouts',\n pluginId: 'marketplace',\n label: 'Manage marketplace payouts',\n description:\n 'Connect the payout account the workspace’s paid listings pay into, finish its setup, and open its payouts.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n {\n // Declared connections (AGL-4193): the publisher's OAuth app per listing.\n key: 'marketplace.connectionCredentials',\n pluginId: 'marketplace',\n label: 'Set a listing’s connection credentials',\n description:\n 'Set or replace the OAuth app client id and secret a published plugin connects sites with. They are stored sealed and are never shown again.',\n defaults: { admin: true, editor: false, viewer: false },\n groupLabel: 'Marketplace',\n },\n]\n\n/** The refusal a door answers without one: names the permission, never a role. */\nexport function marketplacePublisherPermissionRefusal(key: MarketplacePublisherPermission): string {\n const label = MARKETPLACE_PUBLISHER_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["MARKETPLACE_PUBLISHER_PROFILE_PERMISSION","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","MARKETPLACE_PAYOUTS_PERMISSION","MARKETPLACE_CONNECTION_CREDENTIALS_PERMISSION","MARKETPLACE_PUBLISHER_PERMISSIONS","key","pluginId","label","description","defaults","admin","editor","viewer","groupLabel","marketplacePublisherPermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAmCD,OAAO,MAAMA,2CAA2E,+BAA8B;AACtH,OAAO,MAAMC,yCAAyE,6BAA4B;AAClH,OAAO,MAAMC,iCAAiE,sBAAqB;AACnG,uFAAuF,GACvF,OAAO,MAAMC,gDACX,oCAAmC;AAErC,kFAAkF,GAClF,OAAO,MAAMC,oCAAiE;IAC5E;QACEC,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;IACA;QACE,0EAA0E;QAC1ER,KAAK;QACLC,UAAU;QACVC,OAAO;QACPC,aACE;QACFC,UAAU;YAAEC,OAAO;YAAMC,QAAQ;YAAOC,QAAQ;QAAM;QACtDC,YAAY;IACd;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,SAASC,sCAAsCT,GAAmC;;QACzED;IAAd,MAAMG,iBAAQH,0CAAAA,kCAAkCW,IAAI,CAAC,CAACC,QAAUA,MAAMX,GAAG,KAAKA,yBAAhED,wCAAsEG,KAAK,mBAAIF;IAC7F,OAAO,CAAC,4BAA4B,EAAEE,MAAM,6BAA6B,CAAC;AAC5E"}
@@ -22,5 +22,14 @@ export type MarketplaceSitePermission = 'marketplace.siteInstall' | 'marketplace
22
22
  export declare const MARKETPLACE_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>>;
23
23
  /** The keys as the plugin declares them into the org permission catalog. */
24
24
  export declare const MARKETPLACE_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[];
25
+ /**
26
+ * Declared connections (AGL-4193): connecting a marketplace plugin on a site
27
+ * to another service — the business's own account there. Its preset is the
28
+ * site Admin alone, who connected a business's own accounts before (sales
29
+ * channels' "Connect Google and Meta"). A list of its own, so the two keys
30
+ * above keep their shared preset; the catalog places it right after them.
31
+ */
32
+ export declare const MARKETPLACE_CONNECTION_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>>;
33
+ export declare const MARKETPLACE_CONNECTION_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[];
25
34
  /** The refusal a door answers without one: names the permission, never a role. */
26
35
  export declare function marketplaceSitePermissionRefusal(key: MarketplaceSitePermission): string;
@@ -52,6 +52,35 @@
52
52
  presetFromSiteRole: true
53
53
  }
54
54
  ];
55
+ /**
56
+ * Declared connections (AGL-4193): connecting a marketplace plugin on a site
57
+ * to another service — the business's own account there. Its preset is the
58
+ * site Admin alone, who connected a business's own accounts before (sales
59
+ * channels' "Connect Google and Meta"). A list of its own, so the two keys
60
+ * above keep their shared preset; the catalog places it right after them.
61
+ */ export const MARKETPLACE_CONNECTION_SITE_ROLE_PRESET = {
62
+ admin: true,
63
+ editor: false,
64
+ author: false,
65
+ viewer: false
66
+ };
67
+ export const MARKETPLACE_CONNECTION_SITE_PERMISSIONS = [
68
+ {
69
+ key: 'marketplace.siteConnections',
70
+ label: 'Manage a site’s plugin connections',
71
+ description: 'Connect a marketplace plugin on a site to another service (such as a business profile it reads), choose what it reads, see where it stands, and disconnect it. Follows the site role unless a role or member sets it.',
72
+ roleDefaults: {
73
+ owner: true,
74
+ admin: true,
75
+ editor: false,
76
+ viewer: false
77
+ },
78
+ hostRoleDefaults: _extends({}, MARKETPLACE_CONNECTION_SITE_ROLE_PRESET),
79
+ group: 'marketplace',
80
+ groupLabel: 'Marketplace',
81
+ presetFromSiteRole: true
82
+ }
83
+ ];
55
84
  /** The refusal a door answers without one: names the permission, never a role. */ export function marketplaceSitePermissionRefusal(key) {
56
85
  var _ref;
57
86
  var _MARKETPLACE_SITE_PERMISSIONS_find;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-site-permissions.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 type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\n\n/*\n * THE MARKETPLACE ON A SITE (AGL-4047).\n *\n * Installing a listing into a site (a template, theme, layout, component,\n * email design or plugin) and publishing a site's own to the marketplace\n * asked two things: the workspace's key (`installPlugins`,\n * `publishToMarketplace`), and the member's role ON THE SITE, Admin or\n * Editor. The second is now a key per act whose preset is that site role\n * (`presetFromSiteRole`), so every member keeps exactly the access they had,\n * and a custom role, a member's own permissions or a site's toggle can grant\n * or refuse it. The workspace's key is still asked beside it.\n */\n\n/** The marketplace's keys whose preset is the site role. */\nexport type MarketplaceSitePermission = 'marketplace.siteInstall' | 'marketplace.sitePublish'\n\n/** Who installed into or published from a site before these were keys: the site's Admin and Editor. */\nexport const MARKETPLACE_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: true,\n author: false,\n viewer: false,\n}\n\n/** The keys as the plugin declares them into the org permission catalog. */\nexport const MARKETPLACE_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'marketplace.siteInstall',\n label: 'Install marketplace items into a site',\n description:\n 'Install templates, themes, layouts, components, email designs and plugins from the marketplace into a site (with \"Install from the marketplace\"). Follows the site role unless a role or member sets it.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: { ...MARKETPLACE_SITE_ROLE_PRESET },\n group: 'marketplace',\n groupLabel: 'Marketplace',\n presetFromSiteRole: true,\n },\n {\n key: 'marketplace.sitePublish',\n label: 'Publish a site’s items to the marketplace',\n description:\n 'Publish a site’s templates, themes, layouts, components and email designs to the marketplace (with \"Publish to the marketplace\"). Follows the site role unless a role or member sets it.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: { ...MARKETPLACE_SITE_ROLE_PRESET },\n group: 'marketplace',\n groupLabel: 'Marketplace',\n presetFromSiteRole: true,\n },\n]\n\n/** The refusal a door answers without one: names the permission, never a role. */\nexport function marketplaceSitePermissionRefusal(key: MarketplaceSitePermission): string {\n const label = MARKETPLACE_SITE_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["MARKETPLACE_SITE_ROLE_PRESET","admin","editor","author","viewer","MARKETPLACE_SITE_PERMISSIONS","key","label","description","roleDefaults","owner","hostRoleDefaults","group","groupLabel","presetFromSiteRole","marketplaceSitePermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAqBD,qGAAqG,GACrG,OAAO,MAAMA,+BAA0E;IACrFC,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV,EAAC;AAED,0EAA0E,GAC1E,OAAO,MAAMC,+BAA0E;IACrF;QACEC,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkB,aAAKX;QACvBY,OAAO;QACPC,YAAY;QACZC,oBAAoB;IACtB;IACA;QACER,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkB,aAAKX;QACvBY,OAAO;QACPC,YAAY;QACZC,oBAAoB;IACtB;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,SAASC,iCAAiCT,GAA8B;;QAC/DD;IAAd,MAAME,iBAAQF,qCAAAA,6BAA6BW,IAAI,CAAC,CAACC,QAAUA,MAAMX,GAAG,KAAKA,yBAA3DD,mCAAiEE,KAAK,mBAAID;IACxF,OAAO,CAAC,4BAA4B,EAAEC,MAAM,6BAA6B,CAAC;AAC5E"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/marketplace-site-permissions.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 type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\n\n/*\n * THE MARKETPLACE ON A SITE (AGL-4047).\n *\n * Installing a listing into a site (a template, theme, layout, component,\n * email design or plugin) and publishing a site's own to the marketplace\n * asked two things: the workspace's key (`installPlugins`,\n * `publishToMarketplace`), and the member's role ON THE SITE, Admin or\n * Editor. The second is now a key per act whose preset is that site role\n * (`presetFromSiteRole`), so every member keeps exactly the access they had,\n * and a custom role, a member's own permissions or a site's toggle can grant\n * or refuse it. The workspace's key is still asked beside it.\n */\n\n/** The marketplace's keys whose preset is the site role. */\nexport type MarketplaceSitePermission = 'marketplace.siteInstall' | 'marketplace.sitePublish'\n\n/** Who installed into or published from a site before these were keys: the site's Admin and Editor. */\nexport const MARKETPLACE_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: true,\n author: false,\n viewer: false,\n}\n\n/** The keys as the plugin declares them into the org permission catalog. */\nexport const MARKETPLACE_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'marketplace.siteInstall',\n label: 'Install marketplace items into a site',\n description:\n 'Install templates, themes, layouts, components, email designs and plugins from the marketplace into a site (with \"Install from the marketplace\"). Follows the site role unless a role or member sets it.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: { ...MARKETPLACE_SITE_ROLE_PRESET },\n group: 'marketplace',\n groupLabel: 'Marketplace',\n presetFromSiteRole: true,\n },\n {\n key: 'marketplace.sitePublish',\n label: 'Publish a site’s items to the marketplace',\n description:\n 'Publish a site’s templates, themes, layouts, components and email designs to the marketplace (with \"Publish to the marketplace\"). Follows the site role unless a role or member sets it.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: { ...MARKETPLACE_SITE_ROLE_PRESET },\n group: 'marketplace',\n groupLabel: 'Marketplace',\n presetFromSiteRole: true,\n },\n]\n\n/**\n * Declared connections (AGL-4193): connecting a marketplace plugin on a site\n * to another service — the business's own account there. Its preset is the\n * site Admin alone, who connected a business's own accounts before (sales\n * channels' \"Connect Google and Meta\"). A list of its own, so the two keys\n * above keep their shared preset; the catalog places it right after them.\n */\nexport const MARKETPLACE_CONNECTION_SITE_ROLE_PRESET: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: false,\n author: false,\n viewer: false,\n}\n\nexport const MARKETPLACE_CONNECTION_SITE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'marketplace.siteConnections',\n label: 'Manage a site’s plugin connections',\n description:\n 'Connect a marketplace plugin on a site to another service (such as a business profile it reads), choose what it reads, see where it stands, and disconnect it. Follows the site role unless a role or member sets it.',\n roleDefaults: { owner: true, admin: true, editor: false, viewer: false },\n hostRoleDefaults: { ...MARKETPLACE_CONNECTION_SITE_ROLE_PRESET },\n group: 'marketplace',\n groupLabel: 'Marketplace',\n presetFromSiteRole: true,\n },\n]\n\n/** The refusal a door answers without one: names the permission, never a role. */\nexport function marketplaceSitePermissionRefusal(key: MarketplaceSitePermission): string {\n const label = MARKETPLACE_SITE_PERMISSIONS.find((entry) => entry.key === key)?.label ?? key\n return `Your role does not include \"${label}\" — ask an organization admin`\n}\n"],"names":["MARKETPLACE_SITE_ROLE_PRESET","admin","editor","author","viewer","MARKETPLACE_SITE_PERMISSIONS","key","label","description","roleDefaults","owner","hostRoleDefaults","group","groupLabel","presetFromSiteRole","MARKETPLACE_CONNECTION_SITE_ROLE_PRESET","MARKETPLACE_CONNECTION_SITE_PERMISSIONS","marketplaceSitePermissionRefusal","find","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAqBD,qGAAqG,GACrG,OAAO,MAAMA,+BAA0E;IACrFC,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV,EAAC;AAED,0EAA0E,GAC1E,OAAO,MAAMC,+BAA0E;IACrF;QACEC,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkB,aAAKX;QACvBY,OAAO;QACPC,YAAY;QACZC,oBAAoB;IACtB;IACA;QACER,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkB,aAAKX;QACvBY,OAAO;QACPC,YAAY;QACZC,oBAAoB;IACtB;CACD,CAAA;AAED;;;;;;CAMC,GACD,OAAO,MAAMC,0CAAqF;IAChGd,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV,EAAC;AAED,OAAO,MAAMY,0CAAqF;IAChG;QACEV,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAOE,QAAQ;QAAM;QACvEO,kBAAkB,aAAKI;QACvBH,OAAO;QACPC,YAAY;QACZC,oBAAoB;IACtB;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,SAASG,iCAAiCX,GAA8B;;QAC/DD;IAAd,MAAME,iBAAQF,qCAAAA,6BAA6Ba,IAAI,CAAC,CAACC,QAAUA,MAAMb,GAAG,KAAKA,yBAA3DD,mCAAiEE,KAAK,mBAAID;IACxF,OAAO,CAAC,4BAA4B,EAAEC,MAAM,6BAA6B,CAAC;AAC5E"}
@@ -0,0 +1,88 @@
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
+ import { type PluginConnectionStatusEntry, type PluginConnectionView } from './plugin-connections';
18
+ /** The address a publisher registers at the provider as the OAuth app's redirect URI. */
19
+ export declare function pluginConnectionRedirectUri(origin?: string): string;
20
+ /** A view as the card reads it: the sync may also say when it runs next. */
21
+ export type PluginConnectionCardView = PluginConnectionView & {
22
+ nextSyncAtMs?: number | null;
23
+ };
24
+ /**
25
+ * Where one declared connection stands for a site, in the order the card
26
+ * checks: a deployment without the key first, then the site's own grant,
27
+ * then what the publisher has set up.
28
+ */
29
+ export type PluginConnectionPhase = 'not-configured' | 'not-set-up' | 'not-connected' | 'choosing' | 'connected' | 'reconnect' | 'error';
30
+ export declare function pluginConnectionPhase(entry: PluginConnectionStatusEntry, configured: boolean): PluginConnectionPhase;
31
+ /** The first declared resource the site has not chosen yet, in order. */
32
+ export declare function nextResourceToChoose(entry: Pick<PluginConnectionStatusEntry, 'resources' | 'connection'>): {
33
+ id: string;
34
+ label: string;
35
+ } | null;
36
+ /** The status chip each phase wears. */
37
+ export declare const PLUGIN_CONNECTION_PHASE_CHIP: Readonly<Record<PluginConnectionPhase, {
38
+ label: string;
39
+ tone: 'neutral' | 'info' | 'success' | 'warning' | 'error';
40
+ }>>;
41
+ /** What a site owner is told when a deployment cannot run connections. */
42
+ export declare const PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT = "Connecting plugins to other services is not available here yet.";
43
+ /** What a site owner is told while the publisher has not set the connection up. */
44
+ export declare function pluginConnectionNotSetUpText(label: string): string;
45
+ /** The query the connect callback adds to the page it returns to. */
46
+ export declare const PLUGIN_CONNECTION_RETURN_PARAMS: {
47
+ readonly listing: "pluginConnectionListing";
48
+ readonly connection: "pluginConnection";
49
+ readonly result: "pluginConnectionResult";
50
+ };
51
+ export type PluginConnectionResult = 'connected' | 'denied' | 'expired' | 'failed' | 'not-permitted' | 'not-set-up';
52
+ export interface PluginConnectionReturn {
53
+ listingId: string;
54
+ connectionId: string;
55
+ result: PluginConnectionResult;
56
+ /** The page's address without the three parameters. */
57
+ cleanUrl: string;
58
+ }
59
+ /**
60
+ * The outcome the connect callback left on `href` for `listingId`, and the
61
+ * address to replace it with, or `null` when it carries none for it.
62
+ */
63
+ export declare function readPluginConnectionReturn(href: string, listingId: string): PluginConnectionReturn | null;
64
+ /** The snackbar a returned connect shows. */
65
+ export declare function pluginConnectionResultMessage(result: PluginConnectionResult, label: string): {
66
+ variant: 'success' | 'warning' | 'error';
67
+ message: string;
68
+ };
69
+ /**
70
+ * When the next scheduled sync runs: what the sync recorded when it says,
71
+ * else the last one plus the pinned manifest's interval. A connection with
72
+ * every choice made and no sync yet runs on the next pass.
73
+ */
74
+ export declare function pluginConnectionNextSyncAtMs(view: PluginConnectionCardView | null, intervalHours: number | null | undefined): number | 'soon' | null;
75
+ /** The sync interval a pinned manifest declares for one connection, if any. */
76
+ export declare function declaredSyncIntervalHours(manifest: unknown, connectionId: string): number | null;
77
+ /** The connections a pinned manifest declares, by id and label: what the card can name before the status answers. */
78
+ export declare function declaredPluginConnections(manifest: unknown): Array<{
79
+ id: string;
80
+ label: string;
81
+ description?: string;
82
+ }>;
83
+ /** What a site owner is told when "Sync now" comes within a minute of the last sync. */
84
+ export declare function pluginConnectionTooSoonText(label: string, retryAfterMs: number | null): string;
85
+ /** Where a connection's data goes and comes from, as the card says it. */
86
+ export declare function pluginConnectionSharesText(hosts: readonly string[] | undefined): string | null;
87
+ /** A date and time as the cards print it. */
88
+ export declare function formatConnectionTime(ms: number): string;