@azure-net/node-kit 0.0.0-stage → 0.1.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +301 -2
  4. package/VALIDATION.md +866 -0
  5. package/dist/delivery/http/ResponseBuilder.d.ts +44 -0
  6. package/dist/delivery/http/ResponseBuilder.js +135 -0
  7. package/dist/delivery/http/index.d.ts +1 -0
  8. package/dist/delivery/http/index.js +1 -0
  9. package/dist/delivery/index.d.ts +1 -0
  10. package/dist/delivery/index.js +1 -0
  11. package/dist/domain/Ensure.d.ts +2 -0
  12. package/dist/domain/Ensure.js +7 -0
  13. package/dist/domain/EnsureError.d.ts +4 -0
  14. package/dist/domain/EnsureError.js +7 -0
  15. package/dist/domain/index.d.ts +2 -0
  16. package/dist/domain/index.js +2 -0
  17. package/dist/infrastructure/background/BackgroundTask.d.ts +12 -0
  18. package/dist/infrastructure/background/BackgroundTask.js +41 -0
  19. package/dist/infrastructure/background/index.d.ts +1 -0
  20. package/dist/infrastructure/background/index.js +1 -0
  21. package/dist/infrastructure/environment/Env.d.ts +19 -0
  22. package/dist/infrastructure/environment/Env.js +33 -0
  23. package/dist/infrastructure/environment/index.d.ts +1 -0
  24. package/dist/infrastructure/environment/index.js +1 -0
  25. package/dist/infrastructure/index.d.ts +2 -0
  26. package/dist/infrastructure/index.js +2 -0
  27. package/dist/tools/DateHelper.d.ts +13 -0
  28. package/dist/tools/DateHelper.js +77 -0
  29. package/dist/tools/DurationHelper.d.ts +6 -0
  30. package/dist/tools/DurationHelper.js +31 -0
  31. package/dist/tools/EnumHelper.d.ts +26 -0
  32. package/dist/tools/EnumHelper.js +40 -0
  33. package/dist/tools/ErrorHelper.d.ts +3 -0
  34. package/dist/tools/ErrorHelper.js +31 -0
  35. package/dist/tools/NumberHelper.d.ts +19 -0
  36. package/dist/tools/NumberHelper.js +49 -0
  37. package/dist/tools/ObjectHelper.d.ts +28 -0
  38. package/dist/tools/ObjectHelper.js +90 -0
  39. package/dist/tools/PromiseHelper.d.ts +3 -0
  40. package/dist/tools/PromiseHelper.js +7 -0
  41. package/dist/tools/StringHelper.d.ts +75 -0
  42. package/dist/tools/StringHelper.js +127 -0
  43. package/dist/tools/UidHelper.d.ts +56 -0
  44. package/dist/tools/UidHelper.js +132 -0
  45. package/dist/tools/index.d.ts +9 -0
  46. package/dist/tools/index.js +9 -0
  47. package/dist/validation/Engine.d.ts +6 -0
  48. package/dist/validation/Engine.js +406 -0
  49. package/dist/validation/Result.d.ts +31 -0
  50. package/dist/validation/Result.js +264 -0
  51. package/dist/validation/Symbols.d.ts +1 -0
  52. package/dist/validation/Symbols.js +1 -0
  53. package/dist/validation/Types.d.ts +105 -0
  54. package/dist/validation/Types.js +1 -0
  55. package/dist/validation/Validator.d.ts +26 -0
  56. package/dist/validation/Validator.js +95 -0
  57. package/dist/validation/index.d.ts +5 -0
  58. package/dist/validation/index.js +5 -0
  59. package/dist/validation/messages/En.d.ts +3 -0
  60. package/dist/validation/messages/En.js +118 -0
  61. package/dist/validation/messages/Ru.d.ts +3 -0
  62. package/dist/validation/messages/Ru.js +118 -0
  63. package/dist/validation/messages/index.d.ts +7 -0
  64. package/dist/validation/messages/index.js +7 -0
  65. package/dist/validation/rules/CustomRules.d.ts +8 -0
  66. package/dist/validation/rules/CustomRules.js +64 -0
  67. package/dist/validation/rules/Factory.d.ts +4 -0
  68. package/dist/validation/rules/Factory.js +21 -0
  69. package/dist/validation/rules/StringRules.d.ts +105 -0
  70. package/dist/validation/rules/StringRules.js +481 -0
  71. package/dist/validation/rules/StructureRules.d.ts +30 -0
  72. package/dist/validation/rules/StructureRules.js +145 -0
  73. package/dist/validation/rules/ValueRules.d.ts +82 -0
  74. package/dist/validation/rules/ValueRules.js +365 -0
  75. package/dist/validation/rules/index.d.ts +151 -0
  76. package/dist/validation/rules/index.js +19 -0
  77. package/package.json +77 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial framework-independent Node.js toolkit; no runtime dependencies.
