@aglyn/aglyn 1.0.0-beta.231 → 1.0.0-beta.232
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/lib/app-utils/account-acquisition.d.ts +15 -2
- package/src/lib/app-utils/account-acquisition.js +20 -1
- package/src/lib/app-utils/account-acquisition.js.map +1 -1
- package/src/lib/app-utils/artifact-list-queries.d.ts +35 -0
- package/src/lib/app-utils/artifact-list-queries.js +203 -0
- package/src/lib/app-utils/artifact-list-queries.js.map +1 -0
- package/src/lib/app-utils/business-profile.d.ts +171 -0
- package/src/lib/app-utils/business-profile.js +277 -0
- package/src/lib/app-utils/business-profile.js.map +1 -0
- package/src/lib/app-utils/child-contract.d.ts +4 -1
- package/src/lib/app-utils/child-contract.js +5 -2
- package/src/lib/app-utils/child-contract.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +5 -0
- package/src/lib/app-utils/console-routes.js +1 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/content-schema-type.d.ts +9 -0
- package/src/lib/app-utils/content-schema-type.js +4 -0
- package/src/lib/app-utils/content-schema-type.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +41 -5
- package/src/lib/app-utils/docs-help.generated.js +83 -3
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +181 -13
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/entry-list-declaration.d.ts +33 -0
- package/src/lib/app-utils/entry-list-declaration.js +172 -0
- package/src/lib/app-utils/entry-list-declaration.js.map +1 -0
- package/src/lib/app-utils/first-version-seed.d.ts +19 -0
- package/src/lib/app-utils/first-version-seed.js +53 -0
- package/src/lib/app-utils/first-version-seed.js.map +1 -0
- package/src/lib/app-utils/local-business.d.ts +8 -0
- package/src/lib/app-utils/local-business.js +4 -0
- package/src/lib/app-utils/local-business.js.map +1 -1
- package/src/lib/app-utils/page-markdown.js +3 -0
- package/src/lib/app-utils/page-markdown.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +19 -0
- package/src/lib/plugin-manager/feature-plugins.js +8 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +105 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-channel-orders.d.ts +262 -0
- package/src/lib/plugin-manager/plugin-channel-orders.js +32 -0
- package/src/lib/plugin-manager/plugin-channel-orders.js.map +1 -0
- package/src/lib/plugin-manager/plugin-product-catalog.d.ts +5 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -1
- package/src/lib/plugin-manager/plugin-product-writer.d.ts +166 -0
- package/src/lib/plugin-manager/plugin-product-writer.js +31 -0
- package/src/lib/plugin-manager/plugin-product-writer.js.map +1 -0
- package/src/lib/plugin-manager/plugin-theme-presets.d.ts +40 -0
- package/src/lib/plugin-manager/plugin-theme-presets.js +44 -0
- package/src/lib/plugin-manager/plugin-theme-presets.js.map +1 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Orders a store took somewhere else (AGL-3638): a marketplace, a social
|
|
19
|
+
* shop, any outside channel that sells the store's goods and is paid there.
|
|
20
|
+
*
|
|
21
|
+
* Such an order must become a REAL order of the store — numbered with the
|
|
22
|
+
* rest, shown in the same list, shipped from the same dialog, announced to
|
|
23
|
+
* the same subscribers — and its units must leave the same shelf every other
|
|
24
|
+
* channel sells from, in the same write, or two channels sell the last unit
|
|
25
|
+
* twice. The plugin that brings the order in must not write the seller's
|
|
26
|
+
* orders or products itself, so the seller registers
|
|
27
|
+
* {@link PluginChannelOrders} and records each order under its own rules: the
|
|
28
|
+
* order number, the stock decrement and its ledger row, the oversell alert,
|
|
29
|
+
* the order events.
|
|
30
|
+
*
|
|
31
|
+
* ## Once per outside order
|
|
32
|
+
*
|
|
33
|
+
* An import is keyed by `channel.id` and `externalOrderId`. The same pair a
|
|
34
|
+
* second time — a retried run, a page read twice — answers `already` with
|
|
35
|
+
* the record the first import made, and nothing moves.
|
|
36
|
+
*
|
|
37
|
+
* ## The channel was paid, not the store
|
|
38
|
+
*
|
|
39
|
+
* The buyer paid the channel, and the channel pays the merchant out. No
|
|
40
|
+
* money moves through the seller for the order; its refunds and returns
|
|
41
|
+
* happen on the channel. What the channel charged the merchant for the sale
|
|
42
|
+
* (`fees`) is RECORDED on the order for the merchant's books and never
|
|
43
|
+
* charged or collected by anyone here.
|
|
44
|
+
*
|
|
45
|
+
* Money is integer minor units (cents for USD) in `currency`. A seller may
|
|
46
|
+
* refuse an order in a currency its store does not sell in.
|
|
47
|
+
*
|
|
48
|
+
* Import this module by its own subpath
|
|
49
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-channel-orders`); it is not in the
|
|
50
|
+
* barrel.
|
|
51
|
+
*/
|
|
52
|
+
/** One line of an outside order. */
|
|
53
|
+
export interface PluginChannelOrderLine {
|
|
54
|
+
/** The channel's id for the line, which a shipment back to it names. */
|
|
55
|
+
externalLineId: string;
|
|
56
|
+
/** The seller SKU the channel sold it under; `null` when it gave none. */
|
|
57
|
+
sku: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* The product and configuration, when the importer already knows them
|
|
60
|
+
* (it listed them from `core.product-catalog`). Otherwise the seller
|
|
61
|
+
* matches `sku`.
|
|
62
|
+
*/
|
|
63
|
+
productId?: string | null;
|
|
64
|
+
variantId?: string | null;
|
|
65
|
+
/** The channel's name for the item, kept on the order. */
|
|
66
|
+
name: string;
|
|
67
|
+
quantity: number;
|
|
68
|
+
/** Per unit, before tax. */
|
|
69
|
+
unitPriceCents: number;
|
|
70
|
+
}
|
|
71
|
+
/** The address the order ships to. */
|
|
72
|
+
export interface PluginChannelOrderAddress {
|
|
73
|
+
name?: string | null;
|
|
74
|
+
line1?: string | null;
|
|
75
|
+
line2?: string | null;
|
|
76
|
+
city?: string | null;
|
|
77
|
+
state?: string | null;
|
|
78
|
+
postalCode?: string | null;
|
|
79
|
+
/** ISO 3166-1 alpha-2, upper case. */
|
|
80
|
+
country?: string | null;
|
|
81
|
+
phone?: string | null;
|
|
82
|
+
}
|
|
83
|
+
/** A channel's charge on a sale, recorded for the merchant and never collected here. */
|
|
84
|
+
export interface PluginChannelOrderFee {
|
|
85
|
+
label: string;
|
|
86
|
+
amountCents: number;
|
|
87
|
+
}
|
|
88
|
+
export interface PluginChannelOrder {
|
|
89
|
+
hostId: string;
|
|
90
|
+
/** Stable id and display name, as the importer names its channel. */
|
|
91
|
+
channel: {
|
|
92
|
+
id: string;
|
|
93
|
+
label: string;
|
|
94
|
+
};
|
|
95
|
+
externalOrderId: string;
|
|
96
|
+
/** What the merchant sees on the channel. */
|
|
97
|
+
externalRef: string;
|
|
98
|
+
placedAtMs: number;
|
|
99
|
+
/** ISO 4217. */
|
|
100
|
+
currency: string;
|
|
101
|
+
lines: PluginChannelOrderLine[];
|
|
102
|
+
shippingCents: number;
|
|
103
|
+
/** Tax the channel collected; the channel, not the store, remits it. */
|
|
104
|
+
taxCents: number;
|
|
105
|
+
discountCents: number;
|
|
106
|
+
/** What the buyer paid the channel in all. */
|
|
107
|
+
totalCents: number;
|
|
108
|
+
/** The channel's charges on the sale; `null` while the channel has not said. */
|
|
109
|
+
fees: PluginChannelOrderFee[] | null;
|
|
110
|
+
customerName: string | null;
|
|
111
|
+
shippingAddress: PluginChannelOrderAddress | null;
|
|
112
|
+
/** Placed in the channel's sandbox: recorded, but never counted as revenue. */
|
|
113
|
+
testMode: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* How the goods leave the store (AGL-3644): `ship`, the default, by a
|
|
116
|
+
* carrier to `shippingAddress`; `courier`, handed over the counter to the
|
|
117
|
+
* channel's own courier (a delivery app), so nothing is shipped from here
|
|
118
|
+
* and the seller says so to the merchant.
|
|
119
|
+
*/
|
|
120
|
+
handoff?: 'ship' | 'courier';
|
|
121
|
+
}
|
|
122
|
+
/** A line the store's stock could not cover in full: the channel sold more than the shelf held. */
|
|
123
|
+
export interface PluginChannelOrderShortfall {
|
|
124
|
+
lineIndex: number;
|
|
125
|
+
sku: string | null;
|
|
126
|
+
/** Units sold beyond what the shelf held. */
|
|
127
|
+
short: number;
|
|
128
|
+
}
|
|
129
|
+
export type PluginChannelOrderOutcome = {
|
|
130
|
+
outcome: 'created';
|
|
131
|
+
recordId: string;
|
|
132
|
+
/** What a person calls it: `#1042`. */
|
|
133
|
+
displayRef: string;
|
|
134
|
+
/** Which order line each channel line became. */
|
|
135
|
+
lines: Array<{
|
|
136
|
+
lineIndex: number;
|
|
137
|
+
externalLineId: string;
|
|
138
|
+
}>;
|
|
139
|
+
/** Lines whose units the shelf could not cover. The order is recorded all the same. */
|
|
140
|
+
shortfalls: PluginChannelOrderShortfall[];
|
|
141
|
+
/** Lines matching no product, recorded by name with no stock moved. */
|
|
142
|
+
unmatched: number[];
|
|
143
|
+
} | {
|
|
144
|
+
outcome: 'already';
|
|
145
|
+
recordId: string;
|
|
146
|
+
displayRef: string;
|
|
147
|
+
lines: Array<{
|
|
148
|
+
lineIndex: number;
|
|
149
|
+
externalLineId: string;
|
|
150
|
+
}>;
|
|
151
|
+
}
|
|
152
|
+
/** The seller would not record it; `reason` says why, for the merchant. */
|
|
153
|
+
| {
|
|
154
|
+
outcome: 'refused';
|
|
155
|
+
reason: string;
|
|
156
|
+
};
|
|
157
|
+
export interface PluginChannelOrderCancel {
|
|
158
|
+
hostId: string;
|
|
159
|
+
recordId: string;
|
|
160
|
+
/** Shown on the order's timeline. */
|
|
161
|
+
reason: string;
|
|
162
|
+
}
|
|
163
|
+
export type PluginChannelOrderCancelOutcome =
|
|
164
|
+
/** Cancelled, with this many units back on the shelf. */
|
|
165
|
+
{
|
|
166
|
+
outcome: 'cancelled';
|
|
167
|
+
restockedUnits: number;
|
|
168
|
+
} | {
|
|
169
|
+
outcome: 'already';
|
|
170
|
+
}
|
|
171
|
+
/** The seller's rules refused (it has shipped, say); `status` is the seller's word for where it stands. */
|
|
172
|
+
| {
|
|
173
|
+
outcome: 'not_cancellable';
|
|
174
|
+
status: string;
|
|
175
|
+
} | {
|
|
176
|
+
outcome: 'no_such_record';
|
|
177
|
+
};
|
|
178
|
+
/** The channel's courier took the whole order (AGL-3644): it is fulfilled, with nothing shipped from here. */
|
|
179
|
+
export interface PluginChannelOrderHandoff {
|
|
180
|
+
hostId: string;
|
|
181
|
+
recordId: string;
|
|
182
|
+
/** Shown on the order's timeline, e.g. `Picked up by the DoorDash courier`. */
|
|
183
|
+
note: string;
|
|
184
|
+
}
|
|
185
|
+
export type PluginChannelOrderHandoffOutcome = {
|
|
186
|
+
outcome: 'completed';
|
|
187
|
+
} | {
|
|
188
|
+
outcome: 'already';
|
|
189
|
+
}
|
|
190
|
+
/** The seller's rules refused (it was cancelled, say); `status` is the seller's word for where it stands. */
|
|
191
|
+
| {
|
|
192
|
+
outcome: 'not_completable';
|
|
193
|
+
status: string;
|
|
194
|
+
} | {
|
|
195
|
+
outcome: 'no_such_record';
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Money the channel gave back to the buyer of an order it sent (AGL-3644):
|
|
199
|
+
* a refund, or the channel's adjustment of the order — an item removed, a
|
|
200
|
+
* quantity lowered. No money moves here: the channel refunded its buyer and
|
|
201
|
+
* takes it from the merchant's payout, so the seller RECORDS it on the order.
|
|
202
|
+
*
|
|
203
|
+
* `restock` names the units that never left the store (an item taken off the
|
|
204
|
+
* order before it was handed over); the seller puts back at most what the
|
|
205
|
+
* order's own sale took. Empty for a refund of goods already gone.
|
|
206
|
+
*/
|
|
207
|
+
export interface PluginChannelOrderRefund {
|
|
208
|
+
hostId: string;
|
|
209
|
+
recordId: string;
|
|
210
|
+
/** The channel's id for this refund or adjustment: the same id a second time records nothing. */
|
|
211
|
+
refundId: string;
|
|
212
|
+
/** Minor units in the order's currency; `0` for an adjustment that only moves stock. */
|
|
213
|
+
amountCents: number;
|
|
214
|
+
/** Shown on the order's timeline. */
|
|
215
|
+
reason: string;
|
|
216
|
+
restock: Array<{
|
|
217
|
+
lineIndex: number;
|
|
218
|
+
quantity: number;
|
|
219
|
+
}>;
|
|
220
|
+
}
|
|
221
|
+
export type PluginChannelOrderRefundOutcome =
|
|
222
|
+
/** Recorded: `refundedCents` is the order's refunded total now, `restockedUnits` what went back on the shelf. */
|
|
223
|
+
{
|
|
224
|
+
outcome: 'recorded';
|
|
225
|
+
refundedCents: number;
|
|
226
|
+
restockedUnits: number;
|
|
227
|
+
} | {
|
|
228
|
+
outcome: 'already';
|
|
229
|
+
}
|
|
230
|
+
/** The seller would not record it; `reason` says why, for the merchant. */
|
|
231
|
+
| {
|
|
232
|
+
outcome: 'refused';
|
|
233
|
+
reason: string;
|
|
234
|
+
} | {
|
|
235
|
+
outcome: 'no_such_record';
|
|
236
|
+
};
|
|
237
|
+
export interface PluginChannelOrders {
|
|
238
|
+
/** Records an outside order once, with its units off the shelf in the same write. */
|
|
239
|
+
importOrder(order: PluginChannelOrder): Promise<PluginChannelOrderOutcome>;
|
|
240
|
+
/** The channel canceled an order it had sent: cancel it here and put its units back. */
|
|
241
|
+
cancelOrder(request: PluginChannelOrderCancel): Promise<PluginChannelOrderCancelOutcome>;
|
|
242
|
+
/** The channel's charges on a sale, once it says what they were. */
|
|
243
|
+
recordFees(request: {
|
|
244
|
+
hostId: string;
|
|
245
|
+
recordId: string;
|
|
246
|
+
fees: PluginChannelOrderFee[];
|
|
247
|
+
}): Promise<'recorded' | 'no_such_record'>;
|
|
248
|
+
/**
|
|
249
|
+
* The channel's courier took the order (AGL-3644). Optional: a seller that
|
|
250
|
+
* cannot record a hand-off leaves it out, and the importer leaves the order
|
|
251
|
+
* for the merchant to fulfill.
|
|
252
|
+
*/
|
|
253
|
+
completeOrder?(request: PluginChannelOrderHandoff): Promise<PluginChannelOrderHandoffOutcome>;
|
|
254
|
+
/** The channel refunded or adjusted an order it sent (AGL-3644). Optional, as `completeOrder`. */
|
|
255
|
+
recordRefund?(request: PluginChannelOrderRefund): Promise<PluginChannelOrderRefundOutcome>;
|
|
256
|
+
}
|
|
257
|
+
/** Registers the seller. A second plugin is refused naming both. */
|
|
258
|
+
export declare function registerPluginChannelOrders(orders: PluginChannelOrders, options?: {
|
|
259
|
+
pluginId?: string;
|
|
260
|
+
}): void;
|
|
261
|
+
/** The seller, or `null` when no plugin registered one. */
|
|
262
|
+
export declare function pluginChannelOrders(): PluginChannelOrders | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
18
|
+
const PLUGIN_CHANNEL_ORDERS = definePluginServiceContract('core.channel-orders', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
/** Registers the seller. A second plugin is refused naming both. */ export function registerPluginChannelOrders(orders, options) {
|
|
22
|
+
registerPluginService(PLUGIN_CHANNEL_ORDERS, orders, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
23
|
+
pluginId: options.pluginId
|
|
24
|
+
} : {}));
|
|
25
|
+
}
|
|
26
|
+
/** The seller, or `null` when no plugin registered one. */ export function pluginChannelOrders() {
|
|
27
|
+
var _ref;
|
|
28
|
+
var _resolvePluginServices_;
|
|
29
|
+
return (_ref = (_resolvePluginServices_ = resolvePluginServices(PLUGIN_CHANNEL_ORDERS)[0]) == null ? void 0 : _resolvePluginServices_.impl) != null ? _ref : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=plugin-channel-orders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-channel-orders.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 resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Orders a store took somewhere else (AGL-3638): a marketplace, a social\n * shop, any outside channel that sells the store's goods and is paid there.\n *\n * Such an order must become a REAL order of the store — numbered with the\n * rest, shown in the same list, shipped from the same dialog, announced to\n * the same subscribers — and its units must leave the same shelf every other\n * channel sells from, in the same write, or two channels sell the last unit\n * twice. The plugin that brings the order in must not write the seller's\n * orders or products itself, so the seller registers\n * {@link PluginChannelOrders} and records each order under its own rules: the\n * order number, the stock decrement and its ledger row, the oversell alert,\n * the order events.\n *\n * ## Once per outside order\n *\n * An import is keyed by `channel.id` and `externalOrderId`. The same pair a\n * second time — a retried run, a page read twice — answers `already` with\n * the record the first import made, and nothing moves.\n *\n * ## The channel was paid, not the store\n *\n * The buyer paid the channel, and the channel pays the merchant out. No\n * money moves through the seller for the order; its refunds and returns\n * happen on the channel. What the channel charged the merchant for the sale\n * (`fees`) is RECORDED on the order for the merchant's books and never\n * charged or collected by anyone here.\n *\n * Money is integer minor units (cents for USD) in `currency`. A seller may\n * refuse an order in a currency its store does not sell in.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-channel-orders`); it is not in the\n * barrel.\n */\n\n/** One line of an outside order. */\nexport interface PluginChannelOrderLine {\n /** The channel's id for the line, which a shipment back to it names. */\n externalLineId: string\n /** The seller SKU the channel sold it under; `null` when it gave none. */\n sku: string | null\n /**\n * The product and configuration, when the importer already knows them\n * (it listed them from `core.product-catalog`). Otherwise the seller\n * matches `sku`.\n */\n productId?: string | null\n variantId?: string | null\n /** The channel's name for the item, kept on the order. */\n name: string\n quantity: number\n /** Per unit, before tax. */\n unitPriceCents: number\n}\n\n/** The address the order ships to. */\nexport interface PluginChannelOrderAddress {\n name?: string | null\n line1?: string | null\n line2?: string | null\n city?: string | null\n state?: string | null\n postalCode?: string | null\n /** ISO 3166-1 alpha-2, upper case. */\n country?: string | null\n phone?: string | null\n}\n\n/** A channel's charge on a sale, recorded for the merchant and never collected here. */\nexport interface PluginChannelOrderFee {\n label: string\n amountCents: number\n}\n\nexport interface PluginChannelOrder {\n hostId: string\n /** Stable id and display name, as the importer names its channel. */\n channel: { id: string; label: string }\n externalOrderId: string\n /** What the merchant sees on the channel. */\n externalRef: string\n placedAtMs: number\n /** ISO 4217. */\n currency: string\n lines: PluginChannelOrderLine[]\n shippingCents: number\n /** Tax the channel collected; the channel, not the store, remits it. */\n taxCents: number\n discountCents: number\n /** What the buyer paid the channel in all. */\n totalCents: number\n /** The channel's charges on the sale; `null` while the channel has not said. */\n fees: PluginChannelOrderFee[] | null\n customerName: string | null\n shippingAddress: PluginChannelOrderAddress | null\n /** Placed in the channel's sandbox: recorded, but never counted as revenue. */\n testMode: boolean\n /**\n * How the goods leave the store (AGL-3644): `ship`, the default, by a\n * carrier to `shippingAddress`; `courier`, handed over the counter to the\n * channel's own courier (a delivery app), so nothing is shipped from here\n * and the seller says so to the merchant.\n */\n handoff?: 'ship' | 'courier'\n}\n\n/** A line the store's stock could not cover in full: the channel sold more than the shelf held. */\nexport interface PluginChannelOrderShortfall {\n lineIndex: number\n sku: string | null\n /** Units sold beyond what the shelf held. */\n short: number\n}\n\nexport type PluginChannelOrderOutcome =\n | {\n outcome: 'created'\n recordId: string\n /** What a person calls it: `#1042`. */\n displayRef: string\n /** Which order line each channel line became. */\n lines: Array<{ lineIndex: number; externalLineId: string }>\n /** Lines whose units the shelf could not cover. The order is recorded all the same. */\n shortfalls: PluginChannelOrderShortfall[]\n /** Lines matching no product, recorded by name with no stock moved. */\n unmatched: number[]\n }\n | {\n outcome: 'already'\n recordId: string\n displayRef: string\n lines: Array<{ lineIndex: number; externalLineId: string }>\n }\n /** The seller would not record it; `reason` says why, for the merchant. */\n | { outcome: 'refused'; reason: string }\n\nexport interface PluginChannelOrderCancel {\n hostId: string\n recordId: string\n /** Shown on the order's timeline. */\n reason: string\n}\n\nexport type PluginChannelOrderCancelOutcome =\n /** Cancelled, with this many units back on the shelf. */\n | { outcome: 'cancelled'; restockedUnits: number }\n | { outcome: 'already' }\n /** The seller's rules refused (it has shipped, say); `status` is the seller's word for where it stands. */\n | { outcome: 'not_cancellable'; status: string }\n | { outcome: 'no_such_record' }\n\n/** The channel's courier took the whole order (AGL-3644): it is fulfilled, with nothing shipped from here. */\nexport interface PluginChannelOrderHandoff {\n hostId: string\n recordId: string\n /** Shown on the order's timeline, e.g. `Picked up by the DoorDash courier`. */\n note: string\n}\n\nexport type PluginChannelOrderHandoffOutcome =\n | { outcome: 'completed' }\n | { outcome: 'already' }\n /** The seller's rules refused (it was cancelled, say); `status` is the seller's word for where it stands. */\n | { outcome: 'not_completable'; status: string }\n | { outcome: 'no_such_record' }\n\n/**\n * Money the channel gave back to the buyer of an order it sent (AGL-3644):\n * a refund, or the channel's adjustment of the order — an item removed, a\n * quantity lowered. No money moves here: the channel refunded its buyer and\n * takes it from the merchant's payout, so the seller RECORDS it on the order.\n *\n * `restock` names the units that never left the store (an item taken off the\n * order before it was handed over); the seller puts back at most what the\n * order's own sale took. Empty for a refund of goods already gone.\n */\nexport interface PluginChannelOrderRefund {\n hostId: string\n recordId: string\n /** The channel's id for this refund or adjustment: the same id a second time records nothing. */\n refundId: string\n /** Minor units in the order's currency; `0` for an adjustment that only moves stock. */\n amountCents: number\n /** Shown on the order's timeline. */\n reason: string\n restock: Array<{ lineIndex: number; quantity: number }>\n}\n\nexport type PluginChannelOrderRefundOutcome =\n /** Recorded: `refundedCents` is the order's refunded total now, `restockedUnits` what went back on the shelf. */\n | { outcome: 'recorded'; refundedCents: number; restockedUnits: number }\n | { outcome: 'already' }\n /** The seller would not record it; `reason` says why, for the merchant. */\n | { outcome: 'refused'; reason: string }\n | { outcome: 'no_such_record' }\n\nexport interface PluginChannelOrders {\n /** Records an outside order once, with its units off the shelf in the same write. */\n importOrder(order: PluginChannelOrder): Promise<PluginChannelOrderOutcome>\n /** The channel canceled an order it had sent: cancel it here and put its units back. */\n cancelOrder(request: PluginChannelOrderCancel): Promise<PluginChannelOrderCancelOutcome>\n /** The channel's charges on a sale, once it says what they were. */\n recordFees(request: {\n hostId: string\n recordId: string\n fees: PluginChannelOrderFee[]\n }): Promise<'recorded' | 'no_such_record'>\n /**\n * The channel's courier took the order (AGL-3644). Optional: a seller that\n * cannot record a hand-off leaves it out, and the importer leaves the order\n * for the merchant to fulfill.\n */\n completeOrder?(request: PluginChannelOrderHandoff): Promise<PluginChannelOrderHandoffOutcome>\n /** The channel refunded or adjusted an order it sent (AGL-3644). Optional, as `completeOrder`. */\n recordRefund?(request: PluginChannelOrderRefund): Promise<PluginChannelOrderRefundOutcome>\n}\n\nconst PLUGIN_CHANNEL_ORDERS = definePluginServiceContract<PluginChannelOrders>('core.channel-orders', {\n multiple: false,\n})\n\n/** Registers the seller. A second plugin is refused naming both. */\nexport function registerPluginChannelOrders(orders: PluginChannelOrders, options?: { pluginId?: string }): void {\n registerPluginService(PLUGIN_CHANNEL_ORDERS, orders, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The seller, or `null` when no plugin registered one. */\nexport function pluginChannelOrders(): PluginChannelOrders | null {\n return resolvePluginServices(PLUGIN_CHANNEL_ORDERS)[0]?.impl ?? null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_CHANNEL_ORDERS","multiple","registerPluginChannelOrders","orders","options","pluginId","pluginChannelOrders","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA4N1B,MAAMC,wBAAwBH,4BAAiD,uBAAuB;IACpGI,UAAU;AACZ;AAEA,kEAAkE,GAClE,OAAO,SAASC,4BAA4BC,MAA2B,EAAEC,OAA+B;IACtGN,sBAAsBE,uBAAuBG,QAAQ,aAC/CC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPP;IAAP,gBAAOA,0BAAAA,sBAAsBC,sBAAsB,CAAC,EAAE,qBAA/CD,wBAAiDQ,IAAI,mBAAI;AAClE"}
|
|
@@ -99,6 +99,11 @@ export interface CatalogOffer {
|
|
|
99
99
|
hasVariants: boolean;
|
|
100
100
|
productId: string;
|
|
101
101
|
variantId: string;
|
|
102
|
+
/**
|
|
103
|
+
* The merchant's own SKU for this configuration, as entered; absent when
|
|
104
|
+
* they entered none. What a marketplace matches its listing by (AGL-3638).
|
|
105
|
+
*/
|
|
106
|
+
sku?: string;
|
|
102
107
|
/** The product's name. */
|
|
103
108
|
productName: string;
|
|
104
109
|
/** The product's name with this configuration's choices, e.g. `Tee — Red / M`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-product-catalog.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 resolvePluginServices,\n} from './plugin-services'\n\n/**\n * A site's sellable catalog, read whole and in pages by a plugin that does\n * not own it (AGL-3637).\n *\n * A product feed, a marketplace listing sync and an ad catalog all need the\n * same thing: every offer a store sells, with the facts a shopping channel\n * asks for — price in the store's currency, stock, photos, identifiers,\n * shipping. The plugin that keeps the products answers it here, so a reader\n * never learns where products are stored, how variants are modeled or what a\n * deleted product looks like.\n *\n * ## One OFFER per sellable configuration\n *\n * A product with one configuration is one offer whose `id` is the product's\n * own id. A product with several (sizes, colors) is one offer per\n * configuration, each with its own `id` and the product's id as `groupId`,\n * which is what every shopping channel calls the variant group.\n *\n * ## Money is integer minor units in the store's currency\n *\n * `priceMinor` and every other amount are integers (cents for USD) in\n * {@link CatalogStore.currency}. A reader formats them; it never assumes a\n * currency.\n *\n * ## Pages, not a list\n *\n * A store may hold far more products than one read should load, so the\n * catalog is walked in pages: `page()` answers up to about `limit` offers and\n * a `nextCursor` to continue from, `null` on the last page. A cursor is the\n * owner's own opaque string; a reader passes it back untouched.\n *\n * ## One owner\n *\n * A slot, like the tax profile: a site has one catalog, so a second plugin's\n * registration is refused and the incumbent keeps serving. No owner\n * registered is an ordinary answer — {@link pluginProductCatalog} returns\n * `undefined` and a reader serves nothing.\n */\n\n/** The store an offer belongs to, as a channel needs it. */\nexport interface CatalogStore {\n hostId: string\n /** The store's name, as its site calls itself. */\n name: string\n /**\n * The origin the store's pages are served on (`https://shop.example.com`),\n * or `null` when the site has no address yet.\n */\n origin: string | null\n /** ISO 4217, upper case. Every amount in the catalog is in it. */\n currency: string\n /**\n * Whether `/products/{slug}` pages are served. When `false`, every offer's\n * `path` answers 404, and a channel will refuse the offers.\n */\n productPagesServed: boolean\n /**\n * Countries the store's checkout prices shipping to by a live carrier\n * quote rather than its own table (ISO 3166-1 alpha-2, upper case). An\n * offer carries no {@link CatalogShippingPrice} for them, because the price\n * depends on the address; a channel that calculates carrier rates itself\n * prices them from the offer's weight and dimensions.\n */\n carrierPricedCountries: string[]\n}\n\nexport type CatalogAvailability = 'in_stock' | 'out_of_stock' | 'backorder'\nexport type CatalogCondition = 'new' | 'refurbished' | 'used'\nexport type CatalogProductKind = 'physical' | 'digital' | 'service'\n\n/** What one offer costs to ship to one country, cheapest rate first chosen. */\nexport interface CatalogShippingPrice {\n /** ISO 3166-1 alpha-2, upper case. */\n country: string\n /** The rate's name as the shopper sees it at checkout. */\n service: string\n priceMinor: number\n}\n\n/** One sellable configuration of a product. */\nexport interface CatalogOffer {\n /** Stable, unique within the store: the product id, or product and variant. */\n id: string\n /** The product's id: the variant group every configuration shares. */\n groupId: string\n /** Whether the product has more than one configuration. */\n hasVariants: boolean\n productId: string\n variantId: string\n /** The product's name. */\n productName: string\n /** The product's name with this configuration's choices, e.g. `Tee — Red / M`. */\n title: string\n /** Plain text: no markup. May be empty. */\n description: string\n /** Site-relative path of the product page, e.g. `/products/tee`. */\n path: string\n /** Absolute URL of the photo shown for this configuration. */\n imageUrl?: string\n /** Absolute URLs of the product's other photos, in order. */\n additionalImageUrls: string[]\n /** What the shopper pays, before any sale: the regular price. */\n priceMinor: number\n /** A lower price the offer is on sale for now; absent when not on sale. */\n salePriceMinor?: number\n availability: CatalogAvailability\n /** Units in stock, or `null` when stock is not tracked. */\n quantity: number | null\n kind: CatalogProductKind\n /** Whether the product is sold only as a recurring subscription. */\n subscriptionOnly: boolean\n /** What the merchant entered for shopping channels; absent when they did not. */\n condition?: CatalogCondition\n brand?: string\n /** GTIN (UPC, EAN, ISBN, JAN, ITF-14) as entered, digits only. */\n gtin?: string\n mpn?: string\n /** A Google product taxonomy id or full path, as entered. */\n googleProductCategory?: string\n /** The store's own category path, e.g. `Apparel > Shirts`. */\n productType?: string\n /** The configuration's choices, by option name. */\n options: Readonly<Record<string, string>>\n /** The choice of an option named like a color, when there is one. */\n color?: string\n /** The choice of an option named like a size, when there is one. */\n size?: string\n weightGrams?: number\n /** One unit's packed size, in centimeters, when the merchant entered it. */\n dimensionsCm?: { length: number; width: number; height: number }\n /** What it costs to ship one unit, per country the store ships to. */\n shipping: CatalogShippingPrice[]\n /** When the product last changed, epoch ms; absent when unknown. */\n updatedAtMs?: number\n}\n\nexport interface CatalogPage {\n offers: CatalogOffer[]\n /** Where the next page starts, or `null` when this is the last one. */\n nextCursor: string | null\n}\n\nexport interface PluginProductCatalog {\n /** The store, or `null` for a site that does not sell. */\n store(hostId: string): Promise<CatalogStore | null>\n /**\n * One page of the store's live, listed offers: never a draft, archived or\n * deleted product. About `limit` offers — a product's configurations are\n * never split across pages, so a page may run over by one product's worth.\n */\n page(request: { hostId: string; cursor?: string | null; limit: number }): Promise<CatalogPage>\n}\n\nexport const PLUGIN_PRODUCT_CATALOG = definePluginServiceContract<PluginProductCatalog>(\n 'core.product-catalog',\n { multiple: false },\n)\n\n/** Registers the store catalog. A second plugin's is refused, naming both. */\nexport function registerPluginProductCatalog(\n catalog: PluginProductCatalog,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_PRODUCT_CATALOG, catalog, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered catalog, or `undefined` when no plugin sells. */\nexport function pluginProductCatalog(): PluginProductCatalog | undefined {\n return resolvePluginServices(PLUGIN_PRODUCT_CATALOG)[0]?.impl\n}\n\n/**\n * The plugin that publishes the catalog to shopping channels, as the\n * catalog's owner hands it an address of its own (AGL-3637).\n *\n * Before any plugin published feeds, the plugin that keeps the products\n * served one feed itself, and merchants pasted that address into their\n * channel. The address stays the owner's — a request for it reaches the\n * owner's routes — but the feed is written by whoever publishes the catalog\n * now, so the owner answers it from here. Nobody registered is an ordinary\n * answer: {@link pluginCatalogFeed} returns `undefined` and the owner\n * answers 404.\n */\nexport interface PluginCatalogFeed {\n /**\n * Answers a request for the owner's pre-existing feed address for the site\n * `hostId`: the feed, a 404 when the site's publisher has retired the\n * address or does not publish, or a 503 to try later.\n */\n serveLegacyFeed(request: Request, hostId: string): Promise<Response>\n}\n\nexport const PLUGIN_CATALOG_FEED = definePluginServiceContract<PluginCatalogFeed>(\n 'core.catalog-feed',\n { multiple: false },\n)\n\n/** Registers the catalog's feed publisher. A second plugin's is refused, naming both. */\nexport function registerPluginCatalogFeed(\n feed: PluginCatalogFeed,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_CATALOG_FEED, feed, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered feed publisher, or `undefined` when no plugin publishes feeds. */\nexport function pluginCatalogFeed(): PluginCatalogFeed | undefined {\n return resolvePluginServices(PLUGIN_CATALOG_FEED)[0]?.impl\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_PRODUCT_CATALOG","multiple","registerPluginProductCatalog","catalog","options","pluginId","pluginProductCatalog","impl","PLUGIN_CATALOG_FEED","registerPluginCatalogFeed","feed","pluginCatalogFeed"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA2J1B,OAAO,MAAMC,yBAAyBH,4BACpC,wBACA;IAAEI,UAAU;AAAM,GACnB;AAED,4EAA4E,GAC5E,OAAO,SAASC,6BACdC,OAA6B,EAC7BC,OAA+B;IAE/BN,sBAAsBE,wBAAwBG,SAAS,aACjDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,iEAAiE,GACjE,OAAO,SAASC;QACPP;IAAP,QAAOA,0BAAAA,sBAAsBC,uBAAuB,CAAC,EAAE,qBAAhDD,wBAAkDQ,IAAI;AAC/D;AAuBA,OAAO,MAAMC,sBAAsBX,4BACjC,qBACA;IAAEI,UAAU;AAAM,GACnB;AAED,uFAAuF,GACvF,OAAO,SAASQ,0BACdC,IAAuB,EACvBN,OAA+B;IAE/BN,sBAAsBU,qBAAqBE,MAAM,aAC3CN,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,kFAAkF,GAClF,OAAO,SAASM;QACPZ;IAAP,QAAOA,0BAAAA,sBAAsBS,oBAAoB,CAAC,EAAE,qBAA7CT,wBAA+CQ,IAAI;AAC5D"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-product-catalog.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 resolvePluginServices,\n} from './plugin-services'\n\n/**\n * A site's sellable catalog, read whole and in pages by a plugin that does\n * not own it (AGL-3637).\n *\n * A product feed, a marketplace listing sync and an ad catalog all need the\n * same thing: every offer a store sells, with the facts a shopping channel\n * asks for — price in the store's currency, stock, photos, identifiers,\n * shipping. The plugin that keeps the products answers it here, so a reader\n * never learns where products are stored, how variants are modeled or what a\n * deleted product looks like.\n *\n * ## One OFFER per sellable configuration\n *\n * A product with one configuration is one offer whose `id` is the product's\n * own id. A product with several (sizes, colors) is one offer per\n * configuration, each with its own `id` and the product's id as `groupId`,\n * which is what every shopping channel calls the variant group.\n *\n * ## Money is integer minor units in the store's currency\n *\n * `priceMinor` and every other amount are integers (cents for USD) in\n * {@link CatalogStore.currency}. A reader formats them; it never assumes a\n * currency.\n *\n * ## Pages, not a list\n *\n * A store may hold far more products than one read should load, so the\n * catalog is walked in pages: `page()` answers up to about `limit` offers and\n * a `nextCursor` to continue from, `null` on the last page. A cursor is the\n * owner's own opaque string; a reader passes it back untouched.\n *\n * ## One owner\n *\n * A slot, like the tax profile: a site has one catalog, so a second plugin's\n * registration is refused and the incumbent keeps serving. No owner\n * registered is an ordinary answer — {@link pluginProductCatalog} returns\n * `undefined` and a reader serves nothing.\n */\n\n/** The store an offer belongs to, as a channel needs it. */\nexport interface CatalogStore {\n hostId: string\n /** The store's name, as its site calls itself. */\n name: string\n /**\n * The origin the store's pages are served on (`https://shop.example.com`),\n * or `null` when the site has no address yet.\n */\n origin: string | null\n /** ISO 4217, upper case. Every amount in the catalog is in it. */\n currency: string\n /**\n * Whether `/products/{slug}` pages are served. When `false`, every offer's\n * `path` answers 404, and a channel will refuse the offers.\n */\n productPagesServed: boolean\n /**\n * Countries the store's checkout prices shipping to by a live carrier\n * quote rather than its own table (ISO 3166-1 alpha-2, upper case). An\n * offer carries no {@link CatalogShippingPrice} for them, because the price\n * depends on the address; a channel that calculates carrier rates itself\n * prices them from the offer's weight and dimensions.\n */\n carrierPricedCountries: string[]\n}\n\nexport type CatalogAvailability = 'in_stock' | 'out_of_stock' | 'backorder'\nexport type CatalogCondition = 'new' | 'refurbished' | 'used'\nexport type CatalogProductKind = 'physical' | 'digital' | 'service'\n\n/** What one offer costs to ship to one country, cheapest rate first chosen. */\nexport interface CatalogShippingPrice {\n /** ISO 3166-1 alpha-2, upper case. */\n country: string\n /** The rate's name as the shopper sees it at checkout. */\n service: string\n priceMinor: number\n}\n\n/** One sellable configuration of a product. */\nexport interface CatalogOffer {\n /** Stable, unique within the store: the product id, or product and variant. */\n id: string\n /** The product's id: the variant group every configuration shares. */\n groupId: string\n /** Whether the product has more than one configuration. */\n hasVariants: boolean\n productId: string\n variantId: string\n /**\n * The merchant's own SKU for this configuration, as entered; absent when\n * they entered none. What a marketplace matches its listing by (AGL-3638).\n */\n sku?: string\n /** The product's name. */\n productName: string\n /** The product's name with this configuration's choices, e.g. `Tee — Red / M`. */\n title: string\n /** Plain text: no markup. May be empty. */\n description: string\n /** Site-relative path of the product page, e.g. `/products/tee`. */\n path: string\n /** Absolute URL of the photo shown for this configuration. */\n imageUrl?: string\n /** Absolute URLs of the product's other photos, in order. */\n additionalImageUrls: string[]\n /** What the shopper pays, before any sale: the regular price. */\n priceMinor: number\n /** A lower price the offer is on sale for now; absent when not on sale. */\n salePriceMinor?: number\n availability: CatalogAvailability\n /** Units in stock, or `null` when stock is not tracked. */\n quantity: number | null\n kind: CatalogProductKind\n /** Whether the product is sold only as a recurring subscription. */\n subscriptionOnly: boolean\n /** What the merchant entered for shopping channels; absent when they did not. */\n condition?: CatalogCondition\n brand?: string\n /** GTIN (UPC, EAN, ISBN, JAN, ITF-14) as entered, digits only. */\n gtin?: string\n mpn?: string\n /** A Google product taxonomy id or full path, as entered. */\n googleProductCategory?: string\n /** The store's own category path, e.g. `Apparel > Shirts`. */\n productType?: string\n /** The configuration's choices, by option name. */\n options: Readonly<Record<string, string>>\n /** The choice of an option named like a color, when there is one. */\n color?: string\n /** The choice of an option named like a size, when there is one. */\n size?: string\n weightGrams?: number\n /** One unit's packed size, in centimeters, when the merchant entered it. */\n dimensionsCm?: { length: number; width: number; height: number }\n /** What it costs to ship one unit, per country the store ships to. */\n shipping: CatalogShippingPrice[]\n /** When the product last changed, epoch ms; absent when unknown. */\n updatedAtMs?: number\n}\n\nexport interface CatalogPage {\n offers: CatalogOffer[]\n /** Where the next page starts, or `null` when this is the last one. */\n nextCursor: string | null\n}\n\nexport interface PluginProductCatalog {\n /** The store, or `null` for a site that does not sell. */\n store(hostId: string): Promise<CatalogStore | null>\n /**\n * One page of the store's live, listed offers: never a draft, archived or\n * deleted product. About `limit` offers — a product's configurations are\n * never split across pages, so a page may run over by one product's worth.\n */\n page(request: { hostId: string; cursor?: string | null; limit: number }): Promise<CatalogPage>\n}\n\nexport const PLUGIN_PRODUCT_CATALOG = definePluginServiceContract<PluginProductCatalog>(\n 'core.product-catalog',\n { multiple: false },\n)\n\n/** Registers the store catalog. A second plugin's is refused, naming both. */\nexport function registerPluginProductCatalog(\n catalog: PluginProductCatalog,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_PRODUCT_CATALOG, catalog, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered catalog, or `undefined` when no plugin sells. */\nexport function pluginProductCatalog(): PluginProductCatalog | undefined {\n return resolvePluginServices(PLUGIN_PRODUCT_CATALOG)[0]?.impl\n}\n\n/**\n * The plugin that publishes the catalog to shopping channels, as the\n * catalog's owner hands it an address of its own (AGL-3637).\n *\n * Before any plugin published feeds, the plugin that keeps the products\n * served one feed itself, and merchants pasted that address into their\n * channel. The address stays the owner's — a request for it reaches the\n * owner's routes — but the feed is written by whoever publishes the catalog\n * now, so the owner answers it from here. Nobody registered is an ordinary\n * answer: {@link pluginCatalogFeed} returns `undefined` and the owner\n * answers 404.\n */\nexport interface PluginCatalogFeed {\n /**\n * Answers a request for the owner's pre-existing feed address for the site\n * `hostId`: the feed, a 404 when the site's publisher has retired the\n * address or does not publish, or a 503 to try later.\n */\n serveLegacyFeed(request: Request, hostId: string): Promise<Response>\n}\n\nexport const PLUGIN_CATALOG_FEED = definePluginServiceContract<PluginCatalogFeed>(\n 'core.catalog-feed',\n { multiple: false },\n)\n\n/** Registers the catalog's feed publisher. A second plugin's is refused, naming both. */\nexport function registerPluginCatalogFeed(\n feed: PluginCatalogFeed,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_CATALOG_FEED, feed, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered feed publisher, or `undefined` when no plugin publishes feeds. */\nexport function pluginCatalogFeed(): PluginCatalogFeed | undefined {\n return resolvePluginServices(PLUGIN_CATALOG_FEED)[0]?.impl\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_PRODUCT_CATALOG","multiple","registerPluginProductCatalog","catalog","options","pluginId","pluginProductCatalog","impl","PLUGIN_CATALOG_FEED","registerPluginCatalogFeed","feed","pluginCatalogFeed"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAgK1B,OAAO,MAAMC,yBAAyBH,4BACpC,wBACA;IAAEI,UAAU;AAAM,GACnB;AAED,4EAA4E,GAC5E,OAAO,SAASC,6BACdC,OAA6B,EAC7BC,OAA+B;IAE/BN,sBAAsBE,wBAAwBG,SAAS,aACjDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,iEAAiE,GACjE,OAAO,SAASC;QACPP;IAAP,QAAOA,0BAAAA,sBAAsBC,uBAAuB,CAAC,EAAE,qBAAhDD,wBAAkDQ,IAAI;AAC/D;AAuBA,OAAO,MAAMC,sBAAsBX,4BACjC,qBACA;IAAEI,UAAU;AAAM,GACnB;AAED,uFAAuF,GACvF,OAAO,SAASQ,0BACdC,IAAuB,EACvBN,OAA+B;IAE/BN,sBAAsBU,qBAAqBE,MAAM,aAC3CN,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,kFAAkF,GAClF,OAAO,SAASM;QACPZ;IAAP,QAAOA,0BAAAA,sBAAsBS,oBAAoB,CAAC,EAAE,qBAA7CT,wBAA+CQ,IAAI;AAC5D"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Products that come from somewhere else, written by a plugin that does not
|
|
19
|
+
* keep the store's products (AGL-3641).
|
|
20
|
+
*
|
|
21
|
+
* A print-on-demand service, a dropshipping supplier and a fulfillment
|
|
22
|
+
* network each hold a catalog of their own, and a merchant who sells it wants
|
|
23
|
+
* those products in their store without typing them in again — then kept in
|
|
24
|
+
* step as the source changes what it offers. The plugin that keeps the
|
|
25
|
+
* products answers here, so the plugin that reads the source never learns
|
|
26
|
+
* how products are stored, how their addresses are chosen, how the plan's
|
|
27
|
+
* product allowance is counted or what the store's search keys are. Every
|
|
28
|
+
* write keeps the owner's own rules: a create counts against the allowance in
|
|
29
|
+
* the transaction that makes it, and an edit leaves an entry in the site's
|
|
30
|
+
* activity like any other.
|
|
31
|
+
*
|
|
32
|
+
* ## Variants by the caller's key
|
|
33
|
+
*
|
|
34
|
+
* The caller names every configuration by its own stable key — the source's
|
|
35
|
+
* variant id — and the owner answers with its own variant id for each, so an
|
|
36
|
+
* order line can later be traced back to what the source sells. On an
|
|
37
|
+
* update the caller passes back the variant ids it was given; a key with no
|
|
38
|
+
* id is a new configuration, and an existing variant whose id is not passed
|
|
39
|
+
* is one the source no longer offers, removed.
|
|
40
|
+
*
|
|
41
|
+
* ## Money is integer minor units in the store's currency
|
|
42
|
+
*
|
|
43
|
+
* `priceMinor` is an integer (cents for USD) in the currency the store sells
|
|
44
|
+
* in, the `currency` the store's catalog (`core.product-catalog`) names. The
|
|
45
|
+
* caller converts nothing: a source priced in another currency is the
|
|
46
|
+
* caller's to refuse.
|
|
47
|
+
*
|
|
48
|
+
* ## What an update leaves alone
|
|
49
|
+
*
|
|
50
|
+
* The merchant edits an imported product like any other. An update rewrites
|
|
51
|
+
* only what the caller names: prices when `prices` is set, the description
|
|
52
|
+
* and photos when `content` is set, and always the configurations and their
|
|
53
|
+
* availability, because those are what an order can be filled from.
|
|
54
|
+
*
|
|
55
|
+
* ## One owner
|
|
56
|
+
*
|
|
57
|
+
* A slot, like the catalog: a site's products have one keeper, so a second
|
|
58
|
+
* plugin's registration is refused and the incumbent keeps serving. No owner
|
|
59
|
+
* registered is an ordinary answer — {@link pluginProductWriter} returns
|
|
60
|
+
* `undefined` and a caller imports nothing.
|
|
61
|
+
*
|
|
62
|
+
* Import this module by its own subpath
|
|
63
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-product-writer`); it is not in the
|
|
64
|
+
* barrel.
|
|
65
|
+
*/
|
|
66
|
+
/** One axis of variation, e.g. `{ name: 'Size', values: ['S', 'M', 'L'] }`. */
|
|
67
|
+
export interface SourcedProductOption {
|
|
68
|
+
name: string;
|
|
69
|
+
values: string[];
|
|
70
|
+
}
|
|
71
|
+
/** One sellable configuration, as the source offers it. */
|
|
72
|
+
export interface SourcedProductVariant {
|
|
73
|
+
/** The source's own stable id for it. */
|
|
74
|
+
key: string;
|
|
75
|
+
/** The owner's id, when an earlier write returned one. */
|
|
76
|
+
variantId?: string;
|
|
77
|
+
/** Choices by option name; `{}` for a product with one configuration. */
|
|
78
|
+
options: Record<string, string>;
|
|
79
|
+
sku?: string;
|
|
80
|
+
/** What the shopper pays, integer minor units in the store's currency. */
|
|
81
|
+
priceMinor: number;
|
|
82
|
+
weightGrams?: number;
|
|
83
|
+
/** A photo the owner already holds (a media-library URL) for this choice. */
|
|
84
|
+
imageUrl?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the source can make or ship it now. An unavailable variant is
|
|
87
|
+
* kept, sold out, so a product page does not lose a choice for a day's
|
|
88
|
+
* shortage; a variant the source dropped is not passed at all.
|
|
89
|
+
*/
|
|
90
|
+
available: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** A product as the source offers it. */
|
|
93
|
+
export interface SourcedProduct {
|
|
94
|
+
/** The source's stable key for the product, e.g. `printful:123`. */
|
|
95
|
+
sourceKey: string;
|
|
96
|
+
name: string;
|
|
97
|
+
/** Plain text or simple HTML, as the source wrote it. */
|
|
98
|
+
description?: string;
|
|
99
|
+
/** Photos the owner already holds (media-library URLs), first is primary. */
|
|
100
|
+
mediaUrls: string[];
|
|
101
|
+
tags?: string[];
|
|
102
|
+
options: SourcedProductOption[];
|
|
103
|
+
variants: SourcedProductVariant[];
|
|
104
|
+
}
|
|
105
|
+
export interface SourcedProductWrite {
|
|
106
|
+
hostId: string;
|
|
107
|
+
product: SourcedProduct;
|
|
108
|
+
/** The owner's product id, when an earlier write returned one. */
|
|
109
|
+
productId?: string;
|
|
110
|
+
/** A new product is born a draft unless the caller says to list it. */
|
|
111
|
+
status?: 'draft' | 'active';
|
|
112
|
+
/** On an update: rewrite prices from the source. */
|
|
113
|
+
prices?: boolean;
|
|
114
|
+
/** On an update: rewrite the name, description and photos from the source. */
|
|
115
|
+
content?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Whether a `productId` the store no longer has (the merchant deleted it)
|
|
118
|
+
* is made again. Default true; a background re-sync passes false and is
|
|
119
|
+
* answered `missing`, so a product the merchant deleted stays deleted.
|
|
120
|
+
*/
|
|
121
|
+
recreate?: boolean;
|
|
122
|
+
/** The member the write is made for, for the site's activity. */
|
|
123
|
+
actorUid?: string;
|
|
124
|
+
}
|
|
125
|
+
export type SourcedProductWriteOutcome = {
|
|
126
|
+
outcome: 'created' | 'updated' | 'unchanged';
|
|
127
|
+
productId: string;
|
|
128
|
+
/** Every variant passed, with the owner's id for it. */
|
|
129
|
+
variants: Array<{
|
|
130
|
+
key: string;
|
|
131
|
+
variantId: string;
|
|
132
|
+
}>;
|
|
133
|
+
}
|
|
134
|
+
/** The plan's product allowance is used up; nothing was written. */
|
|
135
|
+
| {
|
|
136
|
+
outcome: 'plan_limit';
|
|
137
|
+
limit: number;
|
|
138
|
+
}
|
|
139
|
+
/** The owner's rules refused the product; nothing was written. */
|
|
140
|
+
| {
|
|
141
|
+
outcome: 'invalid';
|
|
142
|
+
message: string;
|
|
143
|
+
}
|
|
144
|
+
/** The site does not sell. */
|
|
145
|
+
| {
|
|
146
|
+
outcome: 'no_store';
|
|
147
|
+
}
|
|
148
|
+
/** `productId` is gone and `recreate` was false; nothing was written. */
|
|
149
|
+
| {
|
|
150
|
+
outcome: 'missing';
|
|
151
|
+
};
|
|
152
|
+
export interface PluginProductWriter {
|
|
153
|
+
/**
|
|
154
|
+
* Creates the product, or updates the one `productId` names. A `productId`
|
|
155
|
+
* the store no longer has (the merchant deleted it) creates a new one,
|
|
156
|
+
* unless `recreate` is false.
|
|
157
|
+
*/
|
|
158
|
+
upsertSourced(write: SourcedProductWrite): Promise<SourcedProductWriteOutcome>;
|
|
159
|
+
}
|
|
160
|
+
export declare const PLUGIN_PRODUCT_WRITER: import("./plugin-services").PluginServiceContract<PluginProductWriter>;
|
|
161
|
+
/** Registers the products' keeper. A second plugin's is refused, naming both. */
|
|
162
|
+
export declare function registerPluginProductWriter(writer: PluginProductWriter, options?: {
|
|
163
|
+
pluginId?: string;
|
|
164
|
+
}): void;
|
|
165
|
+
/** The registered keeper, or `undefined` when no plugin keeps products. */
|
|
166
|
+
export declare function pluginProductWriter(): PluginProductWriter | undefined;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
18
|
+
export const PLUGIN_PRODUCT_WRITER = definePluginServiceContract('core.product-writer', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
/** Registers the products' keeper. A second plugin's is refused, naming both. */ export function registerPluginProductWriter(writer, options) {
|
|
22
|
+
registerPluginService(PLUGIN_PRODUCT_WRITER, writer, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
23
|
+
pluginId: options.pluginId
|
|
24
|
+
} : {}));
|
|
25
|
+
}
|
|
26
|
+
/** The registered keeper, or `undefined` when no plugin keeps products. */ export function pluginProductWriter() {
|
|
27
|
+
var _resolvePluginServices_;
|
|
28
|
+
return (_resolvePluginServices_ = resolvePluginServices(PLUGIN_PRODUCT_WRITER)[0]) == null ? void 0 : _resolvePluginServices_.impl;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=plugin-product-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-product-writer.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 resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Products that come from somewhere else, written by a plugin that does not\n * keep the store's products (AGL-3641).\n *\n * A print-on-demand service, a dropshipping supplier and a fulfillment\n * network each hold a catalog of their own, and a merchant who sells it wants\n * those products in their store without typing them in again — then kept in\n * step as the source changes what it offers. The plugin that keeps the\n * products answers here, so the plugin that reads the source never learns\n * how products are stored, how their addresses are chosen, how the plan's\n * product allowance is counted or what the store's search keys are. Every\n * write keeps the owner's own rules: a create counts against the allowance in\n * the transaction that makes it, and an edit leaves an entry in the site's\n * activity like any other.\n *\n * ## Variants by the caller's key\n *\n * The caller names every configuration by its own stable key — the source's\n * variant id — and the owner answers with its own variant id for each, so an\n * order line can later be traced back to what the source sells. On an\n * update the caller passes back the variant ids it was given; a key with no\n * id is a new configuration, and an existing variant whose id is not passed\n * is one the source no longer offers, removed.\n *\n * ## Money is integer minor units in the store's currency\n *\n * `priceMinor` is an integer (cents for USD) in the currency the store sells\n * in, the `currency` the store's catalog (`core.product-catalog`) names. The\n * caller converts nothing: a source priced in another currency is the\n * caller's to refuse.\n *\n * ## What an update leaves alone\n *\n * The merchant edits an imported product like any other. An update rewrites\n * only what the caller names: prices when `prices` is set, the description\n * and photos when `content` is set, and always the configurations and their\n * availability, because those are what an order can be filled from.\n *\n * ## One owner\n *\n * A slot, like the catalog: a site's products have one keeper, so a second\n * plugin's registration is refused and the incumbent keeps serving. No owner\n * registered is an ordinary answer — {@link pluginProductWriter} returns\n * `undefined` and a caller imports nothing.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-product-writer`); it is not in the\n * barrel.\n */\n\n/** One axis of variation, e.g. `{ name: 'Size', values: ['S', 'M', 'L'] }`. */\nexport interface SourcedProductOption {\n name: string\n values: string[]\n}\n\n/** One sellable configuration, as the source offers it. */\nexport interface SourcedProductVariant {\n /** The source's own stable id for it. */\n key: string\n /** The owner's id, when an earlier write returned one. */\n variantId?: string\n /** Choices by option name; `{}` for a product with one configuration. */\n options: Record<string, string>\n sku?: string\n /** What the shopper pays, integer minor units in the store's currency. */\n priceMinor: number\n weightGrams?: number\n /** A photo the owner already holds (a media-library URL) for this choice. */\n imageUrl?: string\n /**\n * Whether the source can make or ship it now. An unavailable variant is\n * kept, sold out, so a product page does not lose a choice for a day's\n * shortage; a variant the source dropped is not passed at all.\n */\n available: boolean\n}\n\n/** A product as the source offers it. */\nexport interface SourcedProduct {\n /** The source's stable key for the product, e.g. `printful:123`. */\n sourceKey: string\n name: string\n /** Plain text or simple HTML, as the source wrote it. */\n description?: string\n /** Photos the owner already holds (media-library URLs), first is primary. */\n mediaUrls: string[]\n tags?: string[]\n options: SourcedProductOption[]\n variants: SourcedProductVariant[]\n}\n\nexport interface SourcedProductWrite {\n hostId: string\n product: SourcedProduct\n /** The owner's product id, when an earlier write returned one. */\n productId?: string\n /** A new product is born a draft unless the caller says to list it. */\n status?: 'draft' | 'active'\n /** On an update: rewrite prices from the source. */\n prices?: boolean\n /** On an update: rewrite the name, description and photos from the source. */\n content?: boolean\n /**\n * Whether a `productId` the store no longer has (the merchant deleted it)\n * is made again. Default true; a background re-sync passes false and is\n * answered `missing`, so a product the merchant deleted stays deleted.\n */\n recreate?: boolean\n /** The member the write is made for, for the site's activity. */\n actorUid?: string\n}\n\nexport type SourcedProductWriteOutcome =\n | {\n outcome: 'created' | 'updated' | 'unchanged'\n productId: string\n /** Every variant passed, with the owner's id for it. */\n variants: Array<{ key: string; variantId: string }>\n }\n /** The plan's product allowance is used up; nothing was written. */\n | { outcome: 'plan_limit'; limit: number }\n /** The owner's rules refused the product; nothing was written. */\n | { outcome: 'invalid'; message: string }\n /** The site does not sell. */\n | { outcome: 'no_store' }\n /** `productId` is gone and `recreate` was false; nothing was written. */\n | { outcome: 'missing' }\n\nexport interface PluginProductWriter {\n /**\n * Creates the product, or updates the one `productId` names. A `productId`\n * the store no longer has (the merchant deleted it) creates a new one,\n * unless `recreate` is false.\n */\n upsertSourced(write: SourcedProductWrite): Promise<SourcedProductWriteOutcome>\n}\n\nexport const PLUGIN_PRODUCT_WRITER = definePluginServiceContract<PluginProductWriter>(\n 'core.product-writer',\n { multiple: false },\n)\n\n/** Registers the products' keeper. A second plugin's is refused, naming both. */\nexport function registerPluginProductWriter(\n writer: PluginProductWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_PRODUCT_WRITER, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered keeper, or `undefined` when no plugin keeps products. */\nexport function pluginProductWriter(): PluginProductWriter | undefined {\n return resolvePluginServices(PLUGIN_PRODUCT_WRITER)[0]?.impl\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_PRODUCT_WRITER","multiple","registerPluginProductWriter","writer","options","pluginId","pluginProductWriter","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA4I1B,OAAO,MAAMC,wBAAwBH,4BACnC,uBACA;IAAEI,UAAU;AAAM,GACnB;AAED,+EAA+E,GAC/E,OAAO,SAASC,4BACdC,MAA2B,EAC3BC,OAA+B;IAE/BN,sBAAsBE,uBAAuBG,QAAQ,aAC/CC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yEAAyE,GACzE,OAAO,SAASC;QACPP;IAAP,QAAOA,0BAAAA,sBAAsBC,sBAAsB,CAAC,EAAE,qBAA/CD,wBAAiDQ,IAAI;AAC9D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { ConsoleThemePreset } from './feature-plugins';
|
|
18
|
+
/**
|
|
19
|
+
* The built-in themes as the console's SERVER reads them (AGL-3660).
|
|
20
|
+
*
|
|
21
|
+
* The theme picker lists presets from the console extensions the theme page
|
|
22
|
+
* loads, which never run in an API process. A server job that starts a site
|
|
23
|
+
* from one of them — the AI site start picks a base theme and layers the
|
|
24
|
+
* site's own look over it as an override, exactly as a person's pick and
|
|
25
|
+
* edits are stored (`theme-library.ts`) — reads the same presets here, each
|
|
26
|
+
* plugin registering its list from its server declarations.
|
|
27
|
+
*/
|
|
28
|
+
export interface PluginThemePresets {
|
|
29
|
+
presets: readonly ConsoleThemePreset[];
|
|
30
|
+
}
|
|
31
|
+
export declare const PLUGIN_THEME_PRESETS: import("./plugin-services").PluginServiceContract<PluginThemePresets>;
|
|
32
|
+
/** Registers a plugin's built-in themes for server readers. */
|
|
33
|
+
export declare function registerPluginThemePresets(presets: readonly ConsoleThemePreset[], options: {
|
|
34
|
+
pluginId: string;
|
|
35
|
+
}): void;
|
|
36
|
+
/**
|
|
37
|
+
* Every built-in theme the server has registered, in registration order, the
|
|
38
|
+
* first preset under an id winning, as the picker lists them.
|
|
39
|
+
*/
|
|
40
|
+
export declare function listServerThemePresets(): ConsoleThemePreset[];
|