@sixtyninecommerce/admin 0.11.0 → 0.12.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.
Files changed (147) hide show
  1. package/README.md +1501 -23
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +8 -1
  4. package/dist/catalog/documents.d.ts +142 -0
  5. package/dist/catalog/documents.js +114 -0
  6. package/dist/catalog/index.d.ts +48 -7
  7. package/dist/catalog/index.js +6 -1
  8. package/dist/catalog/metadata.d.ts +244 -0
  9. package/dist/catalog/metadata.js +220 -0
  10. package/dist/catalog/properties.d.ts +38 -0
  11. package/dist/catalog/properties.js +52 -1
  12. package/dist/catalog/recommendations.d.ts +33 -6
  13. package/dist/catalog/recommendations.js +94 -2
  14. package/dist/catalog/reviews.d.ts +17 -4
  15. package/dist/catalog/reviews.js +7 -0
  16. package/dist/catalog/variants.d.ts +60 -0
  17. package/dist/catalog/variants.js +39 -0
  18. package/dist/catalog/visibility.d.ts +18 -2
  19. package/dist/client.d.ts +29 -3
  20. package/dist/client.js +19 -8
  21. package/dist/content/assets.d.ts +3 -4
  22. package/dist/content/forms.d.ts +332 -1
  23. package/dist/content/forms.js +140 -4
  24. package/dist/content/items.d.ts +31 -8
  25. package/dist/content/items.js +1 -1
  26. package/dist/content/menus.d.ts +254 -0
  27. package/dist/content/menus.js +70 -0
  28. package/dist/content/strings.d.ts +137 -0
  29. package/dist/content/strings.js +82 -0
  30. package/dist/customers/delivery-terms.d.ts +44 -0
  31. package/dist/customers/delivery-terms.js +32 -0
  32. package/dist/customers/index.d.ts +15 -0
  33. package/dist/customers/index.js +4 -0
  34. package/dist/documents/emails.d.ts +150 -0
  35. package/dist/documents/emails.js +220 -0
  36. package/dist/documents/index.d.ts +13 -0
  37. package/dist/documents/index.js +2 -0
  38. package/dist/fulfillments/index.d.ts +119 -1
  39. package/dist/fulfillments/index.js +124 -4
  40. package/dist/index.d.ts +43 -23
  41. package/dist/index.js +11 -5
  42. package/dist/integrations/exact-online-types.d.ts +542 -0
  43. package/dist/integrations/exact-online.d.ts +541 -5
  44. package/dist/integrations/exact-online.js +714 -1
  45. package/dist/integrations/index.d.ts +23 -8
  46. package/dist/integrations/index.js +9 -4
  47. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  48. package/dist/integrations/pipeline-discoveries.js +65 -0
  49. package/dist/integrations/pipeline-runs.d.ts +119 -0
  50. package/dist/integrations/pipeline-runs.js +87 -0
  51. package/dist/integrations/pipelines.d.ts +73 -2
  52. package/dist/integrations/pipelines.js +36 -1
  53. package/dist/integrations/price-import.d.ts +223 -0
  54. package/dist/integrations/price-import.js +124 -0
  55. package/dist/integrations/types.d.ts +221 -3
  56. package/dist/integrations/types.js +4 -3
  57. package/dist/meta/index.d.ts +10 -0
  58. package/dist/metafields/index.d.ts +8 -1
  59. package/dist/metafields/index.js +10 -0
  60. package/dist/orders/cancel-with-money.d.ts +10 -0
  61. package/dist/orders/cancel-with-money.js +2 -0
  62. package/dist/orders/commit-edit.d.ts +16 -0
  63. package/dist/orders/commit-edit.js +14 -1
  64. package/dist/orders/deposit-policy.d.ts +3 -1
  65. package/dist/orders/index.d.ts +22 -11
  66. package/dist/orders/index.js +5 -2
  67. package/dist/orders/list.d.ts +26 -5
  68. package/dist/orders/list.js +123 -13
  69. package/dist/orders/manual-discounts.d.ts +13 -5
  70. package/dist/orders/money-view.d.ts +149 -0
  71. package/dist/orders/money-view.js +99 -0
  72. package/dist/orders/plan-edit.d.ts +34 -2
  73. package/dist/orders/plan-edit.js +110 -12
  74. package/dist/orders/surcharges.d.ts +12 -0
  75. package/dist/orders/surcharges.js +34 -9
  76. package/dist/payments/account-credit.d.ts +17 -1
  77. package/dist/payments/account-credit.js +25 -2
  78. package/dist/payments/credit-noted-fields.d.ts +8 -1
  79. package/dist/payments/credit-noted-fields.js +34 -2
  80. package/dist/payments/customer-consent.d.ts +30 -0
  81. package/dist/payments/customer-consent.js +49 -0
  82. package/dist/payments/index.d.ts +89 -14
  83. package/dist/payments/index.js +22 -2
  84. package/dist/payments/requests.d.ts +6 -3
  85. package/dist/payments/requests.js +1 -0
  86. package/dist/pickups/index.d.ts +5 -3
  87. package/dist/pickups/index.js +5 -2
  88. package/dist/platform/email-gateways.d.ts +165 -0
  89. package/dist/platform/email-gateways.js +78 -0
  90. package/dist/platform/tax.d.ts +11 -3
  91. package/dist/platform/tax.js +30 -0
  92. package/dist/pos/exchanges.d.ts +6 -1
  93. package/dist/pos/index.d.ts +229 -10
  94. package/dist/pos/index.js +113 -4
  95. package/dist/pos/offline-sales.d.ts +44 -3
  96. package/dist/pos/offline-sales.js +57 -3
  97. package/dist/pos/returns.d.ts +28 -1
  98. package/dist/pos/returns.js +36 -6
  99. package/dist/pricing/price-lists.d.ts +3 -1
  100. package/dist/purchasing/alerts.d.ts +19 -4
  101. package/dist/purchasing/alerts.js +29 -3
  102. package/dist/purchasing/backorders.d.ts +61 -0
  103. package/dist/purchasing/backorders.js +92 -0
  104. package/dist/purchasing/index.d.ts +21 -3
  105. package/dist/purchasing/index.js +8 -0
  106. package/dist/purchasing/links.d.ts +263 -0
  107. package/dist/purchasing/links.js +192 -0
  108. package/dist/purchasing/purchase-orders.d.ts +4 -1
  109. package/dist/purchasing/receipts.d.ts +396 -0
  110. package/dist/purchasing/receipts.js +287 -0
  111. package/dist/purchasing/returns.d.ts +273 -0
  112. package/dist/purchasing/returns.js +229 -0
  113. package/dist/quotes/index.d.ts +20 -3
  114. package/dist/returns/index.d.ts +62 -1
  115. package/dist/returns/index.js +61 -0
  116. package/dist/seo/config.d.ts +128 -0
  117. package/dist/seo/config.js +95 -0
  118. package/dist/seo/redirects.d.ts +125 -0
  119. package/dist/seo/redirects.js +60 -1
  120. package/dist/seo/settings.d.ts +147 -0
  121. package/dist/seo/settings.js +209 -0
  122. package/dist/shipping/index.d.ts +6 -0
  123. package/dist/shipping/index.js +2 -0
  124. package/dist/shipping/methods.d.ts +8 -1
  125. package/dist/shipping/myparcel.d.ts +415 -1
  126. package/dist/shipping/myparcel.js +291 -3
  127. package/dist/shipping/rate-tables.d.ts +149 -4
  128. package/dist/shipping/rate-tables.js +229 -23
  129. package/dist/shipping/ship-to-countries.d.ts +75 -0
  130. package/dist/shipping/ship-to-countries.js +47 -0
  131. package/dist/stock/by-location.d.ts +86 -0
  132. package/dist/stock/by-location.js +94 -0
  133. package/dist/stock/index.d.ts +8 -1
  134. package/dist/stock/index.js +4 -0
  135. package/dist/stock/settings.d.ts +106 -0
  136. package/dist/stock/settings.js +103 -0
  137. package/dist/stock/transfers.d.ts +171 -10
  138. package/dist/stock/transfers.js +82 -4
  139. package/dist/stored-value/index.d.ts +98 -7
  140. package/dist/stored-value/index.js +71 -4
  141. package/dist/tax/display-policy.d.ts +105 -0
  142. package/dist/tax/display-policy.js +84 -0
  143. package/dist/tax/index.d.ts +3 -0
  144. package/dist/tax/index.js +2 -0
  145. package/dist/tenant/email-templates.d.ts +222 -1
  146. package/dist/tenant/email-templates.js +227 -10
  147. package/package.json +2 -2
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * Integrations (headless spec §9.2): the store's connections to other systems. The data
3
- * pipelines' source connections, connectors and import and export set-ups, the e-mail marketing
4
- * connection (`esp`), the AFAS Profit connection with what is sent to it (`afas`) and the Exact
5
- * Online connection with its settings (`exactOnline`); the pipelines themselves, their runs and
6
- * their progress streams follow in phase 7.
3
+ * pipelines with their source connections, connectors and import and export set-ups, the e-mail
4
+ * marketing connection (`esp`), the AFAS Profit connection with what is sent to it (`afas`) and
5
+ * the Exact Online connection with its settings, its inbound sync, its shipping-method mapping
6
+ * and its connection logs (`exactOnline`), and the bulk price import with
7
+ * its AI conversion (`priceImport`).
7
8
  */
