merchi_sdk_ts 1.8.1 → 1.9.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.
@@ -49,57 +49,6 @@ var Domain = /** @class */ (function (_super) {
49
49
  }
50
50
  return _this.activeTheme;
51
51
  };
52
- _this.getDomainId = function () {
53
- if (_this.id === undefined || _this.id === null) {
54
- throw new Error('id is undefined, did you forget to set it?');
55
- }
56
- return _this.id;
57
- };
58
- _this.storefrontV2DomainResource = function (suffix) {
59
- if (suffix === void 0) { suffix = ''; }
60
- return "/domains/".concat(_this.getDomainId(), "/storefront_v2/").concat(suffix);
61
- };
62
- _this.storefrontV2Request = function (resource, method, payload) {
63
- var fetchOptions = {
64
- method: method,
65
- query: [['skip_rights', 'y']]
66
- };
67
- if (payload !== undefined) {
68
- fetchOptions.body = JSON.stringify(payload);
69
- fetchOptions.headers = { 'Content-Type': 'application/json' };
70
- }
71
- return _this.merchi.authenticatedFetch(resource, fetchOptions);
72
- };
73
- _this.getStorefrontV2 = function () {
74
- return _this.storefrontV2Request(_this.storefrontV2DomainResource(), 'GET');
75
- };
76
- _this.provisionStorefrontV2 = function (payload) {
77
- return _this.storefrontV2Request(_this.storefrontV2DomainResource('provision/'), 'POST', payload);
78
- };
79
- _this.createStorefrontChangeRequest = function (payload) {
80
- return _this.storefrontV2Request(_this.storefrontV2DomainResource('requests/'), 'POST', payload);
81
- };
82
- _this.getStorefrontChangeRequest = function (requestId) {
83
- return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/"), 'GET');
84
- };
85
- _this.runStorefrontChangeRequest = function (requestId, payload) {
86
- return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/run/"), 'POST', payload);
87
- };
88
- _this.approveStorefrontChangeRequest = function (requestId, payload) {
89
- return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/approve/"), 'POST', payload);
90
- };
91
- _this.rejectStorefrontChangeRequest = function (requestId, payload) {
92
- return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/reject/"), 'POST', payload);
93
- };
94
- _this.getStorefrontV2Deployments = function () {
95
- return _this.storefrontV2Request(_this.storefrontV2DomainResource('deployments/'), 'GET');
96
- };
97
- _this.getStorefrontV2DeploymentLogs = function (deploymentId) {
98
- return _this.storefrontV2Request(_this.storefrontV2DomainResource("deployments/".concat(String(deploymentId), "/logs/")), 'GET');
99
- };
100
- _this.rollbackStorefrontV2 = function (payload) {
101
- return _this.storefrontV2Request(_this.storefrontV2DomainResource('rollback/'), 'POST', payload);
102
- };
103
52
  return _this;
104
53
  }
105
54
  Domain.resourceName = 'domains';
@@ -265,6 +214,18 @@ var Domain = /** @class */ (function (_super) {
265
214
  Domain.property({ type: String }),
266
215
  __metadata("design:type", String)
267
216
  ], Domain.prototype, "googleMerchantId", void 0);
