@porulle/plugin-channel-connector 0.52.0 → 0.53.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/dist/index.d.ts +0 -2
- package/dist/index.js +0 -63
- package/dist/service.d.ts +0 -1
- package/dist/service.js +2 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/index.ts +0 -67
- package/src/service.ts +2 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/plugin-channel-connector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@hono/zod-openapi": "^1.2.2",
|
|
21
21
|
"hono": "^4.12.5",
|
|
22
|
-
"@porulle/core": "0.
|
|
22
|
+
"@porulle/core": "0.53.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^24.5.2",
|
|
26
26
|
"eslint": "^9.39.1",
|
|
27
27
|
"typescript": "5.9.2",
|
|
28
28
|
"vitest": "^3.2.4",
|
|
29
|
-
"@porulle/
|
|
30
|
-
"@porulle/
|
|
29
|
+
"@porulle/typescript-config": "0.1.0",
|
|
30
|
+
"@porulle/eslint-config": "0.1.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -92,8 +92,6 @@ export type {
|
|
|
92
92
|
CatalogFieldMappingRow,
|
|
93
93
|
CatalogFieldTarget,
|
|
94
94
|
} from "./catalog-field-mapping.js";
|
|
95
|
-
/** ~320 Neon HTTP subrequests per product against a 10,000 per-invocation cap → hard ceiling near 31; 20 leaves margin for heavier products. */
|
|
96
|
-
export const CHANNEL_IMPORT_MAX_ITEMS_PER_INVOCATION = 20;
|
|
97
95
|
|
|
98
96
|
/**
|
|
99
97
|
* How many bounded batches one sweep may walk before it refuses rather than loops.
|
|
@@ -326,71 +324,6 @@ export function channelConnectorPlugin(options: ChannelConnectorPluginOptions =
|
|
|
326
324
|
return { output: { enqueued: stores.length } };
|
|
327
325
|
},
|
|
328
326
|
},
|
|
329
|
-
{
|
|
330
|
-
slug: "channel/import-catalog",
|
|
331
|
-
concurrency: { key: (input: Record<string, unknown>) => String(input.storeId), supersedes: true },
|
|
332
|
-
durableSteps: true,
|
|
333
|
-
handler: async ({ input, ctx }: { input: Record<string, unknown>; ctx: import("@porulle/core").TaskContext }) => {
|
|
334
|
-
const service = new ChannelConnectorService(ctx.db, ctx.services, options);
|
|
335
|
-
const orgId = String(input.orgId);
|
|
336
|
-
const storeId = String(input.storeId);
|
|
337
|
-
const result = await walkBatches(
|
|
338
|
-
ctx,
|
|
339
|
-
"import-catalog",
|
|
340
|
-
storeId,
|
|
341
|
-
async () => {
|
|
342
|
-
const page = await service.importCatalog(orgId, storeId, createSystemActor(orgId), {
|
|
343
|
-
maxItems: CHANNEL_IMPORT_MAX_ITEMS_PER_INVOCATION,
|
|
344
|
-
});
|
|
345
|
-
if (!page.ok) throw new Error(page.error);
|
|
346
|
-
return {
|
|
347
|
-
exhausted: page.value.exhausted,
|
|
348
|
-
counted: page.value.imported,
|
|
349
|
-
cursor: page.value.cursor ?? null,
|
|
350
|
-
...(page.value.warnings ? { warnings: page.value.warnings } : {}),
|
|
351
|
-
// UNCONDITIONAL, unlike `warnings` and `failures` beside it. The step's return value is
|
|
352
|
-
// the only per-batch seam a host application has — `walkBatches` keeps just `last` — so
|
|
353
|
-
// this is where a page gets its identity. Omitting it when empty would make "this batch
|
|
354
|
-
// committed nothing" and "this plugin build does not report entities" the same
|
|
355
|
-
// `undefined` at the seam, and a caller that collapses those enqueues nothing and
|
|
356
|
-
// reports success. The service's own return declares it unconditional for the same
|
|
357
|
-
// reason; dropping it here would have undone that one line later.
|
|
358
|
-
entityIds: page.value.entityIds,
|
|
359
|
-
...(page.value.failures ? { failures: page.value.failures } : {}),
|
|
360
|
-
};
|
|
361
|
-
},
|
|
362
|
-
);
|
|
363
|
-
const jobs = ctx.services.jobs as JobsAdapter;
|
|
364
|
-
// The catalog is always exhausted by the time the walk above returns, so this hand-off is
|
|
365
|
-
// unconditional. It is the ONE enqueue this task is allowed: it starts a DIFFERENT task
|
|
366
|
-
// once, spending a single level of the request chain's 32, rather than one per page the
|
|
367
|
-
// way the continuation it replaced did.
|
|
368
|
-
//
|
|
369
|
-
// A finished catalog is not a usable one. `importCatalog` writes entities, variants and
|
|
370
|
-
// prices and never touches `inventory_levels`, so a store whose sweep ends here has a
|
|
371
|
-
// catalog in which every variant rolls up as out of stock — which is what a consumer
|
|
372
|
-
// projection publishes and what a shopper is shown.
|
|
373
|
-
//
|
|
374
|
-
// Inventory used to arrive from `reconcile`, reachable only through the
|
|
375
|
-
// `channel/reconcile-sweep` cron. A deployment that removes its crons therefore loses a
|
|
376
|
-
// data-plane write silently, with every suite still green. Levelling inventory here keeps
|
|
377
|
-
// it inside the one operator action — "import this store" — instead of behind a second
|
|
378
|
-
// one somebody has to remember.
|
|
379
|
-
await jobs.enqueue("channel/sync-inventory", { orgId, storeId }, {
|
|
380
|
-
organizationId: orgId,
|
|
381
|
-
concurrencyKey: storeId,
|
|
382
|
-
});
|
|
383
|
-
return {
|
|
384
|
-
output: {
|
|
385
|
-
imported: result.counted,
|
|
386
|
-
cursor: result.last.cursor ?? null,
|
|
387
|
-
exhausted: true,
|
|
388
|
-
batches: result.batches,
|
|
389
|
-
...(result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
327
|
{
|
|
395
328
|
slug: "channel/backfill-catalog",
|
|
396
329
|
concurrency: { key: (input: Record<string, unknown>) => String(input.storeId) },
|
package/src/service.ts
CHANGED
|
@@ -2963,21 +2963,11 @@ export class ChannelConnectorService {
|
|
|
2963
2963
|
return PluginErr(registration.error.message, "CONNECTOR_REGISTRATION_FAILED");
|
|
2964
2964
|
}
|
|
2965
2965
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
await jobs.enqueue("channel/import-catalog", { orgId, storeId: (rows[0] as ConnectedStore).id }, {
|
|
2969
|
-
organizationId: orgId,
|
|
2970
|
-
concurrencyKey: (rows[0] as ConnectedStore).id,
|
|
2971
|
-
supersedes: true,
|
|
2972
|
-
});
|
|
2973
|
-
}
|
|
2966
|
+
// Connecting starts no import. The host's operator route starts one (and levels inventory after
|
|
2967
|
+
// it); connect used to enqueue a second, sequential walk that ran beside the host's own.
|
|
2974
2968
|
return Ok(redactStore(store));
|
|
2975
2969
|
}
|
|
2976
2970
|
|
|
2977
|
-
private get optionsJobs(): JobsAdapter | undefined {
|
|
2978
|
-
return this.jobs;
|
|
2979
|
-
}
|
|
2980
|
-
|
|
2981
2971
|
async disconnectStore(orgId: string, id: string): Promise<PluginResult<PublicConnectedStore>> {
|
|
2982
2972
|
return this.disconnectStoreSystem(orgId, id);
|
|
2983
2973
|
}
|