@gesslar/toolkit 4.2.0 → 4.4.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/README.md CHANGED
@@ -101,6 +101,37 @@ The browser version includes: Collection, Data, Disposer, HTML, Notify, Sass,
101
101
  Tantrum, Type (TypeSpec), Util, and Valid. Node-only modules (Cache,
102
102
  DirectoryObject, FileObject, FileSystem, Glog, Term) are not available in the browser version.
103
103
 
104
+ ### Vendor Bundle
105
+
106
+ Pre-built bundles are included for environments without a build pipeline, such
107
+ as webviews (VS Code, Tauri, Electron) or plain HTML pages. No bundler required.
108
+
109
+ #### ES module
110
+
111
+ ```html
112
+ <script type="module">
113
+ import {Data, Collection} from "./node_modules/@gesslar/toolkit/vendor/toolkit.esm.js"
114
+ </script>
115
+ ```
116
+
117
+ Or import via the package export:
118
+
119
+ ```javascript
120
+ import {Data, Collection} from "@gesslar/toolkit/vendor"
121
+ ```
122
+
123
+ #### UMD (global script)
124
+
125
+ ```html
126
+ <script src="./node_modules/@gesslar/toolkit/vendor/toolkit.umd.js"></script>
127
+ <script>
128
+ const {Data, Collection} = Toolkit
129
+ </script>
130
+ ```
131
+
132
+ The vendor bundles contain the same browser-compatible utilities listed above,
133
+ fully self-contained with zero external dependencies.
134
+
104
135
  ## Post Partum
105
136
 
106
137
  If you made it this far, please understand that I have absolutely zero scruples
@@ -117,3 +148,20 @@ off klaxons about my fetish for breaking changes, so you should be all right
117
148
  if you're paying attention. 🤷🏻
118
149
 
119
150
  Sincerely, Senator Yabba of the Dabba (Doo)