8
9
  import type { Transport } from '@sixtyninecommerce/core';
9
10
  import { type AfasModule } from './afas.js';
10
11
  import { type EspModule } from './esp.js';
11
12
  import { type ExactOnlineModule } from './exact-online.js';
12
13
  import { type IntegrationPipelinesModule } from './pipelines.js';
14
+ import { type PriceImportModule } from './price-import.js';
13
15
  export type { AfasConfigModule, AfasModule, AfasPaymentTermsModule, AfasWarehouseMappingsModule, } from './afas.js';
14
16
  export { AFAS_SYNC_LOG_PAGE_LIMIT } from './afas.js';
15
17
  export type { AfasCustomersModule, AfasOrdersModule, AfasStockTransfersModule, } from './afas-actions.js';
@@ -19,22 +21,35 @@ export type { PipelineConnectionsModule } from './connections.js';
19
21
  export { PIPELINE_CONNECTION_PRESERVE_KEYS } from './connections.js';
20
22
  export type { ConnectEspWithTokenInput, EspBackfillResource, EspConnection, EspConnectionTestResult, EspConnectMetadata, EspCredentialsPushState, EspInboundWebhookState, EspModule, EspSyncLog, EspSyncLogFilter, UpdateEspConnectionInput, } from './esp.js';
21
23
  export { ESP_BACKFILL_RESOURCES, ESP_SYNC_LOG_PAGE_LIMIT } from './esp.js';
