entangled-worlds-orbiters-shared 2.17.0 → 3.0.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 +2 -4
- package/package.json +2 -7
- package/dist/capabilities/errors.d.ts +0 -79
- package/dist/capabilities/errors.js +0 -87
- package/dist/capabilities/index.d.ts +0 -9
- package/dist/capabilities/index.js +0 -9
- package/dist/capabilities/registry.d.ts +0 -106
- package/dist/capabilities/registry.js +0 -215
- package/dist/capabilities/schema.d.ts +0 -52
- package/dist/capabilities/schema.js +0 -139
- package/src/capabilities/errors.ts +0 -115
- package/src/capabilities/index.ts +0 -22
- package/src/capabilities/registry.ts +0 -307
- package/src/capabilities/schema.ts +0 -164
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tiny JSON-Schema-subset validator (shared machinery, TypeScript).
|
|
3
|
-
*
|
|
4
|
-
* The ONE validator both registries use (supersedes the duplicated
|
|
5
|
-
* `studio/capabilities/schema.js` + `core/edit/capabilities/schema.ts`). Deliberately NOT a
|
|
6
|
-
* dependency (no zod/ajv) — the app is bundle-weight sensitive and the house style is
|
|
7
|
-
* lightweight guard clauses. Each capability carries an `inputSchema` literal in this subset;
|
|
8
|
-
* the SAME literal is what the future agent layer hands the model as a tool definition, so
|
|
9
|
-
* there is no second schema language to keep in sync.
|
|
10
|
-
*
|
|
11
|
-
* Supported keywords (a pragmatic subset of JSON Schema draft-07):
|
|
12
|
-
* type 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null'
|
|
13
|
-
* (or an array of those — value matches if it satisfies any)
|
|
14
|
-
* properties per-key subschemas (objects)
|
|
15
|
-
* required array of keys that must be present and not undefined (objects)
|
|
16
|
-
* items subschema applied to every array element
|
|
17
|
-
* enum value must be one of the listed values
|
|
18
|
-
* const value must deep-equal the given value
|
|
19
|
-
* nullable when true, null always passes regardless of type
|
|
20
|
-
* minimum / maximum numbers
|
|
21
|
-
* minLength / maxLength strings
|
|
22
|
-
* minItems / maxItems arrays
|
|
23
|
-
* pattern string regex source (tested with RegExp)
|
|
24
|
-
*
|
|
25
|
-
* A schema with no `type` accepts any value (still honouring enum/const/properties if set).
|
|
26
|
-
* Unknown keywords are ignored — forward-compatible, never throws on shape it doesn't know.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { ValidationError, type SchemaIssue } from './errors.js'
|
|
30
|
-
|
|
31
|
-
export type JsonType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null'
|
|
32
|
-
|
|
33
|
-
export interface JsonSchema {
|
|
34
|
-
type?: JsonType | JsonType[]
|
|
35
|
-
properties?: Record<string, JsonSchema>
|
|
36
|
-
required?: string[]
|
|
37
|
-
items?: JsonSchema
|
|
38
|
-
enum?: unknown[]
|
|
39
|
-
const?: unknown
|
|
40
|
-
nullable?: boolean
|
|
41
|
-
minimum?: number
|
|
42
|
-
maximum?: number
|
|
43
|
-
minLength?: number
|
|
44
|
-
maxLength?: number
|
|
45
|
-
minItems?: number
|
|
46
|
-
maxItems?: number
|
|
47
|
-
pattern?: string
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const TYPE_CHECKS: Record<JsonType, (v: unknown) => boolean> = {
|
|
51
|
-
object: (v) => v !== null && typeof v === 'object' && !Array.isArray(v),
|
|
52
|
-
array: (v) => Array.isArray(v),
|
|
53
|
-
string: (v) => typeof v === 'string',
|
|
54
|
-
number: (v) => typeof v === 'number' && Number.isFinite(v),
|
|
55
|
-
integer: (v) => typeof v === 'number' && Number.isInteger(v),
|
|
56
|
-
boolean: (v) => typeof v === 'boolean',
|
|
57
|
-
null: (v) => v === null,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function deepEqual(a: unknown, b: unknown): boolean {
|
|
61
|
-
if (a === b) return true
|
|
62
|
-
if (typeof a !== typeof b || a === null || b === null || typeof a !== 'object') return false
|
|
63
|
-
const ka = Object.keys(a as object)
|
|
64
|
-
const kb = Object.keys(b as object)
|
|
65
|
-
if (ka.length !== kb.length) return false
|
|
66
|
-
return ka.every((k) => deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function checkType(schema: JsonSchema, value: unknown): boolean {
|
|
70
|
-
if (schema.type == null) return true
|
|
71
|
-
const types = Array.isArray(schema.type) ? schema.type : [schema.type]
|
|
72
|
-
return types.some((t) => (TYPE_CHECKS[t] ? TYPE_CHECKS[t](value) : true))
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function describeType(value: unknown): string {
|
|
76
|
-
if (value === null) return 'null'
|
|
77
|
-
if (Array.isArray(value)) return 'array'
|
|
78
|
-
return typeof value
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function validateNode(schema: JsonSchema | undefined, value: unknown, path: string, issues: SchemaIssue[]): void {
|
|
82
|
-
if (!schema || typeof schema !== 'object') return
|
|
83
|
-
const at = path || '(root)'
|
|
84
|
-
|
|
85
|
-
if (schema.nullable && value === null) return
|
|
86
|
-
|
|
87
|
-
if (!checkType(schema, value)) {
|
|
88
|
-
const want = Array.isArray(schema.type) ? schema.type.join('|') : schema.type
|
|
89
|
-
issues.push({ path: at, message: `expected type ${want}, got ${describeType(value)}` })
|
|
90
|
-
return // type is the gate; downstream checks would be noise
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if ('const' in schema && !deepEqual(value, schema.const)) {
|
|
94
|
-
issues.push({ path: at, message: `must equal ${JSON.stringify(schema.const)}` })
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (Array.isArray(schema.enum) && !schema.enum.some((e) => deepEqual(e, value))) {
|
|
98
|
-
issues.push({ path: at, message: `must be one of ${JSON.stringify(schema.enum)}` })
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (typeof value === 'number') {
|
|
102
|
-
if (typeof schema.minimum === 'number' && value < schema.minimum) {
|
|
103
|
-
issues.push({ path: at, message: `must be >= ${schema.minimum}` })
|
|
104
|
-
}
|
|
105
|
-
if (typeof schema.maximum === 'number' && value > schema.maximum) {
|
|
106
|
-
issues.push({ path: at, message: `must be <= ${schema.maximum}` })
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (typeof value === 'string') {
|
|
111
|
-
if (typeof schema.minLength === 'number' && value.length < schema.minLength) {
|
|
112
|
-
issues.push({ path: at, message: `must be at least ${schema.minLength} chars` })
|
|
113
|
-
}
|
|
114
|
-
if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
|
|
115
|
-
issues.push({ path: at, message: `must be at most ${schema.maxLength} chars` })
|
|
116
|
-
}
|
|
117
|
-
if (typeof schema.pattern === 'string' && !new RegExp(schema.pattern).test(value)) {
|
|
118
|
-
issues.push({ path: at, message: `must match /${schema.pattern}/` })
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (Array.isArray(value)) {
|
|
123
|
-
if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
|
|
124
|
-
issues.push({ path: at, message: `must have at least ${schema.minItems} items` })
|
|
125
|
-
}
|
|
126
|
-
if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) {
|
|
127
|
-
issues.push({ path: at, message: `must have at most ${schema.maxItems} items` })
|
|
128
|
-
}
|
|
129
|
-
if (schema.items) {
|
|
130
|
-
value.forEach((el, i) => validateNode(schema.items, el, `${path}[${i}]`, issues))
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (TYPE_CHECKS.object(value)) {
|
|
135
|
-
const obj = value as Record<string, unknown>
|
|
136
|
-
if (Array.isArray(schema.required)) {
|
|
137
|
-
for (const key of schema.required) {
|
|
138
|
-
if (obj[key] === undefined) issues.push({ path: path ? `${path}.${key}` : key, message: 'is required' })
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (schema.properties) {
|
|
142
|
-
for (const [key, sub] of Object.entries(schema.properties)) {
|
|
143
|
-
if (obj[key] !== undefined) validateNode(sub, obj[key], path ? `${path}.${key}` : key, issues)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Returns { ok, issues }. Never throws. */
|
|
150
|
-
export function validate(schema: JsonSchema, value: unknown): { ok: boolean; issues: SchemaIssue[] } {
|
|
151
|
-
const issues: SchemaIssue[] = []
|
|
152
|
-
validateNode(schema ?? {}, value, '', issues)
|
|
153
|
-
return { ok: issues.length === 0, issues }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Throws ValidationError when invalid; returns the value otherwise. Used by the registry. */
|
|
157
|
-
export function assertValid<T>(schema: JsonSchema, value: T, capability: string | null = null): T {
|
|
158
|
-
const { ok, issues } = validate(schema, value)
|
|
159
|
-
if (!ok) {
|
|
160
|
-
const summary = issues.map((i) => `${i.path}: ${i.message}`).join('; ')
|
|
161
|
-
throw new ValidationError(`Invalid input${capability ? ` for "${capability}"` : ''}: ${summary}`, { capability, issues })
|
|
162
|
-
}
|
|
163
|
-
return value
|
|
164
|
-
}
|