217
+ __decorate([
218
+ Domain.property({ type: Boolean }),
219
+ __metadata("design:type", Boolean)
220
+ ], Domain.prototype, "googleProductReviewsFeedEnabled", void 0);
221
+ __decorate([
222
+ Domain.property({ type: String }),
223
+ __metadata("design:type", Object)
224
+ ], Domain.prototype, "googleProductReviewsFeedToken", void 0);
225
+ __decorate([
226
+ Domain.property({ type: String }),
227
+ __metadata("design:type", Object)
228
+ ], Domain.prototype, "googleProductReviewsFeedUrl", void 0);
268
229
  __decorate([
269
230
  Domain.property({ type: String }),
270
231
  __metadata("design:type", Object)
@@ -66,129 +66,3 @@ test('fail to delete non-existant domain', function () {
66
66
  expect(fetch.mock.calls[0][1].method).toBe('DELETE');
67
67
  return invocation.catch(function (e) { return expect(e.statusCode).toEqual(404); });
68
68
  });
69
- test('can get storefront v2 config', function () {
70
- var merchi = new Merchi();
71
- var domain = new merchi.Domain();
72
- domain.id = 42;
73
- var fetch = mockFetch(true, {}, 200);
74
- domain.getStorefrontV2();
75
- var fetchUrl = fetch.mock.calls[0][0];
76
- var query = fetch.mock.calls[0][1].query;
77
- expect(fetch.mock.calls[0][1].method).toBe('GET');
78
- expect(fetchUrl).toContain('/domains/42/storefront_v2/');
79
- expect(query).toContainEqual(['skip_rights', 'y']);
80
- });
81
- test('can provision storefront v2', function () {
82
- var merchi = new Merchi();
83
- var domain = new merchi.Domain();
84
- domain.id = 42;
85
- var fetch = mockFetch(true, {}, 200);
86
- domain.provisionStorefrontV2({ force: true });
87
- var fetchUrl = fetch.mock.calls[0][0];
88
- var body = JSON.parse(fetch.mock.calls[0][1].body);
89
- expect(fetch.mock.calls[0][1].method).toBe('POST');
90
- expect(fetchUrl).toContain('/domains/42/storefront_v2/provision/');
91
- expect(body).toEqual({ force: true });
92
- });
93
- test('can create storefront v2 change request', function () {
94
- var merchi = new Merchi();
95
- var domain = new merchi.Domain();
96
- domain.id = 42;
97
- var fetch = mockFetch(true, {}, 200);
98
- domain.createStorefrontChangeRequest({ prompt: 'update hero' });
99
- var fetchUrl = fetch.mock.calls[0][0];
100
- var body = JSON.parse(fetch.mock.calls[0][1].body);
101
- expect(fetch.mock.calls[0][1].method).toBe('POST');
102
- expect(fetchUrl).toContain('/domains/42/storefront_v2/requests/');
103
- expect(body).toEqual({ prompt: 'update hero' });
104
- });
105
- test('can get storefront v2 change request by id', function () {
106
- var merchi = new Merchi();
107
- var domain = new merchi.Domain();
108
- var fetch = mockFetch(true, {}, 200);
109
- domain.getStorefrontChangeRequest(9);
110
- var fetchUrl = fetch.mock.calls[0][0];
111
- expect(fetch.mock.calls[0][1].method).toBe('GET');
112
- expect(fetchUrl).toContain('/storefront_change_requests/9/');
113
- });
114
- test('can run storefront v2 change request', function () {
115
- var merchi = new Merchi();
116
- var domain = new merchi.Domain();
117
- var fetch = mockFetch(true, {}, 200);
118
- domain.runStorefrontChangeRequest(9, {
119
- status: 'running',
120
- pullRequestNumber: 42,
121
- checksSummary: {
122
- overall: 'passing',
123
- counts: { total: 4, passed: 4, failed: 0, pending: 0, neutral: 0 },
124
- },
125
- checksUpdatedAt: '2026-05-15T00:00:00Z',
126
- });
127
- var fetchUrl = fetch.mock.calls[0][0];
128
- var body = JSON.parse(fetch.mock.calls[0][1].body);
129
- expect(fetch.mock.calls[0][1].method).toBe('POST');
130
- expect(fetchUrl).toContain('/storefront_change_requests/9/run/');
131
- expect(body).toEqual({
132
- status: 'running',
133
- pullRequestNumber: 42,
134
- checksSummary: {
135
- overall: 'passing',
136
- counts: { total: 4, passed: 4, failed: 0, pending: 0, neutral: 0 },
137
- },
138
- checksUpdatedAt: '2026-05-15T00:00:00Z',
139
- });
140
- });
141
- test('can approve storefront v2 change request', function () {
142
- var merchi = new Merchi();
143
- var domain = new merchi.Domain();
144
- var fetch = mockFetch(true, {}, 200);
145
- domain.approveStorefrontChangeRequest(9, { comment: 'looks good' });
146
- var fetchUrl = fetch.mock.calls[0][0];
147
- var body = JSON.parse(fetch.mock.calls[0][1].body);
148
- expect(fetch.mock.calls[0][1].method).toBe('POST');
149
- expect(fetchUrl).toContain('/storefront_change_requests/9/approve/');
150
- expect(body).toEqual({ comment: 'looks good' });
151
- });
152
- test('can reject storefront v2 change request', function () {
153
- var merchi = new Merchi();
154
- var domain = new merchi.Domain();
155
- var fetch = mockFetch(true, {}, 200);
156
- domain.rejectStorefrontChangeRequest(9, { reason: 'needs edits' });
157
- var fetchUrl = fetch.mock.calls[0][0];
158
- var body = JSON.parse(fetch.mock.calls[0][1].body);
159
- expect(fetch.mock.calls[0][1].method).toBe('POST');
160
- expect(fetchUrl).toContain('/storefront_change_requests/9/reject/');
161
- expect(body).toEqual({ reason: 'needs edits' });
162
- });
163
- test('can get storefront v2 deployments', function () {
164
- var merchi = new Merchi();
165
- var domain = new merchi.Domain();
166
- domain.id = 42;
167
- var fetch = mockFetch(true, {}, 200);
168
- domain.getStorefrontV2Deployments();
169
- var fetchUrl = fetch.mock.calls[0][0];
170
- expect(fetch.mock.calls[0][1].method).toBe('GET');
171
- expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/');
172
- });
173
- test('can get storefront v2 deployment logs', function () {
174
- var merchi = new Merchi();
175
- var domain = new merchi.Domain();
176
- domain.id = 42;
177
- var fetch = mockFetch(true, {}, 200);
178
- domain.getStorefrontV2DeploymentLogs('dep_123');
179
- var fetchUrl = fetch.mock.calls[0][0];
180
- expect(fetch.mock.calls[0][1].method).toBe('GET');
181
- expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/dep_123/logs/');
182
- });
183
- test('can rollback storefront v2 deployment', function () {
184
- var merchi = new Merchi();
185
- var domain = new merchi.Domain();
186
- domain.id = 42;
187
- var fetch = mockFetch(true, {}, 200);
188
- domain.rollbackStorefrontV2({ deploymentId: 'dep_123' });
189
- var fetchUrl = fetch.mock.calls[0][0];
190
- var body = JSON.parse(fetch.mock.calls[0][1].body);
191
- expect(fetch.mock.calls[0][1].method).toBe('POST');
192
- expect(fetchUrl).toContain('/domains/42/storefront_v2/rollback/');
193
- expect(body).toEqual({ deploymentId: 'dep_123' });
194
- });
@@ -19,3 +19,33 @@ test('can list notifications with notificationSender filter', function () {
19
19
  expect(query).toContainEqual(['notification_sender', '123']);
20
20
  });
21
21
  });