22
- export type { ExactOnlineConfigModule, ExactOnlineCredentialsModule, ExactOnlineModule, ExactOnlineSamplesModule, ExactOnlineStructureModule, ExactOnlineWebhooksModule, } from './exact-online.js';
24
+ export type { ExactOnlineConfigModule, ExactOnlineConnectionLogsModule, ExactOnlineCredentialsModule, ExactOnlineCustomersModule, ExactOnlineModule, ExactOnlineOrdersModule, ExactOnlineProductsModule, ExactOnlineSamplesModule, ExactOnlineShippingModule, ExactOnlineStructureModule, ExactOnlineSyncModule, ExactOnlineTermsModule, ExactOnlineWebhooksModule, } from './exact-online.js';
25
+ export { EXACT_ONLINE_LOG_EXPORT_TIMEOUT_MS, EXACT_ONLINE_SYNC_LOG_LIMIT } from './exact-online.js';
23
26
  export type * from './exact-online-types.js';
27
+ export type { PipelineDiscoveriesModule } from './pipeline-discoveries.js';
28
+ export type { PipelineRunsModule } from './pipeline-runs.js';
24
29
  export type { IntegrationPipelinesModule, PipelineDraftsModule } from './pipelines.js';
25
30
  export { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipelines.js';
31
+ export type { PriceImportConversion, PriceImportConversionProgress, PriceImportConversionStep, PriceImportConvertInput, PriceImportDocument, PriceImportFollowOptions, PriceImportFormat, PriceImportModule, PriceImportReport, PriceImportRow, PriceImportRowStatus, PriceImportRunInput, PriceImportSummary, PriceImportTaxMode, PriceImportUpload, } from './price-import.js';
32
+ export { PRICE_IMPORT_PREVIEW_TIMEOUT_MS, PRICE_IMPORT_RUN_TIMEOUT_MS } from './price-import.js';
26
33
  export type * from './types.js';
27
34
  export interface IntegrationsModule {
28
35
  /**
29
- * Data pipelines: the source connections (`connections`), the connectors, and the import and
30
- * export set-ups kept on the server (`drafts`).
36
+ * Data pipelines: the pipelines themselves, the source connections (`connections`), the
37
+ * connectors, and the import and export set-ups kept on the server (`drafts`).
31
38
  */
32
39
  pipelines: IntegrationPipelinesModule;
33
40
  /** The store's e-mail marketing connection (sixtynine.email). See {@link EspModule}. */
34
41
  esp: EspModule;
35
42
  /** The store's AFAS Profit connection and its settings. See {@link AfasModule}. */
36
43
  afas: AfasModule;
37
- /** The store's Exact Online connection and its settings. See {@link ExactOnlineModule}. */
44
+ /**
45
+ * The store's Exact Online connection, its settings, its inbound sync, its shipping-method
46
+ * mapping and its connection logs. See {@link ExactOnlineModule}.
47
+ */
38
48
  exactOnline: ExactOnlineModule;
49
+ /**
50
+ * The bulk price import (a dry run, then the rows to write) and the AI conversion of a
51
+ * supplier file into a document it accepts. See {@link PriceImportModule}.
52
+ */
53
+ priceImport: PriceImportModule;
39
54
  }
40
55
  export declare function createIntegrationsModule(transport: Transport): IntegrationsModule;
@@ -1,24 +1,29 @@
1
1
  /**
2
2
  * Integrations (headless spec §9.2): the store's connections to other systems. The data
3
- * pipelines' source connections, connectors and import and export set-ups, the e-mail marketing
4
- * connection (`esp`), the AFAS Profit connection with what is sent to it (`afas`) and the Exact
5
- * Online connection with its settings (`exactOnline`); the pipelines themselves, their runs and
6
- * their progress streams follow in phase 7.
3
+ * pipelines with their source connections, connectors and import and export set-ups, the e-mail
4
+ * marketing connection (`esp`), the AFAS Profit connection with what is sent to it (`afas`) and
5
+ * the Exact Online connection with its settings, its inbound sync, its shipping-method mapping
6
+ * and its connection logs (`exactOnline`), and the bulk price import with
7
+ * its AI conversion (`priceImport`).
7
8
  */
8
9
  import { createAfasModule } from './afas.js';
9
10
  import { createEspModule } from './esp.js';
10
11
  import { createExactOnlineModule } from './exact-online.js';
11
12
  import { createIntegrationPipelinesModule } from './pipelines.js';
13
+ import { createPriceImportModule } from './price-import.js';
12
14
  export { AFAS_SYNC_LOG_PAGE_LIMIT } from './afas.js';
13
15
  export { AFAS_CUSTOMER_SEARCH_LIMIT } from './afas-actions.js';
14
16
  export { PIPELINE_CONNECTION_PRESERVE_KEYS } from './connections.js';
15
17
  export { ESP_BACKFILL_RESOURCES, ESP_SYNC_LOG_PAGE_LIMIT } from './esp.js';
18
+ export { EXACT_ONLINE_LOG_EXPORT_TIMEOUT_MS, EXACT_ONLINE_SYNC_LOG_LIMIT } from './exact-online.js';
16
19
  export { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipelines.js';
20
+ export { PRICE_IMPORT_PREVIEW_TIMEOUT_MS, PRICE_IMPORT_RUN_TIMEOUT_MS } from './price-import.js';
17
21
  export function createIntegrationsModule(transport) {
18
22
  return {
19
23
  pipelines: createIntegrationPipelinesModule(transport),
20
24
  esp: createEspModule(transport),
21
25
  afas: createAfasModule(transport),
22
26
  exactOnline: createExactOnlineModule(transport),
27
+ priceImport: createPriceImportModule(transport),
23
28
  };
24
29
  }
@@ -0,0 +1,65 @@
1
+ import { type PipelineDiscoveryProgressEvent, type ProgressStream, type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ import type { PipelineDiscovery, PipelineSchemaMap, PipelineWatchOptions } from './types.js';
4
+ /**
5
+ * What a source holds and how it maps onto the store: a discovery samples a connection's source
6
+ * (a fixed-schema connector answers at once; any other asks the AI) and proposes a schema map per
7
+ * kind of record. The import set-ups (`drafts.discover`) start theirs themselves; these doors are
8
+ * for following one, and for a caller that runs a discovery outside a set-up.
9
+ *
10
+ * - **A discovery belongs to its connection's store**: another store's is not found (`get`
11
+ * answers `null`; `schemaMaps` and `watch` are refused `NotFoundError`; an older plugins
12
+ * service refuses `watch` inside the opened stream, `PROGRESS_STREAM_REFUSED`).
13
+ * - Refusals: `PIPELINES_NOT_CONFIGURED` (503, `start`): the platform cannot read connection
14
+ * credentials; `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422, `start`); an app below
15
+ * `customerData: full` is refused every door (`CUSTOMER_DATA_NOT_GRANTED`): the schema maps
16
+ * quote sample records of the source.
17
+ * - Reads and `watch` need `ReadCatalog`; `start` needs `UpdateCatalog`.
18
+ */
19
+ export interface PipelineDiscoveriesModule {
20
+ /**
21
+ * Start a discovery of the connection's source. Answers it `pending`; follow it with `watch` or
22
+ * `get`. Not idempotent: every call samples the source again (and may ask the AI again; an
23
+ * unchanged source is answered from a 24-hour cache).
24
+ *
25
+ * @effect write
26
+ * @permission UpdateCatalog
27
+ */
28
+ start(connectionId: string, options?: Call): Promise<PipelineDiscovery>;
29
+ /**
30
+ * One discovery, or `null` when the store has none with that id.
31
+ *
32
+ * @effect read
33
+ * @permission ReadCatalog
34
+ */
35
+ get(discoveryId: string, options?: Call): Promise<PipelineDiscovery | null>;
36
+ /**
37
+ * The discovery's schema maps, the most records first: one per kind of record the source holds,
38
+ * with its field mappings and sample values as stored. Empty until the discovery completes.
39
+ *
40
+ * @effect read
41
+ * @permission ReadCatalog
42
+ */
43
+ schemaMaps(discoveryId: string, options?: Call): Promise<PipelineSchemaMap[]>;
44
+ /**
45
+ * The discovery as it happens: an async iterable of its `progress` events (phase, sentence,
46
+ * percentage), ending after its terminal event, `complete` (with its warnings and the source's
47
+ * detected plugins; read the maps with `schemaMaps`) or `error` (the reason, yielded, not
48
+ * thrown). A discovery that had already ended answers its ended state at once.
49
+ *
50
+ * It opens on the first `next()`, and reopens a connection that drops before the end (see
51
+ * {@link PipelineWatchOptions.maxReconnects}); it never retries a refusal: a 401 or 403, or
52
+ * a discovery the store does not have (another store's included), which the plugins service
53
+ * refuses with a 404 `PIPELINE_DISCOVERY_NOT_FOUND` (`NotFoundError`) before the stream opens;
54
+ * a plugins service from before that refuses inside the opened stream,
55
+ * `PROGRESS_STREAM_REFUSED` (`UpstreamError`). It throws `PROGRESS_STREAM_CLOSED` /
56
+ * `PROGRESS_STREAM_STALLED` once the reopens are spent, and
57
+ * `REQUEST_ABORTED` when `signal` fires. An id that is not a discovery id is refused
58
+ * `PROGRESS_STREAM_ID_INVALID` before anything is sent.
59
+ *
60
+ * @effect read
61
+ * @permission ReadCatalog
62
+ */
63
+ watch(discoveryId: string, options?: PipelineWatchOptions): ProgressStream<PipelineDiscoveryProgressEvent>;
64
+ }
65
+ export declare function createPipelineDiscoveriesModule(transport: Transport): PipelineDiscoveriesModule;
@@ -0,0 +1,65 @@
1
+ import { NotFoundError, openProgressStream, } from '@sixtyninecommerce/core';
2
+ import { PIPELINE_DRAFT_PRESERVE_KEYS } from './pipeline-keys.js';
3
+ const segment = (value) => encodeURIComponent(value);
4
+ const texts = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
5
+ /** The documented fields: the wire also answers the connection row and the result bag. */
6
+ function toDiscovery(raw) {
7
+ return {
8
+ id: raw.id,
9
+ connectionId: raw.connectionId,
10
+ status: raw.status,
11
+ source: raw.source ?? 'ai',
12
+ errorMessage: raw.errorMessage ?? null,
13
+ warnings: texts(raw.result?.warnings),
14
+ detectedPlugins: texts(raw.result?.detectedPlugins),
15
+ startedAt: raw.startedAt ?? null,
16
+ completedAt: raw.completedAt ?? null,
17
+ createdAt: raw.createdAt,
18
+ };
19
+ }
20
+ function toSchemaMap(raw) {
21
+ return {
22
+ id: raw.id,
23
+ discoveryId: raw.discoveryId,
24
+ entityType: raw.entityType,
25
+ enabled: raw.enabled,
26
+ confidence: raw.confidence,
27
+ entityCount: raw.entityCount,
28
+ subCounts: raw.subCounts ?? null,
29
+ sourceSchema: raw.sourceSchema ?? {},
30
+ targetSchema: raw.targetSchema ?? {},
31
+ fieldMappings: (raw.fieldMappings ?? []).map((mapping) => ({
32
+ sourceField: mapping.sourceField,
33
+ sourcePath: mapping.sourcePath,
34
+ targetField: mapping.targetField,
35
+ targetPath: mapping.targetPath,
36
+ transform: mapping.transform ?? null,
37
+ confidence: mapping.confidence,
38
+ enabled: mapping.enabled,
39
+ })),
40
+ };
41
+ }
42
+ export function createPipelineDiscoveriesModule(transport) {
43
+ return {
44
+ async start(connectionId, options) {
45
+ return toDiscovery(await transport.rest('plugins', { method: 'POST', path: `/pipelines/connections/${segment(connectionId)}/discover` }, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS }));
46
+ },
47
+ async get(discoveryId, options) {
48
+ try {
49
+ return toDiscovery(await transport.rest('plugins', { method: 'GET', path: `/pipelines/discoveries/${segment(discoveryId)}` }, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS }));
50
+ }
51
+ catch (error) {
52
+ if (error instanceof NotFoundError)
53
+ return null;
54
+ throw error;
55
+ }
56
+ },
57
+ async schemaMaps(discoveryId, options) {
58
+ const maps = await transport.rest('plugins', { method: 'GET', path: `/pipelines/discoveries/${segment(discoveryId)}/schema-maps` },
59
+ // `source_schema` / `target_schema` carry the source's own names: never re-cased.
60
+ { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS });
61
+ return (Array.isArray(maps) ? maps : []).map(toSchemaMap);
62
+ },
63
+ watch: (discoveryId, options = {}) => openProgressStream(transport, { kind: 'pipeline-discovery', id: discoveryId }, options),
64
+ };
65
+ }
@@ -0,0 +1,119 @@
1
+ import { type Page, type PipelineRunProgressEvent, type ProgressStream, type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call, ListOptions } from '../types.js';
3
+ import type { ListPipelineRunLogsOptions, PipelineRun, PipelineRunLog, PipelineWatchOptions, StartPipelineRunInput } from './types.js';
4
+ /**
5
+ * A pipeline's runs: start one, follow it, pause, resume or cancel it, and read what it did with
6
+ * each record (its logs).
7
+ *
8
+ * - **A run is also a job** with the same id (`admin.jobs`, kind `pipeline.import` or
9
+ * `pipeline.export`), once the jobs resource has indexed it: `admin.jobs.list()` indexes the
10
+ * runs it has not seen yet, and `admin.jobs.wait(run.id)` then waits for the run to end.
11
+ * `get` here reads the run itself, with its per-entity counts.
12
+ * - **Pause and cancel take effect at the next batch boundary**: the batch in flight finishes and
13
+ * is recorded, then the run stops. Only a `running` run can be paused or cancelled (a `paused`
14
+ * one can be cancelled too); a queued (`pending`) one is refused `PIPELINE_RUN_NOT_STARTED`.
15
+ * - **Deleting the pipeline cancels its runs** (`pipelines.delete`): a queued or paused run ends
16
+ * `cancelled` at once, a running one stops after the batch it is working on, and nothing more
17
+ * is imported or sent. Each says so in `errorMessage`. Such a run can still be read here by its
18
+ * id; the pipeline's run list is gone with the pipeline.
19
+ * - Refusals, all `PlatformError`s branching on `code`:
20
+ * - `PIPELINE_RUN_NOT_FOUND` (404): no such run in this store (another store's included);
21
+ * - `PIPELINE_RUN_ALREADY_ENDED`, `PIPELINE_RUN_NOT_STARTED`, `PIPELINE_RUN_NOT_RUNNING`,
22
+ * `PIPELINE_RUN_NOT_PAUSED` (409): the run is not in a state the action accepts;
23
+ * - `PIPELINE_CHANNEL_TOKEN_MISMATCH` (403, `start` and `resume`): the client's channel token
24
+ * is not this store's any more (the store was renamed); build a client with the new one;
25
+ * - `PIPELINE_COLLECTION_*`: a collection rebuild the pipeline cannot do (an export, no
26
+ * products, a resume, `replace` without SuperAdmin);
27
+ * - `PIPELINES_NOT_CONFIGURED` (503): the platform cannot read connection credentials;
28
+ * - `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422): `start` and `resume` in an organization
29
+ * workspace.
30
+ * - Reads need `ReadCatalog`; `start`, `pause`, `resume` and `cancel` need `UpdateCatalog`. An app
31
+ * below `customerData: full` is refused every door (`CUSTOMER_DATA_NOT_GRANTED`): a run's logs
32
+ * quote the records it moved.
33
+ */
34
+ export interface PipelineRunsModule {
35
+ /**
36
+ * Queue a run of the pipeline now, whatever its schedule (a paused pipeline too). Answers the
37
+ * run, `pending`. Not idempotent: every call queues another run, so a caller that lost the
38
+ * answer lists the runs before starting again. An export run sends the store's orders to the
39
+ * other system, which cannot be taken back.
40
+ *
41
+ * @effect irreversible
42
+ * @permission UpdateCatalog
43
+ */
44
+ start(pipelineId: string, input?: StartPipelineRunInput, options?: Call): Promise<PipelineRun>;
45
+ /**
46
+ * The pipeline's runs, newest first. A pipeline the store does not have (deleted included) is a
47
+ * `NotFoundError`.
48
+ *
49
+ * @effect read
50
+ * @permission ReadCatalog
51
+ */
52
+ list(pipelineId: string, options?: ListOptions): Promise<Page<PipelineRun>>;
53
+ /**
54
+ * One run, or `null` when the store has none with that id.
55
+ *
56
+ * @effect read
57
+ * @permission ReadCatalog
58
+ */
59
+ get(runId: string, options?: Call): Promise<PipelineRun | null>;
60
+ /**
61
+ * Pause a `running` run at its next batch boundary; resume it later. A run already paused
62
+ * answers as it is.
63
+ *
64
+ * @effect write
65
+ * @permission UpdateCatalog
66
+ */
67
+ pause(runId: string, options?: Call): Promise<PipelineRun>;
68
+ /**
69
+ * Queue a `paused` run again (`pending`); it continues where it stopped. A run started with a
70
+ * collection rebuild cannot be resumed (`PIPELINE_COLLECTION_REBUILD_NOT_RESUMABLE`).
71
+ *
72
+ * @effect irreversible
73
+ * @permission UpdateCatalog
74
+ */
75
+ resume(runId: string, options?: Call): Promise<PipelineRun>;
76
+ /**
77
+ * Cancel a `running` or `paused` run: it ends `cancelled` now, and a running one stops at its
78
+ * next batch boundary. What it already did stays done. A run already cancelled answers as it is.
79
+ *
80
+ * @effect write
81
+ * @permission UpdateCatalog
82
+ */
83
+ cancel(runId: string, options?: Call): Promise<PipelineRun>;
84
+ /**
85
+ * What the run did with each record, newest first, `total` counting every match. Filter by
86
+ * `entityType` and by `result` (its errors: `errored`; what it skipped: `skipped`). There is no
87
+ * level to filter by: a record's outcome is its `result`.
88
+ *
89
+ * @effect read
90
+ * @permission ReadCatalog
91
+ */
92
+ logs(runId: string, options?: ListPipelineRunLogsOptions): Promise<Page<PipelineRunLog>>;
93
+ /**
94
+ * The run as it happens: an async iterable of its events, each with the run's `phase`, a
95
+ * `message` and the per-entity counts so far (`progress`). `progress` events arrive while it
96
+ * works (at most about one a second, naming the `entityType` it is on), and the iteration ENDS
97
+ * after the one terminal event: `complete`, `error` (the run failed; the reason is the event's
98
+ * `message`, yielded, not thrown), `cancelled` or `paused`. A run that had already ended answers
99
+ * its ended state at once; a queued one says nothing until it starts. A resumed run is followed
100
+ * by watching it again.
101
+ *
102
+ * The counts are the stream's: a run cancelled by its pipeline's deletion ends the stream at
103
+ * once, and the batch it was working on may still finish after that; `get` reads the final
104
+ * counts. It opens on the first `next()` and reopens a connection that drops before the end (see
105
+ * {@link PipelineWatchOptions.maxReconnects}); it never retries a refusal: a 401 or 403, or a
106
+ * run the store does not have (another store's included), which the plugins service refuses
107
+ * with a 404 `PIPELINE_RUN_NOT_FOUND` (`NotFoundError`) before the stream opens; a plugins
108
+ * service from before that refuses inside the opened stream, `PROGRESS_STREAM_REFUSED`
109
+ * (`UpstreamError`). It throws
110
+ * `PROGRESS_STREAM_CLOSED` / `PROGRESS_STREAM_STALLED` once the reopens are spent, and
111
+ * `REQUEST_ABORTED` when `signal` fires. An id that is not a run id is refused
112
+ * `PROGRESS_STREAM_ID_INVALID` before anything is sent.
113
+ *
114
+ * @effect read
115
+ * @permission ReadCatalog
116
+ */
117
+ watch(runId: string, options?: PipelineWatchOptions): ProgressStream<PipelineRunProgressEvent>;
118
+ }
119
+ export declare function createPipelineRunsModule(transport: Transport): PipelineRunsModule;
@@ -0,0 +1,87 @@
1
+ import { createPage, NotFoundError, openProgressStream, readRestPage, } from '@sixtyninecommerce/core';
2
+ import { PIPELINE_DRAFT_PRESERVE_KEYS } from './pipeline-keys.js';
3
+ const PIPELINES = '/pipelines';
4
+ const RUNS = '/pipelines/runs';
5
+ const segment = (value) => encodeURIComponent(value);
6
+ /**
7
+ * The documented fields only. The wire also carries the run's internal id maps (every source id
8
+ * the run mapped, which can run to tens of thousands of entries) and, on some answers, its
9
+ * pipeline's row.
10
+ */
11
+ function toRun(raw) {
12
+ return {
13
+ id: raw.id,
14
+ pipelineId: raw.pipelineId,
15
+ trigger: raw.trigger,
16
+ status: raw.status,
17
+ progress: raw.progress ?? {},
18
+ errorMessage: raw.errorMessage ?? null,
19
+ syncPolicy: raw.syncPolicy,
20
+ collectionAssignmentMode: raw.collectionAssignmentMode ?? 'merge',
21
+ startedAt: raw.startedAt ?? null,
22
+ completedAt: raw.completedAt ?? null,
23
+ createdAt: raw.createdAt,
24
+ updatedAt: raw.updatedAt,
25
+ };
26
+ }
27
+ function toLog(raw) {
28
+ return {
29
+ id: raw.id,
30
+ runId: raw.runId,
31
+ entityType: raw.entityType,
32
+ sourceId: raw.sourceId ?? null,
33
+ targetId: raw.targetId ?? null,
34
+ result: raw.result,
35
+ message: raw.message ?? null,
36
+ createdAt: raw.createdAt,
37
+ };
38
+ }
39
+ export function createPipelineRunsModule(transport) {
40
+ // `progress` (keyed by entity type) and `idMaps` are stored data: preserved, never re-cased.
41
+ const call = async (method, path, options, body) => toRun(await transport.rest('plugins', { method, path, ...(body === undefined ? {} : { body }) }, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS }));
42
+ async function list(pipelineId, options) {
43
+ const body = await transport.rest('plugins', {
44
+ method: 'GET',
45
+ path: `${PIPELINES}/${segment(pipelineId)}/runs`,
46
+ // `cursor=1` asks for a keyset page; without it the door answers every run as an array.
47
+ query: { cursor: 1, after: options?.after, limit: options?.limit },
48
+ }, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS });
49
+ const { items, meta } = readRestPage(body);
50
+ return createPage(items.map(toRun), meta, (cursor) => list(pipelineId, { ...options, after: cursor }));
51
+ }
52
+ async function logs(runId, options) {
53
+ const body = await transport.rest('plugins', {
54
+ method: 'GET',
55
+ path: `${RUNS}/${segment(runId)}/logs`,
56
+ query: {
57
+ cursor: 1,
58
+ entity_type: options?.entityType,
59
+ result: options?.result,
60
+ after: options?.after,
61
+ limit: options?.limit,
62
+ },
63
+ }, options);
64
+ const { items, meta } = readRestPage(body);
65
+ return createPage(items.map(toLog), meta, (cursor) => logs(runId, { ...options, after: cursor }));
66
+ }
67
+ return {
68
+ // No body unless something is overridden: the door's body is optional.
69
+ start: (pipelineId, input, options) => call('POST', `${PIPELINES}/${segment(pipelineId)}/runs`, options, input && Object.keys(input).length > 0 ? input : undefined),
70
+ list,
71
+ async get(runId, options) {
72
+ try {
73
+ return await call('GET', `${RUNS}/${segment(runId)}`, options);
74
+ }
75
+ catch (error) {
76
+ if (error instanceof NotFoundError)
77
+ return null;
78
+ throw error;
79
+ }
80
+ },
81
+ pause: (runId, options) => call('POST', `${RUNS}/${segment(runId)}/pause`, options),
82
+ resume: (runId, options) => call('POST', `${RUNS}/${segment(runId)}/resume`, options),
83
+ cancel: (runId, options) => call('POST', `${RUNS}/${segment(runId)}/cancel`, options),
84
+ logs,
85
+ watch: (runId, options = {}) => openProgressStream(transport, { kind: 'pipeline-run', id: runId }, options),
86
+ };
87
+ }
@@ -1,7 +1,9 @@
1
1
  import { type Page, type Transport } from '@sixtyninecommerce/core';
