@useairfoil/producer-shopify 0.2.0 → 0.2.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/LICENSE.txt +201 -0
- package/README.md +85 -248
- package/dist/index.d.mts +6993 -0
- package/dist/index.mjs +3 -0
- package/dist/main.d.mts +1 -0
- package/dist/main.mjs +700 -0
- package/dist/main.mjs.map +1 -0
- package/dist/manifest.d.mts +32 -0
- package/dist/manifest.mjs +51 -0
- package/dist/manifest.mjs.map +1 -0
- package/dist/{chunk-Bp6m_JJh.js → rolldown-runtime-D7D4PA-g.mjs} +4 -4
- package/dist/src-YBU_KSf5.mjs +2472 -0
- package/dist/src-YBU_KSf5.mjs.map +1 -0
- package/package.json +27 -27
- package/src/{auth.ts → api/auth.ts} +15 -35
- package/src/api/client.ts +429 -0
- package/src/api/graphql.ts +221 -0
- package/src/api/http.ts +25 -0
- package/src/connector.ts +266 -163
- package/src/create-table.ts +10 -0
- package/src/demo/customers.ts +412 -0
- package/src/demo/index.ts +31 -0
- package/src/demo/setup.ts +63 -0
- package/src/demo/shared.ts +114 -0
- package/src/index.ts +35 -21
- package/src/main.ts +14 -8
- package/src/manifest.ts +4 -55
- package/src/resources/carts/row.ts +82 -0
- package/src/resources/carts/webhook.ts +98 -0
- package/src/resources/customers/graphql.ts +175 -0
- package/src/resources/customers/row.ts +140 -0
- package/src/resources/customers/webhook.ts +30 -0
- package/src/resources/orders/graphql.ts +419 -0
- package/src/resources/orders/row.ts +306 -0
- package/src/resources/orders/webhook.ts +248 -0
- package/src/resources/products/graphql.ts +142 -0
- package/src/resources/products/row.ts +122 -0
- package/src/resources/products/webhook.ts +130 -0
- package/src/resources/refunds/graphql.ts +196 -0
- package/src/resources/refunds/row.ts +137 -0
- package/src/resources/refunds/webhook.ts +123 -0
- package/src/resources/shared.ts +300 -0
- package/src/sandbox.ts +3 -3
- package/src/start.ts +4 -6
- package/src/tables.ts +15 -0
- package/dist/index.d.ts +0 -965
- package/dist/index.js +0 -4
- package/dist/main.d.ts +0 -1
- package/dist/main.js +0 -49
- package/dist/main.js.map +0 -1
- package/dist/manifest-BFfx2LkS.js +0 -91
- package/dist/manifest-BFfx2LkS.js.map +0 -1
- package/dist/manifest-CPOEExQU.d.ts +0 -34
- package/dist/manifest.d.ts +0 -2
- package/dist/manifest.js +0 -3
- package/dist/src-DqmiLrYS.js +0 -984
- package/dist/src-DqmiLrYS.js.map +0 -1
- package/src/api.ts +0 -636
- package/src/schemas.ts +0 -319
- package/src/throttle.ts +0 -89
- package/src/transient-retry.ts +0 -74
package/src/connector.ts
CHANGED
|
@@ -6,229 +6,332 @@ import {
|
|
|
6
6
|
Resource,
|
|
7
7
|
Webhook,
|
|
8
8
|
} from "@useairfoil/connector-kit";
|
|
9
|
-
import { Config, Context, Effect, Layer, Redacted, Schema } from "effect";
|
|
10
|
-
import { HttpServerResponse } from "effect/
|
|
9
|
+
import { Config, Context, Effect, Layer, Option, Redacted, Schema } from "effect";
|
|
10
|
+
import { HttpServerResponse } from "effect/http";
|
|
11
11
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
12
12
|
|
|
13
13
|
import type { ShopifyConfig } from "./manifest";
|
|
14
14
|
|
|
15
|
-
import * as
|
|
16
|
-
import * as
|
|
15
|
+
import * as ShopifyAuth from "./api/auth";
|
|
16
|
+
import * as ShopifyApiClient from "./api/client";
|
|
17
|
+
import { CartSchema } from "./resources/carts/row";
|
|
17
18
|
import {
|
|
18
|
-
|
|
19
|
+
CartWebhookEventSchema,
|
|
19
20
|
CartWebhookPayloadSchema,
|
|
21
|
+
fromCartWebhook,
|
|
22
|
+
} from "./resources/carts/webhook";
|
|
23
|
+
import { CustomerSchema } from "./resources/customers/row";
|
|
24
|
+
import {
|
|
25
|
+
CustomerDeleteWebhookPayloadSchema,
|
|
26
|
+
CustomerEmailConsentPayloadSchema,
|
|
27
|
+
CustomerEventSchema,
|
|
28
|
+
CustomerPurchaseSummaryPayloadSchema,
|
|
29
|
+
CustomerSmsConsentPayloadSchema,
|
|
30
|
+
CustomerTagsPayloadSchema,
|
|
31
|
+
CustomerWebhookPayloadSchema,
|
|
32
|
+
} from "./resources/customers/webhook";
|
|
33
|
+
import { OrderSchema } from "./resources/orders/row";
|
|
34
|
+
import {
|
|
35
|
+
fromOrderWebhook,
|
|
36
|
+
OrderDeleteWebhookPayloadSchema,
|
|
37
|
+
OrderEventSchema,
|
|
38
|
+
OrderWebhookPayloadSchema,
|
|
39
|
+
} from "./resources/orders/webhook";
|
|
40
|
+
import { ProductSchema } from "./resources/products/row";
|
|
41
|
+
import {
|
|
42
|
+
fromProductWebhook,
|
|
20
43
|
ProductDeleteWebhookPayloadSchema,
|
|
21
44
|
ProductEventSchema,
|
|
22
|
-
ProductSchema,
|
|
23
45
|
ProductWebhookPayloadSchema,
|
|
24
|
-
|
|
25
|
-
} from "./
|
|
46
|
+
} from "./resources/products/webhook";
|
|
47
|
+
import { RefundSchema } from "./resources/refunds/row";
|
|
48
|
+
import {
|
|
49
|
+
fromRefundWebhook,
|
|
50
|
+
RefundEventSchema,
|
|
51
|
+
RefundWebhookPayloadSchema,
|
|
52
|
+
} from "./resources/refunds/webhook";
|
|
53
|
+
import { gid } from "./resources/shared";
|
|
26
54
|
export { manifest, ShopifyConfigDef } from "./manifest";
|
|
27
55
|
export type { ShopifyConfig } from "./manifest";
|
|
28
56
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const provided = Buffer.from(options.signature, "base64");
|
|
43
|
-
if (provided.length !== digest.length || !timingSafeEqual(digest, provided)) {
|
|
44
|
-
throw new Error("Invalid Shopify webhook signature");
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
catch: (cause) =>
|
|
48
|
-
new ConnectorError({
|
|
49
|
-
message: "Shopify webhook verification failed",
|
|
50
|
-
cause,
|
|
51
|
-
}),
|
|
52
|
-
});
|
|
57
|
+
const isValidWebhookSignature = (
|
|
58
|
+
rawBody: Uint8Array,
|
|
59
|
+
signature: string | undefined,
|
|
60
|
+
secret: string,
|
|
61
|
+
): boolean => {
|
|
62
|
+
if (!signature) return false;
|
|
63
|
+
const digest = createHmac("sha256", secret).update(Buffer.from(rawBody)).digest();
|
|
64
|
+
const provided = Buffer.from(signature, "base64");
|
|
65
|
+
return provided.length === digest.length && timingSafeEqual(digest, provided);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const decodeTriggeredAt = (value: string | undefined) =>
|
|
69
|
+
Option.getOrNull(Schema.decodeUnknownOption(Schema.DateFromString)(value));
|
|
53
70
|
|
|
54
|
-
const
|
|
71
|
+
const backfillPages = <A extends object>(
|
|
72
|
+
fetchPage: (options: {
|
|
73
|
+
readonly first: number;
|
|
74
|
+
readonly after?: string;
|
|
75
|
+
}) => Effect.Effect<ShopifyApiClient.ShopifyPage<A>, ConnectorError>,
|
|
76
|
+
) =>
|
|
77
|
+
Fetch.page({
|
|
78
|
+
pageCursor: Cursor.string(),
|
|
79
|
+
cutoff: Cursor.isoDateTime(),
|
|
80
|
+
fetch: ({ pageCursor }) =>
|
|
81
|
+
fetchPage({
|
|
82
|
+
first: 50,
|
|
83
|
+
after: typeof pageCursor === "string" ? pageCursor : undefined,
|
|
84
|
+
}).pipe(
|
|
85
|
+
Effect.map((page) => ({
|
|
86
|
+
rows: page.items,
|
|
87
|
+
nextPageCursor: page.endCursor ?? undefined,
|
|
88
|
+
hasMore: page.hasMore,
|
|
89
|
+
})),
|
|
90
|
+
),
|
|
91
|
+
});
|
|
55
92
|
|
|
56
93
|
export const make = Effect.fnUntraced(function* (config: ShopifyConfig) {
|
|
57
94
|
const api = yield* ShopifyApiClient.ShopifyApiClient;
|
|
58
95
|
|
|
59
96
|
const Products = Resource.entity({
|
|
60
97
|
name: "products",
|
|
61
|
-
|
|
62
|
-
key: "id",
|
|
98
|
+
rowSchema: ProductSchema,
|
|
63
99
|
version: "updatedAt",
|
|
100
|
+
|
|
64
101
|
check: api.checkProductsAccess,
|
|
65
|
-
backfill:
|
|
66
|
-
|
|
67
|
-
cutoff: Cursor.isoDateTime(),
|
|
68
|
-
fetch: ({ pageCursor, cutoff }) =>
|
|
69
|
-
api
|
|
70
|
-
.fetchProducts({
|
|
71
|
-
first: 50,
|
|
72
|
-
after: typeof pageCursor === "string" ? pageCursor : undefined,
|
|
73
|
-
})
|
|
74
|
-
.pipe(
|
|
75
|
-
Effect.map((page) => ({
|
|
76
|
-
mutations: page.items
|
|
77
|
-
.filter((row) => Date.parse(row.updatedAt) <= Date.parse(String(cutoff)))
|
|
78
|
-
.map(Resource.upsert),
|
|
79
|
-
nextPageCursor: page.endCursor ?? undefined,
|
|
80
|
-
hasMore: page.hasMore,
|
|
81
|
-
})),
|
|
82
|
-
),
|
|
83
|
-
}),
|
|
84
|
-
webhook: Resource.webhook({
|
|
102
|
+
backfill: backfillPages(api.fetchProducts),
|
|
103
|
+
webhook: {
|
|
85
104
|
schema: ProductEventSchema,
|
|
86
105
|
handler: ({ payload }) => {
|
|
87
106
|
if (payload._tag === "delete") {
|
|
88
107
|
return Effect.succeed([
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
{
|
|
109
|
+
id: payload.id,
|
|
110
|
+
updatedAt: payload.version,
|
|
111
|
+
_deleted: true,
|
|
112
|
+
},
|
|
93
113
|
]);
|
|
94
114
|
}
|
|
115
|
+
|
|
95
116
|
const product = payload.payload;
|
|
96
|
-
if (
|
|
97
|
-
product
|
|
98
|
-
product.variants.length === product.variant_gids.length
|
|
99
|
-
) {
|
|
100
|
-
return Effect.succeed([
|
|
101
|
-
Resource.upsert(
|
|
102
|
-
ShopifyNormalize.productWebhook({ ...product, created_at: product.created_at }),
|
|
103
|
-
),
|
|
104
|
-
]);
|
|
117
|
+
if (product.variants.length === product.variant_gids.length) {
|
|
118
|
+
return Effect.succeed([fromProductWebhook(product)]);
|
|
105
119
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
|
|
121
|
+
// Refetch when Shopify cuts off variants.
|
|
122
|
+
const triggeredAt = payload.triggeredAt;
|
|
123
|
+
return Effect.gen(function* () {
|
|
124
|
+
const refetched = yield* api.fetchProductById(product.admin_graphql_api_id);
|
|
125
|
+
if (Option.isSome(refetched)) {
|
|
126
|
+
return [refetched.value];
|
|
127
|
+
}
|
|
128
|
+
// A deleted product has no updatedAt, so use the webhook time.
|
|
129
|
+
return [{ id: product.admin_graphql_api_id, updatedAt: triggeredAt, _deleted: true }];
|
|
130
|
+
});
|
|
110
131
|
},
|
|
111
|
-
}
|
|
132
|
+
},
|
|
112
133
|
});
|
|
113
134
|
|
|
114
|
-
const
|
|
115
|
-
name: "
|
|
116
|
-
|
|
117
|
-
key: "id",
|
|
135
|
+
const Customers = Resource.entity({
|
|
136
|
+
name: "customers",
|
|
137
|
+
rowSchema: CustomerSchema,
|
|
118
138
|
version: "updatedAt",
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
139
|
+
|
|
140
|
+
check: api.checkCustomersAccess,
|
|
141
|
+
backfill: backfillPages(api.fetchCustomers),
|
|
142
|
+
webhook: {
|
|
143
|
+
schema: CustomerEventSchema,
|
|
144
|
+
handler: ({ payload: { _tag, id, version } }) =>
|
|
145
|
+
Effect.gen(function* () {
|
|
146
|
+
// No customer means it was deleted. Its delete webhook can be missed.
|
|
147
|
+
const customer = _tag === "refresh" ? yield* api.fetchCustomerById(id) : Option.none();
|
|
148
|
+
return Option.isNone(customer)
|
|
149
|
+
? [{ id, updatedAt: version, _deleted: true }]
|
|
150
|
+
: [customer.value];
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const Orders = Resource.entity({
|
|
156
|
+
name: "orders",
|
|
157
|
+
rowSchema: OrderSchema,
|
|
158
|
+
version: "updatedAt",
|
|
159
|
+
|
|
160
|
+
check: api.checkOrdersAccess,
|
|
161
|
+
backfill: backfillPages(api.fetchOrders),
|
|
162
|
+
webhook: {
|
|
163
|
+
schema: OrderEventSchema,
|
|
164
|
+
// Do not refetch. Shopify hides older orders without read_all_orders.
|
|
125
165
|
handler: ({ payload }) =>
|
|
126
|
-
Effect.succeed(
|
|
127
|
-
|
|
166
|
+
Effect.succeed(
|
|
167
|
+
payload._tag === "delete"
|
|
168
|
+
? [{ id: payload.id, updatedAt: payload.version, _deleted: true }]
|
|
169
|
+
: [fromOrderWebhook(payload.payload)],
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const Refunds = Resource.entity({
|
|
175
|
+
name: "refunds",
|
|
176
|
+
rowSchema: RefundSchema,
|
|
177
|
+
// Refund webhooks have no updated_at. A later settlement with the same
|
|
178
|
+
// processedAt may not replace the first row.
|
|
179
|
+
version: "processedAt",
|
|
180
|
+
|
|
181
|
+
check: api.checkOrdersAccess,
|
|
182
|
+
backfill: backfillPages(api.fetchRefunds),
|
|
183
|
+
webhook: {
|
|
184
|
+
schema: RefundEventSchema,
|
|
185
|
+
handler: ({ payload }) => Effect.succeed([fromRefundWebhook(payload)]),
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const Carts = Resource.entity({
|
|
190
|
+
name: "carts",
|
|
191
|
+
rowSchema: CartSchema,
|
|
192
|
+
version: "updatedAt",
|
|
193
|
+
|
|
194
|
+
check: api.checkConnection,
|
|
195
|
+
webhook: {
|
|
196
|
+
schema: CartWebhookEventSchema,
|
|
197
|
+
handler: ({ payload }) => Effect.succeed([fromCartWebhook(payload)]),
|
|
198
|
+
},
|
|
128
199
|
});
|
|
129
200
|
|
|
130
201
|
const webhookRoute = Webhook.route({
|
|
131
202
|
path: "/webhooks/shopify",
|
|
132
|
-
ackMode: "after-
|
|
203
|
+
ackMode: "after-ingest",
|
|
133
204
|
schema: Schema.Unknown,
|
|
134
205
|
handler: ({ request, rawBody, payload: json, to }) =>
|
|
135
206
|
Effect.gen(function* () {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
signature: request.headers["x-shopify-hmac-sha256"] ?? null,
|
|
139
|
-
secret: Redacted.value(config.webhookSecret),
|
|
140
|
-
}).pipe(Effect.match({ onFailure: (error) => error, onSuccess: () => undefined }));
|
|
141
|
-
if (verificationError) {
|
|
207
|
+
const signature = request.headers["x-shopify-hmac-sha256"];
|
|
208
|
+
if (!isValidWebhookSignature(rawBody, signature, Redacted.value(config.webhookSecret))) {
|
|
142
209
|
return HttpServerResponse.jsonUnsafe(
|
|
143
|
-
{ ok: false, error:
|
|
210
|
+
{ ok: false, error: "Shopify webhook verification failed" },
|
|
144
211
|
{ status: 401 },
|
|
145
212
|
);
|
|
146
213
|
}
|
|
147
214
|
|
|
148
215
|
const topic = request.headers["x-shopify-topic"] ?? "";
|
|
216
|
+
const decode = <A>(schema: Schema.Decoder<A>) => Schema.decodeUnknownOption(schema)(json);
|
|
217
|
+
const invalid = HttpServerResponse.jsonUnsafe(
|
|
218
|
+
{ ok: false, error: `Invalid Shopify webhook for ${topic}` },
|
|
219
|
+
{ status: 400 },
|
|
220
|
+
);
|
|
221
|
+
// Shopify sends it on every webhook.
|
|
222
|
+
const triggeredAt = decodeTriggeredAt(request.headers["x-shopify-triggered-at"]);
|
|
223
|
+
if (triggeredAt === null) return invalid;
|
|
224
|
+
|
|
149
225
|
switch (topic) {
|
|
150
226
|
case "products/create":
|
|
151
227
|
case "products/update": {
|
|
152
|
-
const payload =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
new ConnectorError({
|
|
156
|
-
message: `Invalid Shopify webhook payload for ${topic}`,
|
|
157
|
-
cause,
|
|
158
|
-
}),
|
|
159
|
-
),
|
|
160
|
-
Effect.match({
|
|
161
|
-
onFailure: (error) => ({ _tag: "Error" as const, error }),
|
|
162
|
-
onSuccess: (value) => ({ _tag: "Success" as const, value }),
|
|
163
|
-
}),
|
|
164
|
-
);
|
|
165
|
-
if (payload._tag === "Error") {
|
|
166
|
-
return HttpServerResponse.jsonUnsafe(
|
|
167
|
-
{ ok: false, error: payload.error.message },
|
|
168
|
-
{ status: 400 },
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
yield* to(Products, { _tag: "upsert", payload: payload.value });
|
|
228
|
+
const payload = decode(ProductWebhookPayloadSchema);
|
|
229
|
+
if (Option.isNone(payload)) return invalid;
|
|
230
|
+
yield* to(Products, { _tag: "upsert", payload: payload.value, triggeredAt });
|
|
172
231
|
break;
|
|
173
232
|
}
|
|
174
233
|
case "products/delete": {
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
return HttpServerResponse.jsonUnsafe(
|
|
178
|
-
{ ok: false, error: "Missing x-shopify-triggered-at header" },
|
|
179
|
-
{ status: 400 },
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
const payload = yield* decodeWebhookPayload(ProductDeleteWebhookPayloadSchema)(
|
|
183
|
-
json,
|
|
184
|
-
).pipe(
|
|
185
|
-
Effect.mapError(
|
|
186
|
-
(cause) =>
|
|
187
|
-
new ConnectorError({
|
|
188
|
-
message: "Invalid Shopify webhook payload for products/delete",
|
|
189
|
-
cause,
|
|
190
|
-
}),
|
|
191
|
-
),
|
|
192
|
-
Effect.match({
|
|
193
|
-
onFailure: (error) => ({ _tag: "Error" as const, error }),
|
|
194
|
-
onSuccess: (value) => ({ _tag: "Success" as const, value }),
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
if (payload._tag === "Error") {
|
|
198
|
-
return HttpServerResponse.jsonUnsafe(
|
|
199
|
-
{ ok: false, error: payload.error.message },
|
|
200
|
-
{ status: 400 },
|
|
201
|
-
);
|
|
202
|
-
}
|
|
234
|
+
const payload = decode(ProductDeleteWebhookPayloadSchema);
|
|
235
|
+
if (Option.isNone(payload)) return invalid;
|
|
203
236
|
yield* to(Products, {
|
|
204
237
|
_tag: "delete",
|
|
205
|
-
id:
|
|
238
|
+
id: gid("Product", payload.value.id),
|
|
206
239
|
version: triggeredAt,
|
|
207
240
|
});
|
|
208
241
|
break;
|
|
209
242
|
}
|
|
210
243
|
case "carts/create":
|
|
211
244
|
case "carts/update": {
|
|
212
|
-
const payload =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
245
|
+
const payload = decode(CartWebhookPayloadSchema);
|
|
246
|
+
if (Option.isNone(payload)) return invalid;
|
|
247
|
+
yield* to(Carts, { ...payload.value, topic });
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "customers/create":
|
|
251
|
+
case "customers/disable":
|
|
252
|
+
case "customers/enable":
|
|
253
|
+
case "customers/update": {
|
|
254
|
+
const payload = decode(CustomerWebhookPayloadSchema);
|
|
255
|
+
if (Option.isNone(payload)) return invalid;
|
|
256
|
+
yield* to(Customers, {
|
|
257
|
+
_tag: "refresh",
|
|
258
|
+
id: payload.value.admin_graphql_api_id,
|
|
259
|
+
version: payload.value.updated_at,
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case "customers/purchasing_summary": {
|
|
264
|
+
const payload = decode(CustomerPurchaseSummaryPayloadSchema);
|
|
265
|
+
if (Option.isNone(payload)) return invalid;
|
|
266
|
+
yield* to(Customers, {
|
|
267
|
+
_tag: "refresh",
|
|
268
|
+
id: payload.value.customerId,
|
|
269
|
+
version: payload.value.occurredAt,
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "customers_email_marketing_consent/update": {
|
|
274
|
+
const payload = decode(CustomerEmailConsentPayloadSchema);
|
|
275
|
+
if (Option.isNone(payload)) return invalid;
|
|
276
|
+
yield* to(Customers, {
|
|
277
|
+
_tag: "refresh",
|
|
278
|
+
id: gid("Customer", payload.value.customer_id),
|
|
279
|
+
version: triggeredAt,
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "customers_marketing_consent/update": {
|
|
284
|
+
const payload = decode(CustomerSmsConsentPayloadSchema);
|
|
285
|
+
if (Option.isNone(payload)) return invalid;
|
|
286
|
+
yield* to(Customers, {
|
|
287
|
+
_tag: "refresh",
|
|
288
|
+
id: gid("Customer", payload.value.id),
|
|
289
|
+
version: triggeredAt,
|
|
290
|
+
});
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case "customer.tags_added":
|
|
294
|
+
case "customer.tags_removed": {
|
|
295
|
+
const payload = decode(CustomerTagsPayloadSchema);
|
|
296
|
+
if (Option.isNone(payload)) return invalid;
|
|
297
|
+
yield* to(Customers, {
|
|
298
|
+
_tag: "refresh",
|
|
299
|
+
id: payload.value.customerId,
|
|
300
|
+
version: payload.value.occurredAt,
|
|
301
|
+
});
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "customers/delete": {
|
|
305
|
+
const payload = decode(CustomerDeleteWebhookPayloadSchema);
|
|
306
|
+
if (Option.isNone(payload)) return invalid;
|
|
307
|
+
yield* to(Customers, {
|
|
308
|
+
_tag: "delete",
|
|
309
|
+
id: gid("Customer", payload.value.id),
|
|
310
|
+
version: triggeredAt,
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case "orders/create":
|
|
315
|
+
case "orders/updated": {
|
|
316
|
+
const payload = decode(OrderWebhookPayloadSchema);
|
|
317
|
+
if (Option.isNone(payload)) return invalid;
|
|
318
|
+
yield* to(Orders, { _tag: "upsert", payload: payload.value });
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "orders/delete": {
|
|
322
|
+
const payload = decode(OrderDeleteWebhookPayloadSchema);
|
|
323
|
+
if (Option.isNone(payload)) return invalid;
|
|
324
|
+
yield* to(Orders, {
|
|
325
|
+
_tag: "delete",
|
|
326
|
+
id: gid("Order", payload.value.id),
|
|
327
|
+
version: triggeredAt,
|
|
328
|
+
});
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case "refunds/create": {
|
|
332
|
+
const payload = decode(RefundWebhookPayloadSchema);
|
|
333
|
+
if (Option.isNone(payload)) return invalid;
|
|
334
|
+
yield* to(Refunds, payload.value);
|
|
232
335
|
break;
|
|
233
336
|
}
|
|
234
337
|
default:
|
|
@@ -244,7 +347,7 @@ export const make = Effect.fnUntraced(function* (config: ShopifyConfig) {
|
|
|
244
347
|
return Connector.define({
|
|
245
348
|
name: "producer-shopify",
|
|
246
349
|
title: "Shopify",
|
|
247
|
-
resources: [Products,
|
|
350
|
+
resources: [Products, Carts, Customers, Orders, Refunds],
|
|
248
351
|
webhooks: [webhookRoute],
|
|
249
352
|
});
|
|
250
353
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConnectorApp, RuntimeConfig } from "@useairfoil/connector-kit";
|
|
2
|
+
import { Command } from "effect/cli";
|
|
3
|
+
|
|
4
|
+
import { tableSchemas } from "./tables";
|
|
5
|
+
|
|
6
|
+
export const createTableCommand = ConnectorApp.makeCreateTableCommand(tableSchemas);
|
|
7
|
+
|
|
8
|
+
export const setupCommand = ConnectorApp.makeSetupCommand(tableSchemas).pipe(
|
|
9
|
+
Command.provide(RuntimeConfig.layerHosted()),
|
|
10
|
+
);
|