@sixtyninecommerce/admin 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2739 -17
- package/dist/api-tokens/index.d.ts +15 -5
- package/dist/api-tokens/index.js +1 -0
- package/dist/apps/event-subscription.d.ts +59 -0
- package/dist/apps/event-subscription.js +32 -0
- package/dist/apps/hook-secret.d.ts +22 -0
- package/dist/apps/hook-secret.js +36 -0
- package/dist/apps/index.d.ts +54 -3
- package/dist/apps/index.js +17 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/auth/queries.d.ts +5 -1
- package/dist/auth/queries.js +6 -0
- package/dist/bulk/index.d.ts +323 -0
- package/dist/bulk/index.js +162 -0
- package/dist/catalog/assets.d.ts +147 -0
- package/dist/catalog/assets.js +164 -0
- package/dist/catalog/collections.d.ts +109 -0
- package/dist/catalog/collections.js +91 -0
- package/dist/catalog/facets.d.ts +103 -0
- package/dist/catalog/facets.js +100 -0
- package/dist/catalog/index.d.ts +115 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/options.d.ts +89 -0
- package/dist/catalog/options.js +88 -0
- package/dist/catalog/products-bulk.d.ts +21 -0
- package/dist/catalog/products-bulk.js +58 -0
- package/dist/catalog/products.d.ts +650 -0
- package/dist/catalog/products.js +458 -0
- package/dist/catalog/properties.d.ts +234 -0
- package/dist/catalog/properties.js +158 -0
- package/dist/catalog/types.d.ts +174 -0
- package/dist/catalog/types.js +141 -0
- package/dist/catalog/variants.d.ts +324 -0
- package/dist/catalog/variants.js +288 -0
- package/dist/catalog/visibility.d.ts +57 -0
- package/dist/catalog/visibility.js +59 -0
- package/dist/client.d.ts +192 -3
- package/dist/client.js +87 -12
- package/dist/compliance/index.d.ts +88 -0
- package/dist/compliance/index.js +88 -0
- package/dist/customer-data-redaction.d.ts +14 -0
- package/dist/customer-data-redaction.js +22 -0
- package/dist/customers/index.d.ts +428 -17
- package/dist/customers/index.js +322 -15
- package/dist/documents/index.d.ts +513 -16
- package/dist/documents/index.js +229 -3
- package/dist/events/index.d.ts +259 -0
- package/dist/events/index.js +147 -0
- package/dist/feeds/ai.d.ts +46 -0
- package/dist/feeds/ai.js +43 -0
- package/dist/feeds/categories.d.ts +50 -0
- package/dist/feeds/categories.js +56 -0
- package/dist/feeds/configs.d.ts +54 -0
- package/dist/feeds/configs.js +85 -0
- package/dist/feeds/identifier-fields.d.ts +25 -0
- package/dist/feeds/identifier-fields.js +29 -0
- package/dist/feeds/index.d.ts +54 -0
- package/dist/feeds/index.js +46 -0
- package/dist/feeds/preserve.d.ts +38 -0
- package/dist/feeds/preserve.js +74 -0
- package/dist/feeds/rules.d.ts +42 -0
- package/dist/feeds/rules.js +56 -0
- package/dist/feeds/types.d.ts +654 -0
- package/dist/feeds/types.js +135 -0
- package/dist/fulfillments/index.d.ts +437 -0
- package/dist/fulfillments/index.js +534 -0
- package/dist/heads/index.d.ts +60 -0
- package/dist/heads/index.js +45 -0
- package/dist/hooks/assignments.d.ts +90 -0
- package/dist/hooks/assignments.js +53 -0
- package/dist/hooks/index.d.ts +97 -0
- package/dist/hooks/index.js +76 -0
- package/dist/index.d.ts +95 -9
- package/dist/index.js +21 -1
- package/dist/integrations/index.d.ts +14 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/pipelines.d.ts +113 -0
- package/dist/integrations/pipelines.js +62 -0
- package/dist/integrations/types.d.ts +176 -0
- package/dist/integrations/types.js +8 -0
- package/dist/jobs/bulk-action-run.d.ts +74 -0
- package/dist/jobs/bulk-action-run.js +69 -0
- package/dist/locations/index.d.ts +6 -2
- package/dist/meta/index.d.ts +32 -0
- package/dist/orders/bulk.d.ts +75 -0
- package/dist/orders/bulk.js +150 -0
- package/dist/orders/commit-edit.d.ts +123 -0
- package/dist/orders/commit-edit.js +142 -0
- package/dist/orders/deposit-policy.d.ts +103 -0
- package/dist/orders/deposit-policy.js +202 -0
- package/dist/orders/get.d.ts +722 -0
- package/dist/orders/get.js +530 -0
- package/dist/orders/index.d.ts +186 -21
- package/dist/orders/index.js +57 -0
- package/dist/orders/line-price-preview.d.ts +297 -0
- package/dist/orders/line-price-preview.js +50 -0
- package/dist/orders/manual-discounts.d.ts +141 -0
- package/dist/orders/manual-discounts.js +97 -0
- package/dist/orders/manual-pricing.d.ts +192 -0
- package/dist/orders/manual-pricing.js +72 -0
- package/dist/orders/order-entry.d.ts +566 -0
- package/dist/orders/order-entry.js +129 -0
- package/dist/orders/parcel-charge.d.ts +45 -0
- package/dist/orders/parcel-charge.js +55 -0
- package/dist/orders/plan-edit.d.ts +489 -0
- package/dist/orders/plan-edit.js +93 -0
- package/dist/orders/status.d.ts +235 -0
- package/dist/orders/status.js +96 -0
- package/dist/orders/surcharges.d.ts +75 -2
- package/dist/orders/surcharges.js +110 -4
- package/dist/payments/account-credit.d.ts +171 -0
- package/dist/payments/account-credit.js +107 -0
- package/dist/payments/index.d.ts +241 -9
- package/dist/payments/index.js +92 -0
- package/dist/payments/methods.d.ts +37 -0
- package/dist/payments/methods.js +33 -0
- package/dist/platform/catalog.d.ts +148 -0
- package/dist/platform/catalog.js +74 -0
- package/dist/platform/entitlements.d.ts +111 -0
- package/dist/platform/entitlements.js +86 -0
- package/dist/platform/legal-entities.d.ts +139 -0
- package/dist/platform/legal-entities.js +126 -0
- package/dist/platform/legal-entity-numbering.d.ts +103 -0
- package/dist/platform/legal-entity-numbering.js +101 -0
- package/dist/platform/organization-membership.d.ts +80 -0
- package/dist/platform/organization-membership.js +71 -0
- package/dist/platform/organization-settings.d.ts +33 -0
- package/dist/platform/organization-settings.js +42 -0
- package/dist/platform/organization-sharing.d.ts +172 -0
- package/dist/platform/organization-sharing.js +120 -0
- package/dist/platform/organizations.d.ts +205 -0
- package/dist/platform/organizations.js +180 -0
- package/dist/platform/provision-store.d.ts +88 -8
- package/dist/platform/provision-store.js +12 -0
- package/dist/platform/reports.d.ts +46 -0
- package/dist/platform/reports.js +31 -0
- package/dist/platform/server-metrics.d.ts +58 -0
- package/dist/platform/server-metrics.js +11 -0
- package/dist/pos/index.d.ts +567 -0
- package/dist/pos/index.js +205 -0
- package/dist/pricing/index.d.ts +408 -0
- package/dist/pricing/index.js +353 -0
- package/dist/pricing/vouchers.d.ts +103 -0
- package/dist/pricing/vouchers.js +69 -0
- package/dist/provenance/index.d.ts +93 -0
- package/dist/provenance/index.js +53 -0
- package/dist/reports/index.d.ts +199 -0
- package/dist/reports/index.js +154 -0
- package/dist/returns/index.d.ts +225 -0
- package/dist/returns/index.js +160 -0
- package/dist/search/index.d.ts +99 -0
- package/dist/search/index.js +62 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
- package/dist/shipping/parcel-count-suggestion.js +73 -0
- package/dist/shipping/parcel-rule.d.ts +48 -0
- package/dist/shipping/parcel-rule.js +91 -0
- package/dist/stock/index.d.ts +166 -0
- package/dist/stock/index.js +115 -0
- package/dist/stock/transfers.d.ts +520 -0
- package/dist/stock/transfers.js +437 -0
- package/dist/stored-value/index.d.ts +154 -0
- package/dist/stored-value/index.js +130 -0
- package/dist/tenant/capabilities.d.ts +10 -0
- package/dist/tenant/entitlements.d.ts +35 -0
- package/dist/tenant/entitlements.js +15 -0
- package/dist/tenant/field-groups.d.ts +64 -0
- package/dist/tenant/field-groups.js +34 -0
- package/dist/tenant/legal-entity.d.ts +32 -0
- package/dist/tenant/legal-entity.js +41 -0
- package/dist/tenant/notifications.d.ts +148 -0
- package/dist/tenant/notifications.js +79 -0
- package/dist/tenant/retention.d.ts +10 -4
- package/dist/tenant/settings-sources.d.ts +103 -0
- package/dist/tenant/settings-sources.js +92 -0
- package/dist/types.d.ts +86 -11
- package/dist/uploads/index.d.ts +11 -7
- package/dist/users/index.d.ts +104 -0
- package/dist/users/index.js +80 -0
- package/package.json +3 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createEventHandler, createEventsClient, createPage, readRestPage, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
/** Most event ids one `redrive` call may name. */
|
|
3
|
+
export const MAX_REDRIVE_EVENT_IDS = 100;
|
|
4
|
+
/** The caller's test `data` is the envelope payload: sent verbatim, never case-converted. */
|
|
5
|
+
const SEND_TEST_PASSTHROUGH = new Set(['data']);
|
|
6
|
+
/** `evidence` is stored JSONB: its keys are data, not structure. */
|
|
7
|
+
const DELIVERY_PASSTHROUGH = new Set(['evidence']);
|
|
8
|
+
function requireId(id) {
|
|
9
|
+
if (typeof id !== 'string' || id.trim() === '') {
|
|
10
|
+
throw new ValidationError({
|
|
11
|
+
code: 'VALIDATION_ERROR',
|
|
12
|
+
message: 'A subscription id is required.',
|
|
13
|
+
status: 400,
|
|
14
|
+
service: 'plugins',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return encodeURIComponent(id);
|
|
18
|
+
}
|
|
19
|
+
/** Refused rather than dropped: the caller believes a retry is safe, and it is not. */
|
|
20
|
+
function refuseIdempotencyKey(options, message) {
|
|
21
|
+
if (options && 'idempotencyKey' in options && options.idempotencyKey !== undefined) {
|
|
22
|
+
throw new ValidationError({
|
|
23
|
+
code: 'IDEMPOTENCY_NOT_SUPPORTED',
|
|
24
|
+
message,
|
|
25
|
+
status: 400,
|
|
26
|
+
service: 'plugins',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function createEventsModule(transport) {
|
|
31
|
+
async function list(options) {
|
|
32
|
+
// `?cursor=1` opts into the paged shape; without it the route answers a bare array and
|
|
33
|
+
// ignores `limit` (`wantsCursor`, spec §18 amendment 9).
|
|
34
|
+
const body = await transport.rest('plugins', {
|
|
35
|
+
method: 'GET',
|
|
36
|
+
path: '/event-subscriptions',
|
|
37
|
+
query: { cursor: 1, after: options?.after, limit: options?.limit },
|
|
38
|
+
}, options);
|
|
39
|
+
const { items, meta } = readRestPage(body);
|
|
40
|
+
return createPage(items, meta, (cursor) => list({ ...options, after: cursor }));
|
|
41
|
+
}
|
|
42
|
+
async function deliveries(id, options) {
|
|
43
|
+
const body = await transport.rest('plugins', {
|
|
44
|
+
method: 'GET',
|
|
45
|
+
path: `/event-subscriptions/${requireId(id)}/deliveries`,
|
|
46
|
+
query: { cursor: 1, after: options?.after, limit: options?.limit },
|
|
47
|
+
}, { ...options, preserveKeys: DELIVERY_PASSTHROUGH });
|
|
48
|
+
const { items, meta } = readRestPage(body);
|
|
49
|
+
return createPage(items, meta, (cursor) => deliveries(id, { ...options, after: cursor }));
|
|
50
|
+
}
|
|
51
|
+
const subscriptions = {
|
|
52
|
+
list,
|
|
53
|
+
deliveries,
|
|
54
|
+
async create(input, options) {
|
|
55
|
+
refuseIdempotencyKey(options, 'events.subscriptions.create takes no idempotency key: its response carries the signing secret, which the platform never stores for replay. Check list() before retrying.');
|
|
56
|
+
return transport.rest('plugins', { method: 'POST', path: '/event-subscriptions', body: input }, options);
|
|
57
|
+
},
|
|
58
|
+
async update(id, input, options) {
|
|
59
|
+
return transport.rest('plugins', { method: 'PUT', path: `/event-subscriptions/${requireId(id)}`, body: input }, options);
|
|
60
|
+
},
|
|
61
|
+
async setEnabled(id, enabled, options) {
|
|
62
|
+
if (typeof enabled !== 'boolean') {
|
|
63
|
+
throw new ValidationError({
|
|
64
|
+
code: 'VALIDATION_ERROR',
|
|
65
|
+
message: 'enabled must be true or false.',
|
|
66
|
+
status: 400,
|
|
67
|
+
service: 'plugins',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return transport.rest('plugins', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
path: `/event-subscriptions/${requireId(id)}/enabled`,
|
|
73
|
+
body: { enabled },
|
|
74
|
+
}, options);
|
|
75
|
+
},
|
|
76
|
+
async remove(id, options) {
|
|
77
|
+
await transport.rest('plugins', { method: 'DELETE', path: `/event-subscriptions/${requireId(id)}`, expect: 'void' }, options);
|
|
78
|
+
},
|
|
79
|
+
async sendTest(id, type, options) {
|
|
80
|
+
if (typeof type !== 'string' || type.trim() === '') {
|
|
81
|
+
throw new ValidationError({
|
|
82
|
+
code: 'VALIDATION_ERROR',
|
|
83
|
+
message: 'An event type is required, e.g. order.created.',
|
|
84
|
+
status: 400,
|
|
85
|
+
service: 'plugins',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const { data, ...call } = options ?? {};
|
|
89
|
+
if (data !== undefined &&
|
|
90
|
+
(data === null || typeof data !== 'object' || Array.isArray(data))) {
|
|
91
|
+
throw new ValidationError({
|
|
92
|
+
code: 'VALIDATION_ERROR',
|
|
93
|
+
message: 'data must be a JSON object: the envelope data to send.',
|
|
94
|
+
status: 400,
|
|
95
|
+
service: 'plugins',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return transport.rest('plugins', {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
path: `/event-subscriptions/${requireId(id)}/test`,
|
|
101
|
+
body: data === undefined ? { type } : { type, data },
|
|
102
|
+
}, { ...call, preserveKeys: SEND_TEST_PASSTHROUGH });
|
|
103
|
+
},
|
|
104
|
+
async rotateSecret(id, options) {
|
|
105
|
+
refuseIdempotencyKey(options, 'events.subscriptions.rotateSecret takes no idempotency key: its response carries the new signing secret, which the platform never stores for replay.');
|
|
106
|
+
const { force, ...call } = options ?? {};
|
|
107
|
+
return transport.rest('plugins', {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
path: `/event-subscriptions/${requireId(id)}/rotate-secret`,
|
|
110
|
+
query: { force: force === true ? true : undefined },
|
|
111
|
+
}, call);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
async function redrives(subscriptionId, options) {
|
|
115
|
+
const body = await transport.rest('plugins', {
|
|
116
|
+
method: 'GET',
|
|
117
|
+
path: `/event-subscriptions/${requireId(subscriptionId)}/redrives`,
|
|
118
|
+
query: { after: options?.after, limit: options?.limit },
|
|
119
|
+
}, options);
|
|
120
|
+
const { items, meta } = readRestPage(body);
|
|
121
|
+
return createPage(items, meta, (cursor) => redrives(subscriptionId, { ...options, after: cursor }));
|
|
122
|
+
}
|
|
123
|
+
async function redrive(subscriptionId, eventIds, options) {
|
|
124
|
+
if (!Array.isArray(eventIds) ||
|
|
125
|
+
eventIds.length === 0 ||
|
|
126
|
+
eventIds.length > MAX_REDRIVE_EVENT_IDS) {
|
|
127
|
+
throw new ValidationError({
|
|
128
|
+
code: 'VALIDATION_ERROR',
|
|
129
|
+
message: `events.redrive takes 1-${String(MAX_REDRIVE_EVENT_IDS)} event ids.`,
|
|
130
|
+
status: 400,
|
|
131
|
+
service: 'plugins',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return transport.rest('plugins', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
path: `/event-subscriptions/${requireId(subscriptionId)}/redrive`,
|
|
137
|
+
body: { eventIds: [...eventIds] },
|
|
138
|
+
}, options);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...createEventsClient(transport),
|
|
142
|
+
subscribe: createEventHandler,
|
|
143
|
+
subscriptions,
|
|
144
|
+
redrive,
|
|
145
|
+
redrives,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
import { type CategorySuggestEnqueueResult, type CategorySuggestStatus, type FeedAiSuggestion, type GenerateTitlePresetInput, type SuggestCategoryMappingsInput, type TitlePresetStatus } from './types.js';
|
|
4
|
+
/** A title preset samples the catalogue and waits on a model; 15 seconds is not enough. */
|
|
5
|
+
export declare const TITLE_PRESET_TIMEOUT_MS = 60000;
|
|
6
|
+
/**
|
|
7
|
+
* Whether a refusal means "this platform has no model configured" rather than
|
|
8
|
+
* "the model failed".
|
|
9
|
+
*
|
|
10
|
+
* The two need different handling: one is a permanent property of the
|
|
11
|
+
* environment (offer nothing, explain), the other is worth retrying. Both AI
|
|
12
|
+
* writes refuse with a 409 `AI_UNAVAILABLE`, while the two READS answer 200
|
|
13
|
+
* with `aiAvailable: false` — availability is a field there, never an error, so
|
|
14
|
+
* a page can render the disabled hint without provoking a failure state.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isAiUnavailable(error: unknown): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The AI helpers: categorising a channel's collections, and proposing a better
|
|
19
|
+
* `title` mapping for one feed.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here is applied on its own. A categorisation pass writes ordinary
|
|
22
|
+
* `suggested` category mappings that `categoryMappings.bulkStatus` decides on;
|
|
23
|
+
* a title preset is stored as a PROPOSAL and only `acceptSuggestion` merges it
|
|
24
|
+
* into the feed's `mappingJson`, through the same compile-before-write path a
|
|
25
|
+
* hand-edited mapping takes.
|
|
26
|
+
*/
|
|
27
|
+
export interface FeedAiModule {
|
|
28
|
+
/**
|
|
29
|
+
* Queue a categorisation pass over the channel's collections. Refuses with a
|
|
30
|
+
* 409 `AI_UNAVAILABLE` when no model is configured — read
|
|
31
|
+
* {@link FeedAiModule.categorySuggestStatus} first and offer the action only
|
|
32
|
+
* when `aiAvailable`.
|
|
33
|
+
*/
|
|
34
|
+
suggestCategories(input?: SuggestCategoryMappingsInput, options?: Call): Promise<CategorySuggestEnqueueResult>;
|
|
35
|
+
/** State of the channel's latest pass. Always 200 — availability is a field, not an error. */
|
|
36
|
+
categorySuggestStatus(options?: Call): Promise<CategorySuggestStatus>;
|
|
37
|
+
/** Ask for one improved `title` mapping for a feed. Slow, and 409s when no model is configured. */
|
|
38
|
+
generateTitlePreset(configId: string, input?: GenerateTitlePresetInput, options?: Call): Promise<FeedAiSuggestion>;
|
|
39
|
+
/** A feed's latest PENDING title proposal, plus whether a new one can be asked for. */
|
|
40
|
+
titlePreset(configId: string, options?: Call): Promise<TitlePresetStatus>;
|
|
41
|
+
/** Record the decision and merge the proposal into the feed's `mappingJson`. */
|
|
42
|
+
acceptSuggestion(suggestionId: string, options?: Call): Promise<FeedAiSuggestion>;
|
|
43
|
+
/** Record the decision. Changes no configuration. */
|
|
44
|
+
dismissSuggestion(suggestionId: string, options?: Call): Promise<FeedAiSuggestion>;
|
|
45
|
+
}
|
|
46
|
+
export declare function createFeedAiModule(transport: Transport): FeedAiModule;
|
package/dist/feeds/ai.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PlatformError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { FEEDS_PRESERVE_KEYS, segment } from './preserve.js';
|
|
3
|
+
import { AI_UNAVAILABLE_CODE, } from './types.js';
|
|
4
|
+
const MAPPINGS = '/feeds/category-mappings';
|
|
5
|
+
const CONFIGS = '/feeds/configs';
|
|
6
|
+
const SUGGESTIONS = '/feeds/ai-suggestions';
|
|
7
|
+
/** A title preset samples the catalogue and waits on a model; 15 seconds is not enough. */
|
|
8
|
+
export const TITLE_PRESET_TIMEOUT_MS = 60_000;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a refusal means "this platform has no model configured" rather than
|
|
11
|
+
* "the model failed".
|
|
12
|
+
*
|
|
13
|
+
* The two need different handling: one is a permanent property of the
|
|
14
|
+
* environment (offer nothing, explain), the other is worth retrying. Both AI
|
|
15
|
+
* writes refuse with a 409 `AI_UNAVAILABLE`, while the two READS answer 200
|
|
16
|
+
* with `aiAvailable: false` — availability is a field there, never an error, so
|
|
17
|
+
* a page can render the disabled hint without provoking a failure state.
|
|
18
|
+
*/
|
|
19
|
+
export function isAiUnavailable(error) {
|
|
20
|
+
return error instanceof PlatformError && error.code === AI_UNAVAILABLE_CODE;
|
|
21
|
+
}
|
|
22
|
+
export function createFeedAiModule(transport) {
|
|
23
|
+
return {
|
|
24
|
+
suggestCategories(input = {}, options) {
|
|
25
|
+
return transport.rest('plugins', { method: 'POST', path: `${MAPPINGS}/suggest`, body: input }, options);
|
|
26
|
+
},
|
|
27
|
+
categorySuggestStatus(options) {
|
|
28
|
+
return transport.rest('plugins', { method: 'GET', path: `${MAPPINGS}/suggest/status` }, options);
|
|
29
|
+
},
|
|
30
|
+
generateTitlePreset(configId, input = {}, options) {
|
|
31
|
+
return transport.rest('plugins', { method: 'POST', path: `${CONFIGS}/${segment(configId)}/title-preset`, body: input }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS, timeoutMs: TITLE_PRESET_TIMEOUT_MS });
|
|
32
|
+
},
|
|
33
|
+
titlePreset(configId, options) {
|
|
34
|
+
return transport.rest('plugins', { method: 'GET', path: `${CONFIGS}/${segment(configId)}/title-preset` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
35
|
+
},
|
|
36
|
+
acceptSuggestion(suggestionId, options) {
|
|
37
|
+
return transport.rest('plugins', { method: 'POST', path: `${SUGGESTIONS}/${segment(suggestionId)}/accept` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
38
|
+
},
|
|
39
|
+
dismissSuggestion(suggestionId, options) {
|
|
40
|
+
return transport.rest('plugins', { method: 'POST', path: `${SUGGESTIONS}/${segment(suggestionId)}/dismiss` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
import type { BulkStatusUpdateResult, BulkUpdateCategoryMappingStatusInput, CreateFeedCategoryMappingInput, FeedCategoryMapping, FeedCategoryMappingSource, FeedCategoryMappingStatus, FeedCategoryMatchType, FeedTaxonomyNode, UpdateFeedCategoryMappingInput } from './types.js';
|
|
4
|
+
export interface ListFeedCategoryMappingsOptions extends ListOptions {
|
|
5
|
+
matchType?: FeedCategoryMatchType;
|
|
6
|
+
status?: FeedCategoryMappingStatus;
|
|
7
|
+
source?: FeedCategoryMappingSource;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchTaxonomyOptions extends Call {
|
|
10
|
+
/** 1…50; omitted ⇒ 20. */
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The channel's catalogue mapped onto the Google product taxonomy.
|
|
15
|
+
*
|
|
16
|
+
* Resolution order at generation time is `collection` → `facet_value` →
|
|
17
|
+
* `default`, and only `approved` rows are ever applied — a `suggested` row is
|
|
18
|
+
* a proposal awaiting a decision, never live configuration.
|
|
19
|
+
*
|
|
20
|
+
* Every taxonomy value that comes back is RESOLVED, never echoed: a path sent
|
|
21
|
+
* by a client is ignored and re-derived from the id against the vendored
|
|
22
|
+
* taxonomy, because a stale or hand-typed path is the classic way a whole feed
|
|
23
|
+
* gets disapproved.
|
|
24
|
+
*/
|
|
25
|
+
export interface FeedCategoryMappingsModule {
|
|
26
|
+
list(options?: ListFeedCategoryMappingsOptions): Promise<Page<FeedCategoryMapping>>;
|
|
27
|
+
get(id: string, options?: Call): Promise<FeedCategoryMapping>;
|
|
28
|
+
create(input: CreateFeedCategoryMappingInput, options?: Call): Promise<FeedCategoryMapping>;
|
|
29
|
+
update(id: string, input: UpdateFeedCategoryMappingInput, options?: Call): Promise<FeedCategoryMapping>;
|
|
30
|
+
delete(id: string, options?: Call): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Approve or reject up to {@link MAX_BULK_STATUS_IDS} mappings in one call —
|
|
33
|
+
* the AI review queue's primary action. Ids belonging to another channel are
|
|
34
|
+
* SKIPPED, not errored, so an `updated` count lower than `ids.length` is
|
|
35
|
+
* information rather than a failure.
|
|
36
|
+
*/
|
|
37
|
+
bulkStatus(input: BulkUpdateCategoryMappingStatusInput, options?: Call): Promise<BulkStatusUpdateResult>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ranked search over the vendored Google product taxonomy.
|
|
41
|
+
*
|
|
42
|
+
* The file is identical for every tenant, so this route takes no channel — but
|
|
43
|
+
* it is still entitlement-gated, so a store without the feeds plugin connected
|
|
44
|
+
* is refused.
|
|
45
|
+
*/
|
|
46
|
+
export interface FeedTaxonomyModule {
|
|
47
|
+
search(query: string, options?: SearchTaxonomyOptions): Promise<FeedTaxonomyNode[]>;
|
|
48
|
+
}
|
|
49
|
+
export declare function createFeedCategoryMappingsModule(transport: Transport): FeedCategoryMappingsModule;
|
|
50
|
+
export declare function createFeedTaxonomyModule(transport: Transport): FeedTaxonomyModule;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createPage, readRestPage } from '@sixtyninecommerce/core';
|
|
2
|
+
import { FEEDS_PRESERVE_KEYS, feedQuery, segment } from './preserve.js';
|
|
3
|
+
const MAPPINGS = '/feeds/category-mappings';
|
|
4
|
+
const TAXONOMY_SEARCH = '/feeds/taxonomy/search';
|
|
5
|
+
export function createFeedCategoryMappingsModule(transport) {
|
|
6
|
+
async function list(options) {
|
|
7
|
+
const body = await transport.rest('plugins', {
|
|
8
|
+
method: 'GET',
|
|
9
|
+
path: MAPPINGS,
|
|
10
|
+
query: feedQuery({
|
|
11
|
+
// The wire spelling, written out: query keys are NOT case-converted by
|
|
12
|
+
// the transport (only bodies and responses are), so `matchType` would
|
|
13
|
+
// reach the DTO as an unknown member and be dropped — silently
|
|
14
|
+
// listing every mapping in the channel instead of the one kind asked
|
|
15
|
+
// for.
|
|
16
|
+
match_type: options?.matchType,
|
|
17
|
+
status: options?.status,
|
|
18
|
+
source: options?.source,
|
|
19
|
+
limit: options?.limit,
|
|
20
|
+
after: options?.after,
|
|
21
|
+
}),
|
|
22
|
+
}, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
23
|
+
const { items, meta } = readRestPage(body);
|
|
24
|
+
return createPage(items, meta, (cursor) => list({ ...options, after: cursor }));
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
list,
|
|
28
|
+
get(id, options) {
|
|
29
|
+
return transport.rest('plugins', { method: 'GET', path: `${MAPPINGS}/${segment(id)}` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
30
|
+
},
|
|
31
|
+
create(input, options) {
|
|
32
|
+
return transport.rest('plugins', { method: 'POST', path: MAPPINGS, body: input }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
33
|
+
},
|
|
34
|
+
update(id, input, options) {
|
|
35
|
+
return transport.rest('plugins', { method: 'PATCH', path: `${MAPPINGS}/${segment(id)}`, body: input }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
36
|
+
},
|
|
37
|
+
async delete(id, options) {
|
|
38
|
+
await transport.rest('plugins', { method: 'DELETE', path: `${MAPPINGS}/${segment(id)}`, expect: 'void' }, options);
|
|
39
|
+
},
|
|
40
|
+
bulkStatus(input, options) {
|
|
41
|
+
return transport.rest('plugins', { method: 'POST', path: `${MAPPINGS}/bulk-status`, body: input }, options);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createFeedTaxonomyModule(transport) {
|
|
46
|
+
return {
|
|
47
|
+
async search(query, options) {
|
|
48
|
+
const body = await transport.rest('plugins', {
|
|
49
|
+
method: 'GET',
|
|
50
|
+
path: TAXONOMY_SEARCH,
|
|
51
|
+
query: feedQuery({ q: query, limit: options?.limit }),
|
|
52
|
+
}, options);
|
|
53
|
+
return body.data;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
import type { CreateFeedConfigInput, FeedConfig, FeedDestination, FeedDestinationKey, FeedItemIssue, FeedItemIssueSeverity, FeedPreview, FeedPreviewDraft, FeedRegenerateResult, FeedRun, FeedRunStatus, RotateFeedTokenResult, UpdateFeedConfigInput } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A preview walks the catalogue and evaluates every rule, so it is slower than
|
|
6
|
+
* a read by an order of magnitude — the SDK's 15-second default would time out
|
|
7
|
+
* a sample the service is still perfectly happy to answer.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FEED_PREVIEW_TIMEOUT_MS = 60000;
|
|
10
|
+
export interface ListFeedConfigsOptions extends ListOptions {
|
|
11
|
+
destination?: FeedDestinationKey;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ListFeedRunsOptions extends ListOptions {
|
|
15
|
+
status?: FeedRunStatus;
|
|
16
|
+
}
|
|
17
|
+
export interface ListFeedIssuesOptions extends ListOptions {
|
|
18
|
+
severity?: FeedItemIssueSeverity;
|
|
19
|
+
/** One issue code, e.g. `no_category`. Free text — codes are not a closed set. */
|
|
20
|
+
code?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Feed configurations, their generation runs and the item issues the last run
|
|
24
|
+
* produced (spec §8.14).
|
|
25
|
+
*
|
|
26
|
+
* Every method is CHANNEL-SCOPED by the client's own tenant: the service reads
|
|
27
|
+
* `x-channel-id` and never takes a channel argument, so a feed created here
|
|
28
|
+
* belongs to the store this client is bound to and no other.
|
|
29
|
+
*/
|
|
30
|
+
export interface FeedConfigsModule {
|
|
31
|
+
/** One page of the channel's feed configurations, newest first. */
|
|
32
|
+
list(options?: ListFeedConfigsOptions): Promise<Page<FeedConfig>>;
|
|
33
|
+
get(id: string, options?: Call): Promise<FeedConfig>;
|
|
34
|
+
create(input: CreateFeedConfigInput, options?: Call): Promise<FeedConfig>;
|
|
35
|
+
update(id: string, input: UpdateFeedConfigInput, options?: Call): Promise<FeedConfig>;
|
|
36
|
+
delete(id: string, options?: Call): Promise<void>;
|
|
37
|
+
/** Mint a new token; the previous public feed URL stops resolving immediately. */
|
|
38
|
+
rotateToken(id: string, options?: Call): Promise<RotateFeedTokenResult>;
|
|
39
|
+
/** Enqueue a regeneration; the run row appears in {@link FeedConfigsModule.runs} seconds later. */
|
|
40
|
+
regenerate(id: string, options?: Call): Promise<FeedRegenerateResult>;
|
|
41
|
+
/** The destination registry and its field templates — a frozen server-side constant. */
|
|
42
|
+
destinations(options?: Call): Promise<FeedDestination[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate a DRAFT configuration against real catalogue rows. Persists
|
|
45
|
+
* nothing — no config, no run, no output — which is why it takes the whole
|
|
46
|
+
* draft rather than an id.
|
|
47
|
+
*/
|
|
48
|
+
preview(draft: FeedPreviewDraft, options?: Call): Promise<FeedPreview>;
|
|
49
|
+
/** Generation history for one feed, newest first. Failed runs included — that is the point. */
|
|
50
|
+
runs(configId: string, options?: ListFeedRunsOptions): Promise<Page<FeedRun>>;
|
|
51
|
+
/** Item issues from the LATEST successful run — there is no run selector. */
|
|
52
|
+
issues(configId: string, options?: ListFeedIssuesOptions): Promise<Page<FeedItemIssue>>;
|
|
53
|
+
}
|
|
54
|
+
export declare function createFeedConfigsModule(transport: Transport): FeedConfigsModule;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createPage, readRestPage } from '@sixtyninecommerce/core';
|
|
2
|
+
import { FEEDS_PRESERVE_KEYS, feedQuery, segment } from './preserve.js';
|
|
3
|
+
const CONFIGS = '/feeds/configs';
|
|
4
|
+
/**
|
|
5
|
+
* A preview walks the catalogue and evaluates every rule, so it is slower than
|
|
6
|
+
* a read by an order of magnitude — the SDK's 15-second default would time out
|
|
7
|
+
* a sample the service is still perfectly happy to answer.
|
|
8
|
+
*/
|
|
9
|
+
export const FEED_PREVIEW_TIMEOUT_MS = 60_000;
|
|
10
|
+
export function createFeedConfigsModule(transport) {
|
|
11
|
+
async function list(options) {
|
|
12
|
+
const body = await transport.rest('plugins', {
|
|
13
|
+
method: 'GET',
|
|
14
|
+
path: CONFIGS,
|
|
15
|
+
query: feedQuery({
|
|
16
|
+
destination: options?.destination,
|
|
17
|
+
enabled: options?.enabled,
|
|
18
|
+
limit: options?.limit,
|
|
19
|
+
after: options?.after,
|
|
20
|
+
}),
|
|
21
|
+
}, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
22
|
+
const { items, meta } = readRestPage(body);
|
|
23
|
+
return createPage(items, meta, (cursor) => list({ ...options, after: cursor }));
|
|
24
|
+
}
|
|
25
|
+
async function runs(configId, options) {
|
|
26
|
+
const body = await transport.rest('plugins', {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
path: `${CONFIGS}/${segment(configId)}/runs`,
|
|
29
|
+
query: feedQuery({
|
|
30
|
+
status: options?.status,
|
|
31
|
+
limit: options?.limit,
|
|
32
|
+
after: options?.after,
|
|
33
|
+
}),
|
|
34
|
+
}, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
35
|
+
const { items, meta } = readRestPage(body);
|
|
36
|
+
return createPage(items, meta, (cursor) => runs(configId, { ...options, after: cursor }));
|
|
37
|
+
}
|
|
38
|
+
async function issues(configId, options) {
|
|
39
|
+
const body = await transport.rest('plugins', {
|
|
40
|
+
method: 'GET',
|
|
41
|
+
path: `${CONFIGS}/${segment(configId)}/issues`,
|
|
42
|
+
query: feedQuery({
|
|
43
|
+
severity: options?.severity,
|
|
44
|
+
code: options?.code,
|
|
45
|
+
limit: options?.limit,
|
|
46
|
+
after: options?.after,
|
|
47
|
+
}),
|
|
48
|
+
}, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
49
|
+
const { items, meta } = readRestPage(body);
|
|
50
|
+
return createPage(items, meta, (cursor) => issues(configId, { ...options, after: cursor }));
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
list,
|
|
54
|
+
runs,
|
|
55
|
+
issues,
|
|
56
|
+
get(id, options) {
|
|
57
|
+
return transport.rest('plugins', { method: 'GET', path: `${CONFIGS}/${segment(id)}` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
58
|
+
},
|
|
59
|
+
create(input, options) {
|
|
60
|
+
return transport.rest('plugins', { method: 'POST', path: CONFIGS, body: input }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
61
|
+
},
|
|
62
|
+
update(id, input, options) {
|
|
63
|
+
return transport.rest('plugins', { method: 'PATCH', path: `${CONFIGS}/${segment(id)}`, body: input }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
64
|
+
},
|
|
65
|
+
async delete(id, options) {
|
|
66
|
+
await transport.rest('plugins', { method: 'DELETE', path: `${CONFIGS}/${segment(id)}`, expect: 'void' }, options);
|
|
67
|
+
},
|
|
68
|
+
rotateToken(id, options) {
|
|
69
|
+
return transport.rest('plugins', { method: 'POST', path: `${CONFIGS}/${segment(id)}/rotate-token` }, options);
|
|
70
|
+
},
|
|
71
|
+
regenerate(id, options) {
|
|
72
|
+
return transport.rest('plugins', { method: 'POST', path: `${CONFIGS}/${segment(id)}/regenerate` }, options);
|
|
73
|
+
},
|
|
74
|
+
async destinations(options) {
|
|
75
|
+
// `{ data: [...] }`, not a page: the registry is a frozen constant with no
|
|
76
|
+
// cursor, and `readRestPage` would dress it up as a page that can never
|
|
77
|
+
// have a second one.
|
|
78
|
+
const body = await transport.rest('plugins', { method: 'GET', path: `${CONFIGS}/destinations` }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS });
|
|
79
|
+
return body.data;
|
|
80
|
+
},
|
|
81
|
+
preview(draft, options) {
|
|
82
|
+
return transport.rest('plugins', { method: 'POST', path: `${CONFIGS}/preview`, body: draft }, { ...options, preserveKeys: FEEDS_PRESERVE_KEYS, timeoutMs: FEED_PREVIEW_TIMEOUT_MS });
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the engine this client talks to declares the product-feed identifier
|
|
5
|
+
* fields — `brand` / `condition` on a product, `gtin` / `mpn` on a variant.
|
|
6
|
+
*
|
|
7
|
+
* They are declared by the product-feeds plugin, and a caller deployed ahead of
|
|
8
|
+
* the engine (the dashboard ships independently) that selects or writes one the
|
|
9
|
+
* engine does not know fails GraphQL validation for the WHOLE document. Ask
|
|
10
|
+
* first; select and write them only when the answer is yes.
|
|
11
|
+
*/
|
|
12
|
+
export interface FeedIdentifierFieldSupport {
|
|
13
|
+
/** `Product.customFields.brand` AND `.condition` exist. */
|
|
14
|
+
product: boolean;
|
|
15
|
+
/** `ProductVariant.customFields.gtin` AND `.mpn` exist. */
|
|
16
|
+
variant: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface FeedIdentifierFieldsModule {
|
|
19
|
+
/**
|
|
20
|
+
* Introspect the engine once. A type the engine does not have at all (no
|
|
21
|
+
* custom fields on that entity) reads as unsupported.
|
|
22
|
+
*/
|
|
23
|
+
identifierFieldSupport(options?: Call): Promise<FeedIdentifierFieldSupport>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createFeedIdentifierFieldsModule(transport: Transport): FeedIdentifierFieldsModule;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const IDENTIFIER_FIELDS_QUERY = /* GraphQL */ `
|
|
2
|
+
query FeedIdentifierFields {
|
|
3
|
+
product: __type(name: "ProductCustomFields") {
|
|
4
|
+
fields {
|
|
5
|
+
name
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
variant: __type(name: "ProductVariantCustomFields") {
|
|
9
|
+
fields {
|
|
10
|
+
name
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
function declares(type, names) {
|
|
16
|
+
const declared = new Set((type?.fields ?? []).map((field) => field.name));
|
|
17
|
+
return names.every((name) => declared.has(name));
|
|
18
|
+
}
|
|
19
|
+
export function createFeedIdentifierFieldsModule(transport) {
|
|
20
|
+
return {
|
|
21
|
+
async identifierFieldSupport(options) {
|
|
22
|
+
const data = await transport.graphql({ api: 'admin', query: IDENTIFIER_FIELDS_QUERY, variables: {} }, options);
|
|
23
|
+
return {
|
|
24
|
+
product: declares(data?.product, ['brand', 'condition']),
|
|
25
|
+
variant: declares(data?.variant, ['gtin', 'mpn']),
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product feeds (spec §8.14): the merchandising surface that turns the
|
|
3
|
+
* catalogue into a Google, Meta, TikTok or Pinterest feed.
|
|
4
|
+
*
|
|
5
|
+
* Five groups under one noun, because they are one workflow:
|
|
6
|
+
*
|
|
7
|
+
* - **the feed itself** — `admin.feeds.list/get/create/update/delete`, plus
|
|
8
|
+
* `preview` (evaluate a draft, persist nothing), `regenerate`, `rotateToken`,
|
|
9
|
+
* `runs` and `issues`;
|
|
10
|
+
* - **`internalFields` / `lookupTables`** — the channel-scoped pieces a rule
|
|
11
|
+
* may reference (`internal:<name>`, `{ kind: 'lookup', tableName }`);
|
|
12
|
+
* - **`categoryMappings` / `taxonomy`** — the catalogue mapped onto the Google
|
|
13
|
+
* product taxonomy, and the search behind every picker;
|
|
14
|
+
* - **`ai`** — the two model-backed helpers: categorising collections, and
|
|
15
|
+
* proposing a better `title` mapping.
|
|
16
|
+
*
|
|
17
|
+
* Everything but `taxonomy` is scoped to the client's own channel through
|
|
18
|
+
* `x-channel-id`; no method takes a channel argument.
|
|
19
|
+
*
|
|
20
|
+
* **The rule payloads ride verbatim.** `mappingJson`, `filtersJson`,
|
|
21
|
+
* `settingsJson`, `rulesJson`, `entriesJson`, `perDestinationJson`,
|
|
22
|
+
* `payloadJson`, `statsJson` and a template's `defaultSource` are stored and
|
|
23
|
+
* echoed byte-for-byte, and the AST inside them is camelCase — see
|
|
24
|
+
* `preserve.ts` for why converting any of it corrupts a merchant's saved rules
|
|
25
|
+
* in a way the round trip hides.
|
|
26
|
+
*/
|
|
27
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
28
|
+
import { type FeedAiModule } from './ai.js';
|
|
29
|
+
import { type FeedCategoryMappingsModule, type FeedTaxonomyModule } from './categories.js';
|
|
30
|
+
import { type FeedConfigsModule } from './configs.js';
|
|
31
|
+
import { type FeedIdentifierFieldsModule } from './identifier-fields.js';
|
|
32
|
+
import { type FeedInternalFieldsModule, type FeedLookupTablesModule } from './rules.js';
|
|
33
|
+
export type { FeedAiModule } from './ai.js';
|
|
34
|
+
export { isAiUnavailable, TITLE_PRESET_TIMEOUT_MS } from './ai.js';
|
|
35
|
+
export type { FeedCategoryMappingsModule, FeedTaxonomyModule, ListFeedCategoryMappingsOptions, SearchTaxonomyOptions, } from './categories.js';
|
|
36
|
+
export type { FeedConfigsModule, ListFeedConfigsOptions, ListFeedIssuesOptions, ListFeedRunsOptions, } from './configs.js';
|
|
37
|
+
export { FEED_PREVIEW_TIMEOUT_MS } from './configs.js';
|
|
38
|
+
export type { FeedIdentifierFieldSupport, FeedIdentifierFieldsModule, } from './identifier-fields.js';
|
|
39
|
+
export { FEEDS_PRESERVE_KEYS } from './preserve.js';
|
|
40
|
+
export type { FeedInternalFieldsModule, FeedLookupTablesModule } from './rules.js';
|
|
41
|
+
export * from './types.js';
|
|
42
|
+
export interface FeedsModule extends FeedConfigsModule, FeedIdentifierFieldsModule {
|
|
43
|
+
/** Computed values a mapping references as `internal:<name>`. */
|
|
44
|
+
internalFields: FeedInternalFieldsModule;
|
|
45
|
+
/** Substitution tables a `lookup` value source resolves through. */
|
|
46
|
+
lookupTables: FeedLookupTablesModule;
|
|
47
|
+
/** The catalogue mapped onto the Google product taxonomy. */
|
|
48
|
+
categoryMappings: FeedCategoryMappingsModule;
|
|
49
|
+
/** Ranked search over the vendored Google taxonomy. Not channel-scoped. */
|
|
50
|
+
taxonomy: FeedTaxonomyModule;
|
|
51
|
+
/** Model-backed helpers. Availability is a field on the reads, a 409 on the writes. */
|
|
52
|
+
ai: FeedAiModule;
|
|
53
|
+
}
|
|
54
|
+
export declare function createFeedsModule(transport: Transport): FeedsModule;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product feeds (spec §8.14): the merchandising surface that turns the
|
|
3
|
+
* catalogue into a Google, Meta, TikTok or Pinterest feed.
|
|
4
|
+
*
|
|
5
|
+
* Five groups under one noun, because they are one workflow:
|
|
6
|
+
*
|
|
7
|
+
* - **the feed itself** — `admin.feeds.list/get/create/update/delete`, plus
|
|
8
|
+
* `preview` (evaluate a draft, persist nothing), `regenerate`, `rotateToken`,
|
|
9
|
+
* `runs` and `issues`;
|
|
10
|
+
* - **`internalFields` / `lookupTables`** — the channel-scoped pieces a rule
|
|
11
|
+
* may reference (`internal:<name>`, `{ kind: 'lookup', tableName }`);
|
|
12
|
+
* - **`categoryMappings` / `taxonomy`** — the catalogue mapped onto the Google
|
|
13
|
+
* product taxonomy, and the search behind every picker;
|
|
14
|
+
* - **`ai`** — the two model-backed helpers: categorising collections, and
|
|
15
|
+
* proposing a better `title` mapping.
|
|
16
|
+
*
|
|
17
|
+
* Everything but `taxonomy` is scoped to the client's own channel through
|
|
18
|
+
* `x-channel-id`; no method takes a channel argument.
|
|
19
|
+
*
|
|
20
|
+
* **The rule payloads ride verbatim.** `mappingJson`, `filtersJson`,
|
|
21
|
+
* `settingsJson`, `rulesJson`, `entriesJson`, `perDestinationJson`,
|
|
22
|
+
* `payloadJson`, `statsJson` and a template's `defaultSource` are stored and
|
|
23
|
+
* echoed byte-for-byte, and the AST inside them is camelCase — see
|
|
24
|
+
* `preserve.ts` for why converting any of it corrupts a merchant's saved rules
|
|
25
|
+
* in a way the round trip hides.
|
|
26
|
+
*/
|
|
27
|
+
import { createFeedAiModule } from './ai.js';
|
|
28
|
+
import { createFeedCategoryMappingsModule, createFeedTaxonomyModule, } from './categories.js';
|
|
29
|
+
import { createFeedConfigsModule } from './configs.js';
|
|
30
|
+
import { createFeedIdentifierFieldsModule, } from './identifier-fields.js';
|
|
31
|
+
import { createFeedInternalFieldsModule, createFeedLookupTablesModule, } from './rules.js';
|
|
32
|
+
export { isAiUnavailable, TITLE_PRESET_TIMEOUT_MS } from './ai.js';
|
|
33
|
+
export { FEED_PREVIEW_TIMEOUT_MS } from './configs.js';
|
|
34
|
+
export { FEEDS_PRESERVE_KEYS } from './preserve.js';
|
|
35
|
+
export * from './types.js';
|
|
36
|
+
export function createFeedsModule(transport) {
|
|
37
|
+
return {
|
|
38
|
+
...createFeedConfigsModule(transport),
|
|
39
|
+
...createFeedIdentifierFieldsModule(transport),
|
|
40
|
+
internalFields: createFeedInternalFieldsModule(transport),
|
|
41
|
+
lookupTables: createFeedLookupTablesModule(transport),
|
|
42
|
+
categoryMappings: createFeedCategoryMappingsModule(transport),
|
|
43
|
+
taxonomy: createFeedTaxonomyModule(transport),
|
|
44
|
+
ai: createFeedAiModule(transport),
|
|
45
|
+
};
|
|
46
|
+
}
|