@labdigital/commercetools-mock 2.66.0 → 3.0.0-beta.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.
Files changed (281) hide show
  1. package/README.md +31 -8
  2. package/dist/abstract-BKFcva6S.mjs +1044 -0
  3. package/dist/abstract-BKFcva6S.mjs.map +1 -0
  4. package/dist/config-BcNSzPZz.d.mts +1718 -0
  5. package/dist/index.d.mts +50 -1633
  6. package/dist/index.mjs +3769 -2653
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/storage/sqlite.d.mts +59 -0
  9. package/dist/storage/sqlite.mjs +234 -0
  10. package/dist/storage/sqlite.mjs.map +1 -0
  11. package/package.json +26 -29
  12. package/src/ctMock.ts +125 -136
  13. package/src/helpers.ts +14 -6
  14. package/src/index.ts +5 -0
  15. package/src/lib/masking.ts +4 -5
  16. package/src/lib/product-review-statistics.test.ts +257 -294
  17. package/src/lib/review-statistics.ts +17 -4
  18. package/src/oauth/helpers.ts +7 -4
  19. package/src/oauth/server.test.ts +102 -62
  20. package/src/oauth/server.ts +215 -213
  21. package/src/oauth/store.ts +20 -6
  22. package/src/orderSearch.ts +3 -3
  23. package/src/product-projection-search.ts +38 -20
  24. package/src/product-search-availability.test.ts +31 -52
  25. package/src/product-search.ts +19 -10
  26. package/src/projectAPI.ts +6 -22
  27. package/src/repositories/abstract.ts +182 -48
  28. package/src/repositories/as-associate.test.ts +19 -19
  29. package/src/repositories/associate-role.ts +12 -23
  30. package/src/repositories/attribute-group.test.ts +23 -23
  31. package/src/repositories/attribute-group.ts +6 -4
  32. package/src/repositories/business-unit.test.ts +63 -57
  33. package/src/repositories/business-unit.ts +107 -55
  34. package/src/repositories/cart/actions.ts +96 -65
  35. package/src/repositories/cart/helpers.ts +15 -11
  36. package/src/repositories/cart/index.test.ts +136 -30
  37. package/src/repositories/cart/index.ts +76 -59
  38. package/src/repositories/cart-discount/actions.ts +12 -44
  39. package/src/repositories/cart-discount/index.ts +20 -8
  40. package/src/repositories/category/actions.ts +27 -27
  41. package/src/repositories/category/index.test.ts +13 -9
  42. package/src/repositories/category/index.ts +40 -23
  43. package/src/repositories/channel.test.ts +53 -51
  44. package/src/repositories/channel.ts +12 -22
  45. package/src/repositories/custom-object.ts +34 -25
  46. package/src/repositories/customer/actions.ts +47 -25
  47. package/src/repositories/customer/index.test.ts +11 -11
  48. package/src/repositories/customer/index.ts +65 -35
  49. package/src/repositories/customer-group.test.ts +44 -42
  50. package/src/repositories/customer-group.ts +12 -22
  51. package/src/repositories/discount-code/actions.ts +3 -19
  52. package/src/repositories/discount-code/index.ts +9 -4
  53. package/src/repositories/discount-group/index.ts +8 -3
  54. package/src/repositories/extension.test.ts +27 -27
  55. package/src/repositories/extension.ts +10 -5
  56. package/src/repositories/helpers.ts +126 -47
  57. package/src/repositories/inventory-entry/actions.ts +3 -24
  58. package/src/repositories/inventory-entry/index.ts +19 -11
  59. package/src/repositories/my-customer.ts +13 -12
  60. package/src/repositories/my-order.ts +5 -2
  61. package/src/repositories/order/actions.ts +84 -56
  62. package/src/repositories/order/index.test.ts +36 -31
  63. package/src/repositories/order/index.ts +83 -49
  64. package/src/repositories/order-edit.ts +8 -3
  65. package/src/repositories/payment/actions.ts +64 -44
  66. package/src/repositories/payment/helpers.ts +3 -3
  67. package/src/repositories/payment/index.ts +28 -12
  68. package/src/repositories/product/actions.ts +133 -98
  69. package/src/repositories/product/helpers.ts +29 -16
  70. package/src/repositories/product/index.ts +42 -25
  71. package/src/repositories/product-discount.ts +6 -4
  72. package/src/repositories/product-projection.ts +41 -21
  73. package/src/repositories/product-selection.ts +8 -15
  74. package/src/repositories/product-tailoring.ts +22 -3
  75. package/src/repositories/product-type.ts +45 -4
  76. package/src/repositories/project.ts +57 -13
  77. package/src/repositories/quote/actions.ts +5 -28
  78. package/src/repositories/quote/index.ts +29 -6
  79. package/src/repositories/quote-request/actions.ts +5 -28
  80. package/src/repositories/quote-request/index.test.ts +3 -3
  81. package/src/repositories/quote-request/index.ts +31 -11
  82. package/src/repositories/quote-staged/actions.ts +5 -28
  83. package/src/repositories/quote-staged/index.ts +22 -8
  84. package/src/repositories/recurrence-policy/index.ts +6 -4
  85. package/src/repositories/recurring-order/actions.ts +7 -32
  86. package/src/repositories/recurring-order/index.ts +8 -6
  87. package/src/repositories/review.test.ts +147 -142
  88. package/src/repositories/review.ts +31 -37
  89. package/src/repositories/shipping-method/actions.ts +11 -28
  90. package/src/repositories/shipping-method/index.ts +26 -15
  91. package/src/repositories/shopping-list/actions.ts +21 -31
  92. package/src/repositories/shopping-list/index.ts +44 -22
  93. package/src/repositories/standalone-price.ts +6 -4
  94. package/src/repositories/state.ts +15 -9
  95. package/src/repositories/store.ts +21 -32
  96. package/src/repositories/subscription.test.ts +22 -22
  97. package/src/repositories/subscription.ts +8 -3
  98. package/src/repositories/tax-category/index.ts +8 -3
  99. package/src/repositories/type/actions.ts +21 -3
  100. package/src/repositories/type/index.ts +5 -3
  101. package/src/repositories/zone.test.ts +112 -77
  102. package/src/repositories/zone.ts +5 -3
  103. package/src/schemas/generated/associate-role.ts +13 -0
  104. package/src/schemas/generated/attribute-group.ts +12 -0
  105. package/src/schemas/generated/business-unit.ts +38 -0
  106. package/src/schemas/generated/cart-discount.ts +33 -0
  107. package/src/schemas/generated/cart.ts +61 -0
  108. package/src/schemas/generated/category.ts +25 -0
  109. package/src/schemas/generated/channel.ts +21 -0
  110. package/src/schemas/generated/common.ts +1372 -0
  111. package/src/schemas/generated/custom-object.ts +11 -0
  112. package/src/schemas/generated/customer-group.ts +11 -0
  113. package/src/schemas/generated/customer.ts +47 -0
  114. package/src/schemas/generated/discount-code.ts +25 -0
  115. package/src/schemas/generated/discount-group.ts +13 -0
  116. package/src/schemas/generated/extension.ts +15 -0
  117. package/src/schemas/generated/index.ts +42 -0
  118. package/src/schemas/generated/inventory-entry.ts +20 -0
  119. package/src/schemas/generated/my-quote-request.ts +10 -0
  120. package/src/schemas/generated/order-edit.ts +18 -0
  121. package/src/schemas/generated/order-from-cart.ts +25 -0
  122. package/src/schemas/generated/payment.ts +30 -0
  123. package/src/schemas/generated/product-discount.ts +20 -0
  124. package/src/schemas/generated/product-selection.ts +18 -0
  125. package/src/schemas/generated/product-tailoring.ts +26 -0
  126. package/src/schemas/generated/product-type.ts +12 -0
  127. package/src/schemas/generated/product.ts +37 -0
  128. package/src/schemas/generated/quote-request.ts +19 -0
  129. package/src/schemas/generated/quote.ts +18 -0
  130. package/src/schemas/generated/recurrence-policy.ts +15 -0
  131. package/src/schemas/generated/recurring-order.ts +19 -0
  132. package/src/schemas/generated/review.ts +24 -0
  133. package/src/schemas/generated/shipping-method.ts +24 -0
  134. package/src/schemas/generated/shopping-list.ts +28 -0
  135. package/src/schemas/generated/staged-quote.ts +18 -0
  136. package/src/schemas/generated/standalone-price.ts +32 -0
  137. package/src/schemas/generated/state.ts +20 -0
  138. package/src/schemas/generated/store.ts +23 -0
  139. package/src/schemas/generated/subscription.ts +20 -0
  140. package/src/schemas/generated/tax-category.ts +12 -0
  141. package/src/schemas/generated/type.ts +17 -0
  142. package/src/schemas/generated/zone.ts +12 -0
  143. package/src/schemas/update-request.ts +3 -5
  144. package/src/server.ts +32 -4
  145. package/src/services/abstract.ts +207 -101
  146. package/src/services/as-associate-cart.test.ts +28 -36
  147. package/src/services/as-associate-cart.ts +15 -12
  148. package/src/services/as-associate-order.test.ts +33 -40
  149. package/src/services/as-associate-order.ts +15 -12
  150. package/src/services/as-associate-quote-request.ts +15 -12
  151. package/src/services/as-associate-shopping-list.test.ts +25 -35
  152. package/src/services/as-associate-shopping-list.ts +15 -12
  153. package/src/services/as-associate.test.ts +21 -15
  154. package/src/services/as-associate.ts +23 -22
  155. package/src/services/associate-roles.test.ts +16 -22
  156. package/src/services/associate-roles.ts +2 -2
  157. package/src/services/attribute-group.test.ts +40 -44
  158. package/src/services/attribute-group.ts +2 -2
  159. package/src/services/business-units.test.ts +227 -163
  160. package/src/services/business-units.ts +2 -2
  161. package/src/services/cart-discount.test.ts +253 -187
  162. package/src/services/cart-discount.ts +2 -2
  163. package/src/services/cart.test.ts +833 -832
  164. package/src/services/cart.ts +31 -12
  165. package/src/services/category.test.ts +208 -130
  166. package/src/services/category.ts +2 -2
  167. package/src/services/channel.test.ts +39 -44
  168. package/src/services/channel.ts +2 -2
  169. package/src/services/custom-object.test.ts +103 -79
  170. package/src/services/custom-object.ts +106 -38
  171. package/src/services/customer-group.test.ts +39 -44
  172. package/src/services/customer-group.ts +2 -2
  173. package/src/services/customer.test.ts +357 -292
  174. package/src/services/customer.ts +70 -23
  175. package/src/services/discount-code.test.ts +57 -68
  176. package/src/services/discount-code.ts +2 -2
  177. package/src/services/discount-group.test.ts +111 -134
  178. package/src/services/discount-group.ts +2 -2
  179. package/src/services/draft-validation.test.ts +255 -0
  180. package/src/services/extension.test.ts +39 -44
  181. package/src/services/extension.ts +2 -2
  182. package/src/services/inventory-entry.test.ts +106 -87
  183. package/src/services/inventory-entry.ts +2 -2
  184. package/src/services/my-business-unit.test.ts +82 -112
  185. package/src/services/my-business-unit.ts +25 -19
  186. package/src/services/my-cart.test.ts +46 -41
  187. package/src/services/my-cart.ts +32 -28
  188. package/src/services/my-customer.test.ts +153 -88
  189. package/src/services/my-customer.ts +130 -61
  190. package/src/services/my-order.ts +15 -12
  191. package/src/services/my-payment.test.ts +30 -24
  192. package/src/services/my-payment.ts +2 -2
  193. package/src/services/my-shopping-list.ts +2 -2
  194. package/src/services/order.test.ts +332 -276
  195. package/src/services/order.ts +45 -27
  196. package/src/services/payment.test.ts +31 -29
  197. package/src/services/payment.ts +2 -2
  198. package/src/services/product-discount.test.ts +39 -46
  199. package/src/services/product-discount.ts +2 -2
  200. package/src/services/product-projection.test.ts +176 -166
  201. package/src/services/product-projection.ts +31 -15
  202. package/src/services/product-selection.test.ts +17 -9
  203. package/src/services/product-selection.ts +2 -2
  204. package/src/services/product-type.test.ts +80 -21
  205. package/src/services/product-type.ts +2 -2
  206. package/src/services/product.test.ts +569 -534
  207. package/src/services/product.ts +14 -7
  208. package/src/services/project.test.ts +22 -12
  209. package/src/services/project.ts +28 -13
  210. package/src/services/quote-request.test.ts +36 -39
  211. package/src/services/quote-request.ts +2 -2
  212. package/src/services/quote-staged.ts +2 -2
  213. package/src/services/quote.ts +2 -2
  214. package/src/services/recurrence-policy.test.ts +114 -139
  215. package/src/services/recurrence-policy.ts +2 -2
  216. package/src/services/recurring-order.test.ts +149 -194
  217. package/src/services/recurring-order.ts +2 -2
  218. package/src/services/reviews.test.ts +127 -106
  219. package/src/services/reviews.ts +2 -2
  220. package/src/services/shipping-method.test.ts +96 -125
  221. package/src/services/shipping-method.ts +24 -12
  222. package/src/services/shopping-list.test.ts +183 -141
  223. package/src/services/shopping-list.ts +2 -2
  224. package/src/services/standalone-price.test.ts +60 -46
  225. package/src/services/standalone-price.ts +2 -2
  226. package/src/services/state.test.ts +20 -25
  227. package/src/services/state.ts +2 -2
  228. package/src/services/store.test.ts +26 -45
  229. package/src/services/store.ts +2 -2
  230. package/src/services/subscription.test.ts +39 -44
  231. package/src/services/subscription.ts +2 -2
  232. package/src/services/tax-category.test.ts +33 -36
  233. package/src/services/tax-category.ts +2 -2
  234. package/src/services/type.test.ts +45 -44
  235. package/src/services/type.ts +2 -2
  236. package/src/services/zone.test.ts +40 -44
  237. package/src/services/zone.ts +2 -2
  238. package/src/shipping.ts +41 -11
  239. package/src/storage/abstract.ts +248 -17
  240. package/src/storage/in-memory.ts +147 -290
  241. package/src/storage/sqlite.ts +429 -0
  242. package/src/storage/storage-map.ts +75 -0
  243. package/src/storage/storage.test-helpers.ts +97 -0
  244. package/src/storage/storage.test.ts +802 -0
  245. package/src/testing/associate-role.ts +28 -0
  246. package/src/testing/attribute-group.ts +27 -0
  247. package/src/testing/business-unit.ts +9 -8
  248. package/src/testing/cart-discount.ts +34 -0
  249. package/src/testing/cart.ts +20 -0
  250. package/src/testing/category.ts +25 -0
  251. package/src/testing/channel.ts +23 -0
  252. package/src/testing/custom-object.ts +27 -0
  253. package/src/testing/customer-group.ts +26 -0
  254. package/src/testing/customer.ts +36 -33
  255. package/src/testing/discount-code.ts +29 -0
  256. package/src/testing/discount-group.ts +27 -0
  257. package/src/testing/extension.ts +32 -0
  258. package/src/testing/index.ts +33 -0
  259. package/src/testing/inventory-entry.ts +26 -0
  260. package/src/testing/order.ts +27 -0
  261. package/src/testing/payment.ts +23 -0
  262. package/src/testing/product-discount.ts +33 -0
  263. package/src/testing/product-selection.ts +28 -0
  264. package/src/testing/product-type.ts +27 -0
  265. package/src/testing/product.ts +38 -0
  266. package/src/testing/quote-request.ts +29 -0
  267. package/src/testing/recurrence-policy.ts +33 -0
  268. package/src/testing/recurring-order.ts +32 -0
  269. package/src/testing/review.ts +24 -0
  270. package/src/testing/shipping-method.ts +31 -0
  271. package/src/testing/shopping-list.ts +25 -0
  272. package/src/testing/standalone-price.ts +31 -0
  273. package/src/testing/state.ts +21 -0
  274. package/src/testing/store.ts +26 -0
  275. package/src/testing/subscription.ts +38 -0
  276. package/src/testing/tax-category.ts +27 -0
  277. package/src/testing/type.ts +9 -6
  278. package/src/testing/zone.ts +22 -0
  279. package/src/validate.test.ts +122 -0
  280. package/src/validate.ts +78 -7
  281. package/src/.env +0 -0
