@poppinss/utils 7.0.0-next.3 → 7.0.0-next.5

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/README.md CHANGED
@@ -627,6 +627,123 @@ const rawValue = secret.release()
627
627
  rawValue === opaque_raw_token // true
628
628
  ```
629
629
 
630
+ ## ImportsBag
631
+
632
+ The `ImportsBag` class helps you manage and deduplicate import statements when generating code. It automatically merges imports from the same source and generates properly formatted import statements.
633
+
634
+ This is particularly useful when building code generators, AST transformers, or any tool that needs to collect and output import statements.
635
+
636
+ ```ts
637
+ import { ImportsBag } from '@poppinss/utils'
638
+
639
+ const bag = new ImportsBag()
640
+
641
+ // Add named imports
642
+ bag.add({
643
+ source: 'lodash',
644
+ namedImports: ['debounce'],
645
+ })
646
+
647
+ // Add more imports from the same source - they will be merged
648
+ bag.add({
649
+ source: 'lodash',
650
+ namedImports: ['throttle', 'debounce'], // duplicate "debounce" will be removed
651
+ })
652
+
653
+ // Add default import with named imports
654
+ bag.add({
655
+ source: 'react',
656
+ defaultImport: 'React',
657
+ namedImports: ['useState', 'useEffect'],
658
+ })
659
+
660
+ // Add type imports
661
+ bag.add({
662
+ source: 'express',
663
+ typeImports: ['Request', 'Response'],
664
+ })
665
+
666
+ // Generate import statements
667
+ console.log(bag.toString())
668
+ // import { debounce, throttle } from 'lodash'
669
+ // import React, { useState, useEffect } from 'react'
670
+ // import type { Request, Response } from 'express'
671
+ ```
672
+
673
+ ### Import Types
674
+
675
+ The `ImportsBag` supports three types of imports:
676
+
677
+ - **Default imports**: `defaultImport: 'React'` generates `import React from 'react'`
678
+ - **Named imports**: `namedImports: ['useState']` generates `import { useState } from 'react'`
679
+ - **Type imports**: `typeImports: ['FC']` generates `import type { FC } from 'react'`
680
+
681
+ You can combine default and named imports in a single statement, but type imports are always generated as separate statements.
682
+
683
+ ```ts
684
+ bag.add({
685
+ source: 'react',
686
+ defaultImport: 'React',
687
+ namedImports: ['useState'],
688
+ typeImports: ['FC'],
689
+ })
690
+
691
+ console.log(bag.toString())
692
+ // import React, { useState } from 'react'
693
+ // import type { FC } from 'react'
694
+ ```
695
+
696
+ ### Deduplication
697
+
698
+ The `ImportsBag` automatically deduplicates imports from the same source:
699
+
700
+ - Named and type imports are deduplicated when calling `toArray()` or `toString()`
701
+ - Default imports are replaced (the last one wins)
702
+ - Imports are merged by source, so multiple `add()` calls for the same source will combine all imports
703
+
704
+ ```ts
705
+ bag.add({ source: 'lodash', namedImports: ['debounce'] })
706
+ bag.add({ source: 'lodash', namedImports: ['throttle'] })
707
+ bag.add({ source: 'lodash', namedImports: ['debounce'] }) // duplicate
708
+
709
+ console.log(bag.toString())
710
+ // import { debounce, throttle } from 'lodash'
711
+ ```
712
+
713
+ ### Methods
714
+
715
+ #### add(importInfo)
716
+
717
+ Add an import to the bag. Returns `this` for method chaining.
718
+
719
+ ```ts
720
+ bag
721
+ .add({ source: 'lodash', namedImports: ['debounce'] })
722
+ .add({ source: 'express', typeImports: ['Request'] })
723
+ ```
724
+
725
+ #### toArray()
726
+
727
+ Returns an array of deduplicated `ImportInfo` objects.
728
+
729
+ ```ts
730
+ const imports = bag.toArray()
731
+ // [
732
+ // { source: 'lodash', defaultImport: undefined, namedImports: ['debounce'], typeImports: undefined },
733
+ // { source: 'express', defaultImport: undefined, namedImports: undefined, typeImports: ['Request'] }
734
+ // ]
735
+ ```
736
+
737
+ #### toString()
738
+
739
+ Generates formatted import statements as a string.
740
+
741
+ ```ts
742
+ const code = bag.toString()
743
+ // import { debounce } from 'lodash'
744
+ // import type { Request } from 'express'
745
+ ```
746
+
630
747
  [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/utils/checks.yml?style=for-the-badge
631
748
  [gh-workflow-url]: https://github.com/poppinss/utils/actions/workflows/checks.yml 'Github action'
632
749
  [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
@@ -0,0 +1,23 @@
1
+ import "node:module";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ export { __toESM as n, __commonJSMin as t };
@@ -0,0 +1,2 @@
1
+ import { Exception, InvalidArgumentsException, RuntimeException, createError } from "@poppinss/exception";
2
+ export { createError as i, InvalidArgumentsException as n, RuntimeException as r, Exception as t };
package/build/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export { naturalSort } from './src/natural_sort.js';
6
6
  export { isScriptFile } from './src/is_script_file.js';
7
7
  export { importDefault } from './src/import_default.js';
8
8
  export { MessageBuilder } from './src/message_builder.js';
9
+ export { ImportsBag, type ImportInfo } from './src/imports_bag.js';
9
10
  export { defineStaticProperty } from './src/define_static_property.js';
package/build/index.js CHANGED
@@ -1,179 +1,154 @@
1
- import {
2
- RuntimeException
3
- } from "./chunk-SKNTF5Q5.js";
4
- import {
5
- isScriptFile,
6
- naturalSort
7
- } from "./chunk-XFX47BKO.js";
8
- import {
9
- safeParse,
10
- safeStringify
11
- } from "./chunk-YFSCSJNE.js";
12
-
13
- // src/secret.ts
14
- var REDACTED = "[redacted]";
15
- var Secret = class _Secret {
16
- /** The secret value */
17
- #value;
18
- #keyword;
19
- constructor(value, redactedKeyword) {
20
- this.#value = value;
21
- this.#keyword = redactedKeyword || REDACTED;
22
- }
23
- toJSON() {
24
- return this.#keyword;
25
- }
26
- valueOf() {
27
- return this.#keyword;
28
- }
29
- [Symbol.for("nodejs.util.inspect.custom")]() {
30
- return this.#keyword;
31
- }
32
- toLocaleString() {
33
- return this.#keyword;
34
- }
35
- toString() {
36
- return this.#keyword;
37
- }
38
- /**
39
- * Returns the original value
40
- */
41
- release() {
42
- return this.#value;
43
- }
44
- /**
45
- * Transform the original value and create a new
46
- * secret from it.
47
- */
48
- map(transformFunc) {
49
- return new _Secret(transformFunc(this.#value));
50
- }
1
+ import { n as __toESM } from "./chunk-BNZ4_A-W.js";
2
+ import { n as isScriptFile, r as naturalSort, t as require_main } from "./main-CdreHl1_.js";
3
+ import { r as RuntimeException } from "./exception-L7vjh-Gv.js";
4
+ import { n as safeParse, t as safeStringify } from "./main-aO4h7ygK.js";
5
+ import { flattie } from "flattie";
6
+ import { Buffer } from "node:buffer";
7
+ import { timingSafeEqual } from "node:crypto";
8
+ import string from "@poppinss/string";
9
+ const REDACTED = "[redacted]";
10
+ var Secret = class Secret {
11
+ #value;
12
+ #keyword;
13
+ constructor(value, redactedKeyword) {
14
+ this.#value = value;
15
+ this.#keyword = redactedKeyword || REDACTED;
16
+ }
17
+ toJSON() {
18
+ return this.#keyword;
19
+ }
20
+ valueOf() {
21
+ return this.#keyword;
22
+ }
23
+ [Symbol.for("nodejs.util.inspect.custom")]() {
24
+ return this.#keyword;
25
+ }
26
+ toLocaleString() {
27
+ return this.#keyword;
28
+ }
29
+ toString() {
30
+ return this.#keyword;
31
+ }
32
+ release() {
33
+ return this.#value;
34
+ }
35
+ map(transformFunc) {
36
+ return new Secret(transformFunc(this.#value));
37
+ }
51
38
  };
52
-
53
- // src/compose.ts
54
39
  function compose(superclass, ...mixins) {
55
- return mixins.reduce((c, mixin) => mixin(c), superclass);
40
+ return mixins.reduce((c, mixin) => mixin(c), superclass);
56
41
  }
57
-
58
- // src/flatten.ts
59
- import { flattie } from "flattie";
60
42
  function flatten(input, glue, keepNullish) {
61
- return flattie(input, glue, keepNullish);
43
+ return flattie(input, glue, keepNullish);
62
44
  }
63
-
64
- // src/safe_equal.ts
65
- import { Buffer } from "buffer";
66
- import { timingSafeEqual } from "crypto";
67
45
  function safeEqual(trustedValue, userInput) {
68
- if (typeof trustedValue === "string" && typeof userInput === "string") {
69
- const trustedLength = Buffer.byteLength(trustedValue);
70
- const trustedValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
71
- trustedValueBuffer.write(trustedValue);
72
- const userValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
73
- userValueBuffer.write(userInput);
74
- return timingSafeEqual(trustedValueBuffer, userValueBuffer) && trustedLength === Buffer.byteLength(userInput);
75
- }
76
- return timingSafeEqual(
77
- Buffer.from(trustedValue),
78
- Buffer.from(userInput)
79
- );
46
+ if (typeof trustedValue === "string" && typeof userInput === "string") {
47
+ const trustedLength = Buffer.byteLength(trustedValue);
48
+ const trustedValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
49
+ trustedValueBuffer.write(trustedValue);
50
+ const userValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
51
+ userValueBuffer.write(userInput);
52
+ return timingSafeEqual(trustedValueBuffer, userValueBuffer) && trustedLength === Buffer.byteLength(userInput);
53
+ }
54
+ return timingSafeEqual(Buffer.from(trustedValue), Buffer.from(userInput));
80
55
  }
81
-
82
- // src/import_default.ts
83
56
  async function importDefault(importFn, filePath) {
84
- const moduleExports = await importFn();
85
- if (!("default" in moduleExports)) {
86
- const errorMessage = filePath ? `Missing "export default" in module "${filePath}"` : `Missing "export default" from lazy import "${importFn}"`;
87
- throw new RuntimeException(errorMessage, {
88
- cause: {
89
- source: importFn
90
- }
91
- });
92
- }
93
- return moduleExports.default;
57
+ const moduleExports = await importFn();
58
+ if (!("default" in moduleExports)) throw new RuntimeException(filePath ? `Missing "export default" in module "${filePath}"` : `Missing "export default" from lazy import "${importFn}"`, { cause: { source: importFn } });
59
+ return moduleExports.default;
94
60
  }
95
-
96
- // src/message_builder.ts
97
- import string from "@poppinss/string";
98
61
  var MessageBuilder = class {
99
- #getExpiryDate(expiresIn) {
100
- if (!expiresIn) {
101
- return void 0;
102
- }
103
- const expiryMs = string.milliseconds.parse(expiresIn);
104
- return new Date(Date.now() + expiryMs);
105
- }
106
- /**
107
- * Returns a boolean telling, if message has been expired or not
108
- */
109
- #isExpired(message) {
110
- if (!message.expiryDate) {
111
- return false;
112
- }
113
- const expiryDate = new Date(message.expiryDate);
114
- return Number.isNaN(expiryDate.getTime()) || expiryDate < /* @__PURE__ */ new Date();
115
- }
116
- /**
117
- * Builds a message by encoding expiry date and purpose inside it.
118
- */
119
- build(message, expiresIn, purpose) {
120
- const expiryDate = this.#getExpiryDate(expiresIn);
121
- return safeStringify({ message, purpose, expiryDate });
122
- }
123
- /**
124
- * Verifies the message for expiry and purpose.
125
- */
126
- verify(message, purpose) {
127
- const parsed = safeParse(message);
128
- if (typeof parsed !== "object" || !parsed) {
129
- return null;
130
- }
131
- if (!parsed.message) {
132
- return null;
133
- }
134
- if (parsed.purpose !== purpose) {
135
- return null;
136
- }
137
- if (this.#isExpired(parsed)) {
138
- return null;
139
- }
140
- return parsed.message;
141
- }
62
+ #getExpiryDate(expiresIn) {
63
+ if (!expiresIn) return;
64
+ const expiryMs = string.milliseconds.parse(expiresIn);
65
+ return new Date(Date.now() + expiryMs);
66
+ }
67
+ #isExpired(message) {
68
+ if (!message.expiryDate) return false;
69
+ const expiryDate = new Date(message.expiryDate);
70
+ return Number.isNaN(expiryDate.getTime()) || expiryDate < /* @__PURE__ */ new Date();
71
+ }
72
+ build(message, expiresIn, purpose) {
73
+ return safeStringify({
74
+ message,
75
+ purpose,
76
+ expiryDate: this.#getExpiryDate(expiresIn)
77
+ });
78
+ }
79
+ verify(message, purpose) {
80
+ const parsed = safeParse(message);
81
+ if (typeof parsed !== "object" || !parsed) return null;
82
+ if (!parsed.message) return null;
83
+ if (parsed.purpose !== purpose) return null;
84
+ if (this.#isExpired(parsed)) return null;
85
+ return parsed.message;
86
+ }
142
87
  };
143
-
144
- // src/define_static_property.ts
145
- import lodash from "@poppinss/utils/lodash";
146
- function defineStaticProperty(self, propertyName, {
147
- initialValue,
148
- strategy
149
- }) {
150
- if (!self.hasOwnProperty(propertyName)) {
151
- const value = self[propertyName];
152
- if (strategy === "define" || value === void 0) {
153
- Object.defineProperty(self, propertyName, {
154
- value: initialValue,
155
- configurable: true,
156
- enumerable: true,
157
- writable: true
158
- });
159
- return;
160
- }
161
- Object.defineProperty(self, propertyName, {
162
- value: typeof strategy === "function" ? strategy(value) : lodash.cloneDeep(value),
163
- configurable: true,
164
- enumerable: true,
165
- writable: true
166
- });
167
- }
168
- }
169
- export {
170
- MessageBuilder,
171
- Secret,
172
- compose,
173
- defineStaticProperty,
174
- flatten,
175
- importDefault,
176
- isScriptFile,
177
- naturalSort,
178
- safeEqual
88
+ var ImportsBag = class {
89
+ #imports = /* @__PURE__ */ new Map();
90
+ #generateImportStatement(imp) {
91
+ const parts = [];
92
+ if (imp.defaultImport || imp.namedImports && imp.namedImports.length > 0) {
93
+ const importParts = [];
94
+ if (imp.defaultImport) importParts.push(imp.defaultImport);
95
+ if (imp.namedImports && imp.namedImports.length > 0) importParts.push(`{ ${imp.namedImports.join(", ")} }`);
96
+ parts.push(`import ${importParts.join(", ")} from '${imp.source}'`);
97
+ }
98
+ if (imp.typeImports && imp.typeImports.length > 0) parts.push(`import type { ${imp.typeImports.join(", ")} } from '${imp.source}'`);
99
+ return parts.join("\n");
100
+ }
101
+ add(importInfo) {
102
+ const existing = this.#imports.get(importInfo.source);
103
+ if (existing) {
104
+ if (importInfo.defaultImport) existing.defaultImport = importInfo.defaultImport;
105
+ if (importInfo.namedImports) {
106
+ if (!existing.namedImports) existing.namedImports = [];
107
+ existing.namedImports.push(...importInfo.namedImports);
108
+ }
109
+ if (importInfo.typeImports) {
110
+ if (!existing.typeImports) existing.typeImports = [];
111
+ existing.typeImports.push(...importInfo.typeImports);
112
+ }
113
+ } else this.#imports.set(importInfo.source, {
114
+ source: importInfo.source,
115
+ defaultImport: importInfo.defaultImport,
116
+ namedImports: importInfo.namedImports ? [...importInfo.namedImports] : void 0,
117
+ typeImports: importInfo.typeImports ? [...importInfo.typeImports] : void 0
118
+ });
119
+ return this;
120
+ }
121
+ toArray() {
122
+ return Array.from(this.#imports.values()).map((imp) => ({
123
+ source: imp.source,
124
+ defaultImport: imp.defaultImport,
125
+ namedImports: imp.namedImports ? [...new Set(imp.namedImports)] : void 0,
126
+ typeImports: imp.typeImports ? [...new Set(imp.typeImports)] : void 0
127
+ }));
128
+ }
129
+ toString() {
130
+ return this.toArray().map((imp) => this.#generateImportStatement(imp)).join("\n");
131
+ }
179
132
  };
133
+ var import_main = /* @__PURE__ */ __toESM(require_main());
134
+ function defineStaticProperty(self, propertyName, { initialValue, strategy }) {
135
+ if (!self.hasOwnProperty(propertyName)) {
136
+ const value = self[propertyName];
137
+ if (strategy === "define" || value === void 0) {
138
+ Object.defineProperty(self, propertyName, {
139
+ value: initialValue,
140
+ configurable: true,
141
+ enumerable: true,
142
+ writable: true
143
+ });
144
+ return;
145
+ }
146
+ Object.defineProperty(self, propertyName, {
147
+ value: typeof strategy === "function" ? strategy(value) : import_main.default.cloneDeep(value),
148
+ configurable: true,
149
+ enumerable: true,
150
+ writable: true
151
+ });
152
+ }
153
+ }
154
+ export { ImportsBag, MessageBuilder, Secret, compose, defineStaticProperty, flatten, importDefault, isScriptFile, naturalSort, safeEqual };