@shop-on/theme-server 0.1.0 → 0.2.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.
@@ -1,3 +1,4 @@
1
+ import { requestTraceHeaders } from "./trace.js";
1
2
  import { getStorefrontToken, storefrontApiUrl } from "./graphql.js";
2
3
  import { isIP } from "node:net";
3
4
  //#region src/analytics-proxy.ts
@@ -8,7 +9,8 @@ const PATHS = {
8
9
  async function proxyAnalytics({ endpoint, body, visitToken, visitorToken, userAgent, clientIp, fbp, fbc, eventSourceUrl }) {
9
10
  const headers = {
10
11
  Authorization: `Bearer ${await getStorefrontToken()}`,
11
- "Content-Type": "application/json"
12
+ "Content-Type": "application/json",
13
+ ...requestTraceHeaders()
12
14
  };
13
15
  if (visitToken) headers["Ahoy-Visit"] = visitToken;
14
16
  if (visitorToken) headers["Ahoy-Visitor"] = visitorToken;
@@ -1,10 +1,8 @@
1
- import { useSession } from "@tanstack/react-start/server";
1
+ import { SealedSession } from "@shop-on/customer-runtime/sealed-session";
2
2
  //#region src/cart-session.d.ts
3
3
  export interface CartSessionData {
4
4
  token?: string;
5
5
  expiresAtMs?: number;
6
6
  }
7
- type CartSession = ReturnType<typeof useSession<CartSessionData>>;
8
- export declare const useCartSession: () => CartSession;
9
- //#endregion
10
- export {};
7
+ export declare const useCartSession: () => Promise<SealedSession<CartSessionData>>;
8
+ //#endregion
@@ -22,8 +22,8 @@ const sessionConfig = createServerOnlyFn(async () => {
22
22
  };
23
23
  });
24
24
  const useCartSession = createServerOnlyFn(async () => {
25
- const { useSession } = await import("@tanstack/react-start/server");
26
- return useSession(await sessionConfig());
25
+ const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
26
+ return openSession(await sessionConfig());
27
27
  });
28
28
  //#endregion
29
29
  export { useCartSession };
@@ -22,8 +22,8 @@ const sessionConfig = createServerOnlyFn(async () => {
22
22
  };
23
23
  });
24
24
  const loadSession = createServerOnlyFn(async () => {
25
- const { useSession } = await import("@tanstack/react-start/server");
26
- return useSession(await sessionConfig());
25
+ const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
26
+ return openSession(await sessionConfig());
27
27
  });
