@uniforge/platform-shopify 0.1.0-alpha.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.
Files changed (61) hide show
  1. package/dist/auth/index.d.cts +246 -0
  2. package/dist/auth/index.d.ts +246 -0
  3. package/dist/auth/index.js +623 -0
  4. package/dist/auth/index.js.map +1 -0
  5. package/dist/auth/index.mjs +586 -0
  6. package/dist/auth/index.mjs.map +1 -0
  7. package/dist/billing/index.d.cts +58 -0
  8. package/dist/billing/index.d.ts +58 -0
  9. package/dist/billing/index.js +226 -0
  10. package/dist/billing/index.js.map +1 -0
  11. package/dist/billing/index.mjs +196 -0
  12. package/dist/billing/index.mjs.map +1 -0
  13. package/dist/graphql/index.d.cts +17 -0
  14. package/dist/graphql/index.d.ts +17 -0
  15. package/dist/graphql/index.js +67 -0
  16. package/dist/graphql/index.js.map +1 -0
  17. package/dist/graphql/index.mjs +40 -0
  18. package/dist/graphql/index.mjs.map +1 -0
  19. package/dist/index.d.cts +16 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.js +37 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/index.mjs +11 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/multi-store/index.d.cts +28 -0
  26. package/dist/multi-store/index.d.ts +28 -0
  27. package/dist/multi-store/index.js +181 -0
  28. package/dist/multi-store/index.js.map +1 -0
  29. package/dist/multi-store/index.mjs +152 -0
  30. package/dist/multi-store/index.mjs.map +1 -0
  31. package/dist/performance/index.d.cts +22 -0
  32. package/dist/performance/index.d.ts +22 -0
  33. package/dist/performance/index.js +64 -0
  34. package/dist/performance/index.js.map +1 -0
  35. package/dist/performance/index.mjs +35 -0
  36. package/dist/performance/index.mjs.map +1 -0
  37. package/dist/platform/index.d.cts +16 -0
  38. package/dist/platform/index.d.ts +16 -0
  39. package/dist/platform/index.js +150 -0
  40. package/dist/platform/index.js.map +1 -0
  41. package/dist/platform/index.mjs +121 -0
  42. package/dist/platform/index.mjs.map +1 -0
  43. package/dist/rbac/index.d.cts +38 -0
  44. package/dist/rbac/index.d.ts +38 -0
  45. package/dist/rbac/index.js +56 -0
  46. package/dist/rbac/index.js.map +1 -0
  47. package/dist/rbac/index.mjs +29 -0
  48. package/dist/rbac/index.mjs.map +1 -0
  49. package/dist/security/index.d.cts +26 -0
  50. package/dist/security/index.d.ts +26 -0
  51. package/dist/security/index.js +102 -0
  52. package/dist/security/index.js.map +1 -0
  53. package/dist/security/index.mjs +69 -0
  54. package/dist/security/index.mjs.map +1 -0
  55. package/dist/webhooks/index.d.cts +36 -0
  56. package/dist/webhooks/index.d.ts +36 -0
  57. package/dist/webhooks/index.js +147 -0
  58. package/dist/webhooks/index.js.map +1 -0
  59. package/dist/webhooks/index.mjs +118 -0
  60. package/dist/webhooks/index.mjs.map +1 -0
  61. package/package.json +95 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/billing/mutations.ts","../../src/billing/service.ts","../../src/billing/webhook-handler.ts"],"sourcesContent":["/**\n * Shopify billing GraphQL mutations.\n */\n\nexport const APP_SUBSCRIPTION_CREATE_MUTATION = `\n mutation AppSubscriptionCreate($name: String!, $lineItems: [AppSubscriptionLineItemInput!]!, $returnUrl: URL!, $trialDays: Int) {\n appSubscriptionCreate(name: $name, lineItems: $lineItems, returnUrl: $returnUrl, trialDays: $trialDays) {\n appSubscription {\n id\n name\n status\n lineItems {\n id\n plan {\n pricingDetails {\n ... on AppRecurringPricing {\n price { amount currencyCode }\n interval\n }\n ... on AppUsagePricing {\n balanceUsed { amount currencyCode }\n cappedAmount { amount currencyCode }\n terms\n }\n }\n }\n }\n confirmationUrl\n }\n userErrors {\n field\n message\n }\n }\n }\n`;\n\nexport const APP_USAGE_RECORD_CREATE_MUTATION = `\n mutation AppUsageRecordCreate($subscriptionLineItemId: ID!, $price: MoneyInput!, $description: String!) {\n appUsageRecordCreate(subscriptionLineItemId: $subscriptionLineItemId, price: $price, description: $description) {\n appUsageRecord {\n id\n }\n userErrors {\n field\n message\n }\n }\n }\n`;\n","/**\n * Shopify billing service.\n *\n * Wraps the core BillingService with Shopify GraphQL mutations\n * for creating subscriptions and usage records.\n */\n\nimport type { GraphQLClient } from '@uniforge/platform-core/graphql';\nimport type {\n BillingService,\n Subscription,\n UsageRecord,\n PlanPricing,\n} from '@uniforge/platform-core/billing';\nimport {\n APP_SUBSCRIPTION_CREATE_MUTATION,\n APP_USAGE_RECORD_CREATE_MUTATION,\n} from './mutations.js';\n\n/** Configuration for the Shopify billing service. */\nexport interface ShopifyBillingServiceConfig {\n graphqlClient: GraphQLClient;\n billingService: BillingService;\n}\n\ninterface AppSubscriptionCreateResponse {\n appSubscriptionCreate: {\n appSubscription: {\n id: string;\n name: string;\n status: string;\n lineItems: Array<{ id: string }>;\n confirmationUrl: string;\n } | null;\n userErrors: Array<{ field: string[]; message: string }>;\n };\n}\n\ninterface AppUsageRecordCreateResponse {\n appUsageRecordCreate: {\n appUsageRecord: { id: string } | null;\n userErrors: Array<{ field: string[]; message: string }>;\n };\n}\n\nfunction mapLineItemToShopifyInput(item: PlanPricing): Record<string, unknown> {\n if (item.type === 'recurring') {\n return {\n plan: {\n appRecurringPricingDetails: {\n price: {\n amount: item.price.amount,\n currencyCode: item.price.currencyCode,\n },\n interval: item.interval,\n },\n },\n };\n }\n\n return {\n plan: {\n appUsagePricingDetails: {\n cappedAmount: {\n amount: item.cappedAmount.amount,\n currencyCode: item.cappedAmount.currencyCode,\n },\n terms: item.terms,\n },\n },\n };\n}\n\n/** Create a Shopify billing service that wraps BillingService with GraphQL operations. */\nexport function createShopifyBillingService(config: ShopifyBillingServiceConfig) {\n const { graphqlClient, billingService } = config;\n\n return {\n async createSubscription(input: {\n shopDomain: string;\n planName: string;\n returnUrl: string;\n lineItems: PlanPricing[];\n trialDays?: number;\n }): Promise<{ subscription: Subscription; confirmationUrl: string }> {\n const variables: Record<string, unknown> = {\n name: input.planName,\n lineItems: input.lineItems.map(mapLineItemToShopifyInput),\n returnUrl: input.returnUrl,\n };\n if (input.trialDays !== undefined) {\n variables['trialDays'] = input.trialDays;\n }\n\n const response = await graphqlClient.mutate<AppSubscriptionCreateResponse>(\n APP_SUBSCRIPTION_CREATE_MUTATION,\n variables,\n );\n\n if (response.errors?.length) {\n throw new Error(\n `GraphQL errors: ${response.errors.map((e) => e.message).join(', ')}`,\n );\n }\n\n const result = response.data!.appSubscriptionCreate;\n if (result.userErrors.length > 0) {\n throw new Error(\n `Shopify billing error: ${result.userErrors.map((e) => e.message).join(', ')}`,\n );\n }\n\n const appSubscription = result.appSubscription!;\n const shopifySubscriptionId = appSubscription.id;\n const { confirmationUrl } = appSubscription;\n\n const subscriptionInput: Parameters<typeof billingService.createSubscription>[0] = {\n shopDomain: input.shopDomain,\n planName: input.planName,\n returnUrl: input.returnUrl,\n shopifySubscriptionId,\n confirmationUrl,\n };\n if (input.trialDays !== undefined) {\n subscriptionInput.trialDays = input.trialDays;\n }\n\n const subscription = await billingService.createSubscription(subscriptionInput);\n\n return { subscription, confirmationUrl };\n },\n\n async createUsageRecord(input: {\n subscriptionLineItemId: string;\n amount: string;\n currencyCode: string;\n description: string;\n subscriptionId: string;\n idempotencyKey?: string;\n }): Promise<UsageRecord> {\n const response = await graphqlClient.mutate<AppUsageRecordCreateResponse>(\n APP_USAGE_RECORD_CREATE_MUTATION,\n {\n subscriptionLineItemId: input.subscriptionLineItemId,\n price: {\n amount: input.amount,\n currencyCode: input.currencyCode,\n },\n description: input.description,\n },\n );\n\n if (response.errors?.length) {\n throw new Error(\n `GraphQL errors: ${response.errors.map((e) => e.message).join(', ')}`,\n );\n }\n\n const result = response.data!.appUsageRecordCreate;\n if (result.userErrors.length > 0) {\n throw new Error(\n `Shopify usage record error: ${result.userErrors.map((e) => e.message).join(', ')}`,\n );\n }\n\n const usageRecordInput: Parameters<typeof billingService.createUsageRecord>[0] = {\n subscriptionId: input.subscriptionId,\n description: input.description,\n amount: input.amount,\n currencyCode: input.currencyCode,\n };\n if (input.idempotencyKey !== undefined) {\n usageRecordInput.idempotencyKey = input.idempotencyKey;\n }\n\n return billingService.createUsageRecord(usageRecordInput);\n },\n\n getSubscription(id: string) {\n return billingService.getSubscription(id);\n },\n\n getActiveSubscription(shopDomain: string) {\n return billingService.getActiveSubscription(shopDomain);\n },\n\n cancelSubscription(id: string) {\n return billingService.cancelSubscription(id);\n },\n };\n}\n","/**\n * Shopify subscription webhook handler.\n *\n * Handles APP_SUBSCRIPTIONS_UPDATE webhooks to sync subscription\n * status changes from Shopify into the local database.\n */\n\nimport type { BillingService, SubscriptionStatus } from '@uniforge/platform-core/billing';\nimport type {\n WebhookHandler,\n WebhookPayload,\n WebhookHandlerResult,\n} from '@uniforge/platform-core/webhooks';\n\ninterface AppSubscriptionUpdatePayload {\n app_subscription: {\n admin_graphql_api_id: string;\n name: string;\n status: string;\n };\n}\n\n/** Create a webhook handler for APP_SUBSCRIPTIONS_UPDATE events. */\nexport function createSubscriptionWebhookHandler(\n billingService: BillingService,\n): WebhookHandler {\n return {\n async handle(payload: WebhookPayload): Promise<WebhookHandlerResult> {\n try {\n const data = payload.payload as AppSubscriptionUpdatePayload;\n const subscription = data.app_subscription;\n\n if (!subscription?.admin_graphql_api_id || !subscription?.status) {\n return { success: false, error: 'Invalid webhook payload: missing subscription data' };\n }\n\n const shopifyId = subscription.admin_graphql_api_id;\n const status = subscription.status as SubscriptionStatus;\n\n const updated = await billingService.updateSubscriptionByShopifyId(shopifyId, { status });\n\n if (!updated) {\n return { success: false, error: `Subscription not found for Shopify ID: ${shopifyId}` };\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,17 @@
1
+ import { GraphQLClientConfig, RateLimiter, GraphQLCache, GraphQLClient } from '@uniforge/platform-core/graphql';
2
+
3
+ /**
4
+ * Shopify-specific GraphQL client that wraps the core client
5
+ * with rate limiting and caching support.
6
+ */
7
+
8
+ /** Extended config for the Shopify GraphQL client. */
9
+ interface ShopifyGraphQLClientConfig extends GraphQLClientConfig {
10
+ rateLimiter?: RateLimiter;
11
+ cache?: GraphQLCache;
12
+ cacheKeyPrefix?: string;
13
+ }
14
+ /** Create a Shopify GraphQL client with optional rate limiting and caching. */
15
+ declare function createShopifyGraphQLClient(config: ShopifyGraphQLClientConfig): GraphQLClient;
16
+
17
+ export { type ShopifyGraphQLClientConfig, createShopifyGraphQLClient };
@@ -0,0 +1,17 @@
1
+ import { GraphQLClientConfig, RateLimiter, GraphQLCache, GraphQLClient } from '@uniforge/platform-core/graphql';
2
+
3
+ /**
4
+ * Shopify-specific GraphQL client that wraps the core client
5
+ * with rate limiting and caching support.
6
+ */
7
+
8
+ /** Extended config for the Shopify GraphQL client. */
9
+ interface ShopifyGraphQLClientConfig extends GraphQLClientConfig {
10
+ rateLimiter?: RateLimiter;
11
+ cache?: GraphQLCache;
12
+ cacheKeyPrefix?: string;
13
+ }
14
+ /** Create a Shopify GraphQL client with optional rate limiting and caching. */
15
+ declare function createShopifyGraphQLClient(config: ShopifyGraphQLClientConfig): GraphQLClient;
16
+
17
+ export { type ShopifyGraphQLClientConfig, createShopifyGraphQLClient };
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/graphql/index.ts
21
+ var graphql_exports = {};
22
+ __export(graphql_exports, {
23
+ createShopifyGraphQLClient: () => createShopifyGraphQLClient
24
+ });
25
+ module.exports = __toCommonJS(graphql_exports);
26
+
27
+ // src/graphql/client.ts
28
+ var import_graphql = require("@uniforge/core/graphql");
29
+ var import_graphql2 = require("@uniforge/core/graphql");
30
+ function createShopifyGraphQLClient(config) {
31
+ const baseClient = (0, import_graphql.createGraphQLClient)(config);
32
+ const { rateLimiter, cache } = config;
33
+ const cacheKeyPrefix = config.cacheKeyPrefix ?? "uniforge:gql:";
34
+ if (!rateLimiter && !cache) {
35
+ return baseClient;
36
+ }
37
+ async function executeWithMiddleware(operation, variables, isMutation) {
38
+ if (cache && !isMutation) {
39
+ const cacheKey = (0, import_graphql2.generateCacheKey)(cacheKeyPrefix, operation, variables);
40
+ const cached = await cache.get(cacheKey);
41
+ if (cached) {
42
+ return cached;
43
+ }
44
+ }
45
+ if (rateLimiter) {
46
+ await rateLimiter.acquire();
47
+ }
48
+ const response = isMutation ? await baseClient.mutate(operation, variables) : await baseClient.query(operation, variables);
49
+ if (rateLimiter && response.extensions?.cost) {
50
+ rateLimiter.updateFromResponse(response.extensions.cost);
51
+ }
52
+ if (cache && !isMutation && !response.errors?.length) {
53
+ const cacheKey = (0, import_graphql2.generateCacheKey)(cacheKeyPrefix, operation, variables);
54
+ await cache.set(cacheKey, response);
55
+ }
56
+ return response;
57
+ }
58
+ return {
59
+ query: (query, variables) => executeWithMiddleware(query, variables, false),
60
+ mutate: (mutation, variables) => executeWithMiddleware(mutation, variables, true)
61
+ };
62
+ }
63
+ // Annotate the CommonJS export names for ESM import in node:
64
+ 0 && (module.exports = {
65
+ createShopifyGraphQLClient
66
+ });
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/graphql/index.ts","../../src/graphql/client.ts"],"sourcesContent":["/**\n * @uniforge/platform-shopify - GraphQL\n *\n * Shopify-specific GraphQL client with rate limiting and caching.\n */\n\nexport { createShopifyGraphQLClient } from './client';\nexport type { ShopifyGraphQLClientConfig } from './client';\n","/**\n * Shopify-specific GraphQL client that wraps the core client\n * with rate limiting and caching support.\n */\n\nimport type {\n GraphQLClient,\n GraphQLClientConfig,\n GraphQLResponse,\n GraphQLCache,\n RateLimiter,\n} from '@uniforge/platform-core/graphql';\nimport { createGraphQLClient } from '@uniforge/core/graphql';\nimport { generateCacheKey } from '@uniforge/core/graphql';\n\n/** Extended config for the Shopify GraphQL client. */\nexport interface ShopifyGraphQLClientConfig extends GraphQLClientConfig {\n rateLimiter?: RateLimiter;\n cache?: GraphQLCache;\n cacheKeyPrefix?: string;\n}\n\n/** Create a Shopify GraphQL client with optional rate limiting and caching. */\nexport function createShopifyGraphQLClient(\n config: ShopifyGraphQLClientConfig,\n): GraphQLClient {\n const baseClient = createGraphQLClient(config);\n const { rateLimiter, cache } = config;\n const cacheKeyPrefix = config.cacheKeyPrefix ?? 'uniforge:gql:';\n\n if (!rateLimiter && !cache) {\n return baseClient;\n }\n\n async function executeWithMiddleware<T>(\n operation: string,\n variables: Record<string, unknown> | undefined,\n isMutation: boolean,\n ): Promise<GraphQLResponse<T>> {\n // Check cache for queries (not mutations)\n if (cache && !isMutation) {\n const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);\n const cached = await cache.get<GraphQLResponse<T>>(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n // Acquire rate limit token\n if (rateLimiter) {\n await rateLimiter.acquire();\n }\n\n // Execute the request\n const response = isMutation\n ? await baseClient.mutate<T>(operation, variables)\n : await baseClient.query<T>(operation, variables);\n\n // Update rate limiter from response cost\n if (rateLimiter && response.extensions?.cost) {\n rateLimiter.updateFromResponse(response.extensions.cost);\n }\n\n // Cache successful query responses (no errors)\n if (cache && !isMutation && !response.errors?.length) {\n const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);\n await cache.set(cacheKey, response);\n }\n\n return response;\n }\n\n return {\n query: <T>(query: string, variables?: Record<string, unknown>) =>\n executeWithMiddleware<T>(query, variables, false),\n mutate: <T>(mutation: string, variables?: Record<string, unknown>) =>\n executeWithMiddleware<T>(mutation, variables, true),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYA,qBAAoC;AACpC,IAAAA,kBAAiC;AAU1B,SAAS,2BACd,QACe;AACf,QAAM,iBAAa,oCAAoB,MAAM;AAC7C,QAAM,EAAE,aAAa,MAAM,IAAI;AAC/B,QAAM,iBAAiB,OAAO,kBAAkB;AAEhD,MAAI,CAAC,eAAe,CAAC,OAAO;AAC1B,WAAO;AAAA,EACT;AAEA,iBAAe,sBACb,WACA,WACA,YAC6B;AAE7B,QAAI,SAAS,CAAC,YAAY;AACxB,YAAM,eAAW,kCAAiB,gBAAgB,WAAW,SAAS;AACtE,YAAM,SAAS,MAAM,MAAM,IAAwB,QAAQ;AAC3D,UAAI,QAAQ;AACV,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,aAAa;AACf,YAAM,YAAY,QAAQ;AAAA,IAC5B;AAGA,UAAM,WAAW,aACb,MAAM,WAAW,OAAU,WAAW,SAAS,IAC/C,MAAM,WAAW,MAAS,WAAW,SAAS;AAGlD,QAAI,eAAe,SAAS,YAAY,MAAM;AAC5C,kBAAY,mBAAmB,SAAS,WAAW,IAAI;AAAA,IACzD;AAGA,QAAI,SAAS,CAAC,cAAc,CAAC,SAAS,QAAQ,QAAQ;AACpD,YAAM,eAAW,kCAAiB,gBAAgB,WAAW,SAAS;AACtE,YAAM,MAAM,IAAI,UAAU,QAAQ;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,OAAO,CAAI,OAAe,cACxB,sBAAyB,OAAO,WAAW,KAAK;AAAA,IAClD,QAAQ,CAAI,UAAkB,cAC5B,sBAAyB,UAAU,WAAW,IAAI;AAAA,EACtD;AACF;","names":["import_graphql"]}
@@ -0,0 +1,40 @@
1
+ // src/graphql/client.ts
2
+ import { createGraphQLClient } from "@uniforge/core/graphql";
3
+ import { generateCacheKey } from "@uniforge/core/graphql";
4
+ function createShopifyGraphQLClient(config) {
5
+ const baseClient = createGraphQLClient(config);
6
+ const { rateLimiter, cache } = config;
7
+ const cacheKeyPrefix = config.cacheKeyPrefix ?? "uniforge:gql:";
8
+ if (!rateLimiter && !cache) {
9
+ return baseClient;
10
+ }
11
+ async function executeWithMiddleware(operation, variables, isMutation) {
12
+ if (cache && !isMutation) {
13
+ const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);
14
+ const cached = await cache.get(cacheKey);
15
+ if (cached) {
16
+ return cached;
17
+ }
18
+ }
19
+ if (rateLimiter) {
20
+ await rateLimiter.acquire();
21
+ }
22
+ const response = isMutation ? await baseClient.mutate(operation, variables) : await baseClient.query(operation, variables);
23
+ if (rateLimiter && response.extensions?.cost) {
24
+ rateLimiter.updateFromResponse(response.extensions.cost);
25
+ }
26
+ if (cache && !isMutation && !response.errors?.length) {
27
+ const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);
28
+ await cache.set(cacheKey, response);
29
+ }
30
+ return response;
31
+ }
32
+ return {
33
+ query: (query, variables) => executeWithMiddleware(query, variables, false),
34
+ mutate: (mutation, variables) => executeWithMiddleware(mutation, variables, true)
35
+ };
36
+ }
37
+ export {
38
+ createShopifyGraphQLClient
39
+ };
40
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/graphql/client.ts"],"sourcesContent":["/**\n * Shopify-specific GraphQL client that wraps the core client\n * with rate limiting and caching support.\n */\n\nimport type {\n GraphQLClient,\n GraphQLClientConfig,\n GraphQLResponse,\n GraphQLCache,\n RateLimiter,\n} from '@uniforge/platform-core/graphql';\nimport { createGraphQLClient } from '@uniforge/core/graphql';\nimport { generateCacheKey } from '@uniforge/core/graphql';\n\n/** Extended config for the Shopify GraphQL client. */\nexport interface ShopifyGraphQLClientConfig extends GraphQLClientConfig {\n rateLimiter?: RateLimiter;\n cache?: GraphQLCache;\n cacheKeyPrefix?: string;\n}\n\n/** Create a Shopify GraphQL client with optional rate limiting and caching. */\nexport function createShopifyGraphQLClient(\n config: ShopifyGraphQLClientConfig,\n): GraphQLClient {\n const baseClient = createGraphQLClient(config);\n const { rateLimiter, cache } = config;\n const cacheKeyPrefix = config.cacheKeyPrefix ?? 'uniforge:gql:';\n\n if (!rateLimiter && !cache) {\n return baseClient;\n }\n\n async function executeWithMiddleware<T>(\n operation: string,\n variables: Record<string, unknown> | undefined,\n isMutation: boolean,\n ): Promise<GraphQLResponse<T>> {\n // Check cache for queries (not mutations)\n if (cache && !isMutation) {\n const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);\n const cached = await cache.get<GraphQLResponse<T>>(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n // Acquire rate limit token\n if (rateLimiter) {\n await rateLimiter.acquire();\n }\n\n // Execute the request\n const response = isMutation\n ? await baseClient.mutate<T>(operation, variables)\n : await baseClient.query<T>(operation, variables);\n\n // Update rate limiter from response cost\n if (rateLimiter && response.extensions?.cost) {\n rateLimiter.updateFromResponse(response.extensions.cost);\n }\n\n // Cache successful query responses (no errors)\n if (cache && !isMutation && !response.errors?.length) {\n const cacheKey = generateCacheKey(cacheKeyPrefix, operation, variables);\n await cache.set(cacheKey, response);\n }\n\n return response;\n }\n\n return {\n query: <T>(query: string, variables?: Record<string, unknown>) =>\n executeWithMiddleware<T>(query, variables, false),\n mutate: <T>(mutation: string, variables?: Record<string, unknown>) =>\n executeWithMiddleware<T>(mutation, variables, true),\n };\n}\n"],"mappings":";AAYA,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AAU1B,SAAS,2BACd,QACe;AACf,QAAM,aAAa,oBAAoB,MAAM;AAC7C,QAAM,EAAE,aAAa,MAAM,IAAI;AAC/B,QAAM,iBAAiB,OAAO,kBAAkB;AAEhD,MAAI,CAAC,eAAe,CAAC,OAAO;AAC1B,WAAO;AAAA,EACT;AAEA,iBAAe,sBACb,WACA,WACA,YAC6B;AAE7B,QAAI,SAAS,CAAC,YAAY;AACxB,YAAM,WAAW,iBAAiB,gBAAgB,WAAW,SAAS;AACtE,YAAM,SAAS,MAAM,MAAM,IAAwB,QAAQ;AAC3D,UAAI,QAAQ;AACV,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,aAAa;AACf,YAAM,YAAY,QAAQ;AAAA,IAC5B;AAGA,UAAM,WAAW,aACb,MAAM,WAAW,OAAU,WAAW,SAAS,IAC/C,MAAM,WAAW,MAAS,WAAW,SAAS;AAGlD,QAAI,eAAe,SAAS,YAAY,MAAM;AAC5C,kBAAY,mBAAmB,SAAS,WAAW,IAAI;AAAA,IACzD;AAGA,QAAI,SAAS,CAAC,cAAc,CAAC,SAAS,QAAQ,QAAQ;AACpD,YAAM,WAAW,iBAAiB,gBAAgB,WAAW,SAAS;AACtE,YAAM,MAAM,IAAI,UAAU,QAAQ;AAAA,IACpC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,OAAO,CAAI,OAAe,cACxB,sBAAyB,OAAO,WAAW,KAAK;AAAA,IAClD,QAAQ,CAAI,UAAkB,cAC5B,sBAAyB,UAAU,WAAW,IAAI;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @uniforge/platform-shopify
3
+ *
4
+ * Shopify platform adapter implementing the platform-core interfaces.
5
+ */
6
+ declare const VERSION = "0.0.0";
7
+ /**
8
+ * Shopify adapter placeholder.
9
+ * Full implementation will include OAuth, GraphQL, webhooks, and billing.
10
+ */
11
+ declare const shopifyAdapter: {
12
+ name: string;
13
+ version: string;
14
+ };
15
+
16
+ export { VERSION, shopifyAdapter };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @uniforge/platform-shopify
3
+ *
4
+ * Shopify platform adapter implementing the platform-core interfaces.
5
+ */
6
+ declare const VERSION = "0.0.0";
7
+ /**
8
+ * Shopify adapter placeholder.
9
+ * Full implementation will include OAuth, GraphQL, webhooks, and billing.
10
+ */
11
+ declare const shopifyAdapter: {
12
+ name: string;
13
+ version: string;
14
+ };
15
+
16
+ export { VERSION, shopifyAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ VERSION: () => VERSION,
24
+ shopifyAdapter: () => shopifyAdapter
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var VERSION = "0.0.0";
28
+ var shopifyAdapter = {
29
+ name: "shopify",
30
+ version: VERSION
31
+ };
32
+ // Annotate the CommonJS export names for ESM import in node:
33
+ 0 && (module.exports = {
34
+ VERSION,
35
+ shopifyAdapter
36
+ });
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @uniforge/platform-shopify\n *\n * Shopify platform adapter implementing the platform-core interfaces.\n */\n\nexport const VERSION = '0.0.0';\n\n/**\n * Shopify adapter placeholder.\n * Full implementation will include OAuth, GraphQL, webhooks, and billing.\n */\nexport const shopifyAdapter = {\n name: 'shopify',\n version: VERSION,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,IAAM,UAAU;AAMhB,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACX;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,11 @@
1
+ // src/index.ts
2
+ var VERSION = "0.0.0";
3
+ var shopifyAdapter = {
4
+ name: "shopify",
5
+ version: VERSION
6
+ };
7
+ export {
8
+ VERSION,
9
+ shopifyAdapter
10
+ };
11
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @uniforge/platform-shopify\n *\n * Shopify platform adapter implementing the platform-core interfaces.\n */\n\nexport const VERSION = '0.0.0';\n\n/**\n * Shopify adapter placeholder.\n * Full implementation will include OAuth, GraphQL, webhooks, and billing.\n */\nexport const shopifyAdapter = {\n name: 'shopify',\n version: VERSION,\n};\n"],"mappings":";AAMO,IAAM,UAAU;AAMhB,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACX;","names":[]}
@@ -0,0 +1,28 @@
1
+ import { Session } from '@uniforge/platform-core/auth';
2
+ import { AccountContext } from '@uniforge/platform-core/multi-store';
3
+
4
+ /**
5
+ * Extract account context from an authenticated Shopify session.
6
+ */
7
+
8
+ /**
9
+ * Extract AccountContext from a Shopify session and Prisma database.
10
+ * Returns null if the session's shop is not linked to any account.
11
+ */
12
+ declare function extractAccountFromSession(session: Session, prisma: any): Promise<AccountContext | null>;
13
+
14
+ /**
15
+ * Store switching validation for multi-store enterprise accounts.
16
+ */
17
+ /** Error thrown when a store switch is invalid. */
18
+ declare class StoreSwitchError extends Error {
19
+ readonly code: 'NOT_IN_ACCOUNT' | 'ACCESS_DENIED' | 'STORE_INACTIVE' | 'STORE_NOT_FOUND';
20
+ constructor(message: string, code: 'NOT_IN_ACCOUNT' | 'ACCESS_DENIED' | 'STORE_INACTIVE' | 'STORE_NOT_FOUND');
21
+ }
22
+ /**
23
+ * Validate that a user can switch to a target store within their account.
24
+ * Throws StoreSwitchError if the switch is not allowed.
25
+ */
26
+ declare function validateStoreSwitch(currentShopDomain: string, targetShopDomain: string, userEmail: string, prisma: any): Promise<void>;
27
+
28
+ export { StoreSwitchError, extractAccountFromSession, validateStoreSwitch };
@@ -0,0 +1,28 @@
1
+ import { Session } from '@uniforge/platform-core/auth';
2
+ import { AccountContext } from '@uniforge/platform-core/multi-store';
3
+
4
+ /**
5
+ * Extract account context from an authenticated Shopify session.
6
+ */
7
+
8
+ /**
9
+ * Extract AccountContext from a Shopify session and Prisma database.
10
+ * Returns null if the session's shop is not linked to any account.
11
+ */
12
+ declare function extractAccountFromSession(session: Session, prisma: any): Promise<AccountContext | null>;
13
+
14
+ /**
15
+ * Store switching validation for multi-store enterprise accounts.
16
+ */
17
+ /** Error thrown when a store switch is invalid. */
18
+ declare class StoreSwitchError extends Error {
19
+ readonly code: 'NOT_IN_ACCOUNT' | 'ACCESS_DENIED' | 'STORE_INACTIVE' | 'STORE_NOT_FOUND';
20
+ constructor(message: string, code: 'NOT_IN_ACCOUNT' | 'ACCESS_DENIED' | 'STORE_INACTIVE' | 'STORE_NOT_FOUND');
21
+ }
22
+ /**
23
+ * Validate that a user can switch to a target store within their account.
24
+ * Throws StoreSwitchError if the switch is not allowed.
25
+ */
26
+ declare function validateStoreSwitch(currentShopDomain: string, targetShopDomain: string, userEmail: string, prisma: any): Promise<void>;
27
+
28
+ export { StoreSwitchError, extractAccountFromSession, validateStoreSwitch };
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/multi-store/index.ts
21
+ var multi_store_exports = {};
22
+ __export(multi_store_exports, {
23
+ StoreSwitchError: () => StoreSwitchError,
24
+ extractAccountFromSession: () => extractAccountFromSession,
25
+ validateStoreSwitch: () => validateStoreSwitch
26
+ });
27
+ module.exports = __toCommonJS(multi_store_exports);
28
+
29
+ // src/multi-store/account-extraction.ts
30
+ async function extractAccountFromSession(session, prisma) {
31
+ const accountStore = await prisma.accountStore.findUnique({
32
+ where: { shopDomain: session.shop },
33
+ include: { account: true }
34
+ });
35
+ if (!accountStore) {
36
+ return null;
37
+ }
38
+ const stores = await prisma.accountStore.findMany({
39
+ where: { accountId: accountStore.accountId }
40
+ });
41
+ let member = null;
42
+ if (session.onlineAccessInfo?.associatedUser?.email) {
43
+ const memberRow = await prisma.accountMember.findUnique({
44
+ where: {
45
+ accountId_userEmail: {
46
+ accountId: accountStore.accountId,
47
+ userEmail: session.onlineAccessInfo.associatedUser.email
48
+ }
49
+ }
50
+ });
51
+ if (memberRow) {
52
+ member = {
53
+ id: memberRow.id,
54
+ accountId: memberRow.accountId,
55
+ userEmail: memberRow.userEmail,
56
+ role: memberRow.role,
57
+ allStoresAccess: memberRow.allStoresAccess,
58
+ allowedStoreIds: Array.isArray(memberRow.allowedStoreIds) ? memberRow.allowedStoreIds : JSON.parse(String(memberRow.allowedStoreIds)),
59
+ permissions: Array.isArray(memberRow.permissions) ? memberRow.permissions : JSON.parse(String(memberRow.permissions)),
60
+ createdAt: memberRow.createdAt,
61
+ updatedAt: memberRow.updatedAt
62
+ };
63
+ }
64
+ }
65
+ return {
66
+ account: {
67
+ id: accountStore.account.id,
68
+ name: accountStore.account.name,
69
+ type: accountStore.account.type,
70
+ isActive: accountStore.account.isActive,
71
+ createdAt: accountStore.account.createdAt,
72
+ updatedAt: accountStore.account.updatedAt
73
+ },
74
+ currentStore: {
75
+ id: accountStore.id,
76
+ accountId: accountStore.accountId,
77
+ shopDomain: accountStore.shopDomain,
78
+ storeName: accountStore.storeName,
79
+ region: accountStore.region,
80
+ currency: accountStore.currency,
81
+ locale: accountStore.locale,
82
+ timezone: accountStore.timezone,
83
+ storeSettings: typeof accountStore.storeSettings === "string" ? JSON.parse(accountStore.storeSettings) : accountStore.storeSettings ?? {},
84
+ isActive: accountStore.isActive,
85
+ isPrimary: accountStore.isPrimary,
86
+ createdAt: accountStore.createdAt,
87
+ updatedAt: accountStore.updatedAt
88
+ },
89
+ member,
90
+ stores: stores.map((s) => ({
91
+ id: s.id,
92
+ accountId: s.accountId,
93
+ shopDomain: s.shopDomain,
94
+ storeName: s.storeName,
95
+ region: s.region,
96
+ currency: s.currency,
97
+ locale: s.locale,
98
+ timezone: s.timezone,
99
+ storeSettings: typeof s.storeSettings === "string" ? JSON.parse(s.storeSettings) : s.storeSettings ?? {},
100
+ isActive: s.isActive,
101
+ isPrimary: s.isPrimary,
102
+ createdAt: s.createdAt,
103
+ updatedAt: s.updatedAt
104
+ }))
105
+ };
106
+ }
107
+
108
+ // src/multi-store/store-switching.ts
109
+ var StoreSwitchError = class extends Error {
110
+ constructor(message, code) {
111
+ super(message);
112
+ this.code = code;
113
+ this.name = "StoreSwitchError";
114
+ }
115
+ };
116
+ async function validateStoreSwitch(currentShopDomain, targetShopDomain, userEmail, prisma) {
117
+ const currentStore = await prisma.accountStore.findUnique({
118
+ where: { shopDomain: currentShopDomain }
119
+ });
120
+ if (!currentStore) {
121
+ throw new StoreSwitchError(
122
+ "Current store is not linked to any account",
123
+ "NOT_IN_ACCOUNT"
124
+ );
125
+ }
126
+ const targetStore = await prisma.accountStore.findUnique({
127
+ where: { shopDomain: targetShopDomain }
128
+ });
129
+ if (!targetStore) {
130
+ throw new StoreSwitchError(
131
+ `Target store "${targetShopDomain}" not found`,
132
+ "STORE_NOT_FOUND"
133
+ );
134
+ }
135
+ if (currentStore.accountId !== targetStore.accountId) {
136
+ throw new StoreSwitchError(
137
+ "Cannot switch to a store in a different account",
138
+ "NOT_IN_ACCOUNT"
139
+ );
140
+ }
141
+ if (!targetStore.isActive) {
142
+ throw new StoreSwitchError(
143
+ `Target store "${targetShopDomain}" is inactive`,
144
+ "STORE_INACTIVE"
145
+ );
146
+ }
147
+ const member = await prisma.accountMember.findUnique({
148
+ where: {
149
+ accountId_userEmail: {
150
+ accountId: currentStore.accountId,
151
+ userEmail
152
+ }
153
+ }
154
+ });
155
+ if (!member) {
156
+ throw new StoreSwitchError(
157
+ "User is not a member of this account",
158
+ "ACCESS_DENIED"
159
+ );
160
+ }
161
+ if (member.role === "owner" || member.role === "admin") {
162
+ return;
163
+ }
164
+ if (member.allStoresAccess) {
165
+ return;
166
+ }
167
+ const allowedIds = Array.isArray(member.allowedStoreIds) ? member.allowedStoreIds : JSON.parse(String(member.allowedStoreIds));
168
+ if (!allowedIds.includes(targetStore.id)) {
169
+ throw new StoreSwitchError(
170
+ "User does not have access to the target store",
171
+ "ACCESS_DENIED"
172
+ );
173
+ }
174
+ }
175
+ // Annotate the CommonJS export names for ESM import in node:
176
+ 0 && (module.exports = {
177
+ StoreSwitchError,
178
+ extractAccountFromSession,
179
+ validateStoreSwitch
180
+ });
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/multi-store/index.ts","../../src/multi-store/account-extraction.ts","../../src/multi-store/store-switching.ts"],"sourcesContent":["/**\n * @uniforge/platform-shopify - Multi-Store\n *\n * Shopify-specific multi-store account extraction and store switching.\n */\n\nexport { extractAccountFromSession } from './account-extraction';\nexport { validateStoreSwitch, StoreSwitchError } from './store-switching';\n","/**\n * Extract account context from an authenticated Shopify session.\n */\n\nimport type { Session } from '@uniforge/platform-core/auth';\nimport type { AccountContext, AccountRole } from '@uniforge/platform-core/multi-store';\n\n/**\n * Extract AccountContext from a Shopify session and Prisma database.\n * Returns null if the session's shop is not linked to any account.\n */\nexport async function extractAccountFromSession(\n session: Session,\n prisma: any,\n): Promise<AccountContext | null> {\n const accountStore = await prisma.accountStore.findUnique({\n where: { shopDomain: session.shop },\n include: { account: true },\n });\n\n if (!accountStore) {\n return null;\n }\n\n const stores = await prisma.accountStore.findMany({\n where: { accountId: accountStore.accountId },\n });\n\n // Try to find the member based on online access info\n let member = null;\n if (session.onlineAccessInfo?.associatedUser?.email) {\n const memberRow = await prisma.accountMember.findUnique({\n where: {\n accountId_userEmail: {\n accountId: accountStore.accountId,\n userEmail: session.onlineAccessInfo.associatedUser.email,\n },\n },\n });\n if (memberRow) {\n member = {\n id: memberRow.id,\n accountId: memberRow.accountId,\n userEmail: memberRow.userEmail,\n role: memberRow.role as AccountRole,\n allStoresAccess: memberRow.allStoresAccess,\n allowedStoreIds: Array.isArray(memberRow.allowedStoreIds) ? memberRow.allowedStoreIds as string[] : JSON.parse(String(memberRow.allowedStoreIds)) as string[],\n permissions: Array.isArray(memberRow.permissions) ? memberRow.permissions as string[] : JSON.parse(String(memberRow.permissions)) as string[],\n createdAt: memberRow.createdAt,\n updatedAt: memberRow.updatedAt,\n };\n }\n }\n\n return {\n account: {\n id: accountStore.account.id,\n name: accountStore.account.name,\n type: accountStore.account.type as 'individual' | 'enterprise',\n isActive: accountStore.account.isActive,\n createdAt: accountStore.account.createdAt,\n updatedAt: accountStore.account.updatedAt,\n },\n currentStore: {\n id: accountStore.id,\n accountId: accountStore.accountId,\n shopDomain: accountStore.shopDomain,\n storeName: accountStore.storeName,\n region: accountStore.region,\n currency: accountStore.currency,\n locale: accountStore.locale,\n timezone: accountStore.timezone,\n storeSettings: typeof accountStore.storeSettings === 'string'\n ? JSON.parse(accountStore.storeSettings) as Record<string, unknown>\n : (accountStore.storeSettings as Record<string, unknown>) ?? {},\n isActive: accountStore.isActive,\n isPrimary: accountStore.isPrimary,\n createdAt: accountStore.createdAt,\n updatedAt: accountStore.updatedAt,\n },\n member,\n stores: stores.map((s: any) => ({\n id: s.id,\n accountId: s.accountId,\n shopDomain: s.shopDomain,\n storeName: s.storeName,\n region: s.region,\n currency: s.currency,\n locale: s.locale,\n timezone: s.timezone,\n storeSettings: typeof s.storeSettings === 'string'\n ? JSON.parse(s.storeSettings) as Record<string, unknown>\n : (s.storeSettings as Record<string, unknown>) ?? {},\n isActive: s.isActive,\n isPrimary: s.isPrimary,\n createdAt: s.createdAt,\n updatedAt: s.updatedAt,\n })),\n };\n}\n","/**\n * Store switching validation for multi-store enterprise accounts.\n */\n\n/** Error thrown when a store switch is invalid. */\nexport class StoreSwitchError extends Error {\n constructor(\n message: string,\n public readonly code: 'NOT_IN_ACCOUNT' | 'ACCESS_DENIED' | 'STORE_INACTIVE' | 'STORE_NOT_FOUND',\n ) {\n super(message);\n this.name = 'StoreSwitchError';\n }\n}\n\n/**\n * Validate that a user can switch to a target store within their account.\n * Throws StoreSwitchError if the switch is not allowed.\n */\nexport async function validateStoreSwitch(\n currentShopDomain: string,\n targetShopDomain: string,\n userEmail: string,\n prisma: any,\n): Promise<void> {\n // Find the current store's account\n const currentStore = await prisma.accountStore.findUnique({\n where: { shopDomain: currentShopDomain },\n });\n\n if (!currentStore) {\n throw new StoreSwitchError(\n 'Current store is not linked to any account',\n 'NOT_IN_ACCOUNT',\n );\n }\n\n // Find the target store\n const targetStore = await prisma.accountStore.findUnique({\n where: { shopDomain: targetShopDomain },\n });\n\n if (!targetStore) {\n throw new StoreSwitchError(\n `Target store \"${targetShopDomain}\" not found`,\n 'STORE_NOT_FOUND',\n );\n }\n\n // Verify same account\n if (currentStore.accountId !== targetStore.accountId) {\n throw new StoreSwitchError(\n 'Cannot switch to a store in a different account',\n 'NOT_IN_ACCOUNT',\n );\n }\n\n // Check if target store is active\n if (!targetStore.isActive) {\n throw new StoreSwitchError(\n `Target store \"${targetShopDomain}\" is inactive`,\n 'STORE_INACTIVE',\n );\n }\n\n // Check member access\n const member = await prisma.accountMember.findUnique({\n where: {\n accountId_userEmail: {\n accountId: currentStore.accountId,\n userEmail,\n },\n },\n });\n\n if (!member) {\n throw new StoreSwitchError(\n 'User is not a member of this account',\n 'ACCESS_DENIED',\n );\n }\n\n // Owner and admin can access all stores\n if (member.role === 'owner' || member.role === 'admin') {\n return;\n }\n\n // allStoresAccess bypasses store-level checks\n if (member.allStoresAccess) {\n return;\n }\n\n // Check allowed store IDs\n const allowedIds: string[] = Array.isArray(member.allowedStoreIds)\n ? member.allowedStoreIds as string[]\n : JSON.parse(String(member.allowedStoreIds)) as string[];\n\n if (!allowedIds.includes(targetStore.id)) {\n throw new StoreSwitchError(\n 'User does not have access to the target store',\n 'ACCESS_DENIED',\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,eAAsB,0BACpB,SACA,QACgC;AAChC,QAAM,eAAe,MAAM,OAAO,aAAa,WAAW;AAAA,IACxD,OAAO,EAAE,YAAY,QAAQ,KAAK;AAAA,IAClC,SAAS,EAAE,SAAS,KAAK;AAAA,EAC3B,CAAC;AAED,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM,OAAO,aAAa,SAAS;AAAA,IAChD,OAAO,EAAE,WAAW,aAAa,UAAU;AAAA,EAC7C,CAAC;AAGD,MAAI,SAAS;AACb,MAAI,QAAQ,kBAAkB,gBAAgB,OAAO;AACnD,UAAM,YAAY,MAAM,OAAO,cAAc,WAAW;AAAA,MACtD,OAAO;AAAA,QACL,qBAAqB;AAAA,UACnB,WAAW,aAAa;AAAA,UACxB,WAAW,QAAQ,iBAAiB,eAAe;AAAA,QACrD;AAAA,MACF;AAAA,IACF,CAAC;AACD,QAAI,WAAW;AACb,eAAS;AAAA,QACP,IAAI,UAAU;AAAA,QACd,WAAW,UAAU;AAAA,QACrB,WAAW,UAAU;AAAA,QACrB,MAAM,UAAU;AAAA,QAChB,iBAAiB,UAAU;AAAA,QAC3B,iBAAiB,MAAM,QAAQ,UAAU,eAAe,IAAI,UAAU,kBAA8B,KAAK,MAAM,OAAO,UAAU,eAAe,CAAC;AAAA,QAChJ,aAAa,MAAM,QAAQ,UAAU,WAAW,IAAI,UAAU,cAA0B,KAAK,MAAM,OAAO,UAAU,WAAW,CAAC;AAAA,QAChI,WAAW,UAAU;AAAA,QACrB,WAAW,UAAU;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,MACP,IAAI,aAAa,QAAQ;AAAA,MACzB,MAAM,aAAa,QAAQ;AAAA,MAC3B,MAAM,aAAa,QAAQ;AAAA,MAC3B,UAAU,aAAa,QAAQ;AAAA,MAC/B,WAAW,aAAa,QAAQ;AAAA,MAChC,WAAW,aAAa,QAAQ;AAAA,IAClC;AAAA,IACA,cAAc;AAAA,MACZ,IAAI,aAAa;AAAA,MACjB,WAAW,aAAa;AAAA,MACxB,YAAY,aAAa;AAAA,MACzB,WAAW,aAAa;AAAA,MACxB,QAAQ,aAAa;AAAA,MACrB,UAAU,aAAa;AAAA,MACvB,QAAQ,aAAa;AAAA,MACrB,UAAU,aAAa;AAAA,MACvB,eAAe,OAAO,aAAa,kBAAkB,WACjD,KAAK,MAAM,aAAa,aAAa,IACpC,aAAa,iBAA6C,CAAC;AAAA,MAChE,UAAU,aAAa;AAAA,MACvB,WAAW,aAAa;AAAA,MACxB,WAAW,aAAa;AAAA,MACxB,WAAW,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,QAAQ,OAAO,IAAI,CAAC,OAAY;AAAA,MAC9B,IAAI,EAAE;AAAA,MACN,WAAW,EAAE;AAAA,MACb,YAAY,EAAE;AAAA,MACd,WAAW,EAAE;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,eAAe,OAAO,EAAE,kBAAkB,WACtC,KAAK,MAAM,EAAE,aAAa,IACzB,EAAE,iBAA6C,CAAC;AAAA,MACrD,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE;AAAA,MACb,WAAW,EAAE;AAAA,MACb,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACJ;AACF;;;AC9FO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YACE,SACgB,MAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAMA,eAAsB,oBACpB,mBACA,kBACA,WACA,QACe;AAEf,QAAM,eAAe,MAAM,OAAO,aAAa,WAAW;AAAA,IACxD,OAAO,EAAE,YAAY,kBAAkB;AAAA,EACzC,CAAC;AAED,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc,MAAM,OAAO,aAAa,WAAW;AAAA,IACvD,OAAO,EAAE,YAAY,iBAAiB;AAAA,EACxC,CAAC;AAED,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR,iBAAiB,gBAAgB;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa,cAAc,YAAY,WAAW;AACpD,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,CAAC,YAAY,UAAU;AACzB,UAAM,IAAI;AAAA,MACR,iBAAiB,gBAAgB;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,OAAO,cAAc,WAAW;AAAA,IACnD,OAAO;AAAA,MACL,qBAAqB;AAAA,QACnB,WAAW,aAAa;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,WAAW,OAAO,SAAS,SAAS;AACtD;AAAA,EACF;AAGA,MAAI,OAAO,iBAAiB;AAC1B;AAAA,EACF;AAGA,QAAM,aAAuB,MAAM,QAAQ,OAAO,eAAe,IAC7D,OAAO,kBACP,KAAK,MAAM,OAAO,OAAO,eAAe,CAAC;AAE7C,MAAI,CAAC,WAAW,SAAS,YAAY,EAAE,GAAG;AACxC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}