@softwear/latestcollectioncore 1.0.130 → 1.0.132

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.
package/.cursorrules ADDED
@@ -0,0 +1,353 @@
1
+ # latestcollectioncore Library Rules
2
+
3
+ **Context**: This is a shared TypeScript library used by both erp8 (frontend) and latest-collection-server (backend). See parent `.cursorrules` for overall architecture.
4
+
5
+ ## Core Principles
6
+
7
+ 1. **Pure and portable**: No side effects, runs in browser AND Node.js
8
+ 2. **Strictly typed**: All exports have TypeScript types
9
+ 3. **Tested**: Unit tests for all functions
10
+ 4. **Semver discipline**: Breaking changes = major version bump
11
+
12
+ ## What Belongs Here
13
+
14
+ ✅ **Include**:
15
+
16
+ - Pure utility functions (data transformation, validation, formatting)
17
+ - Shared TypeScript types/interfaces
18
+ - Constants used by multiple repos
19
+ - Business logic calculations (no I/O)
20
+ - Format converters (barcode, dates, currency)
21
+
22
+ ❌ **Exclude**:
23
+
24
+ - Node.js-specific code (`fs`, `path`, `http`)
25
+ - Browser-specific code (`window`, `document`, `localStorage`)
26
+ - Database operations
27
+ - API calls
28
+ - UI components
29
+ - Environment-dependent config
30
+
31
+ ## Development Rules
32
+
33
+ ### Function Design
34
+
35
+ **Pure functions only**:
36
+
37
+ ```typescript
38
+ // ✅ Good - pure function
39
+ export function calculateDiscount(price: number, percent: number): number {
40
+ return price * (1 - percent / 100)
41
+ }
42
+
43
+ // ❌ Bad - side effect
44
+ export function saveDiscount(price: number, percent: number): number {
45
+ localStorage.setItem('lastDiscount', percent.toString()) // Browser-only!
46
+ return price * (1 - percent / 100)
47
+ }
48
+ ```
49
+
50
+ **Dependency injection for testing**:
51
+
52
+ ```typescript
53
+ // ✅ Good - inject dependencies
54
+ export function processData(data: Data[], deps?: { logger?: (msg: string) => void }): Result {
55
+ deps?.logger?.('Processing started')
56
+ // ... pure logic
57
+ return result
58
+ }
59
+
60
+ // ❌ Bad - hardcoded dependency
61
+ import { logger } from './logger' // Might not work in browser
62
+ export function processData(data: Data[]): Result {
63
+ logger.log('Processing started')
64
+ return result
65
+ }
66
+ ```
67
+
68
+ ### Type Definitions
69
+
70
+ **Export types from index.ts**:
71
+
72
+ ```typescript
73
+ // src/types.ts
74
+ export interface Sku {
75
+ id: string
76
+ barcode: string
77
+ description: string
78
+ price: number
79
+ }
80
+
81
+ // src/index.ts
82
+ export { Sku } from './types'
83
+ export { deepCopy } from './utils/deepCopy'
84
+ ```
85
+
86
+ **Use discriminated unions for complex types**:
87
+
88
+ ```typescript
89
+ export type Result<T> = { success: true; data: T } | { success: false; error: string }
90
+ ```
91
+
92
+ ### Testing
93
+
94
+ **Every exported function needs a test**:
95
+
96
+ ```typescript
97
+ // test/deepCopy.test.js
98
+ import { deepCopy } from '../src/utils/deepCopy'
99
+
100
+ describe('deepCopy', () => {
101
+ it('creates independent copy', () => {
102
+ const original = { a: { b: 1 } }
103
+ const copy = deepCopy(original)
104
+ copy.a.b = 2
105
+ expect(original.a.b).toBe(1)
106
+ })
107
+ })
108
+ ```
109
+
110
+ **Test in both Node and browser environments** (if applicable):
111
+
112
+ - Use framework-agnostic test runner (e.g., Mocha, Jest)
113
+ - Avoid Node-specific assertions
114
+
115
+ ## File Structure
116
+
117
+ ✅ Keep folder structure flat
118
+ ❌ Clutter `/src` folder with unneccesary subfolders
119
+
120
+ ```
121
+ latestcollectioncore/
122
+ ├── src/
123
+ │ ├── index.ts # Main export file
124
+ │ ├── types.ts # Shared TypeScript types
125
+ │ ├── deepCopy.ts
126
+ │ ├── articleStatus.ts
127
+ │ ├── formatCurrency.ts
128
+ │ ├── inventory.ts
129
+ │ └── validateBarcode.ts
130
+ ├── test/
131
+ │ ├── deepCopy.test.js
132
+ │ └── validateBarcode.test.js
133
+ ├── dist/ # Compiled output
134
+ ├── package.json
135
+ └── tsconfig.json
136
+ ```
137
+
138
+ ## Versioning & Publishing
139
+
140
+ ### Semver Rules
141
+
142
+ - **Patch** (1.0.x): Bug fixes, no API changes
143
+ - **Minor** (1.x.0): New functions, backward compatible
144
+ - **Major** (x.0.0): Breaking changes to existing exports
145
+
146
+ ### Before Publishing Checklist
147
+
148
+ 1. ✅ All tests pass: `npm test`
149
+ 2. ✅ TypeScript compiles: `npm run build`
150
+ 3. ✅ No Node/browser-specific code
151
+ 4. ✅ Exports added to `index.ts`
152
+ 5. ✅ Version bumped in `package.json`
153
+ 6. ✅ Changelog updated
154
+
155
+ ### Breaking Change Examples
156
+
157
+ - Removing or renaming exported function
158
+ - Changing function signature (parameters, return type)
159
+ - Changing behavior of existing function
160
+ - Removing or renaming exported type
161
+
162
+ ### Non-Breaking Change Examples
163
+
164
+ - Adding new exported function
165
+ - Adding optional parameters with defaults
166
+ - Adding new exported type
167
+ - Internal refactoring (no export changes)
168
+
169
+ ## Common Utilities
170
+
171
+ ### deepCopy
172
+
173
+ **Critical for erp8's database pattern** - always deep copy before modifying objects from globalStore:
174
+
175
+ ```typescript
176
+ import { deepCopy } from '@softwear/latestcollectioncore'
177
+
178
+ const original = globalStore.getLatestCollectionObject('sku')[id]
179
+ const modified = deepCopy(original)
180
+ modified.price = 100
181
+ // Now updateObjects will detect the diff
182
+ ```
183
+
184
+ ### Validation
185
+
186
+ ```typescript
187
+ // validateBarcode.ts
188
+ export function isValidEAN13(barcode: string): boolean {
189
+ if (!/^\d{13}$/.test(barcode)) return false
190
+ // ... checksum validation
191
+ return true
192
+ }
193
+ ```
194
+
195
+ ### Formatting
196
+
197
+ ```typescript
198
+ // formatCurrency.ts
199
+ export function formatCurrency(amount: number, currency: string = 'EUR'): string {
200
+ return new Intl.NumberFormat('nl-NL', {
201
+ style: 'currency',
202
+ currency,
203
+ }).format(amount)
204
+ }
205
+ ```
206
+
207
+ ## Import Restrictions
208
+
209
+ ### Allowed Standard Imports
210
+
211
+ - ✅ Pure JavaScript standard library (Array, Object, Math, Date, etc.)
212
+ - ✅ TypeScript types (no runtime)
213
+ - ✅ Other functions within latestcollectioncore
214
+
215
+ ### Forbidden Imports
216
+
217
+ - ❌ `fs`, `path`, `http`, `process` (Node.js only)
218
+ - ❌ `window`, `document`, `localStorage` (Browser only)
219
+ - ❌ `axios`, `express`, `vue` (app-specific)
220
+ - ❌ External dependencies (minimize; justify each)
221
+
222
+ ### External Dependencies
223
+
224
+ - Keep `package.json` dependencies minimal
225
+ - Prefer zero-dependency implementations for simple utilities
226
+ - If needed, choose universal libraries (work in Node + browser)
227
+ - Document why each dependency is required
228
+
229
+ ## Documentation
230
+
231
+ ### JSDoc for Complex Functions
232
+
233
+ ````typescript
234
+ /**
235
+ * Calculates inventory from transaction history
236
+ *
237
+ * @param transactions - Array of stock transactions
238
+ * @param warehouseId - Optional warehouse filter
239
+ * @returns Map of SKU ID to current quantity
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const inventory = calculateInventory(transactions, 'WH001')
244
+ * console.log(inventory['12345']) // 42
245
+ * ```
246
+ */
247
+ export function calculateInventory(transactions: Transaction[], warehouseId?: string): Map<string, number> {
248
+ // ...
249
+ }
250
+ ````
251
+
252
+ ### README.md
253
+
254
+ - List all major exported functions
255
+ - Provide usage examples
256
+ - Explain design principles
257
+ - Document breaking changes in each version
258
+
259
+ ## Error Handling
260
+
261
+ **Return errors, don't throw**:
262
+
263
+ ```typescript
264
+ // ✅ Good - explicit error handling
265
+ export function parseBarcode(input: string): Result<Barcode> {
266
+ if (!isValidEAN13(input)) {
267
+ return { success: false, error: 'Invalid EAN-13 format' }
268
+ }
269
+ return { success: true, data: { ... } }
270
+ }
271
+
272
+ // ❌ Bad - throws exception
273
+ export function parseBarcode(input: string): Barcode {
274
+ if (!isValidEAN13(input)) {
275
+ throw new Error('Invalid EAN-13 format')
276
+ }
277
+ return { ... }
278
+ }
279
+ ```
280
+
281
+ **Why**: Throwing errors crosses module boundaries unpredictably; explicit returns are clearer.
282
+
283
+ ## Performance
284
+
285
+ - **Avoid premature optimization** - clarity first
286
+ - **Benchmark hot paths** - use `console.time()` in tests
287
+ - **Document O(n) complexity** for non-trivial algorithms
288
+ - **Lazy evaluation** for expensive computations:
289
+ ```typescript
290
+ export function expensiveCalc(data: Data): () => Result {
291
+ return () => {
292
+ /* compute only when called */
293
+ }
294
+ }
295
+ ```
296
+
297
+ ## Common Pitfalls
298
+
299
+ - ❌ Importing Node.js `fs` to read config files
300
+ - ❌ Using `process.env` for environment variables
301
+ - ❌ Mutating input parameters (always return new objects)
302
+ - ❌ Using `console.log` (pass logger via deps if needed)
303
+ - ❌ Publishing without testing in both erp8 and server
304
+ - ✅ Keep functions small and focused
305
+ - ✅ Write tests first (TDD when possible)
306
+ - ✅ Document breaking changes in CHANGELOG
307
+ - ✅ Use TypeScript strict mode
308
+
309
+ ## Publishing Workflow
310
+
311
+ ```bash
312
+ # 1. Make changes
313
+ # 2. Update version in package.json
314
+ npm version patch # or minor, or major
315
+
316
+ # 3. Build
317
+ npm run build
318
+
319
+ # 4. Test
320
+ npm test
321
+
322
+ # 5. Publish
323
+ npm publish
324
+
325
+ # 6. Update dependent repos
326
+ cd ../erp8 && npm update @softwear/latestcollectioncore
327
+ cd ../latest-collection-server && npm update @softwear/latestcollectioncore
328
+ ```
329
+
330
+ ## Integration with Other Repos
331
+
332
+ ### erp8 Usage
333
+
334
+ ```typescript
335
+ import { deepCopy, formatCurrency } from '@softwear/latestcollectioncore'
336
+
337
+ const sku = deepCopy(globalStore.getLatestCollectionObject('sku')[id])
338
+ const priceFormatted = formatCurrency(sku.price)
339
+ ```
340
+
341
+ ### latest-collection-server Usage
342
+
343
+ ```typescript
344
+ import { isValidEAN13 } from '@softwear/latestcollectioncore'
345
+
346
+ if (!isValidEAN13(sku.barcode)) {
347
+ throw { status: 400, message: 'Invalid barcode' }
348
+ }
349
+ ```
350
+
351
+ Both repos should always use the same version of latestcollectioncore to avoid compatibility issues.
352
+
353
+
@@ -0,0 +1,3 @@
1
+ export default function (brand: string, metaBrandSetting: {
2
+ [key: string]: any;
3
+ }): string;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const hashBrand_1 = __importDefault(require("./hashBrand"));
7
+ function default_1(brand, metaBrandSetting) {
8
+ var _a;
9
+ const hashedBrand = (0, hashBrand_1.default)(brand);
10
+ return ((_a = metaBrandSetting[hashedBrand]) === null || _a === void 0 ? void 0 : _a.name) || brand;
11
+ }
12
+ exports.default = default_1;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { default as ean13 } from './ean13';
5
5
  export { default as edifact } from './edifact';
