@useairfoil/producer-shopify 0.2.0 → 0.2.1

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 (60) hide show
  1. package/README.md +85 -248
  2. package/dist/index.d.mts +6993 -0
  3. package/dist/index.mjs +3 -0
  4. package/dist/main.d.mts +1 -0
  5. package/dist/main.mjs +698 -0
  6. package/dist/main.mjs.map +1 -0
  7. package/dist/manifest.d.mts +32 -0
  8. package/dist/manifest.mjs +51 -0
  9. package/dist/manifest.mjs.map +1 -0
  10. package/dist/{chunk-Bp6m_JJh.js → rolldown-runtime-D7D4PA-g.mjs} +4 -4
  11. package/dist/src-Bgvuhcfk.mjs +2473 -0
  12. package/dist/src-Bgvuhcfk.mjs.map +1 -0
  13. package/package.json +19 -19
  14. package/src/{auth.ts → api/auth.ts} +15 -35
  15. package/src/api/client.ts +429 -0
  16. package/src/api/graphql.ts +221 -0
  17. package/src/api/http.ts +25 -0
  18. package/src/connector.ts +266 -163
  19. package/src/create-table.ts +5 -0
  20. package/src/demo/customers.ts +412 -0
  21. package/src/demo/index.ts +31 -0
  22. package/src/demo/setup.ts +63 -0
  23. package/src/demo/shared.ts +114 -0
  24. package/src/index.ts +35 -21
  25. package/src/main.ts +8 -8
  26. package/src/manifest.ts +4 -55
  27. package/src/resources/carts/row.ts +58 -0
  28. package/src/resources/carts/webhook.ts +98 -0
  29. package/src/resources/customers/graphql.ts +175 -0
  30. package/src/resources/customers/row.ts +112 -0
  31. package/src/resources/customers/webhook.ts +30 -0
  32. package/src/resources/orders/graphql.ts +419 -0
  33. package/src/resources/orders/row.ts +180 -0
  34. package/src/resources/orders/webhook.ts +248 -0
  35. package/src/resources/products/graphql.ts +142 -0
  36. package/src/resources/products/row.ts +92 -0
  37. package/src/resources/products/webhook.ts +130 -0
  38. package/src/resources/refunds/graphql.ts +196 -0
  39. package/src/resources/refunds/row.ts +97 -0
  40. package/src/resources/refunds/webhook.ts +123 -0
  41. package/src/resources/shared.ts +293 -0
  42. package/src/sandbox.ts +3 -3
  43. package/src/start.ts +3 -5
  44. package/src/tables.ts +15 -0
  45. package/dist/index.d.ts +0 -965
  46. package/dist/index.js +0 -4
  47. package/dist/main.d.ts +0 -1
  48. package/dist/main.js +0 -49
  49. package/dist/main.js.map +0 -1
  50. package/dist/manifest-BFfx2LkS.js +0 -91
  51. package/dist/manifest-BFfx2LkS.js.map +0 -1
  52. package/dist/manifest-CPOEExQU.d.ts +0 -34
  53. package/dist/manifest.d.ts +0 -2
  54. package/dist/manifest.js +0 -3
  55. package/dist/src-DqmiLrYS.js +0 -984
  56. package/dist/src-DqmiLrYS.js.map +0 -1
  57. package/src/api.ts +0 -636
  58. package/src/schemas.ts +0 -319
  59. package/src/throttle.ts +0 -89
  60. package/src/transient-retry.ts +0 -74