2
2
  import type { Call, ListOptions } from '../types.js';
3
3
  import { type PipelineConnectionsModule } from './connections.js';
4
- import type { CommittedPipelineDraft, ImportDependency, PipelineConnector, PipelineDirection, PipelineDraft, PipelineDraftSummary, UpdatePipelineDraftInput, UpdatePipelineDraftSchemaMapInput } from './types.js';
4
+ import { type PipelineDiscoveriesModule } from './pipeline-discoveries.js';
5
+ import { type PipelineRunsModule } from './pipeline-runs.js';
6
+ import type { CommittedPipelineDraft, ImportDependency, ListPipelinesOptions, Pipeline, PipelineConnector, PipelineDirection, PipelineDraft, PipelineDraftSummary, UpdatePipelineDraftInput, UpdatePipelineDraftSchemaMapInput, UpdatePipelineInput } from './types.js';
5
7
  export { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
6
8
  /**
7
9
  * Half-finished import and export set-ups, kept on the server for the store (headless spec §7).
@@ -90,9 +92,78 @@ export interface PipelineDraftsModule {
90
92
  */
91
93
  discard(id: string, options?: Call): Promise<void>;
92
94
  }
93
- /** Data pipelines (imports and exports between the store and another system). */
95
+ /**
96
+ * Data pipelines (imports and exports between the store and another system): the pipelines
97
+ * themselves (`list`, `get`, `update`, `delete`), their runs and the runs' logs (`runs`), the
98
+ * set-ups that create them (`drafts`), the discoveries of a source (`discoveries`), their source
99
+ * connections and the connectors.
100
+ *
101
+ * A pipeline is created by committing a set-up (`drafts.commit`). A run is followed live with
102
+ * `runs.watch` (or read with `runs.get`), and is also a `jobs` entry.
103
+ *
104
+ * - **Schedules are wall-clock times in the pipeline's own `timezone`** (an IANA zone): `daily`
105
+ * runs at 03:00 there and `weekly` on Mondays at 03:00, through its daylight saving changes.
106
+ * A set-up starts with the store's zone; a pipeline made before zones were asked for holds `UTC`.
107
+ * - **Pausing is `update(id, { isActive: false })`**: the schedule is unregistered and a commerce
108
+ * plugin's export through it (Hendriks) is refused until `isActive: true`. Starting a run by hand
109
+ * still works.
110
+ * - Reads need `ReadCatalog`, `update` and `delete` need `UpdateCatalog`. Another store's pipeline
111
+ * is not found (`get` answers `null`, a write a `NotFoundError`). An organization workspace is
112
+ * not a store: an `update` there is refused `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422) unless it
113
+ * only pauses. An app below `customerData: full` is refused every door (`CUSTOMER_DATA_NOT_GRANTED`).
114
+ */
94
115
  export interface IntegrationPipelinesModule {
116
+ /**
117
+ * The store's pipelines, newest first, each with the connection it runs over (never a
118
+ * credential). `direction` lists only imports or only exports.
119
+ *
120
+ * @effect read
121
+ * @permission ReadCatalog
122
+ */
123
+ list(options?: ListPipelinesOptions): Promise<Page<Pipeline>>;
124
+ /**
125
+ * One pipeline, or `null` when the store has none with that id (deleted, or another store's).
126
+ *
127
+ * @effect read
128
+ * @permission ReadCatalog
129
+ */
130
+ get(id: string, options?: Call): Promise<Pipeline | null>;
131
+ /**
132
+ * Change a pipeline: its name, its schedule (`scheduleType`, `cronExpression`, `timezone`), its
133
+ * sync policy, whether it is paused (`isActive`), or an export's filter (`config.triggerFilter`).
134
+ * A field left out stays as stored. A change to the schedule or to `isActive` re-registers the
135
+ * schedule at once. Refused, saving nothing, with a `ValidationError` (400) coded:
136
+ *
137
+ * - `PIPELINE_CRON_REQUIRED`: a `custom` schedule with no cron;
138
+ * - `PIPELINE_CRON_NEVER_FIRES`: a cron the scheduler can never run (`0 0 31 4 *`), checked in
139
+ * `timezone`;
140
+ * - `PIPELINE_EXPORT_FILTER_UNSUPPORTED`, `PIPELINE_EXPORT_FILTER_BLANK`,
141
+ * `PIPELINE_EXPORT_FILTER_VALUE_UNKNOWN`: a filter key the export does not honour, a key with
142
+ * no value, or a value no order can have (`details[].field` is `triggerFilter`). An
143
+ * unhonoured filter would export everything, an impossible one nothing at all;
144
+ * - `VALIDATION_FAILED`: a field of the wrong shape (an unknown zone, a cron that is not 5
145
+ * parts).
146
+ *
147
+ * @effect write
148
+ * @permission UpdateCatalog
149
+ */
150
+ update(id: string, input: UpdatePipelineInput, options?: Call): Promise<Pipeline>;
151
+ /**
152
+ * Delete a pipeline: its schedule is unregistered at once and it leaves the list. Its runs that
153
+ * have not ended are cancelled in the same step: a queued or paused one at once, a running one
154
+ * after the batch it is working on, and nothing more is imported or sent (each says so in
155
+ * `errorMessage`; read them with `runs.get`). Its past runs and their logs are kept. Its
156
+ * connection is not deleted, and can be once nothing else uses it.
157
+ *
158
+ * @effect destructive
159
+ * @permission UpdateCatalog
160
+ */
161
+ delete(id: string, options?: Call): Promise<void>;
162
+ /** The pipelines' runs and their logs. See {@link PipelineRunsModule}. */
163
+ runs: PipelineRunsModule;
95
164
  drafts: PipelineDraftsModule;
165
+ /** The discoveries of a connection's source. See {@link PipelineDiscoveriesModule}. */
166
+ discoveries: PipelineDiscoveriesModule;
96
167
  /** The store's source connections. See {@link PipelineConnectionsModule}. */
97
168
  connections: PipelineConnectionsModule;
98
169
  /**
@@ -1,7 +1,10 @@
1
- import { createPage, readRestPage } from '@sixtyninecommerce/core';
1
+ import { createPage, NotFoundError, readRestPage, } from '@sixtyninecommerce/core';
2
2
  import { createPipelineConnectionsModule, listPipelineConnectors, } from './connections.js';
3
+ import { createPipelineDiscoveriesModule, } from './pipeline-discoveries.js';
3
4
  import { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
5
+ import { createPipelineRunsModule } from './pipeline-runs.js';
4
6
  export { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
7
+ const PIPELINES = '/pipelines';
5
8
  const DRAFTS = '/pipelines/drafts';
6
9
  const segment = (value) => encodeURIComponent(value);
7
10
  export function createIntegrationPipelinesModule(transport) {
@@ -34,8 +37,40 @@ export function createIntegrationPipelinesModule(transport) {
34
37
  await transport.rest('plugins', { method: 'DELETE', path: `${DRAFTS}/${segment(id)}`, expect: 'void' }, options);
35
38
  },
36
39
  };
40
+ async function listPipelines(options) {
41
+ const body = await transport.rest('plugins', {
42
+ method: 'GET',
43
+ path: PIPELINES,
44
+ // `cursor=1` asks for a keyset page; without it the door answers the whole list as an array.
45
+ query: {
46
+ cursor: 1,
47
+ direction: options?.direction,
48
+ after: options?.after,
49
+ limit: options?.limit,
50
+ },
51
+ }, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS });
52
+ const { items, meta } = readRestPage(body);
53
+ return createPage(items, meta, (cursor) => listPipelines({ ...options, after: cursor }));
54
+ }
37
55
  return {
56
+ list: listPipelines,
57
+ async get(id, options) {
58
+ try {
59
+ return await read(`${PIPELINES}/${segment(id)}`, options);
60
+ }
61
+ catch (error) {
62
+ if (error instanceof NotFoundError)
63
+ return null;
64
+ throw error;
65
+ }
66
+ },
67
+ update: (id, input, options) => write('PATCH', `${PIPELINES}/${segment(id)}`, input, options),
68
+ async delete(id, options) {
69
+ await transport.rest('plugins', { method: 'DELETE', path: `${PIPELINES}/${segment(id)}`, expect: 'void' }, options);
70
+ },
71
+ runs: createPipelineRunsModule(transport),
38
72
  drafts,
73
+ discoveries: createPipelineDiscoveriesModule(transport),
39
74
  connections: createPipelineConnectionsModule(transport),
40
75
  connectors: (options) => listPipelineConnectors(transport, options),
41
76
  async importDependencies(options) {