@@ -0,0 +1,429 @@
1
+ import path from "node:path";
2
+ import { DatabaseSync, type StatementSync } from "node:sqlite";
3
+ import type {
4
+ CustomObject,
5
+ InvalidInputError,
6
+ Project,
7
+ } from "@commercetools/platform-sdk";
8
+ import { CommercetoolsError } from "#src/exceptions.ts";
9
+ import { cloneObject } from "../helpers.ts";
10
+ import { parseQueryExpression } from "../lib/predicateParser.ts";
11
+ import type {
12
+ PagedQueryResponseMap,
13
+ ResourceMap,
14
+ ResourceType,
15
+ } from "../types.ts";
16
+ import type { GetParams, QueryParams } from "./abstract.ts";
17
+ import { AbstractStorage } from "./abstract.ts";
18
+
19
+ export type SQLiteStorageOptions = {
20
+ /**
21
+ * Path to the SQLite database file.
22
+ * Defaults to `commercetools-mock.db` in the current working directory.
23
+ * Use `':memory:'` for an in-memory database.
24
+ */
25
+ filename?: string;
26
+ };
27
+
28
+ const DEFAULT_PROJECT: Omit<Project, "key"> = {
29
+ name: "",
30
+ countries: [],
31
+ currencies: [],
32
+ languages: [],
33
+ createdAt: "2018-10-04T11:32:12.603Z",
34
+ trialUntil: "2018-12",
35
+ carts: {
36
+ countryTaxRateFallbackEnabled: false,
37
+ deleteDaysAfterLastModification: 90,
38
+ priceRoundingMode: "HalfEven",
39
+ taxRoundingMode: "HalfEven",
40
+ },
41
+ shoppingLists: {
42
+ deleteDaysAfterLastModification: 360,
43
+ },
44
+ messages: { enabled: false, deleteDaysAfterCreation: 15 },
45
+ shippingRateInputType: undefined,
46
+ externalOAuth: undefined,
47
+ searchIndexing: {
48
+ products: {
49
+ status: "Deactivated",
50
+ },
51
+ productsSearch: {
52
+ status: "Deactivated",
53
+ },
54
+ orders: {
55
+ status: "Deactivated",
56
+ },
57
+ customers: {
58
+ status: "Deactivated",
59
+ },
60
+ businessUnits: {
61
+ status: "Deactivated",
62
+ },
63
+ },
64
+ discounts: {
65
+ discountCombinationMode: "Stacking",
66
+ },
67
+ version: 1,
68
+ };
69
+
70
+ const SCHEMA = `
71
+ CREATE TABLE IF NOT EXISTS projects (
72
+ project_key TEXT PRIMARY KEY,
73
+ data TEXT NOT NULL
74
+ );
75
+
76
+ CREATE TABLE IF NOT EXISTS resources (
77
+ project_key TEXT NOT NULL,
78
+ type_id TEXT NOT NULL,
79
+ id TEXT NOT NULL,
80
+ key TEXT,
81
+ data TEXT NOT NULL,
82
+ PRIMARY KEY (project_key, type_id, id)
83
+ );
84
+
85
+ CREATE INDEX IF NOT EXISTS idx_resources_key
86
+ ON resources (project_key, type_id, key) WHERE key IS NOT NULL;
87
+
88
+ CREATE INDEX IF NOT EXISTS idx_resources_container_key
89
+ ON resources (
90
+ project_key,
91
+ json_extract(data, '$.container'),
92
+ json_extract(data, '$.key')
93
+ )
94
+ WHERE type_id = 'key-value-document';
95
+ `;
96
+
97
+ export class SQLiteStorage extends AbstractStorage {
98
+ private db: DatabaseSync;
99
+
100
+ // Cache of known project keys to avoid redundant INSERT+SELECT on every add()
101
+ private _knownProjects: Set<string> = new Set();
102
+
103
+ // Prepared statements (lazily created)
104
+ private _stmtInsertProject: StatementSync | null = null;
105
+ private _stmtGetProject: StatementSync | null = null;
106
+ private _stmtUpsertProject: StatementSync | null = null;
107
+ private _stmtInsertResource: StatementSync | null = null;
108
+ private _stmtGetResource: StatementSync | null = null;
109
+ private _stmtGetResourceByKey: StatementSync | null = null;
110
+ private _stmtAllResources: StatementSync | null = null;
111
+ private _stmtDeleteResource: StatementSync | null = null;
112
+ private _stmtClearResources: StatementSync | null = null;
113
+ private _stmtGetResourceByContainerAndKey: StatementSync | null = null;
114
+ private _stmtCountResources: StatementSync | null = null;
115
+
116
+ constructor(options: SQLiteStorageOptions = {}) {
117
+ super();
118
+
119
+ const filename =
120
+ options.filename ?? path.join(process.cwd(), "commercetools-mock.db");
121
+
122
+ this.db = new DatabaseSync(filename);
123
+ this.db.exec(SCHEMA);
124
+ }
125
+
126
+ /**
127
+ * Close the database connection. Call this when you're done using the storage.
128
+ */
129
+ override close(): void {
130
+ if (this.db.isOpen) {
131
+ this.db.close();
132
+ }
133
+ }
134
+
135
+ private get stmtInsertProject() {
136
+ if (!this._stmtInsertProject) {
137
+ this._stmtInsertProject = this.db.prepare(
138
+ "INSERT OR IGNORE INTO projects (project_key, data) VALUES (?, ?)",
139
+ );
140
+ }
141
+ return this._stmtInsertProject;
142
+ }
143
+
144
+ private get stmtGetProject() {
145
+ if (!this._stmtGetProject) {
146
+ this._stmtGetProject = this.db.prepare(
147
+ "SELECT data FROM projects WHERE project_key = ?",
148
+ );
149
+ }
150
+ return this._stmtGetProject;
151
+ }
152
+
153
+ private get stmtUpsertProject() {
154
+ if (!this._stmtUpsertProject) {
155
+ this._stmtUpsertProject = this.db.prepare(
156
+ "INSERT OR REPLACE INTO projects (project_key, data) VALUES (?, ?)",
157
+ );
158
+ }
159
+ return this._stmtUpsertProject;
160
+ }
161
+
162
+ private get stmtInsertResource() {
163
+ if (!this._stmtInsertResource) {
164
+ this._stmtInsertResource = this.db.prepare(
165
+ "INSERT OR REPLACE INTO resources (project_key, type_id, id, key, data) VALUES (?, ?, ?, ?, ?)",
166
+ );
167
+ }
168
+ return this._stmtInsertResource;
169
+ }
170
+
171
+ private get stmtGetResource() {
172
+ if (!this._stmtGetResource) {
173
+ this._stmtGetResource = this.db.prepare(
174
+ "SELECT data FROM resources WHERE project_key = ? AND type_id = ? AND id = ?",
175
+ );
176
+ }
177
+ return this._stmtGetResource;
178
+ }
179
+
180
+ private get stmtGetResourceByKey() {
181
+ if (!this._stmtGetResourceByKey) {
182
+ this._stmtGetResourceByKey = this.db.prepare(
183
+ "SELECT data FROM resources WHERE project_key = ? AND type_id = ? AND key = ?",
184
+ );
185
+ }
186
+ return this._stmtGetResourceByKey;
187
+ }
188
+
189
+ private get stmtAllResources() {
190
+ if (!this._stmtAllResources) {
191
+ this._stmtAllResources = this.db.prepare(
192
+ "SELECT data FROM resources WHERE project_key = ? AND type_id = ?",
193
+ );
194
+ }
195
+ return this._stmtAllResources;
196
+ }
197
+
198
+ private get stmtCountResources() {
199
+ if (!this._stmtCountResources) {
200
+ this._stmtCountResources = this.db.prepare(
201
+ "SELECT COUNT(*) as cnt FROM resources WHERE project_key = ? AND type_id = ?",
202
+ );
203
+ }
204
+ return this._stmtCountResources;
205
+ }
206
+
207
+ private get stmtDeleteResource() {
208
+ if (!this._stmtDeleteResource) {
209
+ this._stmtDeleteResource = this.db.prepare(
210
+ "DELETE FROM resources WHERE project_key = ? AND type_id = ? AND id = ?",
211
+ );
212
+ }
213
+ return this._stmtDeleteResource;
214
+ }
215
+
216
+ private get stmtClearResources() {
217
+ if (!this._stmtClearResources) {
218
+ this._stmtClearResources = this.db.prepare("DELETE FROM resources");
219
+ }
220
+ return this._stmtClearResources;
221
+ }
222
+
223
+ private get stmtGetResourceByContainerAndKey() {
224
+ if (!this._stmtGetResourceByContainerAndKey) {
225
+ this._stmtGetResourceByContainerAndKey = this.db.prepare(
226
+ `SELECT data FROM resources
227
+ WHERE project_key = ?
228
+ AND type_id = 'key-value-document'
229
+ AND json_extract(data, '$.container') = ?
230
+ AND json_extract(data, '$.key') = ?`,
231
+ );
232
+ }
233
+ return this._stmtGetResourceByContainerAndKey;
234
+ }
235
+
236
+ private ensureProject(projectKey: string): void {
237
+ if (this._knownProjects.has(projectKey)) {
238
+ return;
239
+ }
240
+ const project: Project = { ...DEFAULT_PROJECT, key: projectKey };
241
+ this.stmtInsertProject.run(projectKey, JSON.stringify(project));
242
+ this._knownProjects.add(projectKey);
243
+ }
244
+
245
+ async addProject(projectKey: string): Promise<Project> {
246
+ this.ensureProject(projectKey);
247
+
248
+ const row = this.stmtGetProject.get(projectKey) as
249
+ | { data: string }
250
+ | undefined;
251
+ if (!row) {
252
+ throw new Error(`Failed to create project ${projectKey}`);
253
+ }
254
+ return JSON.parse(row.data) as Project;
255
+ }
256
+
257
+ async getProject(projectKey: string): Promise<Project> {
258
+ return this.addProject(projectKey);
259
+ }
260
+
261
+ async saveProject(project: Project): Promise<Project> {
262
+ this.stmtUpsertProject.run(project.key, JSON.stringify(project));
263
+ return project;
264
+ }
265
+
266
+ async clear(): Promise<void> {
267
+ this.stmtClearResources.run();
268
+ this._knownProjects.clear();
269
+ }
270
+
271
+ async all<RT extends ResourceType>(
272
+ projectKey: string,
273
+ typeId: RT,
274
+ ): Promise<ResourceMap[RT][]> {
275
+ const rows = this.stmtAllResources.all(projectKey, typeId) as Array<{
276
+ data: string;
277
+ }>;
278
+ return rows.map((row) => JSON.parse(row.data) as ResourceMap[RT]);
279
+ }
280
+
281
+ async count(projectKey: string, typeId: ResourceType): Promise<number> {
282
+ const row = this.stmtCountResources.get(projectKey, typeId) as
283
+ | { cnt: number }
284
+ | undefined;
285
+ return row?.cnt ?? 0;
286
+ }
287
+
288
+ async add<RT extends ResourceType>(
289
+ projectKey: string,
290
+ typeId: RT,
291
+ obj: ResourceMap[RT],
292
+ params: GetParams = {},
293
+ ): Promise<ResourceMap[RT]> {
294
+ // Ensure the project exists (cached, no DB round-trip after first call)
295
+ this.ensureProject(projectKey);
296
+
297
+ const key = (obj as any).key ?? null;
298
+
299
+ this.stmtInsertResource.run(
300
+ projectKey,
301
+ typeId,
302
+ obj.id,
303
+ key,
304
+ JSON.stringify(obj),
305
+ );
306
+
307
+ // Return a clone instead of the caller's reference so that expand()
308
+ // can safely mutate it without affecting the caller's object.
309
+ const clone = cloneObject(obj);
310
+ return this.expand(projectKey, clone, params.expand);
311
+ }
312
+
313
+ async get<RT extends ResourceType>(
314
+ projectKey: string,
315
+ typeId: RT,
316
+ id: string,
317
+ params: GetParams = {},
318
+ ): Promise<ResourceMap[RT] | null> {
319
+ const row = this.stmtGetResource.get(projectKey, typeId, id) as
320
+ | { data: string }
321
+ | undefined;
322
+ if (row) {
323
+ const resource = JSON.parse(row.data) as ResourceMap[RT];
324
+ return this.expand(projectKey, resource, params.expand);
325
+ }
326
+ return null;
327
+ }
328
+
329
+ async getByKey<RT extends ResourceType>(
330
+ projectKey: string,
331
+ typeId: RT,
332
+ key: string,
333
+ params: GetParams = {},
334
+ ): Promise<ResourceMap[RT] | null> {
335
+ const row = this.stmtGetResourceByKey.get(projectKey, typeId, key) as
336
+ | { data: string }
337
+ | undefined;
338
+ if (row) {
339
+ const resource = JSON.parse(row.data) as ResourceMap[RT];
340
+ return this.expand(projectKey, resource, params.expand);
341
+ }
342
+ return null;
343
+ }
344
+
345
+ async delete<RT extends ResourceType>(
346
+ projectKey: string,
347
+ typeId: RT,
348
+ id: string,
349
+ params: GetParams = {},
350
+ ): Promise<ResourceMap[RT] | null> {
351
+ const resource = await this.get(projectKey, typeId, id);
352
+ if (resource) {
353
+ this.stmtDeleteResource.run(projectKey, typeId, id);
354
+ return this.expand(projectKey, resource, params.expand);
355
+ }
356
+ return null;
357
+ }
358
+
359
+ async getByContainerAndKey(
360
+ projectKey: string,
361
+ container: string,
362
+ key: string,
363
+ ): Promise<CustomObject | null> {
364
+ const row = this.stmtGetResourceByContainerAndKey.get(
365
+ projectKey,
366
+ container,
367
+ key,
368
+ ) as { data: string } | undefined;
369
+ if (row) {
370
+ return JSON.parse(row.data) as CustomObject;
371
+ }
372
+ return null;
373
+ }
374
+
375
+ async query<RT extends ResourceType>(
376
+ projectKey: string,
377
+ typeId: RT,
378
+ params: QueryParams,
379
+ ): Promise<PagedQueryResponseMap[RT]> {
380
+ let resources = await this.all<RT>(projectKey, typeId);
381
+
382
+ // Apply predicates
383
+ if (params.where) {
384
+ const vars = Object.fromEntries(
385
+ Object.entries(params)
386
+ .filter(([key]) => key.startsWith("var."))
387
+ .map(([key, value]) => [key.slice(4), value]),
388
+ );
389
+
390
+ try {
391
+ const filterFunc = parseQueryExpression(params.where);
392
+ resources = resources.filter((resource) => filterFunc(resource, vars));
393
+ } catch (err) {
394
+ throw new CommercetoolsError<InvalidInputError>(
395
+ {
396
+ code: "InvalidInput",
397
+ message: (err as any).message,
398
+ },
399
+ 400,
400
+ );
401
+ }
402
+ }
403
+
404
+ // Get the total before slicing the array
405
+ const totalResources = resources.length;
406
+
407
+ // Apply offset, limit
408
+ const offset = params.offset || 0;
409
+ const limit = params.limit || 20;
410
+ resources = resources.slice(offset, offset + limit);
411
+
412
+ // Expand the resources
413
+ if (params.expand !== undefined) {
414
+ resources = await Promise.all(
415
+ resources.map((resource) =>
416
+ this.expand(projectKey, resource, params.expand),
417
+ ),
418
+ );
419
+ }
420
+
421
+ return {
422
+ count: resources.length,
423
+ total: totalResources,
424
+ offset: offset,
425
+ limit: limit,
426
+ results: resources,
427
+ } as PagedQueryResponseMap[RT];
428
+ }
429
+ }
@@ -0,0 +1,75 @@
1
+ import { cloneObject } from "../helpers.ts";
2
+
3
+ /**
4
+ * A Map wrapper that deep-clones values on insertion and retrieval.
5
+ *
6
+ * This ensures that the stored data is fully isolated from external
7
+ * mutations: callers cannot accidentally corrupt the store by modifying
8
+ * an object after inserting it, and retrieved objects are independent
9
+ * copies that can be freely mutated without affecting the store.
10
+ */
11
+ export class StorageMap<K, V> {
12
+ private _map: Map<K, V>;
13
+
14
+ constructor() {
15
+ this._map = new Map();
16
+ }
17
+
18
+ get size(): number {
19
+ return this._map.size;
20
+ }
21
+
22
+ set(key: K, value: V): this {
23
+ this._map.set(key, cloneObject(value));
24
+ return this;
25
+ }
26
+
27
+ get(key: K): V | undefined {
28
+ const value = this._map.get(key);
29
+ if (value === undefined) {
30
+ return undefined;
31
+ }
32
+ return cloneObject(value);
33
+ }
34
+
35
+ has(key: K): boolean {
36
+ return this._map.has(key);
37
+ }
38
+
39
+ delete(key: K): boolean {
40
+ return this._map.delete(key);
41
+ }
42
+
43
+ clear(): void {
44
+ this._map.clear();
45
+ }
46
+
47
+ /**
48
+ * Returns cloned values. Each value is a deep copy.
49
+ */
50
+ values(): IterableIterator<V> {
51
+ const inner = this._map.values();
52
+
53
+ return (function* () {
54
+ for (const value of inner) {
55
+ yield cloneObject(value);
56
+ }
57
+ })() as IterableIterator<V>;
58
+ }
59
+
60
+ /**
61
+ * Returns cloned entries. Each value is a deep copy, keys are returned as-is.
62
+ */
63
+ entries(): IterableIterator<[K, V]> {
64
+ const inner = this._map.entries();
65
+ return (function* () {
66
+ for (const [key, value] of inner) {
67
+ yield [key, cloneObject(value)] as [K, V];
68
+ }
69
+ })() as IterableIterator<[K, V]>;
70
+ }
71
+
72
+ [Symbol.iterator](): IterableIterator<[K, V]> {
73
+ return this.entries();
74
+ }
75
+ }
@@ -0,0 +1,97 @@
1
+ import type {
2
+ Category,
3
+ Channel,
4
+ Customer,
5
+ CustomObject,
6
+ Type,
7
+ } from "@commercetools/platform-sdk";
8
+ import type { Writable } from "#src/types.ts";
9
+ import type { AbstractStorage } from "./abstract.ts";
10
+ import { InMemoryStorage } from "./in-memory.ts";
11
+
12
+ export const makeCategory = (
13
+ overrides: Partial<Writable<Category>> = {},
14
+ ): Category =>
15
+ ({
16
+ id: "cat-1",
17
+ version: 1,
18
+ createdAt: "2024-01-01T00:00:00.000Z",
19
+ lastModifiedAt: "2024-01-01T00:00:00.000Z",
20
+ name: { en: "Test Category" },
21
+ slug: { en: "test-category" },
22
+ orderHint: "0.1",
23
+ ancestors: [],
24
+ ...overrides,
25
+ }) as Category;
26
+
27
+ export const makeChannel = (
28
+ overrides: Partial<Writable<Channel>> = {},
29
+ ): Channel =>
30
+ ({
31
+ id: "channel-1",
32
+ version: 1,
33
+ key: "default-channel",
34
+ createdAt: "2024-01-01T00:00:00.000Z",
35
+ lastModifiedAt: "2024-01-01T00:00:00.000Z",
36
+ roles: [],
37
+ ...overrides,
38
+ }) as Channel;
39
+
40
+ export const makeCustomer = (
41
+ overrides: Partial<Writable<Customer>> = {},
42
+ ): Customer =>
43
+ ({
44
+ id: "customer-1",
45
+ version: 1,
46
+ createdAt: "2024-01-01T00:00:00.000Z",
47
+ lastModifiedAt: "2024-01-01T00:00:00.000Z",
48
+ email: "test@example.com",
49
+ addresses: [],
50
+ isEmailVerified: false,
51
+ authenticationMode: "Password",
52
+ stores: [],
53
+ ...overrides,
54
+ }) as Customer;
55
+
56
+ export const makeType = (overrides: Partial<Writable<Type>> = {}): Type =>
57
+ ({
58
+ id: "type-1",
59
+ version: 1,
60
+ createdAt: "2024-01-01T00:00:00.000Z",
61
+ lastModifiedAt: "2024-01-01T00:00:00.000Z",
62
+ key: "my-type",
63
+ name: { en: "My Type" },
64
+ resourceTypeIds: ["category"],
65
+ fieldDefinitions: [],
66
+ ...overrides,
67
+ }) as Type;
68
+
69
+ export const makeCustomObject = (
70
+ overrides: Partial<Writable<CustomObject>> = {},
71
+ ): CustomObject =>
72
+ ({
73
+ id: "co-1",
74
+ version: 1,
75
+ createdAt: "2024-01-01T00:00:00.000Z",
76
+ lastModifiedAt: "2024-01-01T00:00:00.000Z",
77
+ container: "my-container",
78
+ key: "my-key",
79
+ value: { foo: "bar" },
80
+ ...overrides,
81
+ }) as CustomObject;
82
+
83
+ export const storageEngineName = process.env.STORAGE_ENGINE || "in-memory";
84
+
85
+ export async function createStorage(): Promise<AbstractStorage> {
86
+ switch (storageEngineName) {
87
+ case "in-memory":
88
+ return new InMemoryStorage();
89
+ case "sqlite": {
90
+ // Dynamic import to avoid importing node:sqlite on runtimes that don't have it
91
+ const { SQLiteStorage } = await import("./sqlite.ts");
92
+ return new SQLiteStorage({ filename: ":memory:" });
93
+ }
94
+ default:
95
+ throw new Error(`Unknown STORAGE_ENGINE: ${storageEngineName}`);
96
+ }
97
+ }