@porulle/core 0.49.0 → 0.50.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.ts CHANGED
@@ -162,6 +162,15 @@ export type {
162
162
  CreatePaymentIntentParams,
163
163
  } from "./modules/payments/adapter.js";
164
164
  export { defineChannelConnector } from "./modules/channels/adapter.js";
165
+ export {
166
+ channelCatalogImageSchema,
167
+ channelCatalogItemSchema,
168
+ channelCatalogLocalizedAttributesSchema,
169
+ channelCatalogOptionTypeSchema,
170
+ channelCatalogPriceSchema,
171
+ channelCatalogVariantSchema,
172
+ channelInventoryLevelSchema,
173
+ } from "./modules/channels/catalog-schema.js";
165
174
  export type {
166
175
  ChannelCatalogImage,
167
176
  ChannelCatalogItem,
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import type {
3
+ ChannelCatalogImage,
4
+ ChannelCatalogItem,
5
+ ChannelCatalogLocalizedAttributes,
6
+ ChannelCatalogOptionType,
7
+ ChannelCatalogPrice,
8
+ ChannelCatalogVariant,
9
+ ChannelInventoryLevel,
10
+ } from "./adapter.js";
11
+
12
+ /**
13
+ * The parser for a catalog item that crossed a trust boundary — a queue message, an R2 object, a
14
+ * JSON fixture. Import this rather than re-declaring a subset: a consumer's hand-written schema that
15
+ * kept only the fields it knew about once converged 96 products with no options, prices or
16
+ * inventory, and a reconcile then "re-converged" all of them. The `Equals` checks at the bottom make
17
+ * this schema fail to compile the day it and the interfaces disagree.
18
+ */
19
+ const record = z.record(z.string(), z.unknown());
20
+
21
+ export const channelCatalogLocalizedAttributesSchema = z.object({
22
+ locale: z.string(),
23
+ title: z.string(),
24
+ subtitle: z.string().exactOptional(),
25
+ description: z.string().exactOptional(),
26
+ richDescription: z.unknown().exactOptional(),
27
+ seoTitle: z.string().exactOptional(),
28
+ seoDescription: z.string().exactOptional(),
29
+ });
30
+
31
+ export const channelCatalogImageSchema = z.object({
32
+ externalId: z.string().exactOptional(),
33
+ url: z.string(),
34
+ alt: z.string().exactOptional(),
35
+ role: z.enum(["primary", "gallery", "thumbnail", "video", "document"]),
36
+ sortOrder: z.number().exactOptional(),
37
+ variantExternalIds: z.array(z.string()).exactOptional(),
38
+ });
39
+
40
+ export const channelCatalogOptionTypeSchema = z.object({
41
+ name: z.string(),
42
+ displayName: z.string(),
43
+ sortOrder: z.number().exactOptional(),
44
+ values: z.array(z.object({
45
+ value: z.string(),
46
+ displayValue: z.string(),
47
+ sortOrder: z.number().exactOptional(),
48
+ })),
49
+ });
50
+
51
+ export const channelCatalogPriceSchema = z.object({
52
+ currency: z.string(),
53
+ amount: z.number(),
54
+ compareAtAmount: z.number().exactOptional(),
55
+ });
56
+
57
+ export const channelCatalogVariantSchema = z.object({
58
+ externalId: z.string(),
59
+ sku: z.string().exactOptional(),
60
+ barcode: z.string().exactOptional(),
61
+ metadata: record.exactOptional(),
62
+ optionValues: z.record(z.string(), z.string()).exactOptional(),
63
+ prices: z.array(channelCatalogPriceSchema).exactOptional(),
64
+ });
65
+
66
+ export const channelCatalogItemSchema = z.object({
67
+ externalId: z.string(),
68
+ slug: z.string(),
69
+ title: z.string(),
70
+ description: z.string().exactOptional(),
71
+ variants: z.array(channelCatalogVariantSchema),
72
+ metadata: record.exactOptional(),
73
+ attributes: z.array(channelCatalogLocalizedAttributesSchema).exactOptional(),
74
+ images: z.array(channelCatalogImageSchema).exactOptional(),
75
+ options: z.array(channelCatalogOptionTypeSchema).exactOptional(),
76
+ tags: z.array(z.string()).exactOptional(),
77
+ brand: z.string().exactOptional(),
78
+ categories: z.array(z.string()).exactOptional(),
79
+ status: z.enum(["draft", "active", "archived", "discontinued"]).exactOptional(),
80
+ });
81
+
82
+ export const channelInventoryLevelSchema = z.object({
83
+ externalId: z.string(),
84
+ available: z.number(),
85
+ });
86
+
87
+ type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
88
+ type Assert<T extends true> = T;
89
+ export type ChannelCatalogSchemaChecks = [
90
+ Assert<Equals<z.infer<typeof channelCatalogLocalizedAttributesSchema>, ChannelCatalogLocalizedAttributes>>,
91
+ Assert<Equals<z.infer<typeof channelCatalogImageSchema>, ChannelCatalogImage>>,
92
+ Assert<Equals<z.infer<typeof channelCatalogOptionTypeSchema>, ChannelCatalogOptionType>>,
93
+ Assert<Equals<z.infer<typeof channelCatalogPriceSchema>, ChannelCatalogPrice>>,
94
+ Assert<Equals<z.infer<typeof channelCatalogVariantSchema>, ChannelCatalogVariant>>,
95
+ Assert<Equals<z.infer<typeof channelCatalogItemSchema>, ChannelCatalogItem>>,
96
+ Assert<Equals<z.infer<typeof channelInventoryLevelSchema>, ChannelInventoryLevel>>,
97
+ ];
@@ -503,6 +503,19 @@ export class InventoryService {
503
503
  ctx,
504
504
  );
505
505
 
506
+ // Already at the quantity: nothing to record. A zero-delta adjust would still write a movement
507
+ // and fire `inventory.afterAdjust`, and a periodic reconcile then re-announces every level it
508
+ // walked — 1,243 of them on one sim reconcile, each one a downstream re-projection.
509
+ // The permission still gates it, so a caller without `inventory:adjust` is refused either way.
510
+ if (existingLevel && existingLevel.quantityOnHand === input.quantity) {
511
+ try {
512
+ assertPermission(actor ?? null, "inventory:adjust");
513
+ } catch (error) {
514
+ return Err(toCommerceError(error));
515
+ }
516
+ return Ok(existingLevel);
517
+ }
518
+
506
519
  const currentOnHand = existingLevel?.quantityOnHand ?? 0;
507
520
  const delta = input.quantity - currentOnHand;
508
521