@@ -0,0 +1,248 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ import {
4
+ RestAddressSchema,
5
+ RestAttributeSchema,
6
+ RestDiscountAllocationSchema,
7
+ RestMoneyBagSchema,
8
+ RestTaxLineSchema,
9
+ emptyText,
10
+ fromRest,
11
+ gid,
12
+ normalizeAmount,
13
+ splitTags,
14
+ upper,
15
+ } from "../shared";
16
+ import {
17
+ DiscountAllocationMethodSchema,
18
+ type DiscountApplication,
19
+ DiscountApplicationTypeSchema,
20
+ DiscountTargetSelectionSchema,
21
+ DiscountTargetTypeSchema,
22
+ DiscountValueTypeSchema,
23
+ OrderCancelReasonSchema,
24
+ OrderFinancialStatusSchema,
25
+ type Order,
26
+ } from "./row";
27
+
28
+ const nullableMoney = Schema.NullOr(RestMoneyBagSchema);
29
+
30
+ const RestShippingLineSchema = Schema.Struct({
31
+ id: Schema.NullOr(Schema.Number),
32
+ title: Schema.String,
33
+ code: Schema.NullOr(Schema.String),
34
+ source: Schema.NullOr(Schema.String),
35
+ carrier_identifier: Schema.NullOr(Schema.String),
36
+ phone: Schema.NullOr(Schema.String),
37
+ is_removed: Schema.Boolean,
38
+ price_set: RestMoneyBagSchema,
39
+ discounted_price_set: RestMoneyBagSchema,
40
+ current_discounted_price_set: RestMoneyBagSchema,
41
+ tax_lines: Schema.Array(RestTaxLineSchema),
42
+ discount_allocations: Schema.Array(RestDiscountAllocationSchema),
43
+ });
44
+
45
+ const RestLineItemSchema = Schema.Struct({
46
+ admin_graphql_api_id: Schema.String,
47
+ name: Schema.String,
48
+ title: Schema.String,
49
+ variant_title: Schema.NullOr(Schema.String),
50
+ sku: Schema.NullOr(Schema.String),
51
+ vendor: Schema.NullOr(Schema.String),
52
+ product_id: Schema.NullOr(Schema.Number),
53
+ variant_id: Schema.NullOr(Schema.Number),
54
+ quantity: Schema.Int,
55
+ current_quantity: Schema.Int,
56
+ taxable: Schema.Boolean,
57
+ requires_shipping: Schema.Boolean,
58
+ gift_card: Schema.Boolean,
59
+ price_set: RestMoneyBagSchema,
60
+ total_discount_set: RestMoneyBagSchema,
61
+ tax_lines: Schema.Array(RestTaxLineSchema),
62
+ discount_allocations: Schema.Array(RestDiscountAllocationSchema),
63
+ properties: Schema.Array(RestAttributeSchema),
64
+ });
65
+
66
+ const RestDiscountApplicationSchema = Schema.Struct({
67
+ type: upper(DiscountApplicationTypeSchema),
68
+ code: Schema.optional(Schema.NullOr(Schema.String)),
69
+ title: Schema.optional(Schema.NullOr(Schema.String)),
70
+ description: Schema.optional(Schema.NullOr(Schema.String)),
71
+ allocation_method: upper(DiscountAllocationMethodSchema),
72
+ target_selection: upper(DiscountTargetSelectionSchema),
73
+ target_type: upper(DiscountTargetTypeSchema),
74
+ value_type: upper(DiscountValueTypeSchema),
75
+ value: Schema.String,
76
+ });
77
+
78
+ export const OrderWebhookPayloadSchema = Schema.Struct({
79
+ id: Schema.Number,
80
+ admin_graphql_api_id: Schema.String,
81
+ name: Schema.String,
82
+ email: Schema.NullOr(Schema.String),
83
+ phone: Schema.NullOr(Schema.String),
84
+ customer: Schema.NullOr(Schema.Struct({ admin_graphql_api_id: Schema.String })),
85
+ customer_locale: Schema.NullOr(Schema.String),
86
+ buyer_accepts_marketing: Schema.Boolean,
87
+ billing_address: Schema.NullOr(RestAddressSchema),
88
+ shipping_address: Schema.NullOr(RestAddressSchema),
89
+ note: Schema.NullOr(Schema.String),
90
+ note_attributes: Schema.Array(RestAttributeSchema),
91
+ tags: Schema.String,
92
+ confirmation_number: Schema.NullOr(Schema.String),
93
+ po_number: Schema.NullOr(Schema.String),
94
+ source_name: Schema.NullOr(Schema.String),
95
+ source_identifier: Schema.NullOr(Schema.String),
96
+ app_id: Schema.NullOr(Schema.Number),
97
+ payment_gateway_names: Schema.Array(Schema.String),
98
+ test: Schema.Boolean,
99
+ taxes_included: Schema.Boolean,
100
+ tax_exempt: Schema.Boolean,
101
+ duties_included: Schema.Boolean,
102
+ estimated_taxes: Schema.Boolean,
103
+ total_weight: Schema.NullOr(Schema.Int),
104
+ financial_status: Schema.NullOr(upper(OrderFinancialStatusSchema)),
105
+ cancel_reason: Schema.NullOr(upper(OrderCancelReasonSchema)),
106
+ cancelled_at: Schema.NullOr(Schema.DateFromString),
107
+ closed_at: Schema.NullOr(Schema.DateFromString),
108
+ processed_at: Schema.DateFromString,
109
+ created_at: Schema.DateFromString,
110
+ updated_at: Schema.DateFromString,
111
+ currency: Schema.String,
112
+ presentment_currency: Schema.String,
113
+ discount_codes: Schema.Array(Schema.Struct({ code: Schema.String })),
114
+ discount_applications: Schema.Array(RestDiscountApplicationSchema),
115
+ subtotal_price_set: nullableMoney,
116
+ current_subtotal_price_set: RestMoneyBagSchema,
117
+ total_discounts_set: nullableMoney,
118
+ current_total_discounts_set: RestMoneyBagSchema,
119
+ total_shipping_price_set: RestMoneyBagSchema,
120
+ current_shipping_price_set: RestMoneyBagSchema,
121
+ total_tax_set: nullableMoney,
122
+ current_total_tax_set: RestMoneyBagSchema,
123
+ original_total_duties_set: nullableMoney,
124
+ current_total_duties_set: nullableMoney,
125
+ total_price_set: RestMoneyBagSchema,
126
+ current_total_price_set: RestMoneyBagSchema,
127
+ tax_lines: Schema.Array(RestTaxLineSchema),
128
+ shipping_lines: Schema.Array(RestShippingLineSchema),
129
+ line_items: Schema.Array(RestLineItemSchema),
130
+ });
131
+
132
+ export const OrderDeleteWebhookPayloadSchema = Schema.Struct({ id: Schema.Number });
133
+
134
+ export const OrderEventSchema = Schema.Union([
135
+ Schema.Struct({
136
+ _tag: Schema.Literal("upsert"),
137
+ payload: Schema.toType(OrderWebhookPayloadSchema),
138
+ }),
139
+ Schema.Struct({
140
+ _tag: Schema.Literal("delete"),
141
+ id: Schema.String,
142
+ version: Schema.Date,
143
+ }),
144
+ ]);
145
+
146
+ export type OrderWebhookPayload = Schema.Schema.Type<typeof OrderWebhookPayloadSchema>;
147
+
148
+ const orMoney = (bag: Schema.Schema.Type<typeof RestMoneyBagSchema> | null) =>
149
+ bag === null ? null : fromRest.money(bag);
150
+
151
+ export const fromOrderWebhook = (payload: OrderWebhookPayload): Order => ({
152
+ id: payload.admin_graphql_api_id,
153
+ legacyResourceId: String(payload.id),
154
+ name: payload.name,
155
+ email: emptyText(payload.email),
156
+ phone: emptyText(payload.phone),
157
+ customerId: payload.customer?.admin_graphql_api_id ?? "",
158
+ customerLocale: emptyText(payload.customer_locale),
159
+ customerAcceptsMarketing: payload.buyer_accepts_marketing,
160
+ billingAddress: payload.billing_address ? fromRest.address(payload.billing_address) : null,
161
+ shippingAddress: payload.shipping_address ? fromRest.address(payload.shipping_address) : null,
162
+ note: emptyText(payload.note),
163
+ customAttributes: payload.note_attributes.map(fromRest.attribute),
164
+ tags: splitTags(payload.tags),
165
+ confirmationNumber: emptyText(payload.confirmation_number),
166
+ poNumber: emptyText(payload.po_number),
167
+ sourceName: emptyText(payload.source_name),
168
+ sourceIdentifier: emptyText(payload.source_identifier),
169
+ appId: payload.app_id === null ? "" : gid("App", payload.app_id),
170
+ paymentGatewayNames: payload.payment_gateway_names,
171
+ test: payload.test,
172
+ taxesIncluded: payload.taxes_included,
173
+ taxExempt: payload.tax_exempt,
174
+ dutiesIncluded: payload.duties_included,
175
+ estimatedTaxes: payload.estimated_taxes,
176
+ totalWeight: payload.total_weight,
177
+ displayFinancialStatus: payload.financial_status,
178
+ cancelReason: payload.cancel_reason,
179
+ cancelledAt: payload.cancelled_at,
180
+ closedAt: payload.closed_at,
181
+ processedAt: payload.processed_at,
182
+ createdAt: payload.created_at,
183
+ updatedAt: payload.updated_at,
184
+ currencyCode: payload.currency,
185
+ presentmentCurrencyCode: payload.presentment_currency,
186
+ discountCodes: payload.discount_codes.map((discount) => discount.code),
187
+ discountApplications: payload.discount_applications.map(
188
+ (application, index): DiscountApplication => ({
189
+ index,
190
+ type: application.type,
191
+ code: application.type === "DISCOUNT_CODE" ? (application.code ?? null) : null,
192
+ title: application.type === "DISCOUNT_CODE" ? null : (application.title ?? null),
193
+ description: application.type === "MANUAL" ? (application.description ?? null) : null,
194
+ allocationMethod: application.allocation_method,
195
+ targetSelection: application.target_selection,
196
+ targetType: application.target_type,
197
+ valueType: application.value_type,
198
+ value: normalizeAmount(application.value),
199
+ }),
200
+ ),
201
+ subtotalPriceSet: orMoney(payload.subtotal_price_set),
202
+ currentSubtotalPriceSet: fromRest.money(payload.current_subtotal_price_set),
203
+ totalDiscountsSet: orMoney(payload.total_discounts_set),
204
+ currentTotalDiscountsSet: fromRest.money(payload.current_total_discounts_set),
205
+ totalShippingPriceSet: fromRest.money(payload.total_shipping_price_set),
206
+ currentShippingPriceSet: fromRest.money(payload.current_shipping_price_set),
207
+ totalTaxSet: orMoney(payload.total_tax_set),
208
+ currentTotalTaxSet: fromRest.money(payload.current_total_tax_set),
209
+ originalTotalDutiesSet: orMoney(payload.original_total_duties_set),
210
+ currentTotalDutiesSet: orMoney(payload.current_total_duties_set),
211
+ totalPriceSet: fromRest.money(payload.total_price_set),
212
+ currentTotalPriceSet: fromRest.money(payload.current_total_price_set),
213
+ taxLines: payload.tax_lines.map(fromRest.taxLine),
214
+ shippingLines: payload.shipping_lines.map((line) => ({
215
+ id: line.id === null ? null : gid("ShippingLine", line.id),
216
+ title: line.title,
217
+ code: line.code,
218
+ source: line.source,
219
+ carrierIdentifier: line.carrier_identifier,
220
+ phone: line.phone,
221
+ isRemoved: line.is_removed,
222
+ originalPriceSet: fromRest.money(line.price_set),
223
+ discountedPriceSet: fromRest.money(line.discounted_price_set),
224
+ currentDiscountedPriceSet: fromRest.money(line.current_discounted_price_set),
225
+ taxLines: line.tax_lines.map(fromRest.taxLine),
226
+ discountAllocations: line.discount_allocations.map(fromRest.discountAllocation),
227
+ })),
228
+ lineItems: payload.line_items.map((item) => ({
229
+ id: item.admin_graphql_api_id,
230
+ name: item.name,
231
+ title: item.title,
232
+ variantTitle: item.variant_title,
233
+ sku: item.sku,
234
+ vendor: item.vendor,
235
+ productId: item.product_id === null ? null : gid("Product", item.product_id),
236
+ variantId: item.variant_id === null ? null : gid("ProductVariant", item.variant_id),
237
+ quantity: item.quantity,
238
+ currentQuantity: item.current_quantity,
239
+ taxable: item.taxable,
240
+ requiresShipping: item.requires_shipping,
241
+ isGiftCard: item.gift_card,
242
+ originalUnitPriceSet: fromRest.money(item.price_set),
243
+ totalDiscountSet: fromRest.money(item.total_discount_set),
244
+ taxLines: item.tax_lines.map(fromRest.taxLine),
245
+ discountAllocations: item.discount_allocations.map(fromRest.discountAllocation),
246
+ customAttributes: item.properties.map(fromRest.attribute),
247
+ })),
248
+ });
@@ -0,0 +1,142 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ import { connection, pageQuery } from "../shared";
4
+ import {
5
+ type Product,
6
+ ProductOptionSchema,
7
+ ProductStatusSchema,
8
+ ProductVariantInventoryPolicySchema,
9
+ } from "./row";
10
+
11
+ const ProductVariantFields = `
12
+ id
13
+ legacyResourceId
14
+ title
15
+ sku
16
+ barcode
17
+ price
18
+ compareAtPrice
19
+ inventoryPolicy
20
+ taxable
21
+ createdAt
22
+ updatedAt
23
+ `;
24
+
25
+ const ProductFields = `
26
+ id
27
+ legacyResourceId
28
+ title
29
+ handle
30
+ descriptionHtml
31
+ productType
32
+ vendor
33
+ status
34
+ tags
35
+ createdAt
36
+ updatedAt
37
+ publishedAt
38
+ templateSuffix
39
+ featuredMedia { ... on MediaImage { image { url altText } } }
40
+ options(first: 100) { id name position values }
41
+ variants(first: 25) { nodes { ${ProductVariantFields} } pageInfo { hasNextPage endCursor } }
42
+ `;
43
+
44
+ export const ProductByIdQuery = `#graphql
45
+ query AirfoilProductById($id: ID!) {
46
+ product(id: $id) { ${ProductFields} }
47
+ }
48
+ `;
49
+
50
+ export const ProductVariantsQuery = `#graphql
51
+ query AirfoilProductVariants($id: ID!, $first: Int!, $after: String) {
52
+ node: product(id: $id) {
53
+ connection: variants(first: $first, after: $after) {
54
+ nodes { ${ProductVariantFields} }
55
+ pageInfo { hasNextPage endCursor }
56
+ }
57
+ }
58
+ }
59
+ `;
60
+
61
+ export const ProductsQuery = pageQuery("AirfoilProducts", "products", ProductFields);
62
+
63
+ export const GraphQLProductVariantNodeSchema = Schema.Struct({
64
+ id: Schema.String,
65
+ legacyResourceId: Schema.String,
66
+ title: Schema.String,
67
+ sku: Schema.NullOr(Schema.String),
68
+ barcode: Schema.NullOr(Schema.String),
69
+ price: Schema.String,
70
+ compareAtPrice: Schema.NullOr(Schema.String),
71
+ inventoryPolicy: ProductVariantInventoryPolicySchema,
72
+ taxable: Schema.Boolean,
73
+ createdAt: Schema.DateFromString,
74
+ updatedAt: Schema.DateFromString,
75
+ });
76
+
77
+ const GraphQLFeaturedMediaSchema = Schema.Struct({
78
+ image: Schema.optional(
79
+ Schema.NullOr(
80
+ Schema.Struct({
81
+ url: Schema.String,
82
+ altText: Schema.NullOr(Schema.String),
83
+ }),
84
+ ),
85
+ ),
86
+ });
87
+
88
+ const GraphQLProductNodeSchema = Schema.Struct({
89
+ id: Schema.String,
90
+ legacyResourceId: Schema.String,
91
+ title: Schema.String,
92
+ handle: Schema.String,
93
+ descriptionHtml: Schema.String,
94
+ productType: Schema.String,
95
+ vendor: Schema.String,
96
+ status: ProductStatusSchema,
97
+ tags: Schema.Array(Schema.String),
98
+ createdAt: Schema.DateFromString,
99
+ updatedAt: Schema.DateFromString,
100
+ publishedAt: Schema.NullOr(Schema.DateFromString),
101
+ templateSuffix: Schema.NullOr(Schema.String),
102
+ featuredMedia: Schema.NullOr(GraphQLFeaturedMediaSchema),
103
+ options: Schema.Array(ProductOptionSchema),
104
+ variants: connection(GraphQLProductVariantNodeSchema),
105
+ });
106
+
107
+ export const GraphQLProductsDataSchema = Schema.Struct({
108
+ products: connection(GraphQLProductNodeSchema),
109
+ });
110
+
111
+ export const GraphQLProductByIdDataSchema = Schema.Struct({
112
+ product: Schema.NullOr(GraphQLProductNodeSchema),
113
+ });
114
+
115
+ export type GraphQLProductNode = Schema.Schema.Type<typeof GraphQLProductNodeSchema>;
116
+
117
+ type GraphQLProductVariantNode = Schema.Schema.Type<typeof GraphQLProductVariantNodeSchema>;
118
+
119
+ export const normalizeProductNode = (
120
+ node: GraphQLProductNode,
121
+ variants: ReadonlyArray<GraphQLProductVariantNode>,
122
+ ): Product => ({
123
+ id: node.id,
124
+ legacyResourceId: node.legacyResourceId,
125
+ title: node.title,
126
+ handle: node.handle,
127
+ descriptionHtml: node.descriptionHtml,
128
+ productType: node.productType,
129
+ vendor: node.vendor,
130
+ status: node.status,
131
+ tags: node.tags,
132
+ createdAt: node.createdAt,
133
+ updatedAt: node.updatedAt,
134
+ publishedAt: node.publishedAt,
135
+ templateSuffix: node.templateSuffix ?? "",
136
+ featuredMedia:
137
+ node.featuredMedia === null || node.featuredMedia.image === undefined
138
+ ? null
139
+ : { image: node.featuredMedia.image },
140
+ options: node.options,
141
+ variants,
142
+ });
@@ -0,0 +1,92 @@
1
+ import { Iceberg } from "@useairfoil/connector-kit";
2
+ import * as Schema from "effect/Schema";
3
+
4
+ import { field } from "../shared";
5
+
6
+ export const ProductStatusSchema = Schema.Literals(["ACTIVE", "ARCHIVED", "DRAFT", "UNLISTED"]);
7
+
8
+ export const ProductVariantInventoryPolicySchema = Schema.Literals(["CONTINUE", "DENY"]);
9
+
10
+ const ProductImageSchema = Schema.Struct({
11
+ url: Schema.String.pipe(field(202, "URL of the image.")),
12
+ altText: Schema.NullOr(Schema.String).pipe(field(203, "Alternative text for the image.")),
13
+ });
14
+
15
+ export const ProductFeaturedMediaSchema = Schema.Struct({
16
+ image: Schema.NullOr(ProductImageSchema).pipe(field(201, "Image for the featured media.")),
17
+ });
18
+
19
+ export const ProductOptionSchema = Schema.Struct({
20
+ id: Schema.String.pipe(field(302, "Unique product option identifier.")),
21
+ name: Schema.String.pipe(field(303, "Name of the product option.")),
22
+ position: Schema.Int.pipe(field(304, "Position of the option on the product.")),
23
+ values: Schema.Array(Schema.String.annotate({ fieldId: 306 })).pipe(
24
+ field(305, "Values available for the option."),
25
+ ),
26
+ });
27
+
28
+ export const ProductVariantSchema = Schema.Struct({
29
+ id: Schema.String.pipe(field(402, "Unique product variant identifier.")),
30
+ legacyResourceId: Schema.String.pipe(field(403, "Numeric variant identifier stored as text.")),
31
+ title: Schema.String.pipe(field(404, "Display title of the variant.")),
32
+ sku: Schema.NullOr(Schema.String).pipe(field(405, "Stock keeping unit for the variant.")),
33
+ barcode: Schema.NullOr(Schema.String).pipe(field(406, "Barcode for the variant.")),
34
+ price: Schema.String.pipe(field(407, "Price of the variant.")),
35
+ compareAtPrice: Schema.NullOr(Schema.String).pipe(
36
+ field(408, "Original price used for comparison."),
37
+ ),
38
+ inventoryPolicy: ProductVariantInventoryPolicySchema.pipe(
39
+ field(409, "Behavior when the variant is out of stock."),
40
+ ),
41
+ taxable: Schema.Boolean.pipe(field(410, "Whether the variant is taxable.")),
42
+ createdAt: Schema.Date.pipe(field(411, "Time when the variant was created.")),
43
+ updatedAt: Schema.Date.pipe(field(412, "Time when the variant was last changed.")),
44
+ });
45
+
46
+ export const ProductSchema = Schema.Struct({
47
+ id: Schema.String.pipe(field(1, "Unique product identifier.")),
48
+ legacyResourceId: Schema.String.pipe(field(2, "Numeric product identifier stored as text.")),
49
+ title: Schema.String.pipe(field(3, "Title of the product.")),
50
+ handle: Schema.String.pipe(field(4, "URL handle for the product.")),
51
+ descriptionHtml: Schema.String.pipe(field(5, "Product description as HTML.")),
52
+ productType: Schema.String.pipe(field(6, "Merchant-defined product type.")),
53
+ vendor: Schema.String.pipe(field(7, "Vendor of the product.")),
54
+ status: ProductStatusSchema.pipe(field(8, "Current product status.")),
55
+ tags: Schema.Array(Schema.String.annotate({ fieldId: 101 })).pipe(
56
+ field(9, "Tags added to the product."),
57
+ ),
58
+ createdAt: Schema.Date.pipe(field(10, "Time when the product was created.")),
59
+ updatedAt: Schema.Date.pipe(field(11, "Time when the product was last changed.")),
60
+ publishedAt: Schema.NullOr(Schema.Date).pipe(field(12, "Time when the product was published.")),
61
+ templateSuffix: Schema.NullOr(Schema.String).pipe(
62
+ field(13, "Theme template suffix used by the product."),
63
+ ),
64
+ featuredMedia: Schema.NullOr(ProductFeaturedMediaSchema).pipe(
65
+ field(14, "Featured media for the product."),
66
+ ),
67
+ options: Schema.Array(ProductOptionSchema.annotate({ fieldId: 301 })).pipe(
68
+ field(15, "Options for the product."),
69
+ ),
70
+ variants: Schema.Array(ProductVariantSchema.annotate({ fieldId: 401 })).pipe(
71
+ field(16, "Variants of the product."),
72
+ ),
73
+ _deleted: Schema.optional(Schema.Boolean).pipe(field(17, "Whether the product was deleted.")),
74
+ })
75
+ .annotate({
76
+ description: "Products in a Shopify store.",
77
+ })
78
+ .pipe(Iceberg.identifier("id"));
79
+
80
+ export type ProductStatus = Schema.Schema.Type<typeof ProductStatusSchema>;
81
+
82
+ export type ProductVariantInventoryPolicy = Schema.Schema.Type<
83
+ typeof ProductVariantInventoryPolicySchema
84
+ >;
85
+
86
+ export type ProductFeaturedMedia = Schema.Schema.Type<typeof ProductFeaturedMediaSchema>;
87
+
88
+ export type ProductOption = Schema.Schema.Type<typeof ProductOptionSchema>;
89
+
90
+ export type ProductVariant = Schema.Schema.Type<typeof ProductVariantSchema>;
91
+
92
+ export type Product = Schema.Schema.Type<typeof ProductSchema>;
@@ -0,0 +1,130 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ import { gid, splitTags, upper } from "../shared";
4
+ import {
5
+ type Product,
6
+ type ProductFeaturedMedia,
7
+ type ProductOption,
8
+ ProductStatusSchema,
9
+ type ProductVariant,
10
+ ProductVariantInventoryPolicySchema,
11
+ } from "./row";
12
+
13
+ export const ProductWebhookImageSchema = Schema.Struct({
14
+ src: Schema.String,
15
+ alt: Schema.NullOr(Schema.String),
16
+ });
17
+
18
+ export const ProductWebhookOptionSchema = Schema.Struct({
19
+ id: Schema.Number,
20
+ name: Schema.String,
21
+ position: Schema.Int,
22
+ values: Schema.Array(Schema.String),
23
+ });
24
+
25
+ export const ProductWebhookVariantSchema = Schema.Struct({
26
+ admin_graphql_api_id: Schema.String,
27
+ id: Schema.Number,
28
+ title: Schema.String,
29
+ price: Schema.String,
30
+ inventory_policy: upper(ProductVariantInventoryPolicySchema),
31
+ compare_at_price: Schema.NullOr(Schema.String),
32
+ created_at: Schema.DateFromString,
33
+ updated_at: Schema.DateFromString,
34
+ taxable: Schema.Boolean,
35
+ barcode: Schema.NullOr(Schema.String),
36
+ sku: Schema.NullOr(Schema.String),
37
+ });
38
+
39
+ export const ProductWebhookVariantGidSchema = Schema.Struct({
40
+ admin_graphql_api_id: Schema.String,
41
+ updated_at: Schema.DateFromString,
42
+ });
43
+
44
+ export const ProductWebhookPayloadSchema = Schema.Struct({
45
+ id: Schema.Number,
46
+ admin_graphql_api_id: Schema.String,
47
+ body_html: Schema.NullOr(Schema.String),
48
+ created_at: Schema.DateFromString,
49
+ handle: Schema.String,
50
+ image: Schema.optional(Schema.NullOr(ProductWebhookImageSchema)),
51
+ options: Schema.Array(ProductWebhookOptionSchema),
52
+ product_type: Schema.String,
53
+ published_at: Schema.NullOr(Schema.DateFromString),
54
+ status: upper(ProductStatusSchema),
55
+ tags: Schema.String,
56
+ template_suffix: Schema.NullOr(Schema.String),
57
+ title: Schema.String,
58
+ updated_at: Schema.DateFromString,
59
+ variants: Schema.Array(ProductWebhookVariantSchema),
60
+ variant_gids: Schema.Array(ProductWebhookVariantGidSchema),
61
+ vendor: Schema.String,
62
+ });
63
+
64
+ export const ProductDeleteWebhookPayloadSchema = Schema.Struct({ id: Schema.Number });
65
+
66
+ export const ProductEventSchema = Schema.Union([
67
+ Schema.Struct({
68
+ _tag: Schema.Literal("upsert"),
69
+ payload: Schema.toType(ProductWebhookPayloadSchema),
70
+ triggeredAt: Schema.Date,
71
+ }),
72
+ Schema.Struct({
73
+ _tag: Schema.Literal("delete"),
74
+ id: Schema.String,
75
+ version: Schema.Date,
76
+ }),
77
+ ]);
78
+
79
+ export type ProductWebhookImage = Schema.Schema.Type<typeof ProductWebhookImageSchema>;
80
+
81
+ export type ProductWebhookOption = Schema.Schema.Type<typeof ProductWebhookOptionSchema>;
82
+
83
+ export type ProductWebhookVariant = Schema.Schema.Type<typeof ProductWebhookVariantSchema>;
84
+
85
+ export type ProductWebhookPayload = Schema.Schema.Type<typeof ProductWebhookPayloadSchema>;
86
+
87
+ const normalizeProductOption = (option: ProductWebhookOption): ProductOption => ({
88
+ id: gid("ProductOption", option.id),
89
+ name: option.name,
90
+ position: option.position,
91
+ values: option.values,
92
+ });
93
+
94
+ const normalizeProductVariant = (variant: ProductWebhookVariant): ProductVariant => ({
95
+ id: variant.admin_graphql_api_id,
96
+ legacyResourceId: String(variant.id),
97
+ title: variant.title,
98
+ sku: variant.sku,
99
+ barcode: variant.barcode,
100
+ price: variant.price,
101
+ compareAtPrice: variant.compare_at_price,
102
+ inventoryPolicy: variant.inventory_policy,
103
+ taxable: variant.taxable,
104
+ createdAt: variant.created_at,
105
+ updatedAt: variant.updated_at,
106
+ });
107
+
108
+ const normalizeProductFeaturedMedia = (
109
+ image: ProductWebhookImage | null | undefined,
110
+ ): ProductFeaturedMedia | null =>
111
+ image === null || image === undefined ? null : { image: { url: image.src, altText: image.alt } };
112
+
113
+ export const fromProductWebhook = (payload: ProductWebhookPayload): Product => ({
114
+ id: payload.admin_graphql_api_id,
115
+ legacyResourceId: String(payload.id),
116
+ title: payload.title,
117
+ handle: payload.handle,
118
+ descriptionHtml: payload.body_html ?? "",
119
+ productType: payload.product_type,
120
+ vendor: payload.vendor,
121
+ status: payload.status,
122
+ tags: splitTags(payload.tags),
123
+ createdAt: payload.created_at,
124
+ updatedAt: payload.updated_at,
125
+ publishedAt: payload.published_at,
126
+ templateSuffix: payload.template_suffix ?? "",
127
+ featuredMedia: normalizeProductFeaturedMedia(payload.image),
128
+ options: payload.options.map(normalizeProductOption),
129
+ variants: payload.variants.map(normalizeProductVariant),
130
+ });