6
6
  export { default as ensureArray } from './ensureArray';
7
7
  export { default as findSkuByBarcode } from './findSkuByBarcode';
8
+ export { default as getBrandName } from './getBrandName';
8
9
  export { default as getPreferedPropertyMappings } from './getPreferedPropertyMappings';
9
10
  export { default as hashBrand } from './hashBrand';
10
11
  export { default as hasOnlyDigits } from './hasOnlyDigits';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.transaction = exports.sizeToMap = exports.round2 = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.findSkuByBarcode = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
20
+ exports.transaction = exports.sizeToMap = exports.round2 = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findSkuByBarcode = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
21
21
  var articleStatus_1 = require("./articleStatus");
22
22
  Object.defineProperty(exports, "articleStatus", { enumerable: true, get: function () { return __importDefault(articleStatus_1).default; } });
23
23
  var buildPropertyMappingFn_1 = require("./buildPropertyMappingFn");
@@ -32,6 +32,8 @@ var ensureArray_1 = require("./ensureArray");
32
32
  Object.defineProperty(exports, "ensureArray", { enumerable: true, get: function () { return __importDefault(ensureArray_1).default; } });
33
33
  var findSkuByBarcode_1 = require("./findSkuByBarcode");
34
34
  Object.defineProperty(exports, "findSkuByBarcode", { enumerable: true, get: function () { return __importDefault(findSkuByBarcode_1).default; } });
