@ibiliaze/stringman 3.18.0 → 3.20.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.
package/dist/index.d.ts CHANGED
@@ -141,6 +141,18 @@ export declare const isVideoUrl: (url: string) => boolean;
141
141
  * @throws Error if no endpoint returns a valid IP.
142
142
  */
143
143
  export declare const getPublicIP: () => Promise<string>;
144
+ /**
145
+ * Convert an unknown value into a stable string key for deduping.
146
+ */
147
+ export declare const valueKey: (v: unknown) => string;
148
+ /**
149
+ * Dedupe an array by a key (dot-path supported), preserving the item type.
150
+ *
151
+ * - keepFirst=true => first wins
152
+ * - keepFirst=false => last wins
153
+ * - keepNoKey=true => keep items where key is missing/invalid (kept in original order, before deduped items)
154
+ */
155
+ export declare const dedupeBy: <T>(arr: T[] | undefined, key: keyof T, { keepFirst, keepNoKey }?: DedupeByOptions) => T[];
144
156
  export declare const invalidPw: (password: string, passwordLength?: number) => string | void;
145
157
  export declare const b36: (n: number) => string;
146
158
  export declare const luhn36: (s: string) => string;
@@ -170,3 +182,7 @@ export declare const crypto: {
170
182
  export declare const order: {
171
183
  createNumericOrderId: () => string;
172
184
  };
185
+ type DedupeByOptions = {
186
+ keepFirst?: boolean;
187
+ keepNoKey?: boolean;
188
+ };
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.order = exports.crypto = exports.ticket = exports.luhn36 = exports.b36 = exports.invalidPw = exports.getPublicIP = exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
17
+ exports.order = exports.crypto = exports.ticket = exports.luhn36 = exports.b36 = exports.invalidPw = exports.dedupeBy = exports.valueKey = exports.getPublicIP = exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
18
18
  __exportStar(require("./seat"), exports);
19
19
  const ticket_1 = require("./ticket");
20
20
  const order_1 = require("./order");
@@ -244,6 +244,78 @@ const getPublicIP = async () => {
244
244
  throw new Error('Public IP unavailable');
245
245
  };
246
246
  exports.getPublicIP = getPublicIP;
247
+ /**
248
+ * Convert an unknown value into a stable string key for deduping.
249
+ */
250
+ const valueKey = (v) => {
251
+ try {
252
+ if (v == null)
253
+ return '';
254
+ if (typeof v === 'string')
255
+ return v;
256
+ const key = String(v);
257
+ if (key === '[object Object]')
258
+ return '';
259
+ return key;
260
+ }
261
+ catch (e) {
262
+ console.error('valueKey(): failed to normalise value:', v, e);
263
+ return '';
264
+ }
265
+ };
266
+ exports.valueKey = valueKey;
267
+ /**
268
+ * Dedupe an array by a key (dot-path supported), preserving the item type.
269
+ *
270
+ * - keepFirst=true => first wins
271
+ * - keepFirst=false => last wins
272
+ * - keepNoKey=true => keep items where key is missing/invalid (kept in original order, before deduped items)
273
+ */
274
+ const dedupeBy = (arr = [], key, { keepFirst = true, keepNoKey = true } = {}) => {
275
+ try {
276
+ if (!Array.isArray(arr)) {
277
+ console.error('dedupeBy(): expected an array, got:', arr);
278
+ return [];
279
+ }
280
+ if (typeof key !== 'string')
281
+ throw new Error('Key is not a string');
282
+ // Build a getter for a "dot.path" key like "user.id"
283
+ const getKey = (obj) => {
284
+ try {
285
+ return key.split('.').reduce((acc, k) => acc?.[k], obj);
286
+ }
287
+ catch {
288
+ return undefined;
289
+ }
290
+ };
291
+ // Map preserves insertion order, which gives stable output ordering for "first wins"
292
+ const map = new Map();
293
+ const noKey = [];
294
+ for (const item of arr) {
295
+ const raw = getKey(item);
296
+ const k = (0, exports.valueKey)(raw);
297
+ if (!k) {
298
+ if (keepNoKey)
299
+ noKey.push(item);
300
+ continue;
301
+ }
302
+ if (keepFirst) {
303
+ if (!map.has(k))
304
+ map.set(k, item);
305
+ }
306
+ else {
307
+ map.set(k, item); // last wins
308
+ }
309
+ }
310
+ const deduped = Array.from(map.values());
311
+ return keepNoKey ? noKey.concat(deduped) : deduped;
312
+ }
313
+ catch (e) {
314
+ console.error('dedupeBy(): failed:', e);
315
+ return Array.isArray(arr) ? arr : [];
316
+ }
317
+ };
318
+ exports.dedupeBy = dedupeBy;
247
319
  const invalidPw = (password, passwordLength = 8) => {
248
320
  try {
249
321
  if (password.length < passwordLength)
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@ibiliaze/stringman",
3
- "version": "3.18.0",
3
+ "version": "3.20.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "pub": "npm publish --access public",
10
- "git": "git add .; git commit -m 'changes'; git tag -a 3.18.0 -m '3.18.0'; git push origin 3.18.0; git push",
10
+ "git": "git add .; git commit -m 'changes'; git tag -a 3.20.0 -m '3.20.0'; git push origin 3.20.0; git push",
11
11
  "push": "npm run build; npm run git; npm run pub"
12
12
  },
13
13
  "author": "Ibi Hasanli",
package/src/index.ts CHANGED
@@ -230,6 +230,77 @@ export const getPublicIP = async (): Promise<string> => {
230
230
  throw new Error('Public IP unavailable');
231
231
  };
232
232
 
233
+ /**
234
+ * Convert an unknown value into a stable string key for deduping.
235
+ */
236
+ export const valueKey = (v: unknown): string => {
237
+ try {
238
+ if (v == null) return '';
239
+ if (typeof v === 'string') return v;
240
+
241
+ const key = String(v);
242
+ if (key === '[object Object]') return '';
243
+
244
+ return key;
245
+ } catch (e) {
246
+ console.error('valueKey(): failed to normalise value:', v, e);
247
+ return '';
248
+ }
249
+ };
250
+
251
+ /**
252
+ * Dedupe an array by a key (dot-path supported), preserving the item type.
253
+ *
254
+ * - keepFirst=true => first wins
255
+ * - keepFirst=false => last wins
256
+ * - keepNoKey=true => keep items where key is missing/invalid (kept in original order, before deduped items)
257
+ */
258
+ export const dedupeBy = <T>(arr: T[] = [], key: keyof T, { keepFirst = true, keepNoKey = true }: DedupeByOptions = {}): T[] => {
259
+ try {
260
+ if (!Array.isArray(arr)) {
261
+ console.error('dedupeBy(): expected an array, got:', arr);
262
+ return [];
263
+ }
264
+
265
+ if (typeof key !== 'string') throw new Error('Key is not a string');
266
+
267
+ // Build a getter for a "dot.path" key like "user.id"
268
+ const getKey = (obj: any): unknown => {
269
+ try {
270
+ return key.split('.').reduce((acc, k) => acc?.[k], obj);
271
+ } catch {
272
+ return undefined;
273
+ }
274
+ };
275
+
276
+ // Map preserves insertion order, which gives stable output ordering for "first wins"
277
+ const map = new Map<string, T>();
278
+ const noKey: T[] = [];
279
+
280
+ for (const item of arr) {
281
+ const raw = getKey(item);
282
+ const k = valueKey(raw);
283
+
284
+ if (!k) {
285
+ if (keepNoKey) noKey.push(item);
286
+ continue;
287
+ }
288
+
289
+ if (keepFirst) {
290
+ if (!map.has(k)) map.set(k, item);
291
+ } else {
292
+ map.set(k, item); // last wins
293
+ }
294
+ }
295
+
296
+ const deduped = Array.from(map.values());
297
+ return keepNoKey ? noKey.concat(deduped) : deduped;
298
+ } catch (e) {
299
+ console.error('dedupeBy(): failed:', e);
300
+ return Array.isArray(arr) ? arr : [];
301
+ }
302
+ };
303
+
233
304
  export const invalidPw = (password: string, passwordLength: number = 8): string | void => {
234
305
  try {
235
306
  if (password.length < passwordLength) return `Password must be at least ${passwordLength} characters`;
@@ -292,3 +363,8 @@ export const crypto = {
292
363
  };
293
364
 
294
365
  export const order = { createNumericOrderId };
366
+
367
+ type DedupeByOptions = {
368
+ keepFirst?: boolean;
369
+ keepNoKey?: boolean;
370
+ };