28
28
  const readCustomerSession = createServerOnlyFn(async () => {
29
29
  try {
@@ -2158,6 +2158,22 @@ export type introspection_types = {
2158
2158
  ofType: null;
2159
2159
  };
2160
2160
  defaultValue: null;
2161
+ }, {
2162
+ name: 'latitude';
2163
+ type: {
2164
+ kind: 'SCALAR';
2165
+ name: 'Float';
2166
+ ofType: null;
2167
+ };
2168
+ defaultValue: null;
2169
+ }, {
2170
+ name: 'longitude';
2171
+ type: {
2172
+ kind: 'SCALAR';
2173
+ name: 'Float';
2174
+ ofType: null;
2175
+ };
2176
+ defaultValue: null;
2161
2177
  }, {
2162
2178
  name: 'phone';
2163
2179
  type: {
@@ -2942,6 +2958,22 @@ export type introspection_types = {
2942
2958
  ofType: null;
2943
2959
  };
2944
2960
  };
2961
+ 'latitude': {
2962
+ name: 'latitude';
2963
+ type: {
2964
+ kind: 'SCALAR';
2965
+ name: 'Float';
2966
+ ofType: null;
2967
+ };
2968
+ };
2969
+ 'longitude': {
2970
+ name: 'longitude';
2971
+ type: {
2972
+ kind: 'SCALAR';
2973
+ name: 'Float';
2974
+ ofType: null;
2975
+ };
2976
+ };
2945
2977
  'phone': {
2946
2978
  name: 'phone';
2947
2979
  type: {
@@ -3654,6 +3686,10 @@ export type introspection_types = {
3654
3686
  };
3655
3687
  };
3656
3688
  };
3689
+ 'CourierPromptEnum': {
3690
+ name: 'CourierPromptEnum';
3691
+ enumValues: 'PHONE' | 'PIN' | 'UNAVAILABLE';
3692
+ };
3657
3693
  'CropRegionEnum': {
3658
3694
  name: 'CropRegionEnum';
3659
3695
  enumValues: 'BOTTOM' | 'CENTER' | 'LEFT' | 'RIGHT' | 'TOP';
@@ -6583,6 +6619,22 @@ export type introspection_types = {
6583
6619
  ofType: null;
6584
6620
  };
6585
6621
  };
6622
+ 'latitude': {
6623
+ name: 'latitude';
6624
+ type: {
6625
+ kind: 'SCALAR';
6626
+ name: 'Float';
6627
+ ofType: null;
6628
+ };
6629
+ };
6630
+ 'longitude': {
6631
+ name: 'longitude';
6632
+ type: {
6633
+ kind: 'SCALAR';
6634
+ name: 'Float';
6635
+ ofType: null;
6636
+ };
6637
+ };
6586
6638
  'phone': {
6587
6639
  name: 'phone';
6588
6640
  type: {
@@ -10473,6 +10525,14 @@ export type introspection_types = {
10473
10525
  ofType: null;
10474
10526
  };
10475
10527
  };
10528
+ 'deliveryCourierPrompt': {
10529
+ name: 'deliveryCourierPrompt';
10530
+ type: {
10531
+ kind: 'ENUM';
10532
+ name: 'CourierPromptEnum';
10533
+ ofType: null;
10534
+ };
10535
+ };
10476
10536
  'deliveryOptionHandle': {
10477
10537
  name: 'deliveryOptionHandle';
10478
10538
  type: {
@@ -11877,6 +11937,18 @@ export type introspection_types = {
11877
11937
  };
11878
11938
  };
11879
11939
  };
11940
+ 'cashOnDeliveryAllowed': {
11941
+ name: 'cashOnDeliveryAllowed';
11942
+ type: {
11943
+ kind: 'NON_NULL';
11944
+ name: never;
11945
+ ofType: {
11946
+ kind: 'SCALAR';
11947
+ name: 'Boolean';
11948
+ ofType: null;
11949
+ };
11950
+ };
11951
+ };
11880
11952
  'handle': {
11881
11953
  name: 'handle';
11882
11954
  type: {
@@ -12196,6 +12268,14 @@ export type introspection_types = {
12196
12268
  ofType: null;
12197
12269
  };
12198
12270
  };
12271
+ 'totalAmount': {
12272
+ name: 'totalAmount';
12273
+ type: {
12274
+ kind: 'SCALAR';
12275
+ name: 'Decimal';
12276
+ ofType: null;
12277
+ };
12278
+ };
12199
12279
  'updatedAt': {
12200
12280
  name: 'updatedAt';
12201
12281
  type: {
@@ -15726,6 +15806,18 @@ export type introspection_types = {
15726
15806
  };
15727
15807
  };
15728
15808
  };
15809
+ 'cashOnDeliveryAllowed': {
15810
+ name: 'cashOnDeliveryAllowed';
15811
+ type: {
15812
+ kind: 'NON_NULL';
15813
+ name: never;
15814
+ ofType: {
15815
+ kind: 'SCALAR';
15816
+ name: 'Boolean';
15817
+ ofType: null;
15818
+ };
15819
+ };
15820
+ };
15729
15821
  'handle': {
15730
15822
  name: 'handle';
15731
15823
  type: {
@@ -17848,6 +17940,18 @@ export type introspection_types = {
17848
17940
  };
17849
17941
  };
17850
17942
  };
17943
+ 'cashOnDeliveryAllowed': {
17944
+ name: 'cashOnDeliveryAllowed';
17945
+ type: {
17946
+ kind: 'NON_NULL';
17947
+ name: never;
17948
+ ofType: {
17949
+ kind: 'SCALAR';
17950
+ name: 'Boolean';
17951
+ ofType: null;
17952
+ };
17953
+ };
17954
+ };
17851
17955
  'deliveryDetails': {
17852
17956
  name: 'deliveryDetails';
17853
17957
  type: {
@@ -20026,6 +20130,14 @@ export type introspection_types = {
20026
20130
  name: 'ThemeFilter';
20027
20131
  isOneOf: false;
20028
20132
  inputFields: [{
20133
+ name: 'createdAt';
20134
+ type: {
20135
+ kind: 'INPUT_OBJECT';
20136
+ name: 'DateFilter';
20137
+ ofType: null;
20138
+ };
20139
+ defaultValue: null;
20140
+ }, {
20029
20141
  name: 'handleCont';
20030
20142
  type: {
20031
20143
  kind: 'SCALAR';
@@ -20057,6 +20169,30 @@ export type introspection_types = {
20057
20169
  ofType: null;
20058
20170
  };
20059
20171
  defaultValue: null;
20172
+ }, {
20173
+ name: 'query';
20174
+ type: {
20175
+ kind: 'INPUT_OBJECT';
20176
+ name: 'StringFilter';
20177
+ ofType: null;
20178
+ };
20179
+ defaultValue: null;
20180
+ }, {
20181
+ name: 'status';
20182
+ type: {
20183
+ kind: 'INPUT_OBJECT';
20184
+ name: 'EnumStringFilter';
20185
+ ofType: null;
20186
+ };
20187
+ defaultValue: null;
20188
+ }, {
20189
+ name: 'updatedAt';
20190
+ type: {
20191
+ kind: 'INPUT_OBJECT';
20192
+ name: 'DateFilter';
20193
+ ofType: null;
20194
+ };
20195
+ defaultValue: null;
20060
20196
  }];
20061
20197
  };
20062
20198
  'ThemePreset': {
package/dist/graphql.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createLease } from "./lease.js";
2
2
  import { requestSlot } from "./request-scope.js";
3
+ import { requestTraceHeaders } from "./trace.js";
3
4
  import { initGraphQLTada } from "gql.tada";
4
5
  import { Kind, print } from "graphql";
5
6
  import { ClientError, GraphQLClient } from "graphql-request";
@@ -145,17 +146,18 @@ function extraHeaders(options) {
145
146
  if (options?.acceptLanguage) extra["Accept-Language"] = options.acceptLanguage;
146
147
  return extra;
147
148
  }
148
- function bearerHeaders(token, options) {
149
+ function bearerHeaders(token, options, trace) {
149
150
  return {
150
151
  Authorization: `Bearer ${token}`,
151
- ...extraHeaders(options)
152
+ ...extraHeaders(options),
153
+ ...trace
152
154
  };
153
155
  }
154
156
  function invalidateToken(used) {
155
157
  if (cached?.accessToken === used) cached = null;
156
158
  }
157
- async function executeSingle(document, variables, options) {
158
- const send = async (token) => await buildClient(bearerHeaders(token, options)).request(document, variables);
159
+ async function executeSingle(document, variables, options, trace) {
160
+ const send = async (token) => await buildClient(bearerHeaders(token, options, trace)).request(document, variables);
159
161
  const token = await getToken();
160
162
  try {
161
163
  return await send(token);
@@ -179,11 +181,12 @@ function enqueueBatched(queues, query, variables, options) {
179
181
  let queue = queues.get(key);
180
182
  if (!queue) {
181
183
  const scheduled = [];
184
+ const trace = requestTraceHeaders();
182
185
  queue = scheduled;
183
186
  queues.set(key, scheduled);
184
187
  setTimeout(() => {
185
188
  queues.delete(key);
186
- flushBatch(scheduled, options);
189
+ flushBatch(scheduled, options, trace);
187
190
  }, 0);
188
191
  }
189
192
  queue.push({
@@ -194,15 +197,15 @@ function enqueueBatched(queues, query, variables, options) {
194
197
  });
195
198
  });
196
199
  }
197
- async function flushBatch(entries, options) {
200
+ async function flushBatch(entries, options, trace) {
198
201
  const chunks = [];
199
202
  for (let i = 0; i < entries.length; i += BATCH_MAX_QUERIES) chunks.push(entries.slice(i, i + BATCH_MAX_QUERIES));
200
203
  await Promise.all(chunks.map(async (chunk) => {
201
204
  try {
202
205
  if (chunk.length === 1) {
203
206
  const only = chunk[0];
204
- only.resolve(await executeSingle(only.query, only.variables, options));
205
- } else await executeBatchChunk(chunk, options);
207
+ only.resolve(await executeSingle(only.query, only.variables, options, trace));
208
+ } else await executeBatchChunk(chunk, options, trace);
206
209
  } catch (err) {
207
210
  for (const entry of chunk) entry.reject(err);
208
211
  }
@@ -223,7 +226,7 @@ async function postWithAuthRetry(buildInit) {
223
226
  invalidateToken(token);
224
227
  return await post(await getToken());
225
228
  }
226
- async function executeBatchChunk(chunk, options) {
229
+ async function executeBatchChunk(chunk, options, trace) {
227
230
  const body = JSON.stringify(chunk.map(({ query, variables }) => ({
228
231
  query,
229
232
  variables
@@ -231,7 +234,7 @@ async function executeBatchChunk(chunk, options) {
231
234
  const res = await postWithAuthRetry((token) => ({
232
235
  headers: {
233
236
  "Content-Type": "application/json",
234
- ...bearerHeaders(token, options)
237
+ ...bearerHeaders(token, options, trace)
235
238
  },
236
239
  body
237
240
  }));
@@ -266,7 +269,7 @@ async function executeBatchChunk(chunk, options) {
266
269
  async function request(document, variables, options) {
267
270
  assertServerOnly();
268
271
  const queues = isBatchableQuery(document) ? requestSlot(BATCH_QUEUES, () => /* @__PURE__ */ new Map()) : void 0;
269
- if (!queues) return await executeSingle(document, variables, options);
272
+ if (!queues) return await executeSingle(document, variables, options, requestTraceHeaders());
270
273
  return await enqueueBatched(queues, print(document), variables, options);
271
274
  }
272
275
  /**
@@ -285,6 +288,7 @@ async function request(document, variables, options) {
285
288
  */
286
289
  async function requestUpload(document, variables, files, filesPath, options) {
287
290
  assertServerOnly();
291
+ const trace = requestTraceHeaders();
288
292
  const query = print(document);
289
293
  const map = {};
290
294
  files.forEach((_, i) => {
@@ -305,7 +309,7 @@ async function requestUpload(document, variables, files, filesPath, options) {
305
309
  return form;
306
310
  };
307
311
  const res = await postWithAuthRetry((token) => ({
308
- headers: bearerHeaders(token, options),
312
+ headers: bearerHeaders(token, options, trace),
309
313
  body: buildBody()
310
314
  }));
311
315
  if (!res.ok) throw batchClientError({
@@ -22,8 +22,8 @@ const sessionConfig = createServerOnlyFn(async () => {
22
22
  };
23
23
  });
24
24
  const loadSession = createServerOnlyFn(async () => {
25
- const { useSession } = await import("@tanstack/react-start/server");
26
- return useSession(await sessionConfig());
25
+ const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
26
+ return openSession(await sessionConfig());
27
27
  });
28
28
  const readPasswordSession = createServerOnlyFn(async () => {
29
29
  try {
@@ -714,6 +714,16 @@ input CheckoutAddressInput {
714
714
  firstName: String
715
715
  khorooId: ID
716
716
  lastName: String
717
+
718
+ """
719
+ Delivery pin. Send both or neither.
720
+ """
721
+ latitude: Float
722
+
723
+ """
724
+ Delivery pin. Send both or neither.
725
+ """
726
+ longitude: Float
717
727
  phone: String
718
728
  provinceId: ID
719
729
  zip: String
@@ -963,6 +973,8 @@ type CheckoutShippingAddress {
963
973
  formatted(locale: String): [String!]!
964
974
  khoroo: AddressKhoroo
965
975
  lastName: String
976
+ latitude: Float
977
+ longitude: Float
966
978
  phone: String
967
979
  province: AddressProvince
968
980
  provinceCode: String
@@ -1346,6 +1358,26 @@ type CourierDriverLoginPayload {
1346
1358
  userErrors: [UserError!]!
1347
1359
  }
1348
1360
 
1361
+ """
1362
+ What the buyer must do, or wait for, to see a delivery a courier prices.
1363
+ """
1364
+ enum CourierPromptEnum {
1365
+ """
1366
+ Add a phone number the courier can call.
1367
+ """
1368
+ PHONE
1369
+
1370
+ """
1371
+ Drop a pin on the map: the courier prices from it.
1372
+ """
1373
+ PIN
1374
+
1375
+ """
1376
+ The courier cannot be reached right now; choose another delivery.
1377
+ """
1378
+ UNAVAILABLE
1379
+ }
1380
+
1349
1381
  """
1350
1382
  Crop region for image transforms
1351
1383
  """
@@ -2869,6 +2901,8 @@ type OrderShippingAddress {
2869
2901
  formatted(locale: String): [String!]!
2870
2902
  khoroo: AddressKhoroo
2871
2903
  lastName: String
2904
+ latitude: Float
2905
+ longitude: Float
2872
2906
  phone: String
2873
2907
  province: AddressProvince
2874
2908
  provinceCode: String
@@ -4432,8 +4466,8 @@ type Shop implements Node {
4432
4466
 
4433
4467
  """
4434
4468
  Whether this shop issues eBarimt fiscal receipts (a fiscal engine is enabled).
4435
- Buyer surfaces read this to decide whether to ask the Иргэн / Байгууллага
4436
- receipt question at checkout.
4469
+ Buyer surfaces and the register read this to decide whether to ask the Иргэн /
4470
+ Байгууллага receipt question at checkout.
4437
4471
  """
4438
4472
  issuesFiscalReceipts: Boolean!
4439
4473
  khoroo: AddressKhoroo
@@ -5062,6 +5096,13 @@ type ShopCheckout implements Node {
5062
5096
  completedAt: ISO8601DateTime
5063
5097
  createdAt: ISO8601DateTime!
5064
5098
  customer: ShopCustomer
5099
+
5100
+ """
5101
+ What stands between the buyer and a delivery a courier prices (drop a pin, add
5102
+ a phone, or the courier is unavailable). Null when nothing does, or no
5103
+ shipping address is set yet.
5104
+ """
5105
+ deliveryCourierPrompt: CourierPromptEnum
5065
5106
  deliveryOptionHandle: String
5066
5107
 
5067
5108
  """
@@ -5597,6 +5638,11 @@ A storefront delivery option.
5597
5638
  interface ShopDeliveryOption {
5598
5639
  amount: Money!
5599
5640
 
5641
+ """
5642
+ False for a delivery a courier prices: the courier carries paid orders only, so the buyer pays online.
5643
+ """
5644
+ cashOnDeliveryAllowed: Boolean!
5645
+
5600
5646
  """
5601
5647
  Stable identifier passed back via deliveryOptionHandle to select this option.
5602
5648
  """
@@ -5861,6 +5907,13 @@ type ShopFiscalReceipt {
5861
5907
  which is the only one `ShopOrder.fiscalReceipt` ever returns.
5862
5908
  """
5863
5909
  supersededAt: ISO8601DateTime
5910
+
5911
+ """
5912
+ What the receipt declared to the tax authority — less than the order total
5913
+ when gift cards paid part of it. With the lottery number, it is what the buyer
5914
+ registers the receipt by hand with when the QR cannot be scanned. Null until issued.
5915
+ """
5916
+ totalAmount: Decimal
5864
5917
  updatedAt: ISO8601DateTime!
5865
5918
  }
5866
5919
 
@@ -6827,9 +6880,10 @@ type ShopOrder implements Node {
6827
6880
 
6828
6881
  """
6829
6882
  How the buyer is paying the balance — the oldest non-credit payment row’s
6830
- tender (`manual`, `cash_on_delivery`, `cody_pay`). Null when store credit /
6831
- gift cards covered the whole order and no such row exists. Buyer surfaces read
6832
- it to tell a cash-on-delivery order from a gateway one on the thanks page.
6883
+ tender (`manual`, `cash_on_delivery`, `cash`, `bank_card`, `bank_transfer`,
6884
+ `cody_pay`). Null when store credit / gift cards covered the whole order and
6885
+ no such row exists. Buyer surfaces read it to tell a cash-on-delivery order
6886
+ from a gateway one on the thanks page.
6833
6887
  """
6834
6888
  paymentMethod: String
6835
6889
  phone: String
@@ -7299,6 +7353,7 @@ type ShopPickupDeliveryOption implements ShopDeliveryOption {
7299
7353
  """
7300
7354
  addressLines: [String!]!
7301
7355
  amount: Money!
7356
+ cashOnDeliveryAllowed: Boolean!
7302
7357
  handle: String!
7303
7358
  instructions: String!
7304
7359
  kind: ShopDeliveryKindEnum!
@@ -8303,6 +8358,7 @@ A storefront ship delivery option (quote result).
8303
8358
  """
8304
8359
  type ShopShipDeliveryOption implements ShopDeliveryOption {
8305
8360
  amount: Money!
8361
+ cashOnDeliveryAllowed: Boolean!
8306
8362
  deliveryDetails: String
8307
8363
  handle: String!
8308
8364
  kind: ShopDeliveryKindEnum!
@@ -9099,10 +9155,29 @@ type ThemeEdge {
9099
9155
  }
9100
9156
 
9101
9157
  input ThemeFilter {
9158
+ """
9159
+ Bounds on the creation time.
9160
+ """
9161
+ createdAt: DateFilter
9102
9162
  handleCont: String
9103
9163
  priceEq: Float
9104
9164
  priceGteq: Float
9105
9165
  priceLteq: Float
9166
+
9167
+ """
9168
+ Match against the theme's name or its handle.
9169
+ """
9170
+ query: StringFilter
9171
+
9172
+ """
9173
+ Predicates on the status, keyed by its lowercase label ("draft", "published", "archived").
9174
+ """
9175
+ status: EnumStringFilter
9176
+
9177
+ """
9178
+ Bounds on the last-update time.
9179
+ """
9180
+ updatedAt: DateFilter
9106
9181
  }
9107
9182
 
9108
9183
  """
package/dist/trace.js ADDED
@@ -0,0 +1,5 @@
1
+ import { traceHeaders } from "@shop-on/customer-runtime/trace";
2
+ //#region src/trace.ts
3
+ const requestTraceHeaders = () => traceHeaders({ continueIncoming: true });
4
+ //#endregion
5
+ export { requestTraceHeaders };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shop-on/theme-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "SEE LICENSE IN dist/LICENSE.md",
5
5
  "files": [
6
6
  "dist"
@@ -54,24 +54,24 @@
54
54
  "./package.json": "./package.json"
55
55
  },
56
56
  "dependencies": {
57
- "gql.tada": "1.11.3",
57
+ "gql.tada": "1.11.4",
58
58
  "graphql": "17.0.2",
59
59
  "graphql-request": "7.4.0",
60
- "liquidjs": "10.29.0",
61
- "@shop-on/customer-runtime": "0.1.0",
62
- "@shop-on/theme-runtime": "0.1.0"
60
+ "liquidjs": "10.30.0",
61
+ "@shop-on/theme-runtime": "0.2.0",
62
+ "@shop-on/customer-runtime": "0.2.0"
63
63
  },
64
64
  "devDependencies": {
65
- "@tanstack/react-start": "1.168.60",
66
- "@types/node": "26.6.3",
65
+ "@tanstack/react-start": "1.169.3",
66
+ "@types/node": "26.6.5",
67
67
  "typescript": "7.0.2",
68
- "vitest": "5.0.3"
68
+ "vitest": "5.0.5"
69
69
  },
70
70
  "peerDependencies": {
71
- "@tanstack/react-start": "~1.168.60"
71
+ "@tanstack/react-start": "~1.169.3"
72
72
  },
73
73
  "scripts": {
74
74
  "check-types": "tsc --noEmit",
75
- "test": "vitest run"
75
+ "test": "vitest run --maxWorkers=2"
76
76
  }
77
77
  }