@loydjs/runtime 0.0.0 → 1.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.
- package/README.md +159 -0
- package/dist/index.cjs +120 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -8
- package/dist/index.d.ts +7 -8
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/executor.ts +81 -10
- package/src/freeze.ts +27 -2
- package/src/mode.ts +22 -1
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<h1>@loydjs/runtime</h1>
|
|
4
|
+
|
|
5
|
+
<p><strong>Zero-copy execution engine for Loyd.</strong><br/>
|
|
6
|
+
createExecutor · zeroCopy · abortEarly · deepFreeze · strict mode.</p>
|
|
7
|
+
|
|
8
|
+
[](https://github.com/b3nito404/loyd/actions/workflows/ci.yml)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://bundlephobia.com/package/@loydjs/runtime)
|
|
11
|
+
[](https://www.typescriptlang.org)
|
|
12
|
+
[](https://www.npmjs.com/package/@loydjs/runtime)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
`@loydjs/runtime` provides a high-performance execution layer on top of `@loydjs/compiler`. Instead of calling `compile(schema)` and managing options manually, you create an `Executor` once with your desired options and reuse it across your entire application.
|
|
21
|
+
|
|
22
|
+
The key feature is **zero-copy mode** — on the success path, no result object is allocated. The input is returned directly, eliminating the `{ success, data, issues }` allocation that every other validation library performs on every call.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @loydjs/runtime
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Requires** `@loydjs/core` · `@loydjs/compiler` · Node.js ≥ 20 · TypeScript ≥ 5.4
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `createExecutor(options?)`
|
|
39
|
+
|
|
40
|
+
Creates a reusable executor with fixed options. More efficient than passing options on every call — options are resolved once at creation time.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createExecutor } from "@loydjs/runtime";
|
|
44
|
+
|
|
45
|
+
const executor = createExecutor({
|
|
46
|
+
zeroCopy: true, // skip { success, data, issues } allocation on success
|
|
47
|
+
abortEarly: true, // stop validating after first error per object
|
|
48
|
+
freeze: true, // deep-freeze validated output
|
|
49
|
+
mode: "strict", // "strip" | "strict" | "passthrough"
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const result = executor.run(UserSchema, req.body);
|
|
53
|
+
|
|
54
|
+
if (result.success) {
|
|
55
|
+
console.log(result.data); // typed as User, deep-frozen
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Pre-built executors
|
|
60
|
+
|
|
61
|
+
Three ready-to-use executors for the most common cases:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import {
|
|
65
|
+
defaultExecutor, // mode: "strip", no freeze, no zeroCopy
|
|
66
|
+
zeroCopyExecutor, // mode: "strip", zeroCopy: true
|
|
67
|
+
strictExecutor, // mode: "strict", rejects unknown keys
|
|
68
|
+
} from "@loydjs/runtime";
|
|
69
|
+
|
|
70
|
+
// Fastest possible — no result object allocated on success
|
|
71
|
+
const result = zeroCopyExecutor.run(UserSchema, input);
|
|
72
|
+
|
|
73
|
+
// Reject unknown keys
|
|
74
|
+
const result = strictExecutor.run(UserSchema, input);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `executor.runOrThrow(schema, input)`
|
|
78
|
+
|
|
79
|
+
Throws a descriptive `Error` on failure instead of returning a result object.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const executor = createExecutor({ mode: "strict" });
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const user = executor.runOrThrow(UserSchema, req.body);
|
|
86
|
+
// user is typed as User
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(err.message);
|
|
89
|
+
// "Validation failed: ERR_STRING_INVALID_EMAIL at ["email"]"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `deepFreeze(value)`
|
|
94
|
+
|
|
95
|
+
Recursively freezes an object. Skips already-frozen objects for performance.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { deepFreeze, isDeepFrozen } from "@loydjs/runtime";
|
|
99
|
+
|
|
100
|
+
const frozen = deepFreeze({ name: "Alice", address: { city: "Paris" } });
|
|
101
|
+
isDeepFrozen(frozen); // true
|
|
102
|
+
frozen.name = "Bob"; // throws in strict mode
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `getModeConfig(mode)`
|
|
106
|
+
|
|
107
|
+
Returns the configuration object for a given mode.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { getModeConfig } from "@loydjs/runtime";
|
|
111
|
+
|
|
112
|
+
getModeConfig("strict");
|
|
113
|
+
// { stripUnknownKeys: false, errorOnUnknownKeys: true, passthroughUnknownKeys: false }
|
|
114
|
+
|
|
115
|
+
getModeConfig("passthrough");
|
|
116
|
+
// { stripUnknownKeys: false, errorOnUnknownKeys: false, passthroughUnknownKeys: true }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Executor options
|
|
122
|
+
|
|
123
|
+
| Option | Type | Default | Description |
|
|
124
|
+
|:---|:---|:---|:---|
|
|
125
|
+
| `mode` | `"strip" \| "strict" \| "passthrough"` | `"strip"` | How unknown keys are handled |
|
|
126
|
+
| `zeroCopy` | `boolean` | `false` | Skip result allocation on success |
|
|
127
|
+
| `abortEarly` | `boolean` | `false` | Stop at first validation error |
|
|
128
|
+
| `freeze` | `boolean` | `false` | Deep-freeze validated output |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Mode comparison
|
|
133
|
+
|
|
134
|
+
| Mode | Unknown keys | Use case |
|
|
135
|
+
|:---|:---|:---|
|
|
136
|
+
| `strip` | Removed silently | APIs, form validation |
|
|
137
|
+
| `strict` | Error | Internal services, strict contracts |
|
|
138
|
+
| `passthrough` | Kept as-is | Proxies, partial validation |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Dependencies
|
|
143
|
+
|
|
144
|
+
| Package | Role |
|
|
145
|
+
|:---|:---|
|
|
146
|
+
| `@loydjs/core` | `LoydSchema`, `LoydResult` types |
|
|
147
|
+
| `@loydjs/compiler` | `compile()` for JIT validation |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
**[loyddev-psi.vercel.app](https://loyddev-psi.vercel.app)**
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT © [b3nito404](https://github.com/b3nito404)
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
6
10
|
var __copyProps = (to, from, except, desc) => {
|
|
7
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
12
|
for (let key of __getOwnPropNames(from))
|
|
@@ -15,5 +19,121 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
15
19
|
|
|
16
20
|
// src/index.ts
|
|
17
21
|
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createExecutor: () => createExecutor,
|
|
24
|
+
deepFreeze: () => deepFreeze,
|
|
25
|
+
defaultExecutor: () => defaultExecutor,
|
|
26
|
+
getModeConfig: () => getModeConfig,
|
|
27
|
+
isDeepFrozen: () => isDeepFrozen
|
|
28
|
+
});
|
|
18
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/executor.ts
|
|
32
|
+
var import_compiler = require("@loydjs/compiler");
|
|
33
|
+
var _EMPTY_ISSUES = [];
|
|
34
|
+
function makeSuccessResult(data) {
|
|
35
|
+
return { success: true, data, issues: _EMPTY_ISSUES };
|
|
36
|
+
}
|
|
37
|
+
function createExecutor(options = {}) {
|
|
38
|
+
const resolved = {
|
|
39
|
+
mode: options.mode ?? "strip",
|
|
40
|
+
freeze: options.freeze ?? false,
|
|
41
|
+
zeroCopy: options.zeroCopy ?? false,
|
|
42
|
+
abortEarly: options.abortEarly ?? false
|
|
43
|
+
};
|
|
44
|
+
const { freeze: doFreeze, zeroCopy } = resolved;
|
|
45
|
+
const maybeFreeze = doFreeze ? (v) => deepFreezeImpl(v) : (v) => v;
|
|
46
|
+
return {
|
|
47
|
+
options: resolved,
|
|
48
|
+
run(schema, input) {
|
|
49
|
+
const validator = (0, import_compiler.compile)(schema);
|
|
50
|
+
const result = validator(input);
|
|
51
|
+
if (!result.success) return result;
|
|
52
|
+
const data = maybeFreeze(result.data);
|
|
53
|
+
if (zeroCopy && data === result.data) {
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
return makeSuccessResult(data);
|
|
57
|
+
},
|
|
58
|
+
runOrThrow(schema, input) {
|
|
59
|
+
const validator = (0, import_compiler.compile)(schema);
|
|
60
|
+
const result = validator(input);
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
const first = result.issues[0];
|
|
63
|
+
throw new Error(
|
|
64
|
+
first?.message ?? `Validation failed: ${first?.code ?? "ERR_UNKNOWN"} at ${JSON.stringify(first?.path ?? [])}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return maybeFreeze(result.data);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
var defaultExecutor = createExecutor();
|
|
72
|
+
var zeroCopyExecutor = createExecutor({ zeroCopy: true });
|
|
73
|
+
var strictExecutor = createExecutor({ mode: "strict" });
|
|
74
|
+
function deepFreezeImpl(value) {
|
|
75
|
+
if (value === null || typeof value !== "object") return value;
|
|
76
|
+
if (Object.isFrozen(value)) return value;
|
|
77
|
+
Object.freeze(value);
|
|
78
|
+
for (const key of Object.keys(value)) {
|
|
79
|
+
const v = value[key];
|
|
80
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
81
|
+
deepFreezeImpl(v);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/mode.ts
|
|
88
|
+
var CONFIGS = {
|
|
89
|
+
strip: {
|
|
90
|
+
stripUnknownKeys: true,
|
|
91
|
+
errorOnUnknownKeys: false,
|
|
92
|
+
passthroughUnknownKeys: false
|
|
93
|
+
},
|
|
94
|
+
strict: {
|
|
95
|
+
stripUnknownKeys: false,
|
|
96
|
+
errorOnUnknownKeys: true,
|
|
97
|
+
passthroughUnknownKeys: false
|
|
98
|
+
},
|
|
99
|
+
passthrough: {
|
|
100
|
+
stripUnknownKeys: false,
|
|
101
|
+
errorOnUnknownKeys: false,
|
|
102
|
+
passthroughUnknownKeys: true
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function getModeConfig(mode) {
|
|
106
|
+
return CONFIGS[mode];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/freeze.ts
|
|
110
|
+
function deepFreeze(value) {
|
|
111
|
+
if (value === null || typeof value !== "object") return value;
|
|
112
|
+
if (Object.isFrozen(value)) return value;
|
|
113
|
+
Object.freeze(value);
|
|
114
|
+
for (const key of Object.keys(value)) {
|
|
115
|
+
const v = value[key];
|
|
116
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
117
|
+
deepFreeze(v);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
function isDeepFrozen(value) {
|
|
123
|
+
if (value === null || typeof value !== "object") return true;
|
|
124
|
+
if (!Object.isFrozen(value)) return false;
|
|
125
|
+
for (const key of Object.keys(value)) {
|
|
126
|
+
const v = value[key];
|
|
127
|
+
if (!isDeepFrozen(v)) return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
132
|
+
0 && (module.exports = {
|
|
133
|
+
createExecutor,
|
|
134
|
+
deepFreeze,
|
|
135
|
+
defaultExecutor,
|
|
136
|
+
getModeConfig,
|
|
137
|
+
isDeepFrozen
|
|
138
|
+
});
|
|
19
139
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type { RuntimeMode, ExecutorOptions, Executor } from \"./executor.js\";\nexport { createExecutor, defaultExecutor } from \"./executor.js\";\n\nexport type { ModeConfig } from \"./mode.js\";\nexport { getModeConfig } from \"./mode.js\";\n\nexport { deepFreeze, isDeepFrozen } from \"./freeze.js\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/executor.ts","../src/mode.ts","../src/freeze.ts"],"sourcesContent":["export type { RuntimeMode, ExecutorOptions, Executor } from \"./executor.js\";\nexport { createExecutor, defaultExecutor } from \"./executor.js\";\n\nexport type { ModeConfig } from \"./mode.js\";\nexport { getModeConfig } from \"./mode.js\";\n\nexport { deepFreeze, isDeepFrozen } from \"./freeze.js\";\n","import { compile } from \"@loydjs/compiler\";\nimport type { LoydResult, LoydSchema } from \"@loydjs/core\";\n\nexport type RuntimeMode = \"strict\" | \"strip\" | \"passthrough\";\n\nexport interface ExecutorOptions {\n mode?: RuntimeMode;\n /**\n * @default false\n */\n freeze?: boolean;\n /**\n * Skip creating a new result object on success return input directly.\n * @default false\n */\n zeroCopy?: boolean;\n /**\n * Stop validation after first error per object.\n * @default false\n */\n abortEarly?: boolean;\n}\n\nexport interface Executor {\n run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T>;\n runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T;\n readonly options: Required<ExecutorOptions>;\n}\n\nconst _EMPTY_ISSUES: [] = [];\n\nfunction makeSuccessResult<T>(data: T): LoydResult<T> {\n return { success: true, data, issues: _EMPTY_ISSUES };\n}\n\nexport function createExecutor(options: ExecutorOptions = {}): Executor {\n const resolved: Required<ExecutorOptions> = {\n mode: options.mode ?? \"strip\",\n freeze: options.freeze ?? false,\n zeroCopy: options.zeroCopy ?? false,\n abortEarly: options.abortEarly ?? false,\n };\n\n const { freeze: doFreeze, zeroCopy } = resolved;\n\n const maybeFreeze = doFreeze ? <T>(v: T): T => deepFreezeImpl(v) : <T>(v: T): T => v;\n\n return {\n options: resolved,\n\n run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T> {\n const validator = compile(schema);\n const result = validator(input);\n\n if (!result.success) return result;\n\n const data = maybeFreeze(result.data);\n\n if (zeroCopy && data === result.data) {\n return result;\n }\n\n return makeSuccessResult(data);\n },\n\n runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T {\n const validator = compile(schema);\n const result = validator(input);\n\n if (!result.success) {\n const first = result.issues[0];\n throw new Error(\n first?.message ??\n `Validation failed: ${first?.code ?? \"ERR_UNKNOWN\"} at ${JSON.stringify(first?.path ?? [])}`,\n );\n }\n\n return maybeFreeze(result.data);\n },\n };\n}\n\n/**strip mode*/\nexport const defaultExecutor: Executor = createExecutor();\n\nexport const zeroCopyExecutor: Executor = createExecutor({ zeroCopy: true });\n\nexport const strictExecutor: Executor = createExecutor({ mode: \"strict\" });\n\nfunction deepFreezeImpl<T>(value: T): T {\n if (value === null || typeof value !== \"object\") return value;\n if (Object.isFrozen(value)) return value;\n\n Object.freeze(value);\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n deepFreezeImpl(v);\n }\n }\n\n return value;\n}\n","import type { RuntimeMode } from \"./executor.js\";\n\nexport interface ModeConfig {\n stripUnknownKeys: boolean;\n errorOnUnknownKeys: boolean;\n passthroughUnknownKeys: boolean;\n}\n\nconst CONFIGS: Record<RuntimeMode, ModeConfig> = {\n strip: {\n stripUnknownKeys: true,\n errorOnUnknownKeys: false,\n passthroughUnknownKeys: false,\n },\n strict: {\n stripUnknownKeys: false,\n errorOnUnknownKeys: true,\n passthroughUnknownKeys: false,\n },\n passthrough: {\n stripUnknownKeys: false,\n errorOnUnknownKeys: false,\n passthroughUnknownKeys: true,\n },\n};\n\nexport function getModeConfig(mode: RuntimeMode): ModeConfig {\n return CONFIGS[mode];\n}\n","export function deepFreeze<T>(value: T): Readonly<T> {\n if (value === null || typeof value !== \"object\") return value as Readonly<T>;\n if (Object.isFrozen(value)) return value as Readonly<T>;\n\n Object.freeze(value);\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n deepFreeze(v);\n }\n }\n\n return value as Readonly<T>;\n}\n\nexport function isDeepFrozen(value: unknown): boolean {\n if (value === null || typeof value !== \"object\") return true;\n if (!Object.isFrozen(value)) return false;\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (!isDeepFrozen(v)) return false;\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -8,24 +8,23 @@ interface ExecutorOptions {
|
|
|
8
8
|
*/
|
|
9
9
|
freeze?: boolean;
|
|
10
10
|
/**
|
|
11
|
+
* Skip creating a new result object on success return input directly.
|
|
11
12
|
* @default false
|
|
12
13
|
*/
|
|
13
14
|
zeroCopy?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Stop validation after first error per object.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
abortEarly?: boolean;
|
|
14
20
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Creates an optimized executor with fixed options.
|
|
17
|
-
* More efficient than going through the options on every call.
|
|
18
|
-
* @example
|
|
19
|
-
* const executor = createExecutor({ mode: "strict", freeze: true });
|
|
20
|
-
* const result = executor.run(UserSchema, rawInput);
|
|
21
|
-
*/
|
|
22
21
|
interface Executor {
|
|
23
22
|
run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T>;
|
|
24
23
|
runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T;
|
|
25
24
|
readonly options: Required<ExecutorOptions>;
|
|
26
25
|
}
|
|
27
26
|
declare function createExecutor(options?: ExecutorOptions): Executor;
|
|
28
|
-
/**
|
|
27
|
+
/**strip mode*/
|
|
29
28
|
declare const defaultExecutor: Executor;
|
|
30
29
|
|
|
31
30
|
interface ModeConfig {
|
package/dist/index.d.ts
CHANGED
|
@@ -8,24 +8,23 @@ interface ExecutorOptions {
|
|
|
8
8
|
*/
|
|
9
9
|
freeze?: boolean;
|
|
10
10
|
/**
|
|
11
|
+
* Skip creating a new result object on success return input directly.
|
|
11
12
|
* @default false
|
|
12
13
|
*/
|
|
13
14
|
zeroCopy?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Stop validation after first error per object.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
abortEarly?: boolean;
|
|
14
20
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Creates an optimized executor with fixed options.
|
|
17
|
-
* More efficient than going through the options on every call.
|
|
18
|
-
* @example
|
|
19
|
-
* const executor = createExecutor({ mode: "strict", freeze: true });
|
|
20
|
-
* const result = executor.run(UserSchema, rawInput);
|
|
21
|
-
*/
|
|
22
21
|
interface Executor {
|
|
23
22
|
run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T>;
|
|
24
23
|
runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T;
|
|
25
24
|
readonly options: Required<ExecutorOptions>;
|
|
26
25
|
}
|
|
27
26
|
declare function createExecutor(options?: ExecutorOptions): Executor;
|
|
28
|
-
/**
|
|
27
|
+
/**strip mode*/
|
|
29
28
|
declare const defaultExecutor: Executor;
|
|
30
29
|
|
|
31
30
|
interface ModeConfig {
|
package/dist/index.js
CHANGED
|
@@ -1 +1,108 @@
|
|
|
1
|
+
// src/executor.ts
|
|
2
|
+
import { compile } from "@loydjs/compiler";
|
|
3
|
+
var _EMPTY_ISSUES = [];
|
|
4
|
+
function makeSuccessResult(data) {
|
|
5
|
+
return { success: true, data, issues: _EMPTY_ISSUES };
|
|
6
|
+
}
|
|
7
|
+
function createExecutor(options = {}) {
|
|
8
|
+
const resolved = {
|
|
9
|
+
mode: options.mode ?? "strip",
|
|
10
|
+
freeze: options.freeze ?? false,
|
|
11
|
+
zeroCopy: options.zeroCopy ?? false,
|
|
12
|
+
abortEarly: options.abortEarly ?? false
|
|
13
|
+
};
|
|
14
|
+
const { freeze: doFreeze, zeroCopy } = resolved;
|
|
15
|
+
const maybeFreeze = doFreeze ? (v) => deepFreezeImpl(v) : (v) => v;
|
|
16
|
+
return {
|
|
17
|
+
options: resolved,
|
|
18
|
+
run(schema, input) {
|
|
19
|
+
const validator = compile(schema);
|
|
20
|
+
const result = validator(input);
|
|
21
|
+
if (!result.success) return result;
|
|
22
|
+
const data = maybeFreeze(result.data);
|
|
23
|
+
if (zeroCopy && data === result.data) {
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
return makeSuccessResult(data);
|
|
27
|
+
},
|
|
28
|
+
runOrThrow(schema, input) {
|
|
29
|
+
const validator = compile(schema);
|
|
30
|
+
const result = validator(input);
|
|
31
|
+
if (!result.success) {
|
|
32
|
+
const first = result.issues[0];
|
|
33
|
+
throw new Error(
|
|
34
|
+
first?.message ?? `Validation failed: ${first?.code ?? "ERR_UNKNOWN"} at ${JSON.stringify(first?.path ?? [])}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return maybeFreeze(result.data);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var defaultExecutor = createExecutor();
|
|
42
|
+
var zeroCopyExecutor = createExecutor({ zeroCopy: true });
|
|
43
|
+
var strictExecutor = createExecutor({ mode: "strict" });
|
|
44
|
+
function deepFreezeImpl(value) {
|
|
45
|
+
if (value === null || typeof value !== "object") return value;
|
|
46
|
+
if (Object.isFrozen(value)) return value;
|
|
47
|
+
Object.freeze(value);
|
|
48
|
+
for (const key of Object.keys(value)) {
|
|
49
|
+
const v = value[key];
|
|
50
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
51
|
+
deepFreezeImpl(v);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/mode.ts
|
|
58
|
+
var CONFIGS = {
|
|
59
|
+
strip: {
|
|
60
|
+
stripUnknownKeys: true,
|
|
61
|
+
errorOnUnknownKeys: false,
|
|
62
|
+
passthroughUnknownKeys: false
|
|
63
|
+
},
|
|
64
|
+
strict: {
|
|
65
|
+
stripUnknownKeys: false,
|
|
66
|
+
errorOnUnknownKeys: true,
|
|
67
|
+
passthroughUnknownKeys: false
|
|
68
|
+
},
|
|
69
|
+
passthrough: {
|
|
70
|
+
stripUnknownKeys: false,
|
|
71
|
+
errorOnUnknownKeys: false,
|
|
72
|
+
passthroughUnknownKeys: true
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
function getModeConfig(mode) {
|
|
76
|
+
return CONFIGS[mode];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/freeze.ts
|
|
80
|
+
function deepFreeze(value) {
|
|
81
|
+
if (value === null || typeof value !== "object") return value;
|
|
82
|
+
if (Object.isFrozen(value)) return value;
|
|
83
|
+
Object.freeze(value);
|
|
84
|
+
for (const key of Object.keys(value)) {
|
|
85
|
+
const v = value[key];
|
|
86
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
87
|
+
deepFreeze(v);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function isDeepFrozen(value) {
|
|
93
|
+
if (value === null || typeof value !== "object") return true;
|
|
94
|
+
if (!Object.isFrozen(value)) return false;
|
|
95
|
+
for (const key of Object.keys(value)) {
|
|
96
|
+
const v = value[key];
|
|
97
|
+
if (!isDeepFrozen(v)) return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
createExecutor,
|
|
103
|
+
deepFreeze,
|
|
104
|
+
defaultExecutor,
|
|
105
|
+
getModeConfig,
|
|
106
|
+
isDeepFrozen
|
|
107
|
+
};
|
|
1
108
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/executor.ts","../src/mode.ts","../src/freeze.ts"],"sourcesContent":["import { compile } from \"@loydjs/compiler\";\nimport type { LoydResult, LoydSchema } from \"@loydjs/core\";\n\nexport type RuntimeMode = \"strict\" | \"strip\" | \"passthrough\";\n\nexport interface ExecutorOptions {\n mode?: RuntimeMode;\n /**\n * @default false\n */\n freeze?: boolean;\n /**\n * Skip creating a new result object on success return input directly.\n * @default false\n */\n zeroCopy?: boolean;\n /**\n * Stop validation after first error per object.\n * @default false\n */\n abortEarly?: boolean;\n}\n\nexport interface Executor {\n run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T>;\n runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T;\n readonly options: Required<ExecutorOptions>;\n}\n\nconst _EMPTY_ISSUES: [] = [];\n\nfunction makeSuccessResult<T>(data: T): LoydResult<T> {\n return { success: true, data, issues: _EMPTY_ISSUES };\n}\n\nexport function createExecutor(options: ExecutorOptions = {}): Executor {\n const resolved: Required<ExecutorOptions> = {\n mode: options.mode ?? \"strip\",\n freeze: options.freeze ?? false,\n zeroCopy: options.zeroCopy ?? false,\n abortEarly: options.abortEarly ?? false,\n };\n\n const { freeze: doFreeze, zeroCopy } = resolved;\n\n const maybeFreeze = doFreeze ? <T>(v: T): T => deepFreezeImpl(v) : <T>(v: T): T => v;\n\n return {\n options: resolved,\n\n run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T> {\n const validator = compile(schema);\n const result = validator(input);\n\n if (!result.success) return result;\n\n const data = maybeFreeze(result.data);\n\n if (zeroCopy && data === result.data) {\n return result;\n }\n\n return makeSuccessResult(data);\n },\n\n runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T {\n const validator = compile(schema);\n const result = validator(input);\n\n if (!result.success) {\n const first = result.issues[0];\n throw new Error(\n first?.message ??\n `Validation failed: ${first?.code ?? \"ERR_UNKNOWN\"} at ${JSON.stringify(first?.path ?? [])}`,\n );\n }\n\n return maybeFreeze(result.data);\n },\n };\n}\n\n/**strip mode*/\nexport const defaultExecutor: Executor = createExecutor();\n\nexport const zeroCopyExecutor: Executor = createExecutor({ zeroCopy: true });\n\nexport const strictExecutor: Executor = createExecutor({ mode: \"strict\" });\n\nfunction deepFreezeImpl<T>(value: T): T {\n if (value === null || typeof value !== \"object\") return value;\n if (Object.isFrozen(value)) return value;\n\n Object.freeze(value);\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n deepFreezeImpl(v);\n }\n }\n\n return value;\n}\n","import type { RuntimeMode } from \"./executor.js\";\n\nexport interface ModeConfig {\n stripUnknownKeys: boolean;\n errorOnUnknownKeys: boolean;\n passthroughUnknownKeys: boolean;\n}\n\nconst CONFIGS: Record<RuntimeMode, ModeConfig> = {\n strip: {\n stripUnknownKeys: true,\n errorOnUnknownKeys: false,\n passthroughUnknownKeys: false,\n },\n strict: {\n stripUnknownKeys: false,\n errorOnUnknownKeys: true,\n passthroughUnknownKeys: false,\n },\n passthrough: {\n stripUnknownKeys: false,\n errorOnUnknownKeys: false,\n passthroughUnknownKeys: true,\n },\n};\n\nexport function getModeConfig(mode: RuntimeMode): ModeConfig {\n return CONFIGS[mode];\n}\n","export function deepFreeze<T>(value: T): Readonly<T> {\n if (value === null || typeof value !== \"object\") return value as Readonly<T>;\n if (Object.isFrozen(value)) return value as Readonly<T>;\n\n Object.freeze(value);\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n deepFreeze(v);\n }\n }\n\n return value as Readonly<T>;\n}\n\nexport function isDeepFrozen(value: unknown): boolean {\n if (value === null || typeof value !== \"object\") return true;\n if (!Object.isFrozen(value)) return false;\n\n for (const key of Object.keys(value as object)) {\n const v = (value as Record<string, unknown>)[key];\n if (!isDeepFrozen(v)) return false;\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loydjs/runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Loyd runtime — zero-copy execution engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loyd",
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
+
"README.md",
|
|
29
30
|
"dist",
|
|
30
31
|
"src"
|
|
31
32
|
],
|
|
32
33
|
"sideEffects": false,
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@loydjs/core": "
|
|
35
|
-
"@loydjs/compiler": "
|
|
35
|
+
"@loydjs/core": "1.1.0",
|
|
36
|
+
"@loydjs/compiler": "1.1.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"typescript": "^5.7.2",
|
package/src/executor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compile } from "@loydjs/compiler";
|
|
1
2
|
import type { LoydResult, LoydSchema } from "@loydjs/core";
|
|
2
3
|
|
|
3
4
|
export type RuntimeMode = "strict" | "strip" | "passthrough";
|
|
@@ -9,25 +10,95 @@ export interface ExecutorOptions {
|
|
|
9
10
|
*/
|
|
10
11
|
freeze?: boolean;
|
|
11
12
|
/**
|
|
13
|
+
* Skip creating a new result object on success return input directly.
|
|
12
14
|
* @default false
|
|
13
15
|
*/
|
|
14
16
|
zeroCopy?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Stop validation after first error per object.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
abortEarly?: boolean;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
/**
|
|
18
|
-
* Creates an optimized executor with fixed options.
|
|
19
|
-
* More efficient than going through the options on every call.
|
|
20
|
-
* @example
|
|
21
|
-
* const executor = createExecutor({ mode: "strict", freeze: true });
|
|
22
|
-
* const result = executor.run(UserSchema, rawInput);
|
|
23
|
-
*/
|
|
24
24
|
export interface Executor {
|
|
25
25
|
run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T>;
|
|
26
26
|
runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T;
|
|
27
27
|
readonly options: Required<ExecutorOptions>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
const _EMPTY_ISSUES: [] = [];
|
|
31
|
+
|
|
32
|
+
function makeSuccessResult<T>(data: T): LoydResult<T> {
|
|
33
|
+
return { success: true, data, issues: _EMPTY_ISSUES };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createExecutor(options: ExecutorOptions = {}): Executor {
|
|
37
|
+
const resolved: Required<ExecutorOptions> = {
|
|
38
|
+
mode: options.mode ?? "strip",
|
|
39
|
+
freeze: options.freeze ?? false,
|
|
40
|
+
zeroCopy: options.zeroCopy ?? false,
|
|
41
|
+
abortEarly: options.abortEarly ?? false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const { freeze: doFreeze, zeroCopy } = resolved;
|
|
45
|
+
|
|
46
|
+
const maybeFreeze = doFreeze ? <T>(v: T): T => deepFreezeImpl(v) : <T>(v: T): T => v;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
options: resolved,
|
|
50
|
+
|
|
51
|
+
run<T>(schema: LoydSchema<T>, input: unknown): LoydResult<T> {
|
|
52
|
+
const validator = compile(schema);
|
|
53
|
+
const result = validator(input);
|
|
54
|
+
|
|
55
|
+
if (!result.success) return result;
|
|
56
|
+
|
|
57
|
+
const data = maybeFreeze(result.data);
|
|
58
|
+
|
|
59
|
+
if (zeroCopy && data === result.data) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
31
62
|
|
|
32
|
-
|
|
33
|
-
|
|
63
|
+
return makeSuccessResult(data);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
runOrThrow<T>(schema: LoydSchema<T>, input: unknown): T {
|
|
67
|
+
const validator = compile(schema);
|
|
68
|
+
const result = validator(input);
|
|
69
|
+
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
const first = result.issues[0];
|
|
72
|
+
throw new Error(
|
|
73
|
+
first?.message ??
|
|
74
|
+
`Validation failed: ${first?.code ?? "ERR_UNKNOWN"} at ${JSON.stringify(first?.path ?? [])}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return maybeFreeze(result.data);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**strip mode*/
|
|
84
|
+
export const defaultExecutor: Executor = createExecutor();
|
|
85
|
+
|
|
86
|
+
export const zeroCopyExecutor: Executor = createExecutor({ zeroCopy: true });
|
|
87
|
+
|
|
88
|
+
export const strictExecutor: Executor = createExecutor({ mode: "strict" });
|
|
89
|
+
|
|
90
|
+
function deepFreezeImpl<T>(value: T): T {
|
|
91
|
+
if (value === null || typeof value !== "object") return value;
|
|
92
|
+
if (Object.isFrozen(value)) return value;
|
|
93
|
+
|
|
94
|
+
Object.freeze(value);
|
|
95
|
+
|
|
96
|
+
for (const key of Object.keys(value as object)) {
|
|
97
|
+
const v = (value as Record<string, unknown>)[key];
|
|
98
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
99
|
+
deepFreezeImpl(v);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return value;
|
|
104
|
+
}
|
package/src/freeze.ts
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export function deepFreeze<T>(value: T): Readonly<T> {
|
|
2
|
+
if (value === null || typeof value !== "object") return value as Readonly<T>;
|
|
3
|
+
if (Object.isFrozen(value)) return value as Readonly<T>;
|
|
4
|
+
|
|
5
|
+
Object.freeze(value);
|
|
6
|
+
|
|
7
|
+
for (const key of Object.keys(value as object)) {
|
|
8
|
+
const v = (value as Record<string, unknown>)[key];
|
|
9
|
+
if (v && typeof v === "object" && !Object.isFrozen(v)) {
|
|
10
|
+
deepFreeze(v);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return value as Readonly<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isDeepFrozen(value: unknown): boolean {
|
|
18
|
+
if (value === null || typeof value !== "object") return true;
|
|
19
|
+
if (!Object.isFrozen(value)) return false;
|
|
20
|
+
|
|
21
|
+
for (const key of Object.keys(value as object)) {
|
|
22
|
+
const v = (value as Record<string, unknown>)[key];
|
|
23
|
+
if (!isDeepFrozen(v)) return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
}
|
package/src/mode.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import type { RuntimeMode } from "./executor.js";
|
|
2
|
+
|
|
2
3
|
export interface ModeConfig {
|
|
3
4
|
stripUnknownKeys: boolean;
|
|
4
5
|
errorOnUnknownKeys: boolean;
|
|
5
6
|
passthroughUnknownKeys: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
const CONFIGS: Record<RuntimeMode, ModeConfig> = {
|
|
10
|
+
strip: {
|
|
11
|
+
stripUnknownKeys: true,
|
|
12
|
+
errorOnUnknownKeys: false,
|
|
13
|
+
passthroughUnknownKeys: false,
|
|
14
|
+
},
|
|
15
|
+
strict: {
|
|
16
|
+
stripUnknownKeys: false,
|
|
17
|
+
errorOnUnknownKeys: true,
|
|
18
|
+
passthroughUnknownKeys: false,
|
|
19
|
+
},
|
|
20
|
+
passthrough: {
|
|
21
|
+
stripUnknownKeys: false,
|
|
22
|
+
errorOnUnknownKeys: false,
|
|
23
|
+
passthroughUnknownKeys: true,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function getModeConfig(mode: RuntimeMode): ModeConfig {
|
|
28
|
+
return CONFIGS[mode];
|
|
29
|
+
}
|