22
+ test('list forwards skipCount=true as skip_count query param', function () {
23
+ var merchi = new Merchi();
24
+ var fetch = mockFetch(true, {
25
+ 'notifications': [],
26
+ // backend returns ``available: null`` when skip_count was honoured
27
+ 'available': null,
28
+ 'count': 0
29
+ }, 200);
30
+ var options = { skipCount: true };
31
+ return merchi.Notification.list(options).then(function (response) {
32
+ var query = fetch.mock.calls[0][1]['query'];
33
+ expect(query).toContainEqual(['skip_count', 'true']);
34
+ // ``available`` must round-trip as ``null``; the typed metadata
35
+ // is now ``number | null``, so consumers can detect the absence.
36
+ expect(response.metadata.available).toBeNull();
37
+ });
38
+ });
39
+ test('list omits skip_count when skipCount is not set', function () {
40
+ var merchi = new Merchi();
41
+ var fetch = mockFetch(true, {
42
+ 'notifications': [],
43
+ 'available': 0,
44
+ 'count': 0
45
+ }, 200);
46
+ return merchi.Notification.list({}).then(function () {
47
+ var query = fetch.mock.calls[0][1]['query'];
48
+ var skipCountEntries = query.filter(function (entry) { return entry[0] === 'skip_count'; });
49
+ expect(skipCountEntries).toEqual([]);
50
+ });
51
+ });
package/dist/entity.js CHANGED
@@ -614,6 +614,14 @@ var Entity = /** @class */ (function () {
614
614
  if (options.limit !== undefined) {
615
615
  fetchOptions.query.push(['limit', options.limit.toString()]);
616
616
  }
617
+ if (options.skipCount !== undefined) {
618
+ // Snake-case is the canonical form on the server; the API
619
+ // also accepts ``skipCount`` via its camelCase alias, but
620
+ // matching the wire shape of the other ``*_only`` /
621
+ // ``*_filter`` flags keeps logs and traffic captures
622
+ // consistent.
623
+ fetchOptions.query.push(['skip_count', options.skipCount.toString()]);
624
+ }
617
625
  if (options.q !== undefined) {
618
626
  fetchOptions.query.push(['q', options.q]);
619
627
  }
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ import { Payment } from './entities/payment.js';
51
51
  import { PaymentDevice } from './entities/payment_device.js';
52
52
  import { PhoneNumber } from './entities/phone_number.js';
53
53
  import { Product } from './entities/product.js';
54
+ import { ProductReview } from './entities/product_review.js';
54
55
  import { ProductionComment } from './entities/production_comment.js';
55
56
  import { Quote } from './entities/quote.js';
56
57
  import { QuoteItem } from './entities/quote_item.js';
@@ -82,4 +83,4 @@ import * as constants from './constants/index.js';
82
83
  import * as request from './request.js';
83
84
  import * as util from './util/index.js';
84
85
  import { toastNotifications } from './toasts.js';
85
- export { constants, request, util, toastNotifications, Address, AgentConversation, AgentSkill, AgentSkillVersion, AgentSkillApproval, DomainChatSettings, SupportConversation, SupportMessage, Assignment, AutomaticPaymentRelationship, Bank, Cart, CartItem, CartShipmentGroup, CartShipmentQuote, Category, Company, CompanyInvitation, Component, ComponentTag, ComponentVersion, CountryTax, Discount, DiscountGroup, Domain, DomainInvitation, DomainTag, Draft, DraftComment, DraftTemplate, EmailAddress, EmailCounter, EnrolledDomain, Entity, ExchangeRate, InternalTag, Inventory, InventoryUnitVariation, Invoice, Item, Job, JobComment, MatchingInventory, Menu, MenuItem, Merchi, MerchiFile, Notification, Page, Payment, PaymentDevice, PhoneNumber, Product, ProductionComment, Quote, QuoteItem, Reminder, apiFetch, apiFetchWithProgress, Session, Shipment, ShipmentLog, ShipmentItem, ShipmentItemFulfillment, ShipmentMethod, ShipmentMethodVariation, ShortUrl, SubscriptionPlan, SupplyDomain, SystemRole, Theme, ThemeCssSetting, User, UserCompany, Variation, VariationField, VariationFieldsOption, VariationOption, VariationsGroup, generateUUID, getCookie };
86
+ export { constants, request, util, toastNotifications, Address, AgentConversation, AgentSkill, AgentSkillVersion, AgentSkillApproval, DomainChatSettings, SupportConversation, SupportMessage, Assignment, AutomaticPaymentRelationship, Bank, Cart, CartItem, CartShipmentGroup, CartShipmentQuote, Category, Company, CompanyInvitation, Component, ComponentTag, ComponentVersion, CountryTax, Discount, DiscountGroup, Domain, DomainInvitation, DomainTag, Draft, DraftComment, DraftTemplate, EmailAddress, EmailCounter, EnrolledDomain, Entity, ExchangeRate, InternalTag, Inventory, InventoryUnitVariation, Invoice, Item, Job, JobComment, MatchingInventory, Menu, MenuItem, Merchi, MerchiFile, Notification, Page, Payment, PaymentDevice, PhoneNumber, Product, ProductReview, ProductionComment, Quote, QuoteItem, Reminder, apiFetch, apiFetchWithProgress, Session, Shipment, ShipmentLog, ShipmentItem, ShipmentItemFulfillment, ShipmentMethod, ShipmentMethodVariation, ShortUrl, SubscriptionPlan, SupplyDomain, SystemRole, Theme, ThemeCssSetting, User, UserCompany, Variation, VariationField, VariationFieldsOption, VariationOption, VariationsGroup, generateUUID, getCookie };
package/dist/merchi.js CHANGED
@@ -20,6 +20,7 @@ import { VariationField } from './entities/variation_field.js';
20
20
  import { VariationOption } from './entities/variation_option.js';
21
21
  import { ProductionComment } from './entities/production_comment.js';
22
22
  import { Product } from './entities/product.js';
23
+ import { ProductReview } from './entities/product_review.js';
23
24
  import { InternalTag } from './entities/internal_tag.js';
24
25
  import { Inventory } from './entities/inventory.js';
25
26
  import { InventoryGroup } from './entities/inventory_group.js';
@@ -254,6 +255,7 @@ var Merchi = /** @class */ (function () {
254
255
  this.CountryTax = this.setupClass(CountryTax);
255
256
  this.ShortUrl = this.setupClass(ShortUrl);
256
257
  this.Product = this.setupClass(Product);
258
+ this.ProductReview = this.setupClass(ProductReview);
257
259
  this.SystemRole = this.setupClass(SystemRole);
258
260
  this.CartItem = this.setupClass(CartItem);
259
261
  this.UserCompany = this.setupClass(UserCompany);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merchi_sdk_ts",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "repository": "git@github.com:merchisdk/merchi_sdk_ts.git",
@@ -77,139 +77,3 @@ test('fail to delete non-existant domain', () => {
77
77
  expect(fetch.mock.calls[0][1].method).toBe('DELETE');
78
78
  return invocation.catch(e => expect(e.statusCode).toEqual(404));
79
79
  });
80
-
81
- test('can get storefront v2 config', () => {
82
- const merchi = new Merchi();
83
- const domain = new merchi.Domain();
84
- domain.id = 42;
85
- const fetch = mockFetch(true, {}, 200);
86
- domain.getStorefrontV2();
87
- const fetchUrl = fetch.mock.calls[0][0];
88
- const query = fetch.mock.calls[0][1].query;
89
- expect(fetch.mock.calls[0][1].method).toBe('GET');
90
- expect(fetchUrl).toContain('/domains/42/storefront_v2/');
91
- expect(query).toContainEqual(['skip_rights', 'y']);
92
- });
93
-
94
- test('can provision storefront v2', () => {
95
- const merchi = new Merchi();
96
- const domain = new merchi.Domain();
97
- domain.id = 42;
98
- const fetch = mockFetch(true, {}, 200);
99
- domain.provisionStorefrontV2({force: true});
100
- const fetchUrl = fetch.mock.calls[0][0];
101
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
102
- expect(fetch.mock.calls[0][1].method).toBe('POST');
103
- expect(fetchUrl).toContain('/domains/42/storefront_v2/provision/');
104
- expect(body).toEqual({force: true});
105
- });
106
-
107
- test('can create storefront v2 change request', () => {
108
- const merchi = new Merchi();
109
- const domain = new merchi.Domain();
110
- domain.id = 42;
111
- const fetch = mockFetch(true, {}, 200);
112
- domain.createStorefrontChangeRequest({prompt: 'update hero'});
113
- const fetchUrl = fetch.mock.calls[0][0];
114
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
115
- expect(fetch.mock.calls[0][1].method).toBe('POST');
116
- expect(fetchUrl).toContain('/domains/42/storefront_v2/requests/');
117
- expect(body).toEqual({prompt: 'update hero'});
118
- });
119
-
120
- test('can get storefront v2 change request by id', () => {
121
- const merchi = new Merchi();
122
- const domain = new merchi.Domain();
123
- const fetch = mockFetch(true, {}, 200);
124
- domain.getStorefrontChangeRequest(9);
125
- const fetchUrl = fetch.mock.calls[0][0];
126
- expect(fetch.mock.calls[0][1].method).toBe('GET');
127
- expect(fetchUrl).toContain('/storefront_change_requests/9/');
128
- });
129
-
130
- test('can run storefront v2 change request', () => {
131
- const merchi = new Merchi();
132
- const domain = new merchi.Domain();
133
- const fetch = mockFetch(true, {}, 200);
134
- domain.runStorefrontChangeRequest(9, {
135
- status: 'running',
136
- pullRequestNumber: 42,
137
- checksSummary: {
138
- overall: 'passing',
139
- counts: {total: 4, passed: 4, failed: 0, pending: 0, neutral: 0},
140
- },
141
- checksUpdatedAt: '2026-05-15T00:00:00Z',
142
- });
143
- const fetchUrl = fetch.mock.calls[0][0];
144
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
145
- expect(fetch.mock.calls[0][1].method).toBe('POST');
146
- expect(fetchUrl).toContain('/storefront_change_requests/9/run/');
147
- expect(body).toEqual({
148
- status: 'running',
149
- pullRequestNumber: 42,
150
- checksSummary: {
151
- overall: 'passing',
152
- counts: {total: 4, passed: 4, failed: 0, pending: 0, neutral: 0},
153
- },
154
- checksUpdatedAt: '2026-05-15T00:00:00Z',
155
- });
156
- });
157
-
158
- test('can approve storefront v2 change request', () => {
159
- const merchi = new Merchi();
160
- const domain = new merchi.Domain();
161
- const fetch = mockFetch(true, {}, 200);
162
- domain.approveStorefrontChangeRequest(9, {comment: 'looks good'});
163
- const fetchUrl = fetch.mock.calls[0][0];
164
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
165
- expect(fetch.mock.calls[0][1].method).toBe('POST');
166
- expect(fetchUrl).toContain('/storefront_change_requests/9/approve/');
167
- expect(body).toEqual({comment: 'looks good'});
168
- });
169
-
170
- test('can reject storefront v2 change request', () => {
171
- const merchi = new Merchi();
172
- const domain = new merchi.Domain();
173
- const fetch = mockFetch(true, {}, 200);
174
- domain.rejectStorefrontChangeRequest(9, {reason: 'needs edits'});
175
- const fetchUrl = fetch.mock.calls[0][0];
176
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
177
- expect(fetch.mock.calls[0][1].method).toBe('POST');
178
- expect(fetchUrl).toContain('/storefront_change_requests/9/reject/');
179
- expect(body).toEqual({reason: 'needs edits'});
180
- });
181
-
182
- test('can get storefront v2 deployments', () => {
183
- const merchi = new Merchi();
184
- const domain = new merchi.Domain();
185
- domain.id = 42;
186
- const fetch = mockFetch(true, {}, 200);
187
- domain.getStorefrontV2Deployments();
188
- const fetchUrl = fetch.mock.calls[0][0];
189
- expect(fetch.mock.calls[0][1].method).toBe('GET');
190
- expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/');
191
- });
192
-
193
- test('can get storefront v2 deployment logs', () => {
194
- const merchi = new Merchi();
195
- const domain = new merchi.Domain();
196
- domain.id = 42;
197
- const fetch = mockFetch(true, {}, 200);
198
- domain.getStorefrontV2DeploymentLogs('dep_123');
199
- const fetchUrl = fetch.mock.calls[0][0];
200
- expect(fetch.mock.calls[0][1].method).toBe('GET');
201
- expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/dep_123/logs/');
202
- });
203
-
204
- test('can rollback storefront v2 deployment', () => {
205
- const merchi = new Merchi();
206
- const domain = new merchi.Domain();
207
- domain.id = 42;
208
- const fetch = mockFetch(true, {}, 200);
209
- domain.rollbackStorefrontV2({deploymentId: 'dep_123'});
210
- const fetchUrl = fetch.mock.calls[0][0];
211
- const body = JSON.parse(fetch.mock.calls[0][1].body as string);
212
- expect(fetch.mock.calls[0][1].method).toBe('POST');
213
- expect(fetchUrl).toContain('/domains/42/storefront_v2/rollback/');
214
- expect(body).toEqual({deploymentId: 'dep_123'});
215
- });
@@ -21,43 +21,6 @@ import { DomainChatSettings } from './domain_chat_settings.js';
21
21
  import { Theme } from './theme.js';
22
22
  import { DomainType } from '../constants/domain_types.js';
23
23
  import { ShipmentMethod } from './shipment_method.js';
24
- import { RequestOptions } from '../request.js';
25
-
26
- export type StorefrontChangeRequestStatus =
27
- | 'created'
28
- | 'running'
29
- | 'preview_ready'
30
- | 'approved'
31
- | 'rejected';
32
-
33
- export interface StorefrontChecksSummary {
34
- overall?: 'passing' | 'failing' | 'pending' | 'unknown';
35
- statusState?: string;
36
- counts?: {
37
- total?: number;
38
- passed?: number;
39
- failed?: number;
40
- pending?: number;
41
- neutral?: number;
42
- };
43
- updatedAt?: string;
44
- }
45
-
46
- export interface StorefrontV2ChangeRequest {
47
- id: number;
48
- domainId: number;
49
- storefrontV2Id: number;
50
- status: StorefrontChangeRequestStatus;
51
- prompt: string;
52
- branchName?: string | null;
53
- commitSha?: string | null;
54
- pullRequestNumber?: number | null;
55
- previewUrl?: string | null;
56
- summary?: string | null;
57
- checksSummary?: StorefrontChecksSummary | null;
58
- checksUpdatedAt?: string | null;
59
- errorDetails?: string | null;
60
- }
61
24
 
62
25
  export class Domain extends Entity {
63
26
  protected static resourceName = 'domains';
@@ -184,6 +147,15 @@ export class Domain extends Entity {
184
147
  @Domain.property({type: String})
185
148
  public googleMerchantId?: string;
186
149
 
150
+ @Domain.property({type: Boolean})
151
+ public googleProductReviewsFeedEnabled?: boolean;
152
+
153
+ @Domain.property({type: String})
154
+ public googleProductReviewsFeedToken?: string | null;
155
+
156
+ @Domain.property({type: String})
157
+ public googleProductReviewsFeedUrl?: string | null;
158
+
187
159
  @Domain.property({type: String})
188
160
  public socialBitchute?: string | null;
189
161
 
@@ -319,117 +291,4 @@ export class Domain extends Entity {
319
291
  }
320
292
  return this.activeTheme!;
321
293
  };
322
-
323
- private getDomainId = () => {
324
- if (this.id === undefined || this.id === null) {
325
- throw new Error('id is undefined, did you forget to set it?');
326
- }
327
- return this.id;
328
- };
329
-
330
- private storefrontV2DomainResource = (suffix = '') => {
331
- return `/domains/${this.getDomainId()}/storefront_v2/${suffix}`;
332
- };
333
-
334
- private storefrontV2Request = (
335
- resource: string,
336
- method: 'GET' | 'POST',
337
- payload?: Record<string, any>
338
- ) => {
339
- const fetchOptions: RequestOptions = {
340
- method: method,
341
- query: [['skip_rights', 'y']]
342
- };
343
- if (payload !== undefined) {
344
- fetchOptions.body = JSON.stringify(payload);
345
- fetchOptions.headers = {'Content-Type': 'application/json'};
346
- }
347
- return this.merchi.authenticatedFetch(resource, fetchOptions);
348
- };
349
-
350
- public getStorefrontV2 = () => {
351
- return this.storefrontV2Request(this.storefrontV2DomainResource(), 'GET');
352
- };
353
-
354
- public provisionStorefrontV2 = (payload?: Record<string, any>) => {
355
- return this.storefrontV2Request(
356
- this.storefrontV2DomainResource('provision/'),
357
- 'POST',
358
- payload
359
- );
360
- };
361
-
362
- public createStorefrontChangeRequest = (
363
- payload?: Record<string, any>
364
- ): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
365
- return this.storefrontV2Request(
366
- this.storefrontV2DomainResource('requests/'),
367
- 'POST',
368
- payload
369
- ) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
370
- };
371
-
372
- public getStorefrontChangeRequest = (
373
- requestId: number | string
374
- ): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
375
- return this.storefrontV2Request(
376
- `/storefront_change_requests/${String(requestId)}/`,
377
- 'GET'
378
- ) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
379
- };
380
-
381
- public runStorefrontChangeRequest = (
382
- requestId: number | string,
383
- payload?: Record<string, any>
384
- ): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
385
- return this.storefrontV2Request(
386
- `/storefront_change_requests/${String(requestId)}/run/`,
387
- 'POST',
388
- payload
389
- ) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
390
- };
391
-
392
- public approveStorefrontChangeRequest = (
393
- requestId: number | string,
394
- payload?: Record<string, any>
395
- ): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
396
- return this.storefrontV2Request(
397
- `/storefront_change_requests/${String(requestId)}/approve/`,
398
- 'POST',
399
- payload
400
- ) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
401
- };
402
-
403
- public rejectStorefrontChangeRequest = (
404
- requestId: number | string,
405
- payload?: Record<string, any>
406
- ): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
407
- return this.storefrontV2Request(
408
- `/storefront_change_requests/${String(requestId)}/reject/`,
409
- 'POST',
410
- payload
411
- ) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
412
- };
413
-
414
- public getStorefrontV2Deployments = () => {
415
- return this.storefrontV2Request(
416
- this.storefrontV2DomainResource('deployments/'),
417
- 'GET'
418
- );
419
- };
420
-
421
- public getStorefrontV2DeploymentLogs = (deploymentId: number | string) => {
422
- return this.storefrontV2Request(
423
- this.storefrontV2DomainResource(`deployments/${String(deploymentId)}/logs/`),
424
- 'GET'
425
- );
426
- };
427
-
428
- public rollbackStorefrontV2 = (payload?: Record<string, any>) => {
429
- return this.storefrontV2Request(
430
- this.storefrontV2DomainResource('rollback/'),
431
- 'POST',
432
- payload
433
- );
434
- };
435
294
  }
