@labdigital/commercetools-mock 0.9.1 → 0.10.1

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 (108) hide show
  1. package/README.md +8 -0
  2. package/dist/index.d.ts +354 -188
  3. package/dist/index.global.js +2346 -2209
  4. package/dist/index.global.js.map +1 -1
  5. package/dist/index.js +1968 -1829
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +2171 -2032
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +30 -21
  10. package/src/constants.ts +4 -2
  11. package/src/ctMock.ts +27 -86
  12. package/src/helpers.ts +10 -11
  13. package/src/index.test.ts +1 -1
  14. package/src/lib/haversine.ts +2 -2
  15. package/src/lib/masking.ts +3 -1
  16. package/src/lib/predicateParser.ts +93 -92
  17. package/src/lib/projectionSearchFilter.test.ts +28 -36
  18. package/src/lib/projectionSearchFilter.ts +88 -103
  19. package/src/oauth/store.ts +3 -3
  20. package/src/priceSelector.test.ts +16 -35
  21. package/src/priceSelector.ts +6 -9
  22. package/src/product-projection-search.ts +49 -57
  23. package/src/projectAPI.test.ts +7 -0
  24. package/src/projectAPI.ts +17 -22
  25. package/src/repositories/abstract.ts +102 -51
  26. package/src/repositories/cart-discount.ts +4 -5
  27. package/src/repositories/cart.ts +56 -46
  28. package/src/repositories/category.ts +23 -26
  29. package/src/repositories/channel.ts +5 -6
  30. package/src/repositories/custom-object.ts +41 -32
  31. package/src/repositories/customer-group.ts +4 -5
  32. package/src/repositories/customer.ts +42 -5
  33. package/src/repositories/discount-code.ts +5 -6
  34. package/src/repositories/errors.ts +10 -14
  35. package/src/repositories/extension.ts +16 -15
  36. package/src/repositories/helpers.ts +10 -15
  37. package/src/repositories/index.ts +75 -0
  38. package/src/repositories/inventory-entry.ts +5 -6
  39. package/src/repositories/my-order.ts +2 -2
  40. package/src/repositories/order-edit.ts +39 -0
  41. package/src/repositories/order.test.ts +16 -11
  42. package/src/repositories/order.ts +21 -14
  43. package/src/repositories/payment.ts +9 -10
  44. package/src/repositories/product-discount.ts +5 -25
  45. package/src/repositories/product-projection.ts +12 -5
  46. package/src/repositories/product-selection.ts +40 -0
  47. package/src/repositories/product-type.ts +38 -60
  48. package/src/repositories/product.ts +128 -85
  49. package/src/repositories/project.ts +16 -33
  50. package/src/repositories/quote-request.ts +28 -0
  51. package/src/repositories/quote.ts +28 -0
  52. package/src/repositories/review.ts +34 -0
  53. package/src/repositories/shipping-method.ts +25 -28
  54. package/src/repositories/shopping-list.ts +6 -6
  55. package/src/repositories/staged-quote.ts +29 -0
  56. package/src/repositories/standalone-price.ts +36 -0
  57. package/src/repositories/state.ts +16 -17
  58. package/src/repositories/store.ts +13 -29
  59. package/src/repositories/subscription.ts +4 -5
  60. package/src/repositories/tax-category.ts +9 -26
  61. package/src/repositories/type.ts +24 -27
  62. package/src/repositories/zone.ts +9 -11
  63. package/src/server.ts +5 -0
  64. package/src/services/abstract.ts +43 -12
  65. package/src/services/cart-discount.ts +3 -4
  66. package/src/services/cart.test.ts +9 -11
  67. package/src/services/cart.ts +42 -38
  68. package/src/services/category.test.ts +1 -2
  69. package/src/services/category.ts +3 -4
  70. package/src/services/channel.ts +3 -4
  71. package/src/services/custom-object.test.ts +6 -6
  72. package/src/services/custom-object.ts +4 -5
  73. package/src/services/customer-group.ts +3 -4
  74. package/src/services/customer.test.ts +136 -0
  75. package/src/services/customer.ts +5 -6
  76. package/src/services/discount-code.ts +3 -4
  77. package/src/services/extension.ts +3 -4
  78. package/src/services/index.ts +74 -0
  79. package/src/services/inventory-entry.test.ts +9 -13
  80. package/src/services/inventory-entry.ts +3 -4
  81. package/src/services/my-cart.test.ts +2 -0
  82. package/src/services/my-cart.ts +4 -5
  83. package/src/services/my-customer.ts +3 -4
  84. package/src/services/my-order.ts +4 -5
  85. package/src/services/my-payment.ts +3 -4
  86. package/src/services/order.test.ts +28 -26
  87. package/src/services/order.ts +4 -5
  88. package/src/services/payment.ts +3 -4
  89. package/src/services/product-discount.ts +3 -20
  90. package/src/services/product-projection.test.ts +76 -8
  91. package/src/services/product-projection.ts +4 -5
  92. package/src/services/product-type.ts +3 -20
  93. package/src/services/product.test.ts +200 -90
  94. package/src/services/product.ts +3 -4
  95. package/src/services/project.ts +5 -6
  96. package/src/services/shipping-method.ts +3 -4
  97. package/src/services/shopping-list.ts +3 -4
  98. package/src/services/state.ts +3 -4
  99. package/src/services/store.test.ts +11 -2
  100. package/src/services/store.ts +4 -21
  101. package/src/services/subscription.ts +3 -4
  102. package/src/services/tax-category.ts +3 -20
  103. package/src/services/type.ts +3 -4
  104. package/src/services/zone.ts +3 -4
  105. package/src/storage/abstract.ts +82 -0
  106. package/src/{storage.ts → storage/in-memory.ts} +79 -147
  107. package/src/storage/index.ts +2 -0
  108. package/src/types.ts +52 -83
