@ibiliaze/stringman 3.18.0 → 3.19.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: string, { 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,76 @@ 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
+ // Build a getter for a "dot.path" key like "user.id"
281
+ const getKey = (obj) => {
282
+ try {
283
+ return key.split('.').reduce((acc, k) => acc?.[k], obj);
284
+ }
285
+ catch {
286
+ return undefined;
287
+ }
288
+ };
289
+ // Map preserves insertion order, which gives stable output ordering for "first wins"
290
+ const map = new Map();
291
+ const noKey = [];
292
+ for (const item of arr) {
293
+ const raw = getKey(item);
294
+ const k = (0, exports.valueKey)(raw);
295
+ if (!k) {
296
+ if (keepNoKey)
297
+ noKey.push(item);
298
+ continue;
299
+ }
300
+ if (keepFirst) {
301
+ if (!map.has(k))
302
+ map.set(k, item);
303
+ }
304
+ else {
305
+ map.set(k, item); // last wins
306
+ }
307
+ }
308
+ const deduped = Array.from(map.values());
309
+ return keepNoKey ? noKey.concat(deduped) : deduped;
310
+ }
311
+ catch (e) {
312
+ console.error('dedupeBy(): failed:', e);
313
+ return Array.isArray(arr) ? arr : [];
314
+ }
315
+ };
316
+ exports.dedupeBy = dedupeBy;
247
317
  const invalidPw = (password, passwordLength = 8) => {
248
318
  try {
249
319
  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.19.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.19.0 -m '3.19.0'; git push origin 3.19.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,79 @@ 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>(
259
+ arr: T[] = [],
260
+ key: string,
261
+ { keepFirst = true, keepNoKey = true }: DedupeByOptions = {}
262
+ ): T[] => {
263
+ try {
264
+ if (!Array.isArray(arr)) {
265
+ console.error('dedupeBy(): expected an array, got:', arr);
266
+ return [];
267
+ }
268
+
269
+ // Build a getter for a "dot.path" key like "user.id"
270
+ const getKey = (obj: any): unknown => {
271
+ try {
272
+ return key.split('.').reduce((acc, k) => acc?.[k], obj);
273
+ } catch {
274
+ return undefined;
275
+ }
276
+ };
277
+
278
+ // Map preserves insertion order, which gives stable output ordering for "first wins"
279
+ const map = new Map<string, T>();
280
+ const noKey: T[] = [];
281
+
282
+ for (const item of arr) {
283
+ const raw = getKey(item);
284
+ const k = valueKey(raw);
285
+
286
+ if (!k) {
287
+ if (keepNoKey) noKey.push(item);
288
+ continue;
289
+ }
290
+
291
+ if (keepFirst) {
292
+ if (!map.has(k)) map.set(k, item);
293
+ } else {
294
+ map.set(k, item); // last wins
295
+ }
296
+ }
297
+
298
+ const deduped = Array.from(map.values());
299
+ return keepNoKey ? noKey.concat(deduped) : deduped;
300
+ } catch (e) {
301
+ console.error('dedupeBy(): failed:', e);
302
+ return Array.isArray(arr) ? arr : [];
303
+ }
304
+ };
305
+
233
306
  export const invalidPw = (password: string, passwordLength: number = 8): string | void => {
234
307
  try {
235
308
  if (password.length < passwordLength) return `Password must be at least ${passwordLength} characters`;
@@ -292,3 +365,8 @@ export const crypto = {
292
365
  };
293
366
 
294
367
  export const order = { createNumericOrderId };
368
+
369
+ type DedupeByOptions = {
370
+ keepFirst?: boolean;
371
+ keepNoKey?: boolean;
372
+ };