@@ -22,3 +22,37 @@ test('can list notifications with notificationSender filter', () => {
22
22
  expect(query).toContainEqual(['notification_sender', '123']);
23
23
  });
24
24
  });
25
+
26
+ test('list forwards skipCount=true as skip_count query param', () => {
27
+ const merchi = new Merchi();
28
+ const fetch = mockFetch(true, {
29
+ 'notifications': [],
30
+ // backend returns ``available: null`` when skip_count was honoured
31
+ 'available': null,
32
+ 'count': 0
33
+ }, 200);
34
+ const options = { skipCount: true };
35
+ return merchi.Notification.list(options).then((response) => {
36
+ const query = fetch.mock.calls[0][1]['query'];
37
+ expect(query).toContainEqual(['skip_count', 'true']);
38
+ // ``available`` must round-trip as ``null``; the typed metadata
39
+ // is now ``number | null``, so consumers can detect the absence.
40
+ expect(response.metadata.available).toBeNull();
41
+ });
42
+ });
43
+
44
+ test('list omits skip_count when skipCount is not set', () => {
45
+ const merchi = new Merchi();
46
+ const fetch = mockFetch(true, {
47
+ 'notifications': [],
48
+ 'available': 0,
49
+ 'count': 0
50
+ }, 200);
51
+ return merchi.Notification.list({}).then(() => {
52
+ const query = fetch.mock.calls[0][1]['query'];
53
+ const skipCountEntries = query.filter(
54
+ (entry: [string, string]) => entry[0] === 'skip_count'
55
+ );
56
+ expect(skipCountEntries).toEqual([]);
57
+ });
58
+ });
@@ -0,0 +1,221 @@
1
+ import { Entity } from '../entity.js';
2
+ import type { RequestOptions } from '../request.js';
3
+ import type { Domain } from './domain.js';
4
+ import type { Job } from './job.js';
5
+ import type { Product } from './product.js';
6
+
7
+ /** Create payload; the API expects ``jobId`` — pass ``job`` with at least ``id``. */
8
+ export interface ProductReviewCreateInput {
9
+ job: { id: number };
10
+ rating: number;
11
+ title?: string | null;
12
+ content?: string | null;
13
+ }
14
+
15
+ export interface ProductReviewPatchInput {
16
+ status?: string;
17
+ rejectionReason?: string | null;
18
+ rating?: number;
19
+ title?: string | null;
20
+ content?: string | null;
21
+ }
22
+
23
+ /**
24
+ * Post-purchase product review. List/create are nested under ``/products/:id/reviews/``;
25
+ * updates use ``/product-reviews/:id/``. Do not use :meth:`Entity.create` / :meth:`Entity.list`
26
+ * for this resource.
27
+ *
28
+ * API responses use ``jobId``, ``productId``, and ``domainId``; this entity maps those onto
29
+ * ``job``, ``product``, and ``domain`` (minimal ``{ id }`` objects unless the API nests more).
30
+ */
31
+ export class ProductReview extends Entity {
32
+ protected static resourceName = 'product-reviews';
33
+ protected static singularName = 'productReview';
34
+ protected static pluralName = 'productReviews';
35
+
36
+ @ProductReview.property()
37
+ public id?: number;
38
+
39
+ @ProductReview.property({ type: 'Product' })
40
+ public product?: Product;
41
+
42
+ @ProductReview.property({ type: 'Domain' })
43
+ public domain?: Domain;
44
+
45
+ @ProductReview.property({ type: Number })
46
+ public authorUserId?: number;
47
+
48
+ @ProductReview.property({ type: String })
49
+ public authorName?: string | null;
50
+
51
+ @ProductReview.property({ type: 'Job' })
52
+ public job?: Job;
53
+
54
+ @ProductReview.property({ type: Number })
55
+ public rating?: number;
56
+
57
+ @ProductReview.property({ type: String })
58
+ public title?: string | null;
59
+
60
+ @ProductReview.property({ type: String })
61
+ public content?: string | null;
62
+
63
+ @ProductReview.property({ type: String })
64
+ public status?: string;
65
+
66
+ @ProductReview.property({ type: Number })
67
+ public purchaseInvoiceId?: number | null;
68
+
69
+ /** ISO 8601 string from the API. */
70
+ @ProductReview.property({ type: String })
71
+ public submittedAt?: string | null;
72
+
73
+ /** ISO 8601 string from the API. */
74
+ @ProductReview.property({ type: String })
75
+ public updatedAt?: string | null;
76
+
77
+ @ProductReview.property({ type: String })
78
+ public rejectionReason?: string | null;
79
+
80
+ /**
81
+ * Map flat API fields (``jobId``, ``productId``, ``domainId``) to nested
82
+ * ``job`` / ``product`` / ``domain`` objects before :meth:`Entity.fromJson`.
83
+ */
84
+ public static normalizeApiJson(json: any): any {
85
+ if (json === null || json === undefined || typeof json !== 'object') {
86
+ return json;
87
+ }
88
+ const row: any = { ...json };
89
+ if (row.jobId !== undefined && row.job === undefined) {
90
+ if (row.jobId != null) {
91
+ row.job = { id: row.jobId };
92
+ }
93
+ delete row.jobId;
94
+ }
95
+ if (row.productId !== undefined && row.product === undefined) {
96
+ if (row.productId != null) {
97
+ row.product = { id: row.productId };
98
+ }
99
+ delete row.productId;
100
+ }
101
+ if (row.domainId !== undefined && row.domain === undefined) {
102
+ if (row.domainId != null) {
103
+ row.domain = { id: row.domainId };
104
+ }
105
+ delete row.domainId;
106
+ }
107
+ return row;
108
+ }
109
+
110
+ /**
111
+ * Domain managers/admins: list reviews for a product.
112
+ */
113
+ public static listForProduct(
114
+ this: typeof ProductReview,
115
+ productId: number,
116
+ options?: { withRights?: boolean }
117
+ ): Promise<ProductReview[]> {
118
+ const Constructor = this;
119
+ const resource = `/products/${productId}/reviews/`;
120
+ const fetchOptions: RequestOptions = { method: 'GET' };
121
+ fetchOptions.query = [];
122
+ if (!(options && options.withRights)) {
123
+ fetchOptions.query.push(['skip_rights', 'y']);
124
+ }
125
+ return Constructor.merchi.authenticatedFetch(resource, fetchOptions).then(
126
+ (data: any) => {
127
+ const raw = data.productReviews ?? [];
128
+ return raw.map((row: any) => {
129
+ const e: ProductReview = new Constructor() as ProductReview;
130
+ e.fromJson(ProductReview.normalizeApiJson(row));
131
+ return e;
132
+ });
133
+ }
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Buyer: create a review for the catalog ``productId`` (master product).
139
+ * ``body.job.id`` must be a completed job whose line item resolves to this product.
140
+ */
141
+ public static createForProduct(
142
+ this: typeof ProductReview,
143
+ productId: number,
144
+ body: ProductReviewCreateInput,
145
+ options?: { withRights?: boolean }
146
+ ): Promise<ProductReview> {
147
+ const Constructor = this;
148
+ const resource = `/products/${productId}/reviews/`;
149
+ const wire: Record<string, unknown> = {
150
+ jobId: body.job.id,
151
+ rating: body.rating,
152
+ };
153
+ if (body.title !== undefined) {
154
+ wire.title = body.title;
155
+ }
156
+ if (body.content !== undefined) {
157
+ wire.content = body.content;
158
+ }
159
+ const fetchOptions: RequestOptions = {
160
+ method: 'POST',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify(wire),
163
+ };
164
+ fetchOptions.query = [];
165
+ if (!(options && options.withRights)) {
166
+ fetchOptions.query.push(['skip_rights', 'y']);
167
+ }
168
+ return Constructor.merchi.authenticatedFetch(resource, fetchOptions).then(
169
+ (data: any) => {
170
+ const e: ProductReview = new Constructor() as ProductReview;
171
+ e.fromJson(ProductReview.normalizeApiJson(data.productReview));
172
+ return e;
173
+ }
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Partial update (moderation or author edit). Uses ``multipart/form-data`` fields
179
+ * matching the Merchi API.
180
+ */
181
+ public patch(
182
+ payload: ProductReviewPatchInput,
183
+ options?: { withRights?: boolean }
184
+ ): Promise<this> {
185
+ const id = this.id;
186
+ if (id === undefined) {
187
+ throw new Error('ProductReview.id is required to patch');
188
+ }
189
+ const resource = `/product-reviews/${String(id)}/`;
190
+ const form = new FormData();
191
+ if (payload.status !== undefined) {
192
+ form.set('status', payload.status);
193
+ }
194
+ if (payload.rejectionReason !== undefined) {
195
+ form.set('rejectionReason', payload.rejectionReason ?? '');
196
+ }
197
+ if (payload.rating !== undefined) {
198
+ form.set('rating', String(payload.rating));
199
+ }
200
+ if (payload.title !== undefined) {
201
+ form.set('title', payload.title ?? '');
202
+ }
203
+ if (payload.content !== undefined) {
204
+ form.set('content', payload.content ?? '');
205
+ }
206
+ const fetchOptions: RequestOptions = { method: 'PATCH', body: form };
207
+ fetchOptions.query = [];
208
+ if (!(options && options.withRights)) {
209
+ fetchOptions.query.push(['skip_rights', 'y']);
210
+ }
211
+ const singularName = (this.constructor as typeof ProductReview)
212
+ .singularName;
213
+ return this.merchi.authenticatedFetch(resource, fetchOptions).then(
214
+ (data: any) => {
215
+ this.fromJson(ProductReview.normalizeApiJson(data[singularName]));
216
+ this.cleanDirty();
217
+ return this;
218
+ }
219
+ );
220
+ }
221
+ }
package/src/entity.ts CHANGED
@@ -159,11 +159,22 @@ interface ListOptions {
159
159
  teamOnly?: boolean;
160
160
  turnaroundTimeDays?: number;
161
161
  withRights?: boolean;
162
+ // Opt out of the unbounded ``SELECT count(*)`` the API runs to
163
+ // populate ``available``. When ``skipCount`` is true the response's
164
+ // ``available`` is ``null``. Use this whenever the caller only
165
+ // renders the visible page of rows (search modals, autocomplete,
166
+ // navbar dropdowns, infinite scroll, ...) and never reads
167
+ // ``metadata.available``. Paginated tables that show "Showing X
168
+ // of Y" or compute a page count must NOT set this.
169
+ skipCount?: boolean;
162
170
  }
163
171
 
164
172
  export interface ListMetadata {
165
173
  canCreate?: boolean;
166
- available: number;
174
+ // ``null`` when the caller passed ``skipCount: true``: the API
175
+ // intentionally skipped the ``COUNT(*)`` query. JSON consumers
176
+ // can detect the absence rather than mistaking it for zero.
177
+ available: number | null;
167
178
  count: number;
168
179
  limit: number;
169
180
  offset: number;
@@ -417,6 +428,16 @@ export class Entity {
417
428
  if (options.limit !== undefined) {
418
429
  fetchOptions.query.push(['limit', options.limit.toString()]);
419
430
  }
431
+ if (options.skipCount !== undefined) {
432
+ // Snake-case is the canonical form on the server; the API
433
+ // also accepts ``skipCount`` via its camelCase alias, but
434
+ // matching the wire shape of the other ``*_only`` /
435
+ // ``*_filter`` flags keeps logs and traffic captures
436
+ // consistent.
437
+ fetchOptions.query.push(
438
+ ['skip_count', options.skipCount.toString()]
439
+ );
440
+ }
420
441
  if (options.q !== undefined) {
421
442
  fetchOptions.query.push(['q', options.q]);
422
443
  }
package/src/index.ts CHANGED
@@ -51,6 +51,11 @@ import { Payment } from './entities/payment.js';
51
51
  import { PaymentDevice } from './entities/payment_device.js';
52
52
  import { PhoneNumber } from './entities/phone_number.js';
53
53
  import { Product } from './entities/product.js';
54
+ import { ProductReview } from './entities/product_review.js';
55
+ export type {
56
+ ProductReviewCreateInput,
57
+ ProductReviewPatchInput,
58
+ } from './entities/product_review.js';
54
59
  import { ProductionComment } from './entities/production_comment.js';
55
60
  import { Quote } from './entities/quote.js';
56
61
  import { QuoteItem } from './entities/quote_item.js';
@@ -142,6 +147,7 @@ export {
142
147
  PaymentDevice,
143
148
  PhoneNumber,
144
149
  Product,
150
+ ProductReview,
145
151
  ProductionComment,
146
152
  Quote,
147
153
  QuoteItem,
package/src/merchi.ts CHANGED
@@ -14,6 +14,7 @@ import { VariationField } from './entities/variation_field.js';
14
14
  import { VariationOption } from './entities/variation_option.js';
15
15
  import { ProductionComment } from './entities/production_comment.js';
16
16
  import { Product } from './entities/product.js';
17
+ import { ProductReview } from './entities/product_review.js';
17
18
  import { InternalTag } from './entities/internal_tag.js';
18
19
  import { Inventory } from './entities/inventory.js';
19
20
  import { InventoryGroup } from './entities/inventory_group.js';
@@ -155,6 +156,7 @@ export class Merchi {
155
156
  public Job: typeof Job;
156
157
  public DomainInvitation: typeof DomainInvitation;
157
158
  public Product: typeof Product;
159
+ public ProductReview: typeof ProductReview;
158
160
  public DomainTag: typeof DomainTag;
159
161
  public EmailAddress: typeof EmailAddress;
160
162
  public PhoneNumber: typeof PhoneNumber;
@@ -330,6 +332,9 @@ export class Merchi {
330
332
  this.CountryTax = this.setupClass(CountryTax) as typeof CountryTax;
331
333
  this.ShortUrl = this.setupClass(ShortUrl) as typeof ShortUrl;
332
334
  this.Product = this.setupClass(Product) as typeof Product;
335
+ this.ProductReview = this.setupClass(
336
+ ProductReview
337
+ ) as typeof ProductReview;
333
338
  this.SystemRole = this.setupClass(SystemRole) as typeof SystemRole;
334
339
  this.CartItem = this.setupClass(CartItem) as typeof CartItem;
335
340
  this.UserCompany = this.setupClass(UserCompany) as typeof UserCompany;