35
+ var getBrandName_1 = require("./getBrandName");
36
+ Object.defineProperty(exports, "getBrandName", { enumerable: true, get: function () { return __importDefault(getBrandName_1).default; } });
35
37
  var getPreferedPropertyMappings_1 = require("./getPreferedPropertyMappings");
36
38
  Object.defineProperty(exports, "getPreferedPropertyMappings", { enumerable: true, get: function () { return __importDefault(getPreferedPropertyMappings_1).default; } });
37
39
  var hashBrand_1 = require("./hashBrand");
package/dist/isean13.js CHANGED
@@ -12,6 +12,12 @@ function default_1(value) {
12
12
  if (stringILN.length != 13)
13
13
  return false;
14
14
  const baseNumber = stringILN.substring(0, 12);
15
- return (0, ean13_1.default)(baseNumber) + '' == stringILN;
15
+ try {
16
+ const isean13 = (0, ean13_1.default)(baseNumber) + '' == stringILN;
17
+ return isean13;
18
+ }
19
+ catch (error) {
20
+ return false;
21
+ }
16
22
  }
17
23
  exports.default = default_1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.130",
3
+ "version": "1.0.132",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,6 @@
1
+ import hashBrand from './hashBrand'
2
+
3
+ export default function (brand: string, metaBrandSetting: { [key: string]: any }): string {
4
+ const hashedBrand = hashBrand(brand)
5
+ return metaBrandSetting[hashedBrand]?.name || brand
6
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { default as ean13 } from './ean13'
5
5
  export { default as edifact } from './edifact'
6
6
  export { default as ensureArray } from './ensureArray'
7
7
  export { default as findSkuByBarcode } from './findSkuByBarcode'
8
+ export { default as getBrandName } from './getBrandName'
8
9
  export { default as getPreferedPropertyMappings } from './getPreferedPropertyMappings'
9
10
  export { default as hashBrand } from './hashBrand'
10
11
  export { default as hasOnlyDigits } from './hasOnlyDigits'
package/src/isean13.ts CHANGED
@@ -8,5 +8,10 @@ export default function (value: string | number): boolean {
8
8
  if (stringILN.length != 13) return false
9
9
 
10
10
  const baseNumber = stringILN.substring(0, 12)
11
- return ean13(baseNumber) + '' == stringILN
11
+ try {
12
+ const isean13 = ean13(baseNumber) + '' == stringILN
13
+ return isean13
14
+ } catch (error) {
15
+ return false
16
+ }
12
17
  }
@@ -0,0 +1,16 @@
1
+ const { getBrandName } = require('../dist/index')
2
+ const { expect } = require('chai')
3
+
4
+ const should = require('chai').should()
5
+
6
+ describe('getBrandName function', function () {
7
+ it('should return the brand name if it is in the meta brand setting', function () {
8
+ expect(getBrandName('Marie Jo', { mariejo: { name: 'Marie Jo' } })).to.equal('Marie Jo')
9
+ expect(getBrandName('mariejo', { mariejo: { name: 'Marie Jo' } })).to.equal('Marie Jo')
10
+ expect(getBrandName('MARIEJO', { mariejo: { name: 'Marie Jo' } })).to.equal('Marie Jo')
11
+ expect(getBrandName('Cl ~<>!@#$%.^&*():\'"? ean', { clean: { name: 'Clean' } })).to.equal('Clean')
12
+ })
13
+ it('should return the brand name if it is not in the meta brand setting', function () {
14
+ expect(getBrandName('Not in the meta brand setting', {})).to.equal('Not in the meta brand setting')
15
+ })
16
+ })