@loydjs/runtime 1.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 +96 -108
- 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 +3 -3
- package/src/executor.ts +81 -10
- package/src/freeze.ts +27 -2
- package/src/mode.ts +22 -1
package/README.md
CHANGED
|
@@ -1,171 +1,159 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<h1
|
|
3
|
+
<h1>@loydjs/runtime</h1>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
<p><strong>Zero-copy execution engine for Loyd.</strong><br/>
|
|
6
|
+
createExecutor · zeroCopy · abortEarly · deepFreeze · strict mode.</p>
|
|
7
7
|
|
|
8
8
|
[](https://github.com/b3nito404/loyd/actions/workflows/ci.yml)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
|
-
[](https://bundlephobia.com/package/@loydjs/runtime)
|
|
11
11
|
[](https://www.typescriptlang.org)
|
|
12
|
-
[](https://www.npmjs.com/package/@loydjs/runtime)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
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.
|
|
19
21
|
|
|
20
|
-
- **
|
|
21
|
-
- **JIT compiler** : `compile(schema)` generates a pure JavaScript function via `new Function()`; subsequent calls are **2× faster** than `safeParse` on hot paths
|
|
22
|
-
- **Structured i18n** : validators emit `{ code, path, meta }`, never locale strings; swap locales at runtime without touching your schemas
|
|
23
|
-
- **Two-pass async pipeline** : sync rules execute first; async rules only if sync passes; multiple async rules run in parallel via `Promise.all`
|
|
24
|
-
- **Field dependency graph** : `buildDag(schema, deps)` enables incremental revalidation; change one field -> revalidate only it and its dependents
|
|
25
|
-
- **Native React integration** : `useForm`, `useField`, `useFieldArray` with zero external dependencies, backed by the DAG
|
|
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.
|
|
26
23
|
|
|
27
24
|
---
|
|
28
25
|
|
|
29
26
|
## Installation
|
|
30
27
|
|
|
31
28
|
```sh
|
|
32
|
-
|
|
33
|
-
npm install @loydjs/schema @loydjs/core @loydjs/types
|
|
34
|
-
|
|
35
|
-
# Optional packages
|
|
36
|
-
npm install @loydjs/async # two-pass async pipeline
|
|
37
|
-
npm install @loydjs/compiler # JIT compilation
|
|
38
|
-
npm install @loydjs/error-engine # structured i18n
|
|
39
|
-
npm install @loydjs/react # React hooks (requires @loydjs/graph)
|
|
40
|
-
npm install @loydjs/graph # field dependency DAG
|
|
41
|
-
npm install @loydjs/zod-compat # Zod migration utilities
|
|
42
|
-
npm install @loydjs/openapi # OpenAPI 3.1 / JSON Schema export
|
|
43
|
-
npm install @loydjs/vite # Vite / Rollup AOT plugin
|
|
29
|
+
npm install @loydjs/runtime
|
|
44
30
|
```
|
|
45
|
-
|
|
31
|
+
|
|
32
|
+
> **Requires** `@loydjs/core` · `@loydjs/compiler` · Node.js ≥ 20 · TypeScript ≥ 5.4
|
|
46
33
|
|
|
47
34
|
---
|
|
48
35
|
|
|
49
|
-
##
|
|
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.
|
|
50
41
|
|
|
51
42
|
```ts
|
|
52
|
-
import {
|
|
53
|
-
import { parse, safeParse } from "@loydjs/core";
|
|
54
|
-
import type { Infer } from "@loydjs/types";
|
|
55
|
-
|
|
56
|
-
// 1. Define your schema
|
|
57
|
-
const UserSchema = object({
|
|
58
|
-
name: pipe(string(), minLength(2)),
|
|
59
|
-
email: pipe(string(), email()),
|
|
60
|
-
age: number().int().min(0),
|
|
61
|
-
});
|
|
43
|
+
import { createExecutor } from "@loydjs/runtime";
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
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
|
+
});
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
const user = parse(UserSchema, req.body);
|
|
52
|
+
const result = executor.run(UserSchema, req.body);
|
|
69
53
|
|
|
70
|
-
// 3b. safeParse() never throws
|
|
71
|
-
const result = safeParse(UserSchema, formData);
|
|
72
54
|
if (result.success) {
|
|
73
|
-
console.log(result.data
|
|
74
|
-
} else {
|
|
75
|
-
result.issues.forEach(issue => {
|
|
76
|
-
console.log(issue.code); // "ERR_STRING_TOO_SHORT"
|
|
77
|
-
console.log(issue.path); // ["name"]
|
|
78
|
-
console.log(issue.meta); // { min: 2, actual: 1 }
|
|
79
|
-
});
|
|
55
|
+
console.log(result.data); // typed as User, deep-frozen
|
|
80
56
|
}
|
|
81
57
|
```
|
|
82
58
|
|
|
83
|
-
###
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
import { useForm } from "@loydjs/react";
|
|
87
|
-
|
|
88
|
-
function LoginForm() {
|
|
89
|
-
const { register, handleSubmit, state } = useForm({
|
|
90
|
-
schema: LoginSchema,
|
|
91
|
-
defaultValues: { email: "", password: "" },
|
|
92
|
-
mode: "onChange",
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<form onSubmit={handleSubmit(onValid, onInvalid)}>
|
|
97
|
-
<input {...register("email")} type="email" />
|
|
98
|
-
<input {...register("password")} type="password" />
|
|
99
|
-
<button type="submit" disabled={state.isSubmitting}>Sign in</button>
|
|
100
|
-
</form>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
```
|
|
59
|
+
### Pre-built executors
|
|
104
60
|
|
|
105
|
-
|
|
61
|
+
Three ready-to-use executors for the most common cases:
|
|
106
62
|
|
|
107
63
|
```ts
|
|
108
|
-
import {
|
|
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";
|
|
109
69
|
|
|
110
|
-
|
|
70
|
+
// Fastest possible — no result object allocated on success
|
|
71
|
+
const result = zeroCopyExecutor.run(UserSchema, input);
|
|
111
72
|
|
|
112
|
-
//
|
|
113
|
-
const result =
|
|
73
|
+
// Reject unknown keys
|
|
74
|
+
const result = strictExecutor.run(UserSchema, input);
|
|
114
75
|
```
|
|
115
76
|
|
|
116
|
-
###
|
|
77
|
+
### `executor.runOrThrow(schema, input)`
|
|
78
|
+
|
|
79
|
+
Throws a descriptive `Error` on failure instead of returning a result object.
|
|
117
80
|
|
|
118
81
|
```ts
|
|
119
|
-
|
|
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)`
|
|
120
94
|
|
|
121
|
-
|
|
122
|
-
configureFormatter("fr", fr);
|
|
95
|
+
Recursively freezes an object. Skips already-frozen objects for performance.
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
127
103
|
```
|
|
128
104
|
|
|
129
|
-
###
|
|
105
|
+
### `getModeConfig(mode)`
|
|
106
|
+
|
|
107
|
+
Returns the configuration object for a given mode.
|
|
130
108
|
|
|
131
109
|
```ts
|
|
132
|
-
import {
|
|
110
|
+
import { getModeConfig } from "@loydjs/runtime";
|
|
133
111
|
|
|
134
|
-
|
|
135
|
-
|
|
112
|
+
getModeConfig("strict");
|
|
113
|
+
// { stripUnknownKeys: false, errorOnUnknownKeys: true, passthroughUnknownKeys: false }
|
|
136
114
|
|
|
137
|
-
|
|
138
|
-
|
|
115
|
+
getModeConfig("passthrough");
|
|
116
|
+
// { stripUnknownKeys: false, errorOnUnknownKeys: false, passthroughUnknownKeys: true }
|
|
139
117
|
```
|
|
140
118
|
|
|
141
119
|
---
|
|
142
120
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
| `@loydjs/compiler` | `compile()`, JIT codegen, cache management | ~4 kb |
|
|
152
|
-
| `@loydjs/error-engine` | `createFormatter`, en/fr/es/ar locales | ~3 kb |
|
|
153
|
-
| `@loydjs/graph` | `buildDag`, `validateIncremental`, dirty tracking | ~3 kb |
|
|
154
|
-
| `@loydjs/react` | `useForm`, `useField`, `useFieldArray`, `FormProvider` | ~8 kb |
|
|
155
|
-
| `@loydjs/zod-compat` | `fromZod`, `toZod`, `runCodemod` | ~5 kb |
|
|
156
|
-
| `@loydjs/openapi` | `toOpenApi`, `toJsonSchema`, `toOpenApiComponents` | ~4 kb |
|
|
157
|
-
| `@loydjs/vite` | `loydPlugin()` - Vite/Rollup AOT compilation | ~2 kb |
|
|
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 |
|
|
158
129
|
|
|
159
130
|
---
|
|
160
131
|
|
|
161
|
-
##
|
|
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 |
|
|
162
139
|
|
|
163
|
-
|
|
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
|
|
164
152
|
|
|
165
|
-
**[
|
|
153
|
+
**[loyddev-psi.vercel.app](https://loyddev-psi.vercel.app)**
|
|
166
154
|
|
|
167
155
|
---
|
|
168
156
|
|
|
169
157
|
## License
|
|
170
158
|
|
|
171
|
-
MIT
|
|
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": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Loyd runtime — zero-copy execution engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loyd",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
],
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@loydjs/core": "1.
|
|
36
|
-
"@loydjs/compiler": "1.
|
|
35
|
+
"@loydjs/core": "1.1.0",
|
|
36
|
+
"@loydjs/compiler": "1.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
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
|
+
}
|