@@ -5,14 +5,11 @@ import {
5
5
  ProductProjection,
6
6
  QueryParam,
7
7
  FacetResults,
8
- FacetTerm,
9
8
  TermFacetResult,
10
9
  RangeFacetResult,
11
10
  FilteredFacetResult,
12
11
  } from '@commercetools/platform-sdk'
13
- import { ByProjectKeyProductProjectionsSearchRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/search/by-project-key-product-projections-search-request-builder'
14
12
  import { nestedLookup } from './helpers'
15
- import { ProductService } from './services/product'
16
13
  import { Writable } from './types'
17
14
  import { CommercetoolsError } from './exceptions'
18
15
  import {
@@ -60,13 +57,17 @@ export class ProductProjectionSearch {
60
57
  projectKey: string,
61
58
  params: ProductProjectionSearchParams
62
59
  ): ProductProjectionPagedSearchResponse {
63
- // Get a copy of all the products in the storage engine. We need a copy
64
- // since we will be modifying the data.
65
60
  let resources = this._storage
66
61
  .all(projectKey, 'product')
67
- .map(r => JSON.parse(JSON.stringify(r)))
62
+ .map((r) => this.transform(r, params.staged ?? false))
63
+ .filter((p) => {
64
+ if (!params.staged ?? false) {
65
+ return p.published
66
+ }
67
+ return true
68
+ })
68
69
 
69
- let markMatchingVariant = params.markMatchingVariants ?? false
70
+ const markMatchingVariant = params.markMatchingVariants ?? false
70
71
 
71
72
  // Apply the priceSelector
72
73
  applyPriceSelector(resources, {
@@ -79,15 +80,14 @@ export class ProductProjectionSearch {
79
80
  // Apply filters pre facetting
80
81
  if (params.filter) {
81
82
  try {
82
- const filters = params.filter.map(f =>
83
- parseFilterExpression(f, params.staged ?? false)
84
- )
83
+ const filters = params.filter.map(parseFilterExpression)
85
84
 
86
85
  // Filters can modify the output. So clone the resources first.
87
- resources = resources.filter(resource =>
88
- filters.every(f => f(resource, markMatchingVariant))
86
+ resources = resources.filter((resource) =>
87
+ filters.every((f) => f(resource, markMatchingVariant))
89
88
  )
90
89
  } catch (err) {
90
+ console.error(err)
91
91
  throw new CommercetoolsError<InvalidInputError>(
92
92
  {
93
93
  code: 'InvalidInput',
@@ -104,12 +104,9 @@ export class ProductProjectionSearch {
104
104
  // Apply filters post facetting
105
105
  if (params['filter.query']) {
106
106
  try {
107
- const filters = params['filter.query'].map(f =>
108
- parseFilterExpression(f, params.staged ?? false)
109
- )
110
-
111
- resources = resources.filter(resource =>
112
- filters.every(f => f(resource, markMatchingVariant))
107
+ const filters = params['filter.query'].map(parseFilterExpression)
108
+ resources = resources.filter((resource) =>
109
+ filters.every((f) => f(resource, markMatchingVariant))
113
110
  )
114
111
  } catch (err) {
115
112
  throw new CommercetoolsError<InvalidInputError>(
@@ -122,33 +119,34 @@ export class ProductProjectionSearch {
122
119
  }
123
120
  }
124
121
 
125
- // Get the total before slicing the array
126
- const totalResources = resources.length
127
-
128
- // Apply offset, limit
129
- const offset = params.offset || 0
130
- const limit = params.limit || 20
131
- resources = resources.slice(offset, offset + limit)
132
-
133
122
  // Expand the resources
134
123
  if (params.expand !== undefined) {
135
- resources = resources.map(resource => {
136
- return this._storage.expand(projectKey, resource, params.expand)
137
- })
124
+ resources = resources.map((resource) =>
125
+ this._storage.expand(projectKey, resource, params.expand)
126
+ )
138
127
  }
139
128
 
129
+ // Create a slice for the pagination. If we were working with large datasets
130
+ // then we should have done this before transforming. But that isn't the
131
+ // goal of this library. So lets keep it simple.
132
+ const totalResults = resources.length
133
+ const offset = params.offset || 0
134
+ const limit = params.limit || 20
135
+ const results = resources.slice(offset, offset + limit)
136
+
140
137
  return {
141
- count: totalResources,
142
- total: resources.length,
138
+ count: totalResults,
139
+ total: results.length,
143
140
  offset: offset,
144
141
  limit: limit,
145
- results: resources.map(this.transform),
142
+ results: results,
146
143
  facets: facets,
147
144
  }
148
145
  }
149
146
 
150
- transform(product: Product): ProductProjection {
151
- const obj = product.masterData.current
147
+ transform(product: Product, staged: boolean): ProductProjection {
148
+ const obj = !staged ? product.masterData.current : product.masterData.staged
149
+
152
150
  return {
153
151
  id: product.id,
154
152
  createdAt: product.createdAt,
@@ -163,12 +161,14 @@ export class ProductProjectionSearch {
163
161
  masterVariant: obj.masterVariant,
164
162
  variants: obj.variants,
165
163
  productType: product.productType,
164
+ hasStagedChanges: product.masterData.hasStagedChanges,
165
+ published: product.masterData.published,
166
166
  }
167
167
  }
168
168
 
169
169
  getFacets(
170
170
  params: ProductProjectionSearchParams,
171
- products: Product[]
171
+ products: ProductProjection[]
172
172
  ): FacetResults {
173
173
  if (!params.facet) return {}
174
174
  const staged = false
@@ -179,7 +179,7 @@ export class ProductProjectionSearch {
179
179
 
180
180
  // Term Facet
181
181
  if (expression.type === 'TermExpression') {
182
- result[facet] = this.termFacet(expression.source, products, staged)
182
+ result[facet] = this.termFacet(expression.source, products)
183
183
  }
184
184
 
185
185
  // Range Facet
@@ -187,8 +187,7 @@ export class ProductProjectionSearch {
187
187
  result[expression.source] = this.rangeFacet(
188
188
  expression.source,
189
189
  expression.children,
190
- products,
191
- staged
190
+ products
192
191
  )
193
192
  }
194
193
 
@@ -197,8 +196,7 @@ export class ProductProjectionSearch {
197
196
  result[expression.source] = this.filterFacet(
198
197
  expression.source,
199
198
  expression.children,
200
- products,
201
- staged
199
+ products
202
200
  )
203
201
  }
204
202
  }
@@ -211,11 +209,7 @@ export class ProductProjectionSearch {
211
209
  * - counting products
212
210
  * - correct dataType
213
211
  */
214
- termFacet(
215
- facet: string,
216
- products: Product[],
217
- staged: boolean
218
- ): TermFacetResult {
212
+ termFacet(facet: string, products: ProductProjection[]): TermFacetResult {
219
213
  const result: Writable<TermFacetResult> = {
220
214
  type: 'terms',
221
215
  dataType: 'text',
@@ -227,9 +221,9 @@ export class ProductProjectionSearch {
227
221
  const terms: Record<any, number> = {}
228
222
 
229
223
  if (facet.startsWith('variants.')) {
230
- products.forEach(p => {
231
- const variants = getVariants(p, staged)
232
- variants.forEach(v => {
224
+ products.forEach((p) => {
225
+ const variants = getVariants(p)
226
+ variants.forEach((v) => {
233
227
  result.total++
234
228
 
235
229
  let value = resolveVariantValue(v, facet)
@@ -244,7 +238,7 @@ export class ProductProjectionSearch {
244
238
  })
245
239
  })
246
240
  } else {
247
- products.forEach(p => {
241
+ products.forEach((p) => {
248
242
  const value = nestedLookup(p, facet)
249
243
  result.total++
250
244
  if (value === undefined) {
@@ -266,15 +260,14 @@ export class ProductProjectionSearch {
266
260
  filterFacet(
267
261
  source: string,
268
262
  filters: FilterExpression[] | undefined,
269
- products: Product[],
270
- staged: boolean
263
+ products: ProductProjection[]
271
264
  ): FilteredFacetResult {
272
265
  let count = 0
273
266
  if (source.startsWith('variants.')) {
274
267
  for (const p of products) {
275
- for (const v of getVariants(p, staged)) {
268
+ for (const v of getVariants(p)) {
276
269
  const val = resolveVariantValue(v, source)
277
- if (filters?.some(f => f.match(val))) {
270
+ if (filters?.some((f) => f.match(val))) {
278
271
  count++
279
272
  }
280
273
  }
@@ -292,15 +285,14 @@ export class ProductProjectionSearch {
292
285
  rangeFacet(
293
286
  source: string,
294
287
  ranges: RangeExpression[] | undefined,
295
- products: Product[],
296
- staged: boolean
288
+ products: ProductProjection[]
297
289
  ): RangeFacetResult {
298
290
  const counts =
299
- ranges?.map(range => {
291
+ ranges?.map((range) => {
300
292
  if (source.startsWith('variants.')) {
301
293
  const values = []
302
294
  for (const p of products) {
303
- for (const v of getVariants(p, staged)) {
295
+ for (const v of getVariants(p)) {
304
296
  const val = resolveVariantValue(v, source)
305
297
  if (val === undefined) {
306
298
  continue
@@ -0,0 +1,7 @@
1
+ import { CommercetoolsMock } from './index'
2
+
3
+ test('getRepository', async () => {
4
+ const ctMock = new CommercetoolsMock()
5
+ const repo = ctMock.project('my-project-key').getRepository('order')
6
+ repo.get({ projectKey: 'unittest' }, '1234')
7
+ })
package/src/projectAPI.ts CHANGED
@@ -1,34 +1,31 @@
1
- import { ReferenceTypeId } from '@commercetools/platform-sdk'
2
1
  import { GetParams } from 'repositories/abstract'
3
2
  import { getBaseResourceProperties } from './helpers'
4
3
  import { AbstractStorage } from './storage'
5
- import {
6
- RepositoryMap,
7
- RepositoryTypes,
8
- ResourceMap,
9
- Services,
10
- ServiceTypes,
11
- } from './types'
4
+ import { RepositoryMap } from './repositories'
5
+ import { ResourceMap, ResourceType } from './types'
12
6
 
13
7
  export class ProjectAPI {
14
8
  private projectKey: string
15
9
  private _storage: AbstractStorage
16
- private _services: Services
10
+ private _repositories: RepositoryMap
17
11
 
18
12
  constructor(
19
13
  projectKey: string,
20
- services: Services,
14
+ repositories: RepositoryMap,
21
15
  storage: AbstractStorage
22
16
  ) {
23
17
  this.projectKey = projectKey
24
18
  this._storage = storage
25
- this._services = services
19
+ this._repositories = repositories
26
20
  }
27
21
 
28
- add(typeId: ReferenceTypeId, resource: ResourceMap[ReferenceTypeId]) {
29
- const service = this._services[typeId]
30
- if (service) {
31
- this._storage.add(this.projectKey, typeId as ReferenceTypeId, {
22
+ add<T extends keyof RepositoryMap & keyof ResourceMap>(
23
+ typeId: T,
24
+ resource: ResourceMap[T]
25
+ ) {
26
+ const repository = this._repositories[typeId]
27
+ if (repository) {
28
+ this._storage.add(this.projectKey, typeId, {
32
29
  ...getBaseResourceProperties(),
33
30
  ...resource,
34
31
  })
@@ -37,7 +34,7 @@ export class ProjectAPI {
37
34
  }
38
35
  }
39
36
 
40
- get<RT extends RepositoryTypes>(
37
+ get<RT extends ResourceType>(
41
38
  typeId: RT,
42
39
  id: string,
43
40
  params?: GetParams
@@ -51,12 +48,10 @@ export class ProjectAPI {
51
48
  }
52
49
 
53
50
  // TODO: Not sure if we want to expose this...
54
- getRepository<RT extends keyof RepositoryMap>(
55
- typeId: ServiceTypes
56
- ): RepositoryMap[RT] {
57
- const service = this._services[typeId]
58
- if (service !== undefined) {
59
- return service.repository as RepositoryMap[RT]
51
+ getRepository<RT extends keyof RepositoryMap>(typeId: RT): RepositoryMap[RT] {
52
+ const repository = this._repositories[typeId]
53
+ if (repository !== undefined) {
54
+ return repository as RepositoryMap[RT]
60
55
  }
61
56
  throw new Error('No such repository')
62
57
  }
@@ -1,15 +1,15 @@
1
- import { RepositoryTypes } from './../types'
2
- import deepEqual from 'deep-equal'
3
-
4
1
  import {
5
2
  BaseResource,
6
- InvalidOperationError,
7
3
  Project,
4
+ ResourceNotFoundError,
8
5
  UpdateAction,
9
6
  } from '@commercetools/platform-sdk'
7
+ import deepEqual from 'deep-equal'
8
+ import { CommercetoolsError } from '../exceptions'
9
+ import { cloneObject } from '../helpers'
10
10
  import { AbstractStorage } from '../storage'
11
+ import { ResourceMap, ResourceType, ShallowWritable } from './../types'
11
12
  import { checkConcurrentModification } from './errors'
12
- import { CommercetoolsError } from '../exceptions'
13
13
 
14
14
  export type QueryParams = {
15
15
  expand?: string[]
@@ -26,7 +26,8 @@ export type RepositoryContext = {
26
26
  projectKey: string
27
27
  storeKey?: string
28
28
  }
29
- export abstract class AbstractRepository {
29
+
30
+ export abstract class AbstractRepository<R extends BaseResource | Project> {
30
31
  protected _storage: AbstractStorage
31
32
  protected actions: Partial<
32
33
  Record<
@@ -39,53 +40,80 @@ export abstract class AbstractRepository {
39
40
  this._storage = storage
40
41
  }
41
42
 
42
- abstract save(
43
+ abstract saveNew({ projectKey }: RepositoryContext, resource: R): void
44
+
45
+ abstract saveUpdate(
43
46
  { projectKey }: RepositoryContext,
44
- resource: BaseResource | Project
47
+ version: number,
48
+ resource: R
45
49
  ): void
46
50
 
47
51
  processUpdateActions(
48
52
  context: RepositoryContext,
49
- resource: BaseResource | Project,
53
+ resource: R,
54
+ version: number,
50
55
  actions: UpdateAction[]
51
- ): BaseResource {
56
+ ): R {
52
57
  // Deep-copy
53
- const modifiedResource = JSON.parse(JSON.stringify(resource))
58
+ const updatedResource = cloneObject(resource) as ShallowWritable<R>
59
+ const identifier = (resource as BaseResource).id
60
+ ? (resource as BaseResource).id
61
+ : (resource as Project).key
54
62
 
55
- actions.forEach(action => {
63
+ actions.forEach((action) => {
56
64
  const updateFunc = this.actions[action.action]
57
65
 
58
66
  if (!updateFunc) {
59
67
  console.error(`No mock implemented for update action ${action.action}`)
60
- return
68
+ throw new Error(
69
+ `No mock implemented for update action ${action.action}`
70
+ )
71
+ }
72
+
73
+ const beforeUpdate = cloneObject(resource)
74
+ updateFunc(context, updatedResource, action)
75
+
76
+ // Check if the object is updated. We need to increase the version of
77
+ // an object per action which does an actual modification.
78
+ // This isn't the most performant method to do this (the update action
79
+ // should return a flag) but for now the easiest.
80
+ if (!deepEqual(beforeUpdate, updatedResource)) {
81
+ // We only check the version when there is an actual modification to
82
+ // be stored.
83
+ checkConcurrentModification(resource.version, version, identifier)
84
+
85
+ updatedResource.version += 1
61
86
  }
62
- updateFunc(context, modifiedResource, action)
63
87
  })
64
88
 
65
- if (!deepEqual(modifiedResource, resource)) {
66
- this.save(context, modifiedResource)
89
+ // If all actions succeeded we write the new version
90
+ // to the storage.
91
+ if (resource.version != updatedResource.version) {
92
+ this.saveUpdate(context, version, updatedResource)
67
93
  }
68
94
 
69
- const result = this.postProcessResource(modifiedResource)
95
+ const result = this.postProcessResource(updatedResource)
70
96
  if (!result) {
71
- throw new Error("invalid post process action")
97
+ throw new Error('invalid post process action')
72
98
  }
73
99
  return result
74
100
  }
75
101
 
76
- postProcessResource(resource: BaseResource | null): BaseResource | null {
77
- return resource
78
- }
79
-
102
+ abstract postProcessResource(resource: any): any
80
103
  }
81
104
 
82
- export abstract class AbstractResourceRepository extends AbstractRepository {
83
- abstract create(context: RepositoryContext, draft: any): BaseResource
84
- abstract getTypeId(): RepositoryTypes
105
+ export abstract class AbstractResourceRepository<
106
+ T extends ResourceType
107
+ > extends AbstractRepository<ResourceMap[T]> {
108
+ abstract create(context: RepositoryContext, draft: any): ResourceMap[T]
109
+ abstract getTypeId(): T
85
110
 
86
111
  constructor(storage: AbstractStorage) {
87
112
  super(storage)
88
- this._storage.assertStorage(this.getTypeId())
113
+ }
114
+
115
+ postProcessResource(resource: ResourceMap[T]): ResourceMap[T] {
116
+ return resource
89
117
  }
90
118
 
91
119
  query(context: RepositoryContext, params: QueryParams = {}) {
@@ -98,7 +126,6 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
98
126
 
99
127
  // @ts-ignore
100
128
  result.results = result.results.map(this.postProcessResource)
101
-
102
129
  return result
103
130
  }
104
131
 
@@ -106,58 +133,82 @@ export abstract class AbstractResourceRepository extends AbstractRepository {
106
133
  context: RepositoryContext,
107
134
  id: string,
108
135
  params: GetParams = {}
109
- ): BaseResource | null {
110
- const resource = this._storage.get(context.projectKey, this.getTypeId(), id, params)
111
- return this.postProcessResource(resource)
136
+ ): ResourceMap[T] | null {
137
+ const resource = this._storage.get(
138
+ context.projectKey,
139
+ this.getTypeId(),
140
+ id,
141
+ params
142
+ )
143
+ return resource ? this.postProcessResource(resource) : null
112
144
  }
113
145
 
114
146
  getByKey(
115
147
  context: RepositoryContext,
116
148
  key: string,
117
149
  params: GetParams = {}
118
- ): BaseResource | null {
150
+ ): ResourceMap[T] | null {
119
151
  const resource = this._storage.getByKey(
120
152
  context.projectKey,
121
153
  this.getTypeId(),
122
154
  key,
123
155
  params
124
156
  )
125
- return this.postProcessResource(resource)
157
+ return resource ? this.postProcessResource(resource) : null
126
158
  }
127
159
 
128
160
  delete(
129
161
  context: RepositoryContext,
130
162
  id: string,
131
163
  params: GetParams = {}
132
- ): BaseResource | null {
164
+ ): ResourceMap[T] | null {
133
165
  const resource = this._storage.delete(
134
166
  context.projectKey,
135
167
  this.getTypeId(),
136
168
  id,
137
169
  params
138
170
  )
139
- return this.postProcessResource(resource)
171
+ return resource ? this.postProcessResource(resource) : null
140
172
  }
141
173
 
142
- save(context: RepositoryContext, resource: BaseResource) {
143
- const current = this.get(context, resource.id)
144
-
145
- if (current) {
146
- checkConcurrentModification(current, resource.version)
147
- } else {
148
- if (resource.version !== 0) {
149
- throw new CommercetoolsError<InvalidOperationError>(
150
- {
151
- code: 'InvalidOperation',
152
- message: 'version on create must be 0',
153
- },
154
- 400
155
- )
156
- }
174
+ saveNew(
175
+ context: RepositoryContext,
176
+ resource: ShallowWritable<ResourceMap[T]>
177
+ ) {
178
+ resource.version = 1
179
+ this._storage.add(context.projectKey, this.getTypeId(), resource as any)
180
+ }
181
+
182
+ saveUpdate(
183
+ context: RepositoryContext,
184
+ version: number,
185
+ resource: ShallowWritable<ResourceMap[T]>
186
+ ) {
187
+ // Check if the resource still exists.
188
+ const current = this._storage.get(
189
+ context.projectKey,
190
+ this.getTypeId(),
191
+ resource.id
192
+ )
193
+ if (!current) {
194
+ throw new CommercetoolsError<ResourceNotFoundError>(
195
+ {
196
+ code: 'ResourceNotFound',
197
+ message: 'Resource not found while updating',
198
+ },
199
+ 400
200
+ )
157
201
  }
158
202
 
159
- // @ts-ignore
160
- resource.version += 1
203
+ checkConcurrentModification(current.version, version, resource.id)
204
+
205
+ if (current.version === resource.version) {
206
+ throw new Error('Internal error: no changes to save')
207
+ }
208
+ resource.lastModifiedAt = new Date().toISOString()
209
+
161
210
  this._storage.add(context.projectKey, this.getTypeId(), resource as any)
211
+
212
+ return resource
162
213
  }
163
214
  }
@@ -14,16 +14,15 @@ import {
14
14
  CartDiscountValueFixed,
15
15
  CartDiscountValueGiftLineItem,
16
16
  CartDiscountValueRelative,
17
- ReferenceTypeId,
18
17
  } from '@commercetools/platform-sdk'
19
18
  import { Writable } from 'types'
20
19
  import { getBaseResourceProperties } from '../helpers'
21
20
  import { AbstractResourceRepository, RepositoryContext } from './abstract'
22
21
  import { createTypedMoney } from './helpers'
23
22
 
24
- export class CartDiscountRepository extends AbstractResourceRepository {
25
- getTypeId(): ReferenceTypeId {
26
- return 'cart-discount'
23
+ export class CartDiscountRepository extends AbstractResourceRepository<'cart-discount'> {
24
+ getTypeId() {
25
+ return 'cart-discount' as const
27
26
  }
28
27
 
29
28
  create(context: RepositoryContext, draft: CartDiscountDraft): CartDiscount {
@@ -43,7 +42,7 @@ export class CartDiscountRepository extends AbstractResourceRepository {
43
42
  validUntil: draft.validUntil,
44
43
  value: this.transformValueDraft(draft.value),
45
44
  }
46
- this.save(context, resource)
45
+ this.saveNew(context, resource)
47
46
  return resource
48
47
  }
49
48