151
+
152
+ ## License
153
+
154
+ toolkit is released into the public domain under the
155
+ [Unlicense](UNLICENSE.txt).
156
+
157
+ This package includes or depends on third-party components under their own
158
+ licenses:
159
+
160
+ | Dependency | License |
161
+ | --- | --- |
162
+ | [@gesslar/colours](https://github.com/gesslar/colours) | Unlicense |
163
+ | [ajv](https://github.com/ajv-validator/ajv) | MIT |
164
+ | [DOMPurify](https://github.com/cure53/DOMPurify) | Apache-2.0 / MPL-2.0 |
165
+ | [json5](https://github.com/json5/json5) | MIT |
166
+ | [supports-color](https://github.com/chalk/supports-color) | MIT |
167
+ | [yaml](https://github.com/eemeli/yaml) | ISC |
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "gesslar",
6
6
  "url": "https://gesslar.dev"
7
7
  },
8
- "version": "4.2.0",
8
+ "version": "4.4.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -41,11 +41,16 @@
41
41
  "./node": {
42
42
  "types": "./types/node/index.d.ts",
43
43
  "default": "./src/node/index.js"
44
+ },
45
+ "./vendor": {
46
+ "types": "./types/browser/index.d.ts",
47
+ "default": "./vendor/toolkit.esm.js"
44
48
  }
45
49
  },
46
50
  "files": [
47
51
  "src/",
48
52
  "types/",
53
+ "vendor/",
49
54
  "scripts/",
50
55
  "UNLICENSE.txt"
51
56
  ],
@@ -55,7 +60,8 @@
55
60
  "scripts": {
56
61
  "preinstall": "node ./scripts/check-node-version.mjs",
57
62
  "types": "node -e \"require('fs').rmSync('types',{recursive:true,force:true});\" && tsc -p tsconfig.types.json",
58
- "prepublishOnly": "npm run types",
63
+ "vendor:build": "node scripts/vendor-build.js",
64
+ "prepack": "npm run types && npm run vendor:build",
59
65
  "lint": "eslint src/",
60
66
  "lint:fix": "eslint src/ --fix",
61
67
  "submit": "npm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
@@ -78,8 +84,10 @@
78
84
  },
79
85
  "devDependencies": {
80
86
  "@gesslar/uglier": "^2.2.0",
87
+ "@rollup/plugin-node-resolve": "^16.0.3",
81
88
  "eslint": "^10.0.3",
82
89
  "happy-dom": "^20.8.4",
90
+ "rollup": "^4.59.0",
83
91
  "typescript": "^5.9.3"
84
92
  }
85
93
  }
@@ -0,0 +1,32 @@
1
+ import {readFileSync, mkdirSync} from "node:fs"
2
+ import {rollup} from "rollup"
3
+ import {nodeResolve} from "@rollup/plugin-node-resolve"
4
+
5
+ const vendorDir = "vendor"
6
+ const {version} = JSON.parse(readFileSync("package.json", "utf8"))
7
+
8
+ mkdirSync(vendorDir, {recursive: true})
9
+
10
+ const bundle = await rollup({
11
+ input: "src/browser/index.js",
12
+ plugins: [nodeResolve()],
13
+ })
14
+
15
+ try {
16
+ await bundle.write({
17
+ file: `${vendorDir}/toolkit.esm.js`,
18
+ format: "es",
19
+ banner: `// @gesslar/toolkit v${version} - ES module bundle`,
20
+ })
21
+
22
+ await bundle.write({
23
+ file: `${vendorDir}/toolkit.umd.js`,
24
+ format: "umd",
25
+ name: "Toolkit",
26
+ banner: `// @gesslar/toolkit v${version} - UMD bundle`,
27
+ })
28
+ } finally {
29
+ await bundle.close()
30
+ }
31
+
32
+ console.log(`Built vendor bundles for @gesslar/toolkit@${version}`)
@@ -1,3 +1,4 @@
1
+ import Data from "./Data.js"
1
2
  import Sass from "./Sass.js"
2
3
  import Valid from "./Valid.js"
3
4
  /**
@@ -10,29 +11,45 @@ export default class Time {
10
11
  * The returned promise includes a timerId property that can be used with cancel().
11
12
  *
12
13
  * @param {number} delay - Delay in milliseconds before resolving (must be >= 0)
13
- * @param {unknown} [value] - Optional value to resolve with after the delay
14
- * @returns {Promise<unknown> & {timerId: number}} Promise that resolves with the value after delay, extended with timerId property
14
+ * @param {unknown} [value] - Optional value to resolve with, or a function to invoke after the delay
15
+ * @returns {Promise<unknown> & {timerId: number}} Promise that resolves with the value (or function result) after delay, extended with timerId property
15
16
  * @throws {Sass} If delay is not a number or is negative
16
17
  * @example
17
18
  * // Wait 1 second then continue
18
19
  * await Time.after(1000)
19
20
  *
20
- * // Wait 1 second then get a value
21
- * const result = await Time.after(1000, 'done')
21
+ * // Debounce: only apply the latest input after the user stops typing
22
+ * let pending = null
23
+ * function onInput(text) {
24
+ * Time.cancel(pending) // cancel() is a no-op if not a valid Time promise.
25
+ * pending = Time.after(300, () => applySearch(text))
26
+ * }
22
27
  *
23
- * // Create a cancellable delay
24
- * const promise = Time.after(5000, 'data')
25
- * Time.cancel(promise) // Cancel before it resolves
28
+ * // Timeout a fetch request
29
+ * const result = await Promise.race([
30
+ * fetch("/api/data"),
31
+ * Time.after(5000, () => { throw new Error("Request timed out") })
32
+ * ])
33
+ *
34
+ * // Cancellable delay
35
+ * const promise = Time.after(5000, "data")
36
+ * Time.cancel(promise) // Prevents resolution
26
37
  */
27
38
  static after(delay, value) {
28
39
  Valid.type(delay, "Number", "delay")
29
40
  Valid.assert(delay >= 0, "delay must be non-negative", delay)
30
41
 
31
42
  let timerId
32
- const promise = new Promise(resolve => {
43
+ const promise = new Promise((resolve, reject) => {
33
44
  // Cap at max 32-bit signed integer to avoid Node.js timeout overflow warning
34
45
  const safeDelay = Math.min(delay, 2147483647)
35
- timerId = setTimeout(() => resolve(value), safeDelay)
46
+ timerId = setTimeout(() => {
47
+ try {
48
+ resolve(Data.isType(value, "Function") ? value() : value)
49
+ } catch(e) {
50
+ reject(e)
51
+ }
52
+ }, safeDelay)
36
53
  })
37
54
  promise.timerId = timerId
38
55
 
@@ -8,19 +8,29 @@ export default class Time {
8
8
  * The returned promise includes a timerId property that can be used with cancel().
9
9
  *
10
10
  * @param {number} delay - Delay in milliseconds before resolving (must be >= 0)
11
- * @param {unknown} [value] - Optional value to resolve with after the delay
12
- * @returns {Promise<unknown> & {timerId: number}} Promise that resolves with the value after delay, extended with timerId property
11
+ * @param {unknown} [value] - Optional value to resolve with, or a function to invoke after the delay
12
+ * @returns {Promise<unknown> & {timerId: number}} Promise that resolves with the value (or function result) after delay, extended with timerId property
13
13
  * @throws {Sass} If delay is not a number or is negative
14
14
  * @example
15
15
  * // Wait 1 second then continue
16
16
  * await Time.after(1000)
17
17
  *
18
- * // Wait 1 second then get a value
19
- * const result = await Time.after(1000, 'done')
18
+ * // Debounce: only apply the latest input after the user stops typing
19
+ * let pending = null
20
+ * function onInput(text) {
21
+ * Time.cancel(pending) // cancel() is a no-op if not a valid Time promise.
22
+ * pending = Time.after(300, () => applySearch(text))
23
+ * }
20
24
  *
21
- * // Create a cancellable delay
22
- * const promise = Time.after(5000, 'data')
23
- * Time.cancel(promise) // Cancel before it resolves
25
+ * // Timeout a fetch request
26
+ * const result = await Promise.race([
27
+ * fetch("/api/data"),
28
+ * Time.after(5000, () => { throw new Error("Request timed out") })
29
+ * ])
30
+ *
31
+ * // Cancellable delay
32
+ * const promise = Time.after(5000, "data")
33
+ * Time.cancel(promise) // Prevents resolution
24
34
  */
25
35
  static after(delay: number, value?: unknown): Promise<unknown> & {
26
36
  timerId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"Time.d.ts","sourceRoot":"","sources":["../../../src/browser/lib/Time.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH;IACE;;;;;;;;;;;;;;;;;;OAkBG;IACH,oBAfW,MAAM,UACN,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,GAAG;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CA0BhD;IAED;;;;;;;;;OASG;IACH,uBANW,OAAO,CAAC,OAAO,CAAC,GAAG;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,GACnC,IAAI,CAQhB;CACF"}
1
+ {"version":3,"file":"Time.d.ts","sourceRoot":"","sources":["../../../src/browser/lib/Time.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,oBAzBW,MAAM,UACN,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,GAAG;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CA0ChD;IAED;;;;;;;;;OASG;IACH,uBANW,OAAO,CAAC,OAAO,CAAC,GAAG;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,GACnC,IAAI,CAQhB;CACF"}