@unisian/shopify-admin 0.0.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-token-rejected.d.mts +10 -0
- package/dist/access-token-rejected.mjs +12 -0
- package/dist/admin-api.d.mts +45 -0
- package/dist/admin-api.mjs +103 -0
- package/dist/admin-bulk-query-registry.d.mts +26 -0
- package/dist/admin-bulk-query-registry.mjs +8 -0
- package/dist/admin-operation-registry.d.mts +11 -0
- package/dist/admin-operation-registry.mjs +8 -0
- package/dist/admin-query-failed.d.mts +11 -0
- package/dist/admin-query-failed.mjs +13 -0
- package/dist/bulk/bulk-operation-refused.d.mts +15 -0
- package/dist/bulk/bulk-operation-refused.mjs +18 -0
- package/dist/bulk/bulk-operations-finish.d.mts +13 -0
- package/dist/bulk/bulk-operations-finish.mjs +26 -0
- package/dist/bulk/bulk-result-failed.d.mts +10 -0
- package/dist/bulk/bulk-result-failed.mjs +12 -0
- package/dist/bulk/bulk-results.mjs +131 -0
- package/dist/bulk/bulk-transport.d.mts +33 -0
- package/dist/bulk/bulk-transport.mjs +118 -0
- package/dist/bulk/bulk-upload-failed.d.mts +11 -0
- package/dist/bulk/bulk-upload-failed.mjs +13 -0
- package/dist/bulk/read-only-shopify-bulk.d.mts +26 -0
- package/dist/bulk/read-only-shopify-bulk.mjs +23 -0
- package/dist/bulk/shopify-bulk.d.mts +47 -0
- package/dist/bulk/shopify-bulk.mjs +123 -0
- package/dist/bulk.d.mts +8 -0
- package/dist/bulk.mjs +7 -0
- package/dist/delegate-token-refused.d.mts +10 -0
- package/dist/delegate-token-refused.mjs +12 -0
- package/dist/delegate-token.d.mts +17 -0
- package/dist/delegate-token.mjs +45 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +11 -0
- package/dist/mutation-refused.d.mts +10 -0
- package/dist/mutation-refused.mjs +12 -0
- package/dist/operations.d.mts +12 -0
- package/dist/operations.mjs +1 -0
- package/dist/read-only-admin.d.mts +49 -0
- package/dist/read-only-admin.mjs +60 -0
- package/dist/shopify-layer.d.mts +11 -0
- package/dist/shopify-layer.mjs +8 -0
- package/dist/testing.d.mts +29 -0
- package/dist/testing.mjs +52 -0
- package/package.json +35 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/access-token-rejected.d.ts
|
|
3
|
+
declare const AccessTokenRejected_base: Schema.Class<AccessTokenRejected, Schema.TaggedStruct<"AccessTokenRejected", {
|
|
4
|
+
readonly operation: Schema.String;
|
|
5
|
+
readonly shop: Schema.String;
|
|
6
|
+
}>, import("effect/Cause").YieldableError>;
|
|
7
|
+
export declare class AccessTokenRejected extends AccessTokenRejected_base {
|
|
8
|
+
get message(): string;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/access-token-rejected.ts
|
|
3
|
+
var AccessTokenRejected = class extends Schema.TaggedError()("AccessTokenRejected", {
|
|
4
|
+
operation: Schema.String,
|
|
5
|
+
shop: Schema.String
|
|
6
|
+
}) {
|
|
7
|
+
get message() {
|
|
8
|
+
return `Admin API rejected the access token for ${this.shop} (HTTP 401 on ${this.operation})`;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { AccessTokenRejected };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AccessTokenRejected } from "./access-token-rejected.mjs";
|
|
2
|
+
import { AdminOperationRegistry } from "./admin-operation-registry.mjs";
|
|
3
|
+
import { AdminQueryFailed } from "./admin-query-failed.mjs";
|
|
4
|
+
import { AdminDocument, AdminOperations } from "./operations.mjs";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Context from "effect/Context";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import * as Redacted from "effect/Redacted";
|
|
10
|
+
import { HttpClient } from "effect/http";
|
|
11
|
+
import { AuthenticatedShop, ExchangeError, RefreshError, SessionOwner, ShopifyApp, ShopifyAuth } from "@unisian/shopify-auth";
|
|
12
|
+
import { VariablesArgsFor } from "@unisian/shopify-auth/kernel";
|
|
13
|
+
import { ShopDomain } from "@unisian/shopify-sessions";
|
|
14
|
+
//#region src/admin-api.d.ts
|
|
15
|
+
export interface AdminCredential {
|
|
16
|
+
readonly accessToken: Redacted.Redacted;
|
|
17
|
+
readonly shop: ShopDomain;
|
|
18
|
+
}
|
|
19
|
+
export interface AdminDocumentResult {
|
|
20
|
+
readonly data: Schema.Json | null;
|
|
21
|
+
readonly errors: readonly Schema.Json[];
|
|
22
|
+
readonly extensions: Schema.Json | null;
|
|
23
|
+
readonly status: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const sendAdminDocument: (apiVersion: string, credential: AdminCredential, document: string, variables: Readonly<Record<string, Schema.Json>>) => Effect.Effect<AdminDocumentResult, AdminQueryFailed | AccessTokenRejected, HttpClient.HttpClient>;
|
|
26
|
+
export type VariablesArgs<Q extends AdminDocument> = VariablesArgsFor<AdminOperations[Q]["variables"]>;
|
|
27
|
+
export type AdminError = AdminQueryFailed | AccessTokenRejected | ExchangeError | RefreshError;
|
|
28
|
+
declare const ShopifyAdmin_base: Context.ServiceClass<ShopifyAdmin, "@unisian/shopify-admin/ShopifyAdmin", {
|
|
29
|
+
readonly graphql: <const Q extends AdminDocument>(document: Q, ...variables: VariablesArgs<Q>) => Effect.Effect<AdminOperations[Q]["data"], AdminError>;
|
|
30
|
+
}>;
|
|
31
|
+
export declare class ShopifyAdmin extends ShopifyAdmin_base {
|
|
32
|
+
static readonly make: (authenticated: AuthenticatedShop) => Effect.Effect<{
|
|
33
|
+
readonly graphql: <const Q extends AdminDocument>(document: Q, ...variables: VariablesArgs<Q>) => Effect.Effect<AdminOperations[Q]["data"], AdminError>;
|
|
34
|
+
}, never, AdminOperationRegistry | HttpClient.HttpClient | ShopifyApp | ShopifyAuth>;
|
|
35
|
+
static readonly layer: (authenticated: AuthenticatedShop) => Layer.Layer<ShopifyAdmin, never, AdminOperationRegistry | HttpClient.HttpClient | ShopifyApp | ShopifyAuth>;
|
|
36
|
+
static readonly makeForShop: (shop: ShopDomain) => Effect.Effect<{
|
|
37
|
+
readonly graphql: <const Q extends AdminDocument>(document: Q, ...variables: VariablesArgs<Q>) => Effect.Effect<AdminOperations[Q]["data"], AdminError>;
|
|
38
|
+
}, never, AdminOperationRegistry | HttpClient.HttpClient | SessionOwner | ShopifyApp>;
|
|
39
|
+
static readonly layerForShop: (shop: ShopDomain) => Layer.Layer<ShopifyAdmin, never, AdminOperationRegistry | HttpClient.HttpClient | SessionOwner | ShopifyApp>;
|
|
40
|
+
static readonly makeForToken: (credential: AdminCredential, apiVersion: string) => Effect.Effect<{
|
|
41
|
+
readonly graphql: <const Q extends AdminDocument>(document: Q, ...variables: VariablesArgs<Q>) => Effect.Effect<AdminOperations[Q]["data"], AdminError>;
|
|
42
|
+
}, never, AdminOperationRegistry | HttpClient.HttpClient>;
|
|
43
|
+
static readonly layerForToken: (credential: AdminCredential, apiVersion: string) => Layer.Layer<ShopifyAdmin, never, AdminOperationRegistry | HttpClient.HttpClient>;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { AccessTokenRejected } from "./access-token-rejected.mjs";
|
|
2
|
+
import { AdminOperationRegistry } from "./admin-operation-registry.mjs";
|
|
3
|
+
import { AdminQueryFailed } from "./admin-query-failed.mjs";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import * as Context from "effect/Context";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Layer from "effect/Layer";
|
|
8
|
+
import * as Redacted from "effect/Redacted";
|
|
9
|
+
import * as Ref from "effect/Ref";
|
|
10
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
|
|
11
|
+
import { SessionOwner, ShopifyApp, ShopifyAuth, withoutRequest } from "@unisian/shopify-auth";
|
|
12
|
+
import { logRenewal, operationBody, operationData, operationFor } from "@unisian/shopify-auth/kernel";
|
|
13
|
+
//#region src/admin-api.ts
|
|
14
|
+
const adminRequest = (apiVersion, credential, body) => HttpClientRequest.post(`https://${credential.shop}/admin/api/${apiVersion}/graphql.json`).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeader("X-Shopify-Access-Token", Redacted.value(credential.accessToken)), HttpClientRequest.bodyJsonUnsafe(body));
|
|
15
|
+
const AdminDocumentBody = Schema.Struct({
|
|
16
|
+
data: Schema.optional(Schema.NullOr(Schema.Json)),
|
|
17
|
+
errors: Schema.optional(Schema.Array(Schema.Json)),
|
|
18
|
+
extensions: Schema.optional(Schema.Json)
|
|
19
|
+
});
|
|
20
|
+
const sendAdminDocument = (apiVersion, credential, document, variables) => Effect.gen(function* sendAdminDocumentGen() {
|
|
21
|
+
const operation = "an ad hoc document";
|
|
22
|
+
const failed = (cause) => new AdminQueryFailed({
|
|
23
|
+
cause: withoutRequest(cause),
|
|
24
|
+
operation,
|
|
25
|
+
shop: credential.shop
|
|
26
|
+
});
|
|
27
|
+
const response = yield* (yield* HttpClient.HttpClient).execute(adminRequest(apiVersion, credential, {
|
|
28
|
+
query: document,
|
|
29
|
+
variables
|
|
30
|
+
})).pipe(Effect.mapError(failed));
|
|
31
|
+
if (response.status === 401) return yield* new AccessTokenRejected({
|
|
32
|
+
operation,
|
|
33
|
+
shop: credential.shop
|
|
34
|
+
});
|
|
35
|
+
const body = yield* HttpClientResponse.schemaBodyJson(AdminDocumentBody)(response).pipe(Effect.mapError(failed));
|
|
36
|
+
return {
|
|
37
|
+
data: body.data ?? null,
|
|
38
|
+
errors: body.errors ?? [],
|
|
39
|
+
extensions: body.extensions ?? null,
|
|
40
|
+
status: response.status
|
|
41
|
+
};
|
|
42
|
+
}).pipe(Effect.withSpan("sendAdminDocument"));
|
|
43
|
+
const adminQuery = (apiVersion, session, operation, variables) => Effect.gen(function* sendAdminQuery() {
|
|
44
|
+
const failed = (cause) => new AdminQueryFailed({
|
|
45
|
+
cause: withoutRequest(cause),
|
|
46
|
+
operation: operation.name,
|
|
47
|
+
shop: session.shop
|
|
48
|
+
});
|
|
49
|
+
const client = yield* HttpClient.HttpClient;
|
|
50
|
+
const body = yield* operationBody(operation, variables, failed);
|
|
51
|
+
const response = yield* client.execute(adminRequest(apiVersion, session, body)).pipe(Effect.mapError(failed));
|
|
52
|
+
if (response.status === 401) return yield* new AccessTokenRejected({
|
|
53
|
+
operation: operation.name,
|
|
54
|
+
shop: session.shop
|
|
55
|
+
});
|
|
56
|
+
return yield* operationData(operation.data, failed)(response);
|
|
57
|
+
}).pipe(Effect.withSpan("adminQuery", { attributes: { operation: operation.name } }));
|
|
58
|
+
const sendForShop = (shop) => Effect.gen(function* sendForShopGen() {
|
|
59
|
+
const { apiVersion } = yield* ShopifyApp;
|
|
60
|
+
const client = yield* HttpClient.HttpClient;
|
|
61
|
+
const owner = yield* SessionOwner;
|
|
62
|
+
const send = (operation, variables) => owner.stored(shop).pipe(Effect.tap(logRenewal), Effect.flatMap(({ session }) => adminQuery(apiVersion, session, operation, variables)), Effect.annotateLogs("shop", shop), Effect.provideService(HttpClient.HttpClient, client));
|
|
63
|
+
return send;
|
|
64
|
+
});
|
|
65
|
+
const sendForToken = (credential, apiVersion) => Effect.gen(function* sendForTokenGen() {
|
|
66
|
+
const client = yield* HttpClient.HttpClient;
|
|
67
|
+
const send = (operation, variables) => adminQuery(apiVersion, credential, operation, variables).pipe(Effect.annotateLogs("shop", credential.shop), Effect.provideService(HttpClient.HttpClient, client));
|
|
68
|
+
return send;
|
|
69
|
+
});
|
|
70
|
+
const graphqlOver = (registry, send) => (document, ...[variables]) => {
|
|
71
|
+
return operationFor(registry, document).pipe(Effect.flatMap((operation) => send(operation, variables ?? {})));
|
|
72
|
+
};
|
|
73
|
+
var ShopifyAdmin = class ShopifyAdmin extends Context.Service()("@unisian/shopify-admin/ShopifyAdmin") {
|
|
74
|
+
static make = (authenticated) => Effect.gen(function* make() {
|
|
75
|
+
const { apiVersion } = yield* ShopifyApp;
|
|
76
|
+
const registry = yield* AdminOperationRegistry;
|
|
77
|
+
const client = yield* HttpClient.HttpClient;
|
|
78
|
+
const auth = yield* ShopifyAuth;
|
|
79
|
+
const session = yield* Ref.make(authenticated.session);
|
|
80
|
+
const graphql = (document, ...[variables]) => {
|
|
81
|
+
return operationFor(registry, document).pipe(Effect.flatMap((operation) => {
|
|
82
|
+
const run = Ref.get(session).pipe(Effect.flatMap((current) => adminQuery(apiVersion, current, operation, variables ?? {})));
|
|
83
|
+
return run.pipe(Effect.catchTag("AccessTokenRejected", () => auth.reexchange(authenticated).pipe(Effect.flatMap((renewed) => Ref.set(session, renewed)), Effect.andThen(run))));
|
|
84
|
+
}), Effect.provideService(HttpClient.HttpClient, client));
|
|
85
|
+
};
|
|
86
|
+
return ShopifyAdmin.of({ graphql });
|
|
87
|
+
});
|
|
88
|
+
static layer = (authenticated) => Layer.effect(ShopifyAdmin, ShopifyAdmin.make(authenticated));
|
|
89
|
+
static makeForShop = (shop) => Effect.gen(function* makeForShop() {
|
|
90
|
+
const registry = yield* AdminOperationRegistry;
|
|
91
|
+
const send = yield* sendForShop(shop);
|
|
92
|
+
return ShopifyAdmin.of({ graphql: graphqlOver(registry, send) });
|
|
93
|
+
});
|
|
94
|
+
static layerForShop = (shop) => Layer.effect(ShopifyAdmin, ShopifyAdmin.makeForShop(shop));
|
|
95
|
+
static makeForToken = (credential, apiVersion) => Effect.gen(function* makeForToken() {
|
|
96
|
+
const registry = yield* AdminOperationRegistry;
|
|
97
|
+
const send = yield* sendForToken(credential, apiVersion);
|
|
98
|
+
return ShopifyAdmin.of({ graphql: graphqlOver(registry, send) });
|
|
99
|
+
});
|
|
100
|
+
static layerForToken = (credential, apiVersion) => Layer.effect(ShopifyAdmin, ShopifyAdmin.makeForToken(credential, apiVersion));
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { ShopifyAdmin, adminQuery, sendAdminDocument, sendForShop, sendForToken };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
//#region src/admin-bulk-query-registry.d.ts
|
|
5
|
+
export interface BulkLines {
|
|
6
|
+
readonly line: Schema.Codec<unknown, unknown>;
|
|
7
|
+
readonly connections: Readonly<Record<string, BulkConnection>>;
|
|
8
|
+
}
|
|
9
|
+
export interface BulkConnection extends BulkLines {
|
|
10
|
+
readonly path: readonly string[];
|
|
11
|
+
readonly types: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface AdminBulkQuery extends BulkLines {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly kind: "bulk";
|
|
16
|
+
readonly document: readonly string[];
|
|
17
|
+
readonly slots: readonly string[];
|
|
18
|
+
readonly enums: readonly string[];
|
|
19
|
+
readonly parameters: Schema.Codec<unknown, unknown>;
|
|
20
|
+
readonly node: Schema.Codec<unknown, unknown>;
|
|
21
|
+
}
|
|
22
|
+
declare const AdminBulkQueryRegistry_base: Context.ServiceClass<AdminBulkQueryRegistry, "@unisian/shopify-admin/AdminBulkQueryRegistry", Readonly<Record<string, AdminBulkQuery>>>;
|
|
23
|
+
export declare class AdminBulkQueryRegistry extends AdminBulkQueryRegistry_base {
|
|
24
|
+
static readonly layer: (queries: Readonly<Record<string, AdminBulkQuery>>) => Layer.Layer<AdminBulkQueryRegistry, never, never>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
//#region src/admin-bulk-query-registry.ts
|
|
4
|
+
var AdminBulkQueryRegistry = class AdminBulkQueryRegistry extends Context.Service()("@unisian/shopify-admin/AdminBulkQueryRegistry") {
|
|
5
|
+
static layer = (queries) => Layer.succeed(AdminBulkQueryRegistry, AdminBulkQueryRegistry.of(queries));
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { AdminBulkQueryRegistry };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import { GraphqlOperation } from "@unisian/shopify-auth/kernel";
|
|
5
|
+
//#region src/admin-operation-registry.d.ts
|
|
6
|
+
export type AdminOperation<Data extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>, Variables extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>> = GraphqlOperation<Data, Variables>;
|
|
7
|
+
declare const AdminOperationRegistry_base: Context.ServiceClass<AdminOperationRegistry, "@unisian/shopify-admin/AdminOperationRegistry", Readonly<Record<string, AdminOperation<Schema.Codec<unknown, unknown, never, never>, Schema.Codec<unknown, unknown, never, never>>>>>;
|
|
8
|
+
export declare class AdminOperationRegistry extends AdminOperationRegistry_base {
|
|
9
|
+
static readonly layer: (operations: Readonly<Record<string, AdminOperation>>) => Layer.Layer<AdminOperationRegistry, never, never>;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
//#region src/admin-operation-registry.ts
|
|
4
|
+
var AdminOperationRegistry = class AdminOperationRegistry extends Context.Service()("@unisian/shopify-admin/AdminOperationRegistry") {
|
|
5
|
+
static layer = (operations) => Layer.succeed(AdminOperationRegistry, AdminOperationRegistry.of(operations));
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { AdminOperationRegistry };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/admin-query-failed.d.ts
|
|
3
|
+
declare const AdminQueryFailed_base: Schema.Class<AdminQueryFailed, Schema.TaggedStruct<"AdminQueryFailed", {
|
|
4
|
+
readonly cause: Schema.Defect;
|
|
5
|
+
readonly operation: Schema.String;
|
|
6
|
+
readonly shop: Schema.String;
|
|
7
|
+
}>, import("effect/Cause").YieldableError>;
|
|
8
|
+
export declare class AdminQueryFailed extends AdminQueryFailed_base {
|
|
9
|
+
get message(): string;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/admin-query-failed.ts
|
|
3
|
+
var AdminQueryFailed = class extends Schema.TaggedError()("AdminQueryFailed", {
|
|
4
|
+
cause: Schema.Defect(),
|
|
5
|
+
operation: Schema.String,
|
|
6
|
+
shop: Schema.String
|
|
7
|
+
}) {
|
|
8
|
+
get message() {
|
|
9
|
+
return `Admin API ${this.operation} failed for ${this.shop}`;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { AdminQueryFailed };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-operation-refused.d.ts
|
|
3
|
+
declare const BulkOperationRefused_base: Schema.Class<BulkOperationRefused, Schema.TaggedStruct<"BulkOperationRefused", {
|
|
4
|
+
readonly operation: Schema.String;
|
|
5
|
+
readonly shop: Schema.String;
|
|
6
|
+
readonly userErrors: Schema.$Array<Schema.Struct<{
|
|
7
|
+
readonly code: Schema.NullOr<Schema.String>;
|
|
8
|
+
readonly field: Schema.NullOr<Schema.$Array<Schema.String>>;
|
|
9
|
+
readonly message: Schema.String;
|
|
10
|
+
}>>;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
export declare class BulkOperationRefused extends BulkOperationRefused_base {
|
|
13
|
+
get message(): string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-operation-refused.ts
|
|
3
|
+
var BulkOperationRefused = class extends Schema.TaggedError()("BulkOperationRefused", {
|
|
4
|
+
operation: Schema.String,
|
|
5
|
+
shop: Schema.String,
|
|
6
|
+
userErrors: Schema.Array(Schema.Struct({
|
|
7
|
+
code: Schema.NullOr(Schema.String),
|
|
8
|
+
field: Schema.NullOr(Schema.Array(Schema.String)),
|
|
9
|
+
message: Schema.String
|
|
10
|
+
}))
|
|
11
|
+
}) {
|
|
12
|
+
get message() {
|
|
13
|
+
const reasons = this.userErrors.map((error) => error.message).join("; ");
|
|
14
|
+
return `Shopify refused ${this.operation} for ${this.shop}: ${reasons || "no bulk operation came back"}`;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { BulkOperationRefused };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-operations-finish.d.ts
|
|
3
|
+
export declare const BULK_OPERATIONS_FINISH_TOPIC = "bulk_operations/finish";
|
|
4
|
+
export declare const BulkOperationsFinish: Schema.Struct<{
|
|
5
|
+
readonly admin_graphql_api_id: Schema.String;
|
|
6
|
+
readonly completed_at: Schema.NullOr<Schema.String>;
|
|
7
|
+
readonly created_at: Schema.String;
|
|
8
|
+
readonly error_code: Schema.NullOr<Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["access_denied", "internal_server_error", "timeout"]>]>>;
|
|
9
|
+
readonly status: Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["canceled", "canceling", "completed", "created", "expired", "failed", "running"]>]>;
|
|
10
|
+
readonly type: Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["mutation", "query"]>]>;
|
|
11
|
+
}>;
|
|
12
|
+
export type BulkOperationsFinish = typeof BulkOperationsFinish.Type;
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-operations-finish.ts
|
|
3
|
+
const BULK_OPERATIONS_FINISH_TOPIC = "bulk_operations/finish";
|
|
4
|
+
const lowercase = (values) => Schema.Union([Schema.StringForLiteralAutocomplete, Schema.Literals(values)]);
|
|
5
|
+
const BulkOperationsFinish = Schema.Struct({
|
|
6
|
+
admin_graphql_api_id: Schema.String,
|
|
7
|
+
completed_at: Schema.NullOr(Schema.String),
|
|
8
|
+
created_at: Schema.String,
|
|
9
|
+
error_code: Schema.NullOr(lowercase([
|
|
10
|
+
"access_denied",
|
|
11
|
+
"internal_server_error",
|
|
12
|
+
"timeout"
|
|
13
|
+
])),
|
|
14
|
+
status: lowercase([
|
|
15
|
+
"canceled",
|
|
16
|
+
"canceling",
|
|
17
|
+
"completed",
|
|
18
|
+
"created",
|
|
19
|
+
"expired",
|
|
20
|
+
"failed",
|
|
21
|
+
"running"
|
|
22
|
+
]),
|
|
23
|
+
type: lowercase(["mutation", "query"])
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
26
|
+
export { BULK_OPERATIONS_FINISH_TOPIC, BulkOperationsFinish };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-result-failed.d.ts
|
|
3
|
+
declare const BulkResultFailed_base: Schema.Class<BulkResultFailed, Schema.TaggedStruct<"BulkResultFailed", {
|
|
4
|
+
readonly cause: Schema.Defect;
|
|
5
|
+
readonly operation: Schema.String;
|
|
6
|
+
}>, import("effect/Cause").YieldableError>;
|
|
7
|
+
export declare class BulkResultFailed extends BulkResultFailed_base {
|
|
8
|
+
get message(): string;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
//#region src/bulk/bulk-result-failed.ts
|
|
3
|
+
var BulkResultFailed = class extends Schema.TaggedError()("BulkResultFailed", {
|
|
4
|
+
cause: Schema.Defect(),
|
|
5
|
+
operation: Schema.String
|
|
6
|
+
}) {
|
|
7
|
+
get message() {
|
|
8
|
+
return `Reading the result file of bulk ${this.operation} failed`;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { BulkResultFailed };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { BulkResultFailed } from "./bulk-result-failed.mjs";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { HttpClientRequest, HttpClientResponse } from "effect/http";
|
|
5
|
+
import { withoutRequest } from "@unisian/shopify-auth";
|
|
6
|
+
import * as Stream from "effect/Stream";
|
|
7
|
+
import * as Predicate from "effect/Predicate";
|
|
8
|
+
//#region src/bulk/bulk-results.ts
|
|
9
|
+
const ResultLine = Schema.Record(Schema.String, Schema.Json);
|
|
10
|
+
const decodeObject = Schema.decodeUnknownEffect(Schema.fromJsonString(ResultLine));
|
|
11
|
+
const parentIdOf = (line) => Predicate.isString(line.__parentId) ? line.__parentId : void 0;
|
|
12
|
+
const connectionFor = (parent, typename) => Object.entries(parent.connections).find(([, connection]) => Predicate.isString(typename) && connection.types.includes(typename));
|
|
13
|
+
const holdsConnections = (position) => Object.keys(position.connections).length > 0;
|
|
14
|
+
const unplaced = (parentId, typename) => `a ${JSON.stringify(typename ?? null)} line names parent ${parentId}, which no earlier line holds under a connection for that type`;
|
|
15
|
+
const releasedEarly = (parentId, typename) => `${unplaced(parentId, typename)}; nodes folds a root once the next root starts, unless a connection on it holds a multiple of 250 children, which is the order Shopify writes without groupObjects but doesn't promise: submit with { groupObjects: true }, or read the file with lines`;
|
|
16
|
+
const isJsonObject = Schema.is(ResultLine);
|
|
17
|
+
const resultText = (client, url) => url === null ? Stream.empty : client.execute(HttpClientRequest.get(url)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), HttpClientResponse.stream, Stream.decodeText, Stream.splitLines, Stream.filter((line) => line.trim() !== ""));
|
|
18
|
+
const bulkLines = (query) => (text) => text.pipe(Stream.mapError((cause) => new BulkResultFailed({
|
|
19
|
+
cause: withoutRequest(cause),
|
|
20
|
+
operation: query.name
|
|
21
|
+
})), Stream.mapAccumEffect(() => /* @__PURE__ */ new Map(), (parents, raw) => Effect.gen(function* bulkLine() {
|
|
22
|
+
const line = yield* decodeObject(raw);
|
|
23
|
+
const parentId = parentIdOf(line);
|
|
24
|
+
const parent = parentId === void 0 ? void 0 : parents.get(parentId);
|
|
25
|
+
const position = parentId === void 0 ? query : parent && connectionFor(parent, line.__typename)?.[1];
|
|
26
|
+
if (position === void 0) return yield* Effect.fail(unplaced(parentId ?? "", line.__typename));
|
|
27
|
+
const decoded = yield* Schema.decodeEffect(position.line)(line);
|
|
28
|
+
if (holdsConnections(position) && Predicate.isString(line.id)) parents.set(line.id, position);
|
|
29
|
+
return [parents, [decoded]];
|
|
30
|
+
}).pipe(Effect.mapError((cause) => new BulkResultFailed({
|
|
31
|
+
cause,
|
|
32
|
+
operation: query.name
|
|
33
|
+
})))));
|
|
34
|
+
const childRunLength = 250;
|
|
35
|
+
const startFolding = (line, position) => ({
|
|
36
|
+
children: new Map(Object.keys(position.connections).map((key) => [key, []])),
|
|
37
|
+
line,
|
|
38
|
+
position
|
|
39
|
+
});
|
|
40
|
+
const mayContinueLater = (folding) => [...folding.children.values()].some((children) => children.length > 0 && children.length % childRunLength === 0 || children.some(mayContinueLater));
|
|
41
|
+
const forget = (byId, folding) => {
|
|
42
|
+
if (Predicate.isString(folding.line.id)) byId.delete(folding.line.id);
|
|
43
|
+
for (const children of folding.children.values()) for (const child of children) forget(byId, child);
|
|
44
|
+
};
|
|
45
|
+
const placeAt = (node, path, edges) => {
|
|
46
|
+
const [key, ...rest] = path;
|
|
47
|
+
if (key === void 0) return node;
|
|
48
|
+
if (rest.length === 0) return {
|
|
49
|
+
...node,
|
|
50
|
+
[key]: edges
|
|
51
|
+
};
|
|
52
|
+
const inner = node[key];
|
|
53
|
+
return isJsonObject(inner) ? {
|
|
54
|
+
...node,
|
|
55
|
+
[key]: placeAt(inner, rest, edges)
|
|
56
|
+
} : node;
|
|
57
|
+
};
|
|
58
|
+
const materialise = (folding) => {
|
|
59
|
+
let node = Object.fromEntries(Object.entries(folding.line).filter(([field]) => field !== "__parentId"));
|
|
60
|
+
for (const [key, children] of folding.children) node = placeAt(node, folding.position.connections[key]?.path ?? [key], { edges: children.map((child) => ({ node: materialise(child) })) });
|
|
61
|
+
return node;
|
|
62
|
+
};
|
|
63
|
+
const remember = (fold, folding) => {
|
|
64
|
+
if (holdsConnections(folding.position) && Predicate.isString(folding.line.id)) fold.byId.set(folding.line.id, folding);
|
|
65
|
+
};
|
|
66
|
+
const releaseCurrent = (fold) => {
|
|
67
|
+
const { current } = fold;
|
|
68
|
+
if (current === void 0) return [];
|
|
69
|
+
if (mayContinueLater(current)) {
|
|
70
|
+
fold.held.push(current);
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
forget(fold.byId, current);
|
|
74
|
+
return [materialise(current)];
|
|
75
|
+
};
|
|
76
|
+
const bulkNodes = (query) => (text) => text.pipe(Stream.mapError((cause) => new BulkResultFailed({
|
|
77
|
+
cause: withoutRequest(cause),
|
|
78
|
+
operation: query.name
|
|
79
|
+
})), Stream.mapAccumEffect(() => ({
|
|
80
|
+
byId: /* @__PURE__ */ new Map(),
|
|
81
|
+
current: void 0,
|
|
82
|
+
held: []
|
|
83
|
+
}), (fold, raw) => Effect.gen(function* foldLine() {
|
|
84
|
+
const line = yield* decodeObject(raw);
|
|
85
|
+
const parentId = parentIdOf(line);
|
|
86
|
+
if (parentId === void 0) {
|
|
87
|
+
const released = releaseCurrent(fold);
|
|
88
|
+
fold.current = startFolding(line, query);
|
|
89
|
+
remember(fold, fold.current);
|
|
90
|
+
return [fold, released];
|
|
91
|
+
}
|
|
92
|
+
const parent = fold.byId.get(parentId);
|
|
93
|
+
const connection = parent && connectionFor(parent.position, line.__typename);
|
|
94
|
+
if (parent === void 0 || connection === void 0) return yield* Effect.fail(releasedEarly(parentId, line.__typename));
|
|
95
|
+
const [key, position] = connection;
|
|
96
|
+
const child = startFolding(line, position);
|
|
97
|
+
parent.children.get(key)?.push(child);
|
|
98
|
+
remember(fold, child);
|
|
99
|
+
return [fold, []];
|
|
100
|
+
}).pipe(Effect.mapError((cause) => new BulkResultFailed({
|
|
101
|
+
cause,
|
|
102
|
+
operation: query.name
|
|
103
|
+
}))), { onHalt: (fold) => [...fold.held, ...fold.current === void 0 ? [] : [fold.current]].map(materialise) }), Stream.mapEffect((node) => Schema.decodeEffect(query.node)(node).pipe(Effect.mapError((cause) => new BulkResultFailed({
|
|
104
|
+
cause,
|
|
105
|
+
operation: query.name
|
|
106
|
+
})))));
|
|
107
|
+
const ResultError = Schema.Struct({ message: Schema.String });
|
|
108
|
+
const UnreachedLine = Schema.fromJsonString(Schema.Struct({
|
|
109
|
+
__lineNumber: Schema.optionalKey(Schema.Int),
|
|
110
|
+
data: Schema.Record(Schema.String, Schema.NullOr(Schema.Struct({ userErrors: Schema.Array(ResultError) })))
|
|
111
|
+
}));
|
|
112
|
+
const unreached = (text) => Schema.decodeEffect(UnreachedLine)(text).pipe(Effect.map(({ __lineNumber, data }) => ({
|
|
113
|
+
__lineNumber,
|
|
114
|
+
errors: Object.values(data).flatMap((payload) => payload?.userErrors ?? [])
|
|
115
|
+
})), Effect.filterOrFail(({ errors }) => errors.length > 0));
|
|
116
|
+
const bulkResults = (name, data) => (text) => {
|
|
117
|
+
const decodeLine = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Struct({
|
|
118
|
+
__lineNumber: Schema.optionalKey(Schema.Int),
|
|
119
|
+
data: Schema.optionalKey(Schema.NullOr(data)),
|
|
120
|
+
errors: Schema.optionalKey(Schema.Array(ResultError))
|
|
121
|
+
})));
|
|
122
|
+
return text.pipe(Stream.mapError((cause) => new BulkResultFailed({
|
|
123
|
+
cause: withoutRequest(cause),
|
|
124
|
+
operation: name
|
|
125
|
+
})), Stream.mapEffect((line) => decodeLine(line).pipe(Effect.catch((error) => unreached(line).pipe(Effect.mapError(() => error))), Effect.mapError((cause) => new BulkResultFailed({
|
|
126
|
+
cause,
|
|
127
|
+
operation: name
|
|
128
|
+
})))));
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { bulkLines, bulkNodes, bulkResults, resultText };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "../admin-bulk-query-registry.mjs";
|
|
2
|
+
import { AdminBulkQueries, AdminBulkQueryDocument } from "../operations.mjs";
|
|
3
|
+
import { AdminError } from "../admin-api.mjs";
|
|
4
|
+
import "./bulk-operation-refused.mjs";
|
|
5
|
+
import { BulkResultFailed } from "./bulk-result-failed.mjs";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { HttpClient } from "effect/http";
|
|
9
|
+
import * as Stream from "effect/Stream";
|
|
10
|
+
import { ShopDomain } from "@unisian/shopify-sessions";
|
|
11
|
+
//#region src/bulk/bulk-transport.d.ts
|
|
12
|
+
export declare const BulkOperation: Schema.Struct<{
|
|
13
|
+
readonly completedAt: Schema.NullOr<Schema.String>;
|
|
14
|
+
readonly createdAt: Schema.String;
|
|
15
|
+
readonly errorCode: Schema.NullOr<Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["ACCESS_DENIED", "INTERNAL_SERVER_ERROR", "TIMEOUT"]>]>>;
|
|
16
|
+
readonly id: Schema.String;
|
|
17
|
+
readonly objectCount: Schema.FiniteFromString;
|
|
18
|
+
readonly partialDataUrl: Schema.NullOr<Schema.String>;
|
|
19
|
+
readonly status: Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["CANCELED", "CANCELING", "COMPLETED", "CREATED", "EXPIRED", "FAILED", "RUNNING"]>]>;
|
|
20
|
+
readonly type: Schema.Union<readonly [Schema.StringForLiteralAutocomplete, Schema.Literals<readonly ["MUTATION", "QUERY"]>]>;
|
|
21
|
+
readonly url: Schema.NullOr<Schema.String>;
|
|
22
|
+
}>;
|
|
23
|
+
export type BulkOperation = typeof BulkOperation.Type;
|
|
24
|
+
export interface BulkQueryOptions {
|
|
25
|
+
readonly groupObjects?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type BulkQueryArgs<Q extends AdminBulkQueryDocument> = {} extends AdminBulkQueries[Q]["parameters"] ? [parameters?: AdminBulkQueries[Q]["parameters"], options?: BulkQueryOptions] : [parameters: AdminBulkQueries[Q]["parameters"], options?: BulkQueryOptions];
|
|
28
|
+
export interface BulkReads {
|
|
29
|
+
readonly operation: (id: string) => Effect.Effect<BulkOperation | null, AdminError>;
|
|
30
|
+
readonly lines: <const Q extends AdminBulkQueryDocument>(document: Q, url: string | null) => Stream.Stream<AdminBulkQueries[Q]["line"], BulkResultFailed>;
|
|
31
|
+
readonly nodes: <const Q extends AdminBulkQueryDocument>(document: Q, url: string | null) => Stream.Stream<AdminBulkQueries[Q]["node"], BulkResultFailed>;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { AdminQueryFailed } from "../admin-query-failed.mjs";
|
|
2
|
+
import { sendForShop, sendForToken } from "../admin-api.mjs";
|
|
3
|
+
import { AdminBulkQueryRegistry } from "../admin-bulk-query-registry.mjs";
|
|
4
|
+
import { BulkOperationRefused } from "./bulk-operation-refused.mjs";
|
|
5
|
+
import { bulkLines, bulkNodes, resultText } from "./bulk-results.mjs";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { HttpClient } from "effect/http";
|
|
9
|
+
import { withoutRequest } from "@unisian/shopify-auth";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import * as Predicate from "effect/Predicate";
|
|
12
|
+
//#region src/bulk/bulk-transport.ts
|
|
13
|
+
const shopifyEnum = (values) => Schema.Union([Schema.StringForLiteralAutocomplete, Schema.Literals(values)]);
|
|
14
|
+
const BulkOperation = Schema.Struct({
|
|
15
|
+
completedAt: Schema.NullOr(Schema.String),
|
|
16
|
+
createdAt: Schema.String,
|
|
17
|
+
errorCode: Schema.NullOr(shopifyEnum([
|
|
18
|
+
"ACCESS_DENIED",
|
|
19
|
+
"INTERNAL_SERVER_ERROR",
|
|
20
|
+
"TIMEOUT"
|
|
21
|
+
])),
|
|
22
|
+
id: Schema.String,
|
|
23
|
+
objectCount: Schema.FiniteFromString,
|
|
24
|
+
partialDataUrl: Schema.NullOr(Schema.String),
|
|
25
|
+
status: shopifyEnum([
|
|
26
|
+
"CANCELED",
|
|
27
|
+
"CANCELING",
|
|
28
|
+
"COMPLETED",
|
|
29
|
+
"CREATED",
|
|
30
|
+
"EXPIRED",
|
|
31
|
+
"FAILED",
|
|
32
|
+
"RUNNING"
|
|
33
|
+
]),
|
|
34
|
+
type: shopifyEnum(["MUTATION", "QUERY"]),
|
|
35
|
+
url: Schema.NullOr(Schema.String)
|
|
36
|
+
});
|
|
37
|
+
const bulkOperationFields = "id type status errorCode objectCount url partialDataUrl createdAt completedAt";
|
|
38
|
+
const UserErrors = Schema.Array(Schema.Struct({
|
|
39
|
+
code: Schema.NullOr(Schema.String),
|
|
40
|
+
field: Schema.NullOr(Schema.Array(Schema.String)),
|
|
41
|
+
message: Schema.String
|
|
42
|
+
}));
|
|
43
|
+
const Submitted = Schema.NullOr(Schema.Struct({
|
|
44
|
+
bulkOperation: Schema.NullOr(BulkOperation),
|
|
45
|
+
userErrors: UserErrors
|
|
46
|
+
}));
|
|
47
|
+
const RunQuery = {
|
|
48
|
+
data: Schema.Struct({ bulkOperationRunQuery: Submitted }),
|
|
49
|
+
document: `mutation UnisianBulkRunQuery($query: String!, $groupObjects: Boolean!) { bulkOperationRunQuery(query: $query, groupObjects: $groupObjects) { bulkOperation { ${bulkOperationFields} } userErrors { code field message } } }`,
|
|
50
|
+
kind: "mutation",
|
|
51
|
+
name: "UnisianBulkRunQuery",
|
|
52
|
+
variables: Schema.Struct({
|
|
53
|
+
groupObjects: Schema.Boolean,
|
|
54
|
+
query: Schema.String
|
|
55
|
+
})
|
|
56
|
+
};
|
|
57
|
+
const ReadOperation = {
|
|
58
|
+
data: Schema.Struct({ bulkOperation: Schema.NullOr(BulkOperation) }),
|
|
59
|
+
document: `query UnisianBulkOperation($id: ID!) { bulkOperation(id: $id) { ${bulkOperationFields} } }`,
|
|
60
|
+
kind: "query",
|
|
61
|
+
name: "UnisianBulkOperation",
|
|
62
|
+
variables: Schema.Struct({ id: Schema.String })
|
|
63
|
+
};
|
|
64
|
+
const enumValue = /^[_A-Za-z][_0-9A-Za-z]*$/u;
|
|
65
|
+
const EncodedParameters = Schema.Record(Schema.String, Schema.Json);
|
|
66
|
+
const isJsonList = Schema.is(Schema.Array(Schema.Json));
|
|
67
|
+
const literal = (value, bare) => {
|
|
68
|
+
if (isJsonList(value)) return `[${value.map((item) => literal(item, bare)).join(", ")}]`;
|
|
69
|
+
return bare && Predicate.isString(value) && enumValue.test(value) ? value : JSON.stringify(value ?? null);
|
|
70
|
+
};
|
|
71
|
+
const bulkQueryFor = (registry, document) => {
|
|
72
|
+
const query = registry[document];
|
|
73
|
+
return query ? Effect.succeed(query) : Effect.die(`No generated bulk query for this #graphql template; run pnpm codegen`);
|
|
74
|
+
};
|
|
75
|
+
const reads = ({ send, client, bulkQueries }) => ({
|
|
76
|
+
lines: (document, url) => {
|
|
77
|
+
return bulkQueryFor(bulkQueries, document).pipe(Effect.map((query) => resultText(client, url).pipe(bulkLines(query))), Stream.unwrap);
|
|
78
|
+
},
|
|
79
|
+
nodes: (document, url) => {
|
|
80
|
+
return bulkQueryFor(bulkQueries, document).pipe(Effect.map((query) => resultText(client, url).pipe(bulkNodes(query))), Stream.unwrap);
|
|
81
|
+
},
|
|
82
|
+
operation: (id) => send(ReadOperation, { id }).pipe(Effect.map(({ bulkOperation }) => bulkOperation))
|
|
83
|
+
});
|
|
84
|
+
const submitted = (operation, shop) => (payload) => payload?.bulkOperation && payload.userErrors.length === 0 ? Effect.succeed(payload.bulkOperation) : Effect.fail(new BulkOperationRefused({
|
|
85
|
+
operation,
|
|
86
|
+
shop,
|
|
87
|
+
userErrors: payload?.userErrors ?? []
|
|
88
|
+
}));
|
|
89
|
+
const renderBulkQuery = (query, parameters, shop) => Schema.encodeUnknownEffect(query.parameters)(parameters ?? {}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(EncodedParameters)), Effect.map((encoded) => query.document.map((part, index) => {
|
|
90
|
+
const slot = query.slots[index];
|
|
91
|
+
return slot === void 0 ? part : `${part}${literal(encoded[slot], query.enums.includes(slot))}`;
|
|
92
|
+
}).join("")), Effect.mapError((cause) => new AdminQueryFailed({
|
|
93
|
+
cause: withoutRequest(cause),
|
|
94
|
+
operation: query.name,
|
|
95
|
+
shop
|
|
96
|
+
})));
|
|
97
|
+
const submitQuery = ({ send, shop, bulkQueries }, guard) => (document, ...[parameters, options]) => bulkQueryFor(bulkQueries, document).pipe(Effect.tap((query) => Effect.annotateCurrentSpan("operation", query.name)), Effect.flatMap(guard), Effect.flatMap((query) => renderBulkQuery(query, parameters, shop)), Effect.flatMap((rendered) => send(RunQuery, {
|
|
98
|
+
groupObjects: options?.groupObjects ?? false,
|
|
99
|
+
query: rendered
|
|
100
|
+
})), Effect.flatMap(({ bulkOperationRunQuery }) => submitted("bulkOperationRunQuery", shop)(bulkOperationRunQuery)), Effect.withSpan("ShopifyBulk.query"));
|
|
101
|
+
const transportForShop = (shop) => Effect.gen(function* transportForShopGen() {
|
|
102
|
+
return {
|
|
103
|
+
bulkQueries: yield* AdminBulkQueryRegistry,
|
|
104
|
+
client: yield* HttpClient.HttpClient,
|
|
105
|
+
send: yield* sendForShop(shop),
|
|
106
|
+
shop
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
const transportForToken = (credential, apiVersion) => Effect.gen(function* transportForTokenGen() {
|
|
110
|
+
return {
|
|
111
|
+
bulkQueries: yield* AdminBulkQueryRegistry,
|
|
112
|
+
client: yield* HttpClient.HttpClient,
|
|
113
|
+
send: yield* sendForToken(credential, apiVersion),
|
|
114
|
+
shop: credential.shop
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
//#endregion
|
|
118
|
+
export { BulkOperation, Submitted, bulkOperationFields, reads, submitQuery, submitted, transportForShop, transportForToken };
|