6
+ - Typed helpers for enums, IDs, strings, numbers, dates, durations, objects, promises and errors.
7
+ - Domain invariants through `Ensure` and `EnsureError`.
8
+ - Explicit-source environment parsing with `Env`.
9
+ - Typed HTTP envelopes with `ResponseBuilder` and background I/O tracking with `BackgroundTask`.
10
+ - Array-based validation with sync/async execution, nested error trees, custom rules and English/Russian catalogs.
11
+ - Explicit subpath imports only; no catch-all package-root entry point.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pixel1917
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,302 @@
1
- # Temporary Holding Version
1
+ # @azure-net/node-kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-independent Node.js toolkit for the Azure-net ecosystem.
4
+
5
+ It has no runtime dependencies and does not depend on Svelte, Hono, or Bun APIs.
6
+ The helpers use standard JavaScript and built-in `node:crypto` / `node:util` APIs.
7
+
8
+ ## Installation and imports
9
+
10
+ Requires Node.js 22 or newer. The test suite also runs on Bun.
11
+
12
+ ```sh
13
+ npm install @azure-net/node-kit
14
+ ```
15
+
16
+ There is no package-root import. Choose the entry point by responsibility:
17
+
18
+ ```ts
19
+ import { StringHelper, EnumHelper } from "@azure-net/node-kit/tools";
20
+ import { Ensure, EnsureError } from "@azure-net/node-kit/domain";
21
+ import { ResponseBuilder } from "@azure-net/node-kit/delivery";
22
+ import { Env, BackgroundTask } from "@azure-net/node-kit/infrastructure";
23
+ import { Validator, rules } from "@azure-net/node-kit/validation";
24
+ import { validationMessagesRu } from "@azure-net/node-kit/validation/messages/ru";
25
+ ```
26
+
27
+ Focused entry points are also available: `/delivery/http`,
28
+ `/infrastructure/environment`, `/infrastructure/background`,
29
+ `/validation/messages` and `/validation/messages/en`.
30
+ Imports from the package root or private `dist` paths are deliberately blocked.
31
+
32
+ ## Development
33
+
34
+ ```sh
35
+ pnpm install
36
+ pnpm build
37
+ pnpm check
38
+ pnpm test
39
+ pnpm test:bun
40
+ ```
41
+
42
+ The build emits ESM JavaScript and TypeScript declarations into `dist`.
43
+ Each build first removes stale generated output. Run `pnpm release:check` for
44
+ a clean build, type checks and Node tests. `npm pack` runs that same gate through
45
+ `prepack`; run `pnpm test:bun` separately to verify Bun before publishing.
46
+
47
+ ## Validation
48
+
49
+ See the [Validation API reference](VALIDATION.md) for typed rule arrays, sync/async
50
+ validators, localization, nested errors, the full rule catalog, and HTTP integration.
51
+
52
+ ## Domain assertions
53
+
54
+ ```ts
55
+ import { Ensure, EnsureError } from "@azure-net/node-kit/domain";
56
+ import { StringHelper } from "@azure-net/node-kit/tools";
57
+
58
+ Ensure(
59
+ StringHelper.isValid(name, { maxLength: 255 }),
60
+ "Name must contain 1 to 255 characters.",
61
+ );
62
+
63
+ Ensure(version >= 1, () => new RangeError("Version must be positive."));
64
+ ```
65
+
66
+ `Ensure(condition, error)` returns nothing on success. A failed condition with a
67
+ string throws `EnsureError` with that exact message. A synchronous error factory
68
+ runs only on failure and its Error is thrown unchanged. Exceptions raised by the
69
+ factory also propagate unchanged. The assertion signature supports TypeScript
70
+ narrowing when the condition is a type guard. No inheritance, HTTP status mapping,
71
+ localization or shared state is involved. Both exports are available from
72
+ `@azure-net/node-kit/domain`. `EnsureError` supports the standard `ErrorOptions.cause` option.
73
+
74
+ ## Environment
75
+
76
+ ```ts
77
+ import { Env } from "@azure-net/node-kit/infrastructure/environment";
78
+ import {
79
+ DurationHelper,
80
+ NumberHelper,
81
+ StringHelper,
82
+ } from "@azure-net/node-kit/tools";
83
+
84
+ const env = new Env(process.env); // Or new Env({ PORT: '3000', DB_PASSWORD: '' }).
85
+
86
+ const password = env.get("DB_PASSWORD", { allowEmpty: true });
87
+ const port = env.get("PORT", {
88
+ fallback: "3000",
89
+ parse: ({ value, invalid }) => {
90
+ if (!StringHelper.matches(value, /^\d+$/))
91
+ return invalid("must contain only digits.");
92
+ const number = Number(value);
93
+ return NumberHelper.isValid(number, {
94
+ min: 1,
95
+ max: 65535,
96
+ safeInteger: true,
97
+ })
98
+ ? number
99
+ : invalid("must be an integer between 1 and 65535.");
100
+ },
101
+ }); // number
102
+
103
+ const timeout = env.get("REQUEST_TIMEOUT", {
104
+ fallback: "30s",
105
+ parse: ({ value, invalid }) =>
106
+ DurationHelper.toSeconds(value) ?? invalid("must be a valid duration."),
107
+ }); // number
108
+ ```
109
+
110
+ `Env` is also exported from `@azure-net/node-kit/infrastructure`.
111
+ The constructor requires an explicit object of `string | undefined` values. It holds
112
+ that object by reference and reads its own properties on each call, without copying,
113
+ caching, mutating it or falling back to global environment variables. Pass a copied
114
+ object if a configuration snapshot is desired. There is no `.env` file loader.
115
+
116
+ `get` returns a string, or the inferred result of `parse`. Missing values throw an
117
+ Error unless a string fallback is provided. The fallback applies only to `undefined`
118
+ and passes the same validation and parsing. Empty and whitespace-only strings throw
119
+ unless `allowEmpty: true`; that option does not allow missing values. Values are never
120
+ trimmed or coerced automatically. Non-string runtime values throw even with a fallback.
121
+
122
+ `invalid(message)` throws an Error with the variable name and your message, and has
123
+ return type `never`. Values are not automatically included in errors; do not embed
124
+ secrets in custom messages. Unexpected exceptions from `parse` propagate unchanged.
125
+ No process-global configuration or request state is created.
126
+
127
+ ## Tools
128
+
129
+ ### Durations
130
+
131
+ ```ts
132
+ import { DurationHelper } from "@azure-net/node-kit/tools";
133
+
134
+ DurationHelper.toSeconds(process.env.REQUEST_TIMEOUT ?? "30s"); // number | null
135
+ DurationHelper.toMilliseconds("1.5m"); // 90000
136
+ DurationHelper.toSeconds("500ms"); // 0.5
137
+ DurationHelper.toSeconds("invalid"); // null
138
+ ```
139
+
140
+ Accepts decimal strings with `ms`, `s`, `m`, `h`, or no unit (seconds).
141
+ Whitespace, signs, exponent notation and numeric inputs are rejected without coercion.
142
+ Values must resolve to whole milliseconds between 0 and 2147483647 inclusive.
143
+ Decimal arithmetic is exact: submillisecond values return `null`, not a rounded result.
144
+ Zero is valid. This parser does not read environment variables, apply defaults or throw
145
+ validation errors. Defaults apply only to absent input at the caller, not malformed input.
146
+
147
+ ```ts
148
+ import {
149
+ EnumHelper,
150
+ StringHelper,
151
+ NumberHelper,
152
+ DateHelper,
153
+ UidHelper,
154
+ ObjectHelper,
155
+ PromiseHelper,
156
+ ErrorHelper,
157
+ } from "@azure-net/node-kit/tools";
158
+
159
+ enum Status {
160
+ Created = 0,
161
+ Active = 1,
162
+ }
163
+ const input: unknown = 1;
164
+ EnumHelper.isInEnum(Status, input); // boolean; narrows input to Status
165
+ EnumHelper.getValue(Status, input); // Status | null
166
+ EnumHelper.getValue(Status, "1"); // null, no coercion or name lookup
167
+
168
+ StringHelper.isValid("Name", { maxLength: 255 });
169
+ StringHelper.isValid(null, {
170
+ allowNull: true,
171
+ allowEmpty: true,
172
+ maxLength: 1000,
173
+ });
174
+ StringHelper.isValid(process.env.DB_PASSWORD, { allowEmpty: true });
175
+ StringHelper.isValid("abc", {
176
+ minLength: 1,
177
+ maxLength: 20,
178
+ pattern: /^[a-z]+$/,
179
+ });
180
+
181
+ NumberHelper.isPositiveInteger(1);
182
+ NumberHelper.isValid(25, { min: 1, max: 100, safeInteger: true });
183
+ DateHelper.isBefore("2026-01-01", "2026-02-01");
184
+ UidHelper.isValidUuid(UidHelper.generateUuid());
185
+ ```
186
+
187
+ - `EnumHelper` also retains `associateByIndex`, `associateByKey` and `toEntries`.
188
+ Numeric reverse mappings are excluded. Normal runtime enums and `as const`
189
+ objects are supported; erased `const enum` declarations are not runtime objects.
190
+ - `StringHelper.isValid` requires a nonblank string by default. `allowNull`,
191
+ `allowUndefined` and `allowEmpty` are independent. Length uses UTF-16 code units,
192
+ including surrounding whitespace unless `trim: true` is set. No input is mutated.
193
+ `matches` does not mutate a regex's `lastIndex`. Predicates return booleans, not errors.
194
+ Existing `pluralize`, `truncate`, `capitalize`, `decapitalize` and
195
+ `isEmptyOrWhitespace` methods are retained.
196
+ - `NumberHelper` never coerces values: `''`, `'1'`, `false`, `NaN` and infinities
197
+ are not valid finite numbers. Integer and safe-integer checks are distinct.
198
+ - `DateHelper` accepts valid Dates, integer millisecond timestamps and strict ISO
199
+ strings. Date-only and zone-less strings use UTC. Fractions are truncated to
200
+ milliseconds. Invalid dates return `null` from `compare` and `false` from predicates.
201
+ `isSame` compares instants, not calendar days. `isBetween` accepts reversed bounds;
202
+ its fourth argument controls inclusivity. There are no locale settings or formatters.
203
+ - `UidHelper` uses cryptographic randomness with no fallback. `isValidUuid` checks
204
+ UUID v4. `generateHashId` produces a random hex string, not a cryptographic digest.
205
+ Timestamp IDs without randomness are only process-local; no random ID method
206
+ guarantees uniqueness. Invalid generation parameters throw `RangeError`.
207
+ - `ObjectHelper.deepClone` uses native `structuredClone`: cycles, Dates, Maps, Sets,
208
+ BigInt and typed arrays are supported. Functions cannot be cloned. Custom class
209
+ prototypes, property descriptors and symbol properties are not preserved; Buffer
210
+ is not guaranteed to remain Buffer. It is a data clone, not an instance clone.
211
+ `clone` is shallow and accepts arrays/plain objects only, preserving descriptors.
212
+ `equals` uses `isDeepStrictEqual`; `compareAsString` remains explicitly JSON-only.
213
+ `pick`/`omit` use own properties, support symbols and safely handle `__proto__`.
214
+ `isObjectEmpty` rejects nonobjects and noncollection instances; Map/Set use size.
215
+ `isAllKeysEmpty` treats nullish leaves and empty containers as empty, supports
216
+ cycles and deep nesting, and does not invoke getters (accessors count as nonempty).
217
+ - `PromiseHelper.isPromiseLike` checks for callable `then` (including custom thenables).
218
+ - `ErrorHelper.toError` retains existing Error instances and attaches other input as `cause`.
219
+
220
+ The test suite exercises runtime behavior on Node and Bun, plus compile-time enum
221
+ inference and narrowing. The package does not install process-global request state,
222
+ timers or event listeners.
223
+
224
+ ## HTTP responses
225
+
226
+ ```ts
227
+ import { ResponseBuilder } from "@azure-net/node-kit/delivery";
228
+
229
+ const response = ResponseBuilder.Created(
230
+ { id: "1" },
231
+ {
232
+ message: "User created",
233
+ meta: { requestId: "request-1" },
234
+ },
235
+ );
236
+
237
+ // Hono route: toJSON returns an object, not an encoded string.
238
+ return c.json(response.toJSON(), response.status);
239
+ // Alternatively, return response.toResponse() for a native Fetch Response.
240
+ ```
241
+
242
+ Every response contains `success`, `status`, `message`, `data`, `meta`, `errors`
243
+ and `code`. Omitted payload fields become `null`. Data, metadata and errors are
244
+ inferred independently. Presets retain literal status types for Hono's `c.json`
245
+ and RPC inference. The package has no Hono dependency.
246
+
247
+ `new ResponseBuilder(data, options)` defaults to status 200. Presets:
248
+ `Ok` (200), `Created` (201), `Accepted` (202), `BadRequest` (400),
249
+ `Unauthorized` (401), `Forbidden` (403), `NotFound` (404), `Conflict` (409),
250
+ `UnprocessableEntity` (422), `TooManyRequests` (429), `InternalServerError` (500).
251
+ All accept optional data first and options second. Presets fix the status;
252
+ options can customize `message`, `meta`, `errors` and `code`. Error presets
253
+ provide an uppercase snake-case code; pass `code: null` to omit its value.
254
+ Defaults are English HTTP messages; supply your own localized message as needed.
255
+
256
+ `success` is computed as `status < 400`. Non-null `errors` and `code` on a
257
+ successful response are rejected. Statuses 204, 205, 304 and informational
258
+ statuses are rejected because this builder always emits an envelope with a body.
259
+ The envelope is shallowly frozen; user data is not frozen or copied.
260
+
261
+ `toResponse(headers?)` sets the HTTP status and uses native `Response.json`.
262
+ Payloads must be JSON-serializable: BigInt and circular references throw during
263
+ serialization. Classes follow standard JSON serialization (for example, Dates
264
+ become strings). Do not pass `JSON.stringify(...)` into Hono's `c.json`.
265
+
266
+ ## Background I/O tasks
267
+
268
+ ```ts
269
+ import { BackgroundTask } from "@azure-net/node-kit/infrastructure";
270
+
271
+ const background = new BackgroundTask({
272
+ onError: async (error) => {
273
+ console.error("Background operation failed", error);
274
+ },
275
+ });
276
+
277
+ background.run(() => emailService.sendWelcome(email));
278
+ console.log(background.pendingCount);
279
+
280
+ // After stopping incoming requests, before closing DB and other dependencies:
281
+ await background.drain();
282
+ ```
283
+
284
+ `run` accepts a task factory and returns `void` immediately. Factories start in
285
+ microtasks and run independently without a concurrency limit. Synchronous throws,
286
+ rejected promises and thenables are sent to the required `onError` callback.
287
+ If that callback throws or rejects, the handler failure and original error are
288
+ reported via `console.error`; normal task failures do not reject `drain`.
289
+
290
+ `pendingCount` includes asynchronous error handling. `drain` waits for active
291
+ tasks, their error handlers, and tasks added while it waits. Multiple callers may
292
+ drain concurrently. Completed tasks are removed from the registry. Each manager
293
+ owns its own registry; there are no global listeners, timers or shutdown hooks.
294
+ Do not await `drain` from a task or its error handler: it would wait for itself.
295
+ Stop task producers first if drain is used during shutdown; a never-settling task
296
+ will keep it pending. It neither cancels tasks nor forces the process to stay alive.
297
+
298
+ This is in-process best-effort background I/O, not a durable queue. Process crashes
299
+ can lose tasks. It does not move CPU work to another thread: synchronous work still
300
+ blocks the event loop. Use worker threads for CPU-intensive work and durable queues
301
+ when jobs must survive restarts. Pass captured values rather than keeping entire
302
+ HTTP contexts alive for the duration of a background task.