@porulle/plugin-channel-connector 0.50.1 → 0.50.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -1
- package/dist/service.d.ts +9 -0
- package/dist/service.js +36 -14
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +5 -1
- package/src/service.ts +49 -11
package/dist/index.js
CHANGED
|
@@ -151,7 +151,11 @@ export function channelConnectorPlugin(options = {}) {
|
|
|
151
151
|
handler: async ({ input, ctx }) => {
|
|
152
152
|
const orgId = String(input.orgId);
|
|
153
153
|
const jobs = ctx.services.jobs;
|
|
154
|
-
|
|
154
|
+
// Only stores a registered connector can reach: a reconcile for any other provider (the
|
|
155
|
+
// seed's `manual` point-of-sale store) can only fail with "No connector registered".
|
|
156
|
+
const providers = new Set((options.connectors ?? []).map((connector) => connector.providerId));
|
|
157
|
+
const stores = (await ctx.db.select().from(connectedStores).where(and(eq(connectedStores.organizationId, orgId), eq(connectedStores.status, "connected"))))
|
|
158
|
+
.filter((store) => providers.has(store.provider));
|
|
155
159
|
const window = options.reconcileJitterWindowMs ?? 60 * 60 * 1000;
|
|
156
160
|
for (const store of stores) {
|
|
157
161
|
const offset = createHash("sha256").update(store.id).digest().readUInt32BE(0) % window;
|
package/dist/service.d.ts
CHANGED
|
@@ -42,6 +42,9 @@ export interface ReconcileReport extends Record<string, unknown> {
|
|
|
42
42
|
skipped?: CatalogFieldSkip[];
|
|
43
43
|
conflicts?: CatalogFieldConflict[];
|
|
44
44
|
warnings?: string[];
|
|
45
|
+
/** Items this reconcile could not converge, each with its error. A reconcile that dropped an
|
|
46
|
+
* item is never reported as a clean one. */
|
|
47
|
+
failures?: CatalogConvergenceFailure[];
|
|
45
48
|
}
|
|
46
49
|
export interface CatalogFieldConflict {
|
|
47
50
|
entityId: string;
|
|
@@ -333,6 +336,12 @@ export declare class ChannelConnectorService {
|
|
|
333
336
|
*/
|
|
334
337
|
private taxonomyCache;
|
|
335
338
|
private taxonomyFor;
|
|
339
|
+
/**
|
|
340
|
+
* Create a category or brand, or adopt the one another writer created first. The taxonomy
|
|
341
|
+
* snapshot is per converge, so two stores converging at once can both see a slug missing and both
|
|
342
|
+
* create it; the loser's conflict — returned or thrown — means the row it wanted now exists.
|
|
343
|
+
*/
|
|
344
|
+
private createTaxonomyOrAdopt;
|
|
336
345
|
private applyTaxonomy;
|
|
337
346
|
private applyMedia;
|
|
338
347
|
private getStoreRecord;
|
package/dist/service.js
CHANGED
|
@@ -1152,6 +1152,30 @@ export class ChannelConnectorService {
|
|
|
1152
1152
|
this.taxonomyCache = { orgId, categories: categoryRows, brands: brandRows, tags: tagRows };
|
|
1153
1153
|
return this.taxonomyCache;
|
|
1154
1154
|
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Create a category or brand, or adopt the one another writer created first. The taxonomy
|
|
1157
|
+
* snapshot is per converge, so two stores converging at once can both see a slug missing and both
|
|
1158
|
+
* create it; the loser's conflict — returned or thrown — means the row it wanted now exists.
|
|
1159
|
+
*/
|
|
1160
|
+
async createTaxonomyOrAdopt(create, byId, bySlug, label) {
|
|
1161
|
+
let failure = `${label} was not persisted.`;
|
|
1162
|
+
try {
|
|
1163
|
+
const created = await create();
|
|
1164
|
+
if (created.ok) {
|
|
1165
|
+
const row = await byId(created.value.id);
|
|
1166
|
+
if (row !== undefined)
|
|
1167
|
+
return Ok(row);
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
failure = created.error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
catch (error) {
|
|
1174
|
+
failure = error;
|
|
1175
|
+
}
|
|
1176
|
+
const existing = await bySlug();
|
|
1177
|
+
return existing !== undefined ? Ok(existing) : PluginErr(`${label}: ${errorMessage(failure)}`);
|
|
1178
|
+
}
|
|
1155
1179
|
async applyTaxonomy(orgId, entityId, item, actor, warnings) {
|
|
1156
1180
|
const taxonomy = await this.taxonomyFor(orgId);
|
|
1157
1181
|
// The links this entity already has, read only for the classes the item names. A link already
|
|
@@ -1169,13 +1193,10 @@ export class ChannelConnectorService {
|
|
|
1169
1193
|
continue;
|
|
1170
1194
|
}
|
|
1171
1195
|
if (!category) {
|
|
1172
|
-
const created = await this.catalog.createCategory({ slug }, actor);
|
|
1196
|
+
const created = await this.createTaxonomyOrAdopt(() => this.catalog.createCategory({ slug }, actor), async (id) => (await this.db.select().from(categories).where(eq(categories.id, id)))[0], async () => (await this.db.select().from(categories).where(and(eq(categories.organizationId, orgId), eq(categories.slug, slug))))[0], `Category "${slug}"`);
|
|
1173
1197
|
if (!created.ok)
|
|
1174
|
-
return
|
|
1175
|
-
|
|
1176
|
-
if (!createdCategory)
|
|
1177
|
-
return PluginErr(`Category "${slug}" was not persisted.`);
|
|
1178
|
-
category = createdCategory;
|
|
1198
|
+
return created;
|
|
1199
|
+
category = created.value;
|
|
1179
1200
|
categoryRows.push(category);
|
|
1180
1201
|
}
|
|
1181
1202
|
if (linkedCategories.has(category.id))
|
|
@@ -1189,13 +1210,11 @@ export class ChannelConnectorService {
|
|
|
1189
1210
|
if (item.brand) {
|
|
1190
1211
|
let brand = brandRows.find((row) => row.slug === item.brand);
|
|
1191
1212
|
if (!brand) {
|
|
1192
|
-
const
|
|
1213
|
+
const brandSlug = item.brand;
|
|
1214
|
+
const created = await this.createTaxonomyOrAdopt(() => this.catalog.createBrand({ slug: brandSlug, displayName: brandSlug }, actor), async (id) => (await this.db.select().from(brands).where(eq(brands.id, id)))[0], async () => (await this.db.select().from(brands).where(and(eq(brands.organizationId, orgId), eq(brands.slug, brandSlug))))[0], `Brand "${brandSlug}"`);
|
|
1193
1215
|
if (!created.ok)
|
|
1194
|
-
return
|
|
1195
|
-
|
|
1196
|
-
if (!createdBrand)
|
|
1197
|
-
return PluginErr(`Brand "${item.brand}" was not persisted.`);
|
|
1198
|
-
brand = createdBrand;
|
|
1216
|
+
return created;
|
|
1217
|
+
brand = created.value;
|
|
1199
1218
|
brandRows.push(brand);
|
|
1200
1219
|
}
|
|
1201
1220
|
if (!linkedBrands.has(brand.id)) {
|
|
@@ -2905,8 +2924,10 @@ export class ChannelConnectorService {
|
|
|
2905
2924
|
continue;
|
|
2906
2925
|
}
|
|
2907
2926
|
const taxonomy = await this.applyTaxonomy(orgId, entityId, writable, actor, warnings);
|
|
2908
|
-
if (!taxonomy.ok)
|
|
2909
|
-
|
|
2927
|
+
if (!taxonomy.ok) {
|
|
2928
|
+
failures.push({ externalId: item.externalId, error: taxonomy.error });
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2910
2931
|
const media = await this.applyMedia(orgId, entityId, writable, variantIds.value.value, actor, warnings, owners);
|
|
2911
2932
|
if (!media.ok)
|
|
2912
2933
|
return media;
|
|
@@ -3069,6 +3090,7 @@ export class ChannelConnectorService {
|
|
|
3069
3090
|
...(skipped.length > 0 ? { skipped: uniqueSkipped(skipped) } : {}),
|
|
3070
3091
|
...(converged.value.conflicts.length > 0 ? { conflicts: converged.value.conflicts } : {}),
|
|
3071
3092
|
...(converged.value.warnings.length > 0 ? { warnings: converged.value.warnings } : {}),
|
|
3093
|
+
...(converged.value.failures.length > 0 ? { failures: converged.value.failures } : {}),
|
|
3072
3094
|
};
|
|
3073
3095
|
await this.db.update(connectedStores).set({
|
|
3074
3096
|
lastReconcileAt: new Date(),
|