apcore-js 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 (142) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/.gitmessage +60 -0
  3. package/.pre-commit-config.yaml +28 -0
  4. package/CHANGELOG.md +47 -0
  5. package/CLAUDE.md +68 -0
  6. package/README.md +131 -0
  7. package/apcore-logo.svg +79 -0
  8. package/package.json +37 -0
  9. package/planning/acl-system/overview.md +54 -0
  10. package/planning/acl-system/plan.md +92 -0
  11. package/planning/acl-system/state.json +76 -0
  12. package/planning/acl-system/tasks/acl-core.md +226 -0
  13. package/planning/acl-system/tasks/acl-rule.md +92 -0
  14. package/planning/acl-system/tasks/conditional-rules.md +259 -0
  15. package/planning/acl-system/tasks/pattern-matching.md +152 -0
  16. package/planning/acl-system/tasks/yaml-loading.md +271 -0
  17. package/planning/core-executor/overview.md +53 -0
  18. package/planning/core-executor/plan.md +88 -0
  19. package/planning/core-executor/state.json +76 -0
  20. package/planning/core-executor/tasks/async-support.md +106 -0
  21. package/planning/core-executor/tasks/execution-pipeline.md +113 -0
  22. package/planning/core-executor/tasks/redaction.md +85 -0
  23. package/planning/core-executor/tasks/safety-checks.md +65 -0
  24. package/planning/core-executor/tasks/setup.md +75 -0
  25. package/planning/decorator-bindings/overview.md +62 -0
  26. package/planning/decorator-bindings/plan.md +104 -0
  27. package/planning/decorator-bindings/state.json +87 -0
  28. package/planning/decorator-bindings/tasks/binding-directory.md +79 -0
  29. package/planning/decorator-bindings/tasks/binding-loader.md +148 -0
  30. package/planning/decorator-bindings/tasks/explicit-schemas.md +85 -0
  31. package/planning/decorator-bindings/tasks/function-module.md +127 -0
  32. package/planning/decorator-bindings/tasks/module-factory.md +89 -0
  33. package/planning/decorator-bindings/tasks/schema-modes.md +142 -0
  34. package/planning/middleware-system/overview.md +48 -0
  35. package/planning/middleware-system/plan.md +102 -0
  36. package/planning/middleware-system/state.json +65 -0
  37. package/planning/middleware-system/tasks/adapters.md +170 -0
  38. package/planning/middleware-system/tasks/base.md +115 -0
  39. package/planning/middleware-system/tasks/logging-middleware.md +304 -0
  40. package/planning/middleware-system/tasks/manager.md +313 -0
  41. package/planning/observability/overview.md +53 -0
  42. package/planning/observability/plan.md +119 -0
  43. package/planning/observability/state.json +98 -0
  44. package/planning/observability/tasks/context-logger.md +201 -0
  45. package/planning/observability/tasks/exporters.md +121 -0
  46. package/planning/observability/tasks/metrics-collector.md +162 -0
  47. package/planning/observability/tasks/metrics-middleware.md +141 -0
  48. package/planning/observability/tasks/obs-logging-middleware.md +179 -0
  49. package/planning/observability/tasks/span-model.md +120 -0
  50. package/planning/observability/tasks/tracing-middleware.md +179 -0
  51. package/planning/overview.md +81 -0
  52. package/planning/registry-system/overview.md +57 -0
  53. package/planning/registry-system/plan.md +114 -0
  54. package/planning/registry-system/state.json +109 -0
  55. package/planning/registry-system/tasks/dependencies.md +157 -0
  56. package/planning/registry-system/tasks/entry-point.md +148 -0
  57. package/planning/registry-system/tasks/metadata.md +198 -0
  58. package/planning/registry-system/tasks/registry-core.md +323 -0
  59. package/planning/registry-system/tasks/scanner.md +172 -0
  60. package/planning/registry-system/tasks/schema-export.md +261 -0
  61. package/planning/registry-system/tasks/types.md +124 -0
  62. package/planning/registry-system/tasks/validation.md +177 -0
  63. package/planning/schema-system/overview.md +56 -0
  64. package/planning/schema-system/plan.md +121 -0
  65. package/planning/schema-system/state.json +98 -0
  66. package/planning/schema-system/tasks/exporter.md +153 -0
  67. package/planning/schema-system/tasks/loader.md +106 -0
  68. package/planning/schema-system/tasks/ref-resolver.md +133 -0
  69. package/planning/schema-system/tasks/strict-mode.md +140 -0
  70. package/planning/schema-system/tasks/typebox-generation.md +133 -0
  71. package/planning/schema-system/tasks/types-and-annotations.md +160 -0
  72. package/planning/schema-system/tasks/validator.md +149 -0
  73. package/src/acl.ts +188 -0
  74. package/src/bindings.ts +208 -0
  75. package/src/config.ts +24 -0
  76. package/src/context.ts +75 -0
  77. package/src/decorator.ts +110 -0
  78. package/src/errors.ts +369 -0
  79. package/src/executor.ts +348 -0
  80. package/src/index.ts +81 -0
  81. package/src/middleware/adapters.ts +54 -0
  82. package/src/middleware/base.ts +33 -0
  83. package/src/middleware/index.ts +6 -0
  84. package/src/middleware/logging.ts +103 -0
  85. package/src/middleware/manager.ts +105 -0
  86. package/src/module.ts +41 -0
  87. package/src/observability/context-logger.ts +201 -0
  88. package/src/observability/index.ts +4 -0
  89. package/src/observability/metrics.ts +212 -0
  90. package/src/observability/tracing.ts +187 -0
  91. package/src/registry/dependencies.ts +99 -0
  92. package/src/registry/entry-point.ts +64 -0
  93. package/src/registry/index.ts +8 -0
  94. package/src/registry/metadata.ts +111 -0
  95. package/src/registry/registry.ts +314 -0
  96. package/src/registry/scanner.ts +150 -0
  97. package/src/registry/schema-export.ts +177 -0
  98. package/src/registry/types.ts +32 -0
  99. package/src/registry/validation.ts +38 -0
  100. package/src/schema/annotations.ts +67 -0
  101. package/src/schema/exporter.ts +93 -0
  102. package/src/schema/index.ts +14 -0
  103. package/src/schema/loader.ts +270 -0
  104. package/src/schema/ref-resolver.ts +235 -0
  105. package/src/schema/strict.ts +128 -0
  106. package/src/schema/types.ts +73 -0
  107. package/src/schema/validator.ts +82 -0
  108. package/src/utils/index.ts +1 -0
  109. package/src/utils/pattern.ts +30 -0
  110. package/tests/helpers.ts +30 -0
  111. package/tests/integration/test-acl-safety.test.ts +268 -0
  112. package/tests/integration/test-binding-executor.test.ts +194 -0
  113. package/tests/integration/test-e2e-flow.test.ts +117 -0
  114. package/tests/integration/test-error-propagation.test.ts +259 -0
  115. package/tests/integration/test-middleware-chain.test.ts +120 -0
  116. package/tests/integration/test-observability-integration.test.ts +438 -0
  117. package/tests/observability/test-context-logger.test.ts +123 -0
  118. package/tests/observability/test-metrics.test.ts +89 -0
  119. package/tests/observability/test-tracing.test.ts +131 -0
  120. package/tests/registry/test-dependencies.test.ts +70 -0
  121. package/tests/registry/test-entry-point.test.ts +133 -0
  122. package/tests/registry/test-metadata.test.ts +265 -0
  123. package/tests/registry/test-registry.test.ts +140 -0
  124. package/tests/registry/test-scanner.test.ts +257 -0
  125. package/tests/registry/test-schema-export.test.ts +224 -0
  126. package/tests/registry/test-validation.test.ts +75 -0
  127. package/tests/schema/test-loader.test.ts +97 -0
  128. package/tests/schema/test-ref-resolver.test.ts +105 -0
  129. package/tests/schema/test-strict.test.ts +139 -0
  130. package/tests/schema/test-validator.test.ts +64 -0
  131. package/tests/test-acl.test.ts +206 -0
  132. package/tests/test-bindings.test.ts +227 -0
  133. package/tests/test-config.test.ts +76 -0
  134. package/tests/test-context.test.ts +151 -0
  135. package/tests/test-decorator.test.ts +173 -0
  136. package/tests/test-errors.test.ts +204 -0
  137. package/tests/test-executor.test.ts +252 -0
  138. package/tests/test-middleware-manager.test.ts +185 -0
  139. package/tests/test-middleware.test.ts +86 -0
  140. package/tsconfig.build.json +8 -0
  141. package/tsconfig.json +20 -0
  142. package/vitest.config.ts +18 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Module validation for the registry system.
3
+ *
4
+ * In TypeScript, modules use TypeBox TSchema (which are plain objects) instead of
5
+ * Pydantic BaseModel classes. Duck-type checks validate that the module has
6
+ * inputSchema, outputSchema, description, and execute.
7
+ */
8
+
9
+ export function validateModule(moduleOrClass: unknown): string[] {
10
+ const errors: string[] = [];
11
+ const obj = moduleOrClass as Record<string, unknown>;
12
+
13
+ // Check inputSchema
14
+ const inputSchema = obj['inputSchema'] ?? (obj.constructor as unknown as Record<string, unknown>)?.['inputSchema'];
15
+ if (inputSchema == null || typeof inputSchema !== 'object') {
16
+ errors.push('Missing or invalid inputSchema: must be a TSchema object');
17
+ }
18
+
19
+ // Check outputSchema
20
+ const outputSchema = obj['outputSchema'] ?? (obj.constructor as unknown as Record<string, unknown>)?.['outputSchema'];
21
+ if (outputSchema == null || typeof outputSchema !== 'object') {
22
+ errors.push('Missing or invalid outputSchema: must be a TSchema object');
23
+ }
24
+
25
+ // Check description
26
+ const description = obj['description'];
27
+ if (!description || typeof description !== 'string') {
28
+ errors.push('Missing or empty description');
29
+ }
30
+
31
+ // Check execute
32
+ const execute = obj['execute'];
33
+ if (execute == null || typeof execute !== 'function') {
34
+ errors.push('Missing execute method');
35
+ }
36
+
37
+ return errors;
38
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Annotation conflict resolution — merge YAML and code metadata.
3
+ */
4
+
5
+ import type { ModuleAnnotations, ModuleExample } from '../module.js';
6
+ import { DEFAULT_ANNOTATIONS } from '../module.js';
7
+
8
+ const ANNOTATION_FIELDS: ReadonlyArray<keyof ModuleAnnotations> = [
9
+ 'readonly',
10
+ 'destructive',
11
+ 'idempotent',
12
+ 'requiresApproval',
13
+ 'openWorld',
14
+ ];
15
+
16
+ export function mergeAnnotations(
17
+ yamlAnnotations: Record<string, unknown> | null | undefined,
18
+ codeAnnotations: ModuleAnnotations | null | undefined,
19
+ ): ModuleAnnotations {
20
+ const values: Record<string, unknown> = {};
21
+ for (const f of ANNOTATION_FIELDS) {
22
+ values[f] = DEFAULT_ANNOTATIONS[f];
23
+ }
24
+
25
+ if (codeAnnotations != null) {
26
+ for (const f of ANNOTATION_FIELDS) {
27
+ values[f] = codeAnnotations[f];
28
+ }
29
+ }
30
+
31
+ if (yamlAnnotations != null) {
32
+ for (const [key, val] of Object.entries(yamlAnnotations)) {
33
+ if ((ANNOTATION_FIELDS as readonly string[]).includes(key)) {
34
+ values[key] = val;
35
+ }
36
+ }
37
+ }
38
+
39
+ return values as unknown as ModuleAnnotations;
40
+ }
41
+
42
+ export function mergeExamples(
43
+ yamlExamples: Array<Record<string, unknown>> | null | undefined,
44
+ codeExamples: ModuleExample[] | null | undefined,
45
+ ): ModuleExample[] {
46
+ if (yamlExamples != null) {
47
+ return yamlExamples.map((d) => ({
48
+ title: d['title'] as string,
49
+ inputs: (d['inputs'] as Record<string, unknown>) ?? {},
50
+ output: (d['output'] as Record<string, unknown>) ?? {},
51
+ description: d['description'] as string | undefined,
52
+ }));
53
+ }
54
+ if (codeExamples != null) return codeExamples;
55
+ return [];
56
+ }
57
+
58
+ export function mergeMetadata(
59
+ yamlMetadata: Record<string, unknown> | null | undefined,
60
+ codeMetadata: Record<string, unknown> | null | undefined,
61
+ ): Record<string, unknown> {
62
+ const result = codeMetadata != null ? { ...codeMetadata } : {};
63
+ if (yamlMetadata != null) {
64
+ Object.assign(result, yamlMetadata);
65
+ }
66
+ return result;
67
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * SchemaExporter — converts schemas to platform-specific export formats.
3
+ */
4
+
5
+ import type { ModuleAnnotations, ModuleExample } from '../module.js';
6
+ import { applyLlmDescriptions, stripExtensions, toStrictSchema } from './strict.js';
7
+ import { ExportProfile, type SchemaDefinition } from './types.js';
8
+
9
+ function deepCopy<T>(obj: T): T {
10
+ return JSON.parse(JSON.stringify(obj));
11
+ }
12
+
13
+ export class SchemaExporter {
14
+ export(
15
+ schemaDef: SchemaDefinition,
16
+ profile: ExportProfile,
17
+ annotations?: ModuleAnnotations | null,
18
+ examples?: ModuleExample[] | null,
19
+ name?: string | null,
20
+ ): Record<string, unknown> {
21
+ if (profile === ExportProfile.MCP) {
22
+ return this.exportMcp(schemaDef, annotations, name);
23
+ }
24
+ if (profile === ExportProfile.OpenAI) {
25
+ return this.exportOpenai(schemaDef);
26
+ }
27
+ if (profile === ExportProfile.Anthropic) {
28
+ return this.exportAnthropic(schemaDef, examples);
29
+ }
30
+ return this.exportGeneric(schemaDef);
31
+ }
32
+
33
+ exportMcp(
34
+ schemaDef: SchemaDefinition,
35
+ annotations?: ModuleAnnotations | null,
36
+ name?: string | null,
37
+ ): Record<string, unknown> {
38
+ return {
39
+ name: name ?? schemaDef.moduleId,
40
+ description: schemaDef.description,
41
+ inputSchema: schemaDef.inputSchema,
42
+ annotations: {
43
+ readOnlyHint: annotations?.readonly ?? false,
44
+ destructiveHint: annotations?.destructive ?? false,
45
+ idempotentHint: annotations?.idempotent ?? false,
46
+ openWorldHint: annotations?.openWorld ?? true,
47
+ },
48
+ };
49
+ }
50
+
51
+ exportOpenai(schemaDef: SchemaDefinition): Record<string, unknown> {
52
+ const schema = deepCopy(schemaDef.inputSchema);
53
+ applyLlmDescriptions(schema);
54
+ const strictSchema = toStrictSchema(schema);
55
+ return {
56
+ type: 'function',
57
+ function: {
58
+ name: schemaDef.moduleId.replace(/\./g, '_'),
59
+ description: schemaDef.description,
60
+ parameters: strictSchema,
61
+ strict: true,
62
+ },
63
+ };
64
+ }
65
+
66
+ exportAnthropic(
67
+ schemaDef: SchemaDefinition,
68
+ examples?: ModuleExample[] | null,
69
+ ): Record<string, unknown> {
70
+ const schema = deepCopy(schemaDef.inputSchema);
71
+ applyLlmDescriptions(schema);
72
+ stripExtensions(schema);
73
+ const result: Record<string, unknown> = {
74
+ name: schemaDef.moduleId.replace(/\./g, '_'),
75
+ description: schemaDef.description,
76
+ input_schema: schema,
77
+ };
78
+ if (examples && examples.length > 0) {
79
+ result['input_examples'] = examples.map((ex) => ex.inputs);
80
+ }
81
+ return result;
82
+ }
83
+
84
+ exportGeneric(schemaDef: SchemaDefinition): Record<string, unknown> {
85
+ return {
86
+ module_id: schemaDef.moduleId,
87
+ description: schemaDef.description,
88
+ input_schema: schemaDef.inputSchema,
89
+ output_schema: schemaDef.outputSchema,
90
+ definitions: schemaDef.definitions,
91
+ };
92
+ }
93
+ }
@@ -0,0 +1,14 @@
1
+ export { SchemaStrategy, ExportProfile, validationResultToError } from './types.js';
2
+ export type {
3
+ SchemaDefinition,
4
+ ResolvedSchema,
5
+ SchemaValidationErrorDetail,
6
+ SchemaValidationResult,
7
+ LLMExtensions,
8
+ } from './types.js';
9
+ export { RefResolver } from './ref-resolver.js';
10
+ export { toStrictSchema, applyLlmDescriptions, stripExtensions } from './strict.js';
11
+ export { mergeAnnotations, mergeExamples, mergeMetadata } from './annotations.js';
12
+ export { SchemaLoader, jsonSchemaToTypeBox } from './loader.js';
13
+ export { SchemaValidator } from './validator.js';
14
+ export { SchemaExporter } from './exporter.js';
@@ -0,0 +1,270 @@
1
+ /**
2
+ * SchemaLoader — primary entry point for the schema system.
3
+ *
4
+ * Uses TypeBox for schema representation. Since TypeBox schemas ARE JSON Schema,
5
+ * the conversion layer is minimal.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'node:fs';
9
+ import { resolve, join } from 'node:path';
10
+ import { Type, type TSchema } from '@sinclair/typebox';
11
+ import yaml from 'js-yaml';
12
+ import type { Config } from '../config.js';
13
+ import { SchemaNotFoundError, SchemaParseError } from '../errors.js';
14
+ import { RefResolver } from './ref-resolver.js';
15
+ import type { ResolvedSchema, SchemaDefinition } from './types.js';
16
+ import { SchemaStrategy } from './types.js';
17
+
18
+ export class SchemaLoader {
19
+ private _config: Config;
20
+ private _schemasDir: string;
21
+ private _resolver: RefResolver;
22
+ private _schemaCache: Map<string, SchemaDefinition> = new Map();
23
+ private _modelCache: Map<string, [ResolvedSchema, ResolvedSchema]> = new Map();
24
+
25
+ constructor(config: Config, schemasDir?: string | null) {
26
+ this._config = config;
27
+ if (schemasDir != null) {
28
+ this._schemasDir = resolve(schemasDir);
29
+ } else {
30
+ this._schemasDir = resolve(config.get('schema.root', './schemas') as string);
31
+ }
32
+ const maxDepth = (config.get('schema.max_ref_depth', 32) as number);
33
+ this._resolver = new RefResolver(this._schemasDir, maxDepth);
34
+ }
35
+
36
+ load(moduleId: string): SchemaDefinition {
37
+ const cached = this._schemaCache.get(moduleId);
38
+ if (cached) return cached;
39
+
40
+ const filePath = join(this._schemasDir, moduleId.replace(/\./g, '/') + '.schema.yaml');
41
+ if (!existsSync(filePath)) {
42
+ throw new SchemaNotFoundError(moduleId);
43
+ }
44
+
45
+ let data: unknown;
46
+ try {
47
+ data = yaml.load(readFileSync(filePath, 'utf-8'));
48
+ } catch (e) {
49
+ throw new SchemaParseError(`Invalid YAML in schema for '${moduleId}': ${e}`);
50
+ }
51
+
52
+ if (data === null || data === undefined || typeof data !== 'object' || Array.isArray(data)) {
53
+ throw new SchemaParseError(`Schema file for '${moduleId}' is empty or not a mapping`);
54
+ }
55
+
56
+ const dataObj = data as Record<string, unknown>;
57
+ for (const fieldName of ['input_schema', 'output_schema', 'description']) {
58
+ if (!(fieldName in dataObj)) {
59
+ throw new SchemaParseError(`Missing required field: ${fieldName} in schema for '${moduleId}'`);
60
+ }
61
+ }
62
+
63
+ const definitions: Record<string, unknown> = {
64
+ ...((dataObj['definitions'] as Record<string, unknown>) ?? {}),
65
+ ...((dataObj['$defs'] as Record<string, unknown>) ?? {}),
66
+ };
67
+
68
+ const sd: SchemaDefinition = {
69
+ moduleId: (dataObj['module_id'] as string) ?? moduleId,
70
+ description: dataObj['description'] as string,
71
+ inputSchema: dataObj['input_schema'] as Record<string, unknown>,
72
+ outputSchema: dataObj['output_schema'] as Record<string, unknown>,
73
+ errorSchema: (dataObj['error_schema'] as Record<string, unknown>) ?? null,
74
+ definitions,
75
+ version: (dataObj['version'] as string) ?? '1.0.0',
76
+ documentation: (dataObj['documentation'] as string) ?? null,
77
+ schemaUrl: (dataObj['$schema'] as string) ?? null,
78
+ };
79
+
80
+ this._schemaCache.set(moduleId, sd);
81
+ return sd;
82
+ }
83
+
84
+ resolve(schemaDef: SchemaDefinition): [ResolvedSchema, ResolvedSchema] {
85
+ const resolvedInput = this._resolver.resolve(schemaDef.inputSchema);
86
+ const resolvedOutput = this._resolver.resolve(schemaDef.outputSchema);
87
+
88
+ const inputSchema = jsonSchemaToTypeBox(resolvedInput);
89
+ const outputSchema = jsonSchemaToTypeBox(resolvedOutput);
90
+
91
+ const inputRs: ResolvedSchema = {
92
+ jsonSchema: resolvedInput,
93
+ schema: inputSchema,
94
+ moduleId: schemaDef.moduleId,
95
+ direction: 'input',
96
+ };
97
+ const outputRs: ResolvedSchema = {
98
+ jsonSchema: resolvedOutput,
99
+ schema: outputSchema,
100
+ moduleId: schemaDef.moduleId,
101
+ direction: 'output',
102
+ };
103
+ return [inputRs, outputRs];
104
+ }
105
+
106
+ getSchema(
107
+ moduleId: string,
108
+ nativeInputSchema?: TSchema | null,
109
+ nativeOutputSchema?: TSchema | null,
110
+ ): [ResolvedSchema, ResolvedSchema] {
111
+ const cached = this._modelCache.get(moduleId);
112
+ if (cached) return cached;
113
+
114
+ const strategy = SchemaStrategy[
115
+ (this._config.get('schema.strategy', 'yaml_first') as string)
116
+ .replace(/_([a-z])/g, (_, c: string) => c.toUpperCase())
117
+ .replace(/^./, (c) => c.toUpperCase()) as keyof typeof SchemaStrategy
118
+ ] ?? SchemaStrategy.YamlFirst;
119
+
120
+ let result: [ResolvedSchema, ResolvedSchema] | null = null;
121
+
122
+ if (strategy === SchemaStrategy.YamlFirst) {
123
+ try {
124
+ result = this._loadAndResolve(moduleId);
125
+ } catch (e) {
126
+ if (e instanceof SchemaNotFoundError && nativeInputSchema && nativeOutputSchema) {
127
+ result = this._wrapNative(moduleId, nativeInputSchema, nativeOutputSchema);
128
+ } else {
129
+ throw e;
130
+ }
131
+ }
132
+ } else if (strategy === SchemaStrategy.NativeFirst) {
133
+ if (nativeInputSchema && nativeOutputSchema) {
134
+ result = this._wrapNative(moduleId, nativeInputSchema, nativeOutputSchema);
135
+ } else {
136
+ result = this._loadAndResolve(moduleId);
137
+ }
138
+ } else if (strategy === SchemaStrategy.YamlOnly) {
139
+ result = this._loadAndResolve(moduleId);
140
+ }
141
+
142
+ if (result === null) {
143
+ throw new SchemaNotFoundError(moduleId);
144
+ }
145
+
146
+ this._modelCache.set(moduleId, result);
147
+ return result;
148
+ }
149
+
150
+ private _loadAndResolve(moduleId: string): [ResolvedSchema, ResolvedSchema] {
151
+ const cached = this._modelCache.get(moduleId);
152
+ if (cached) return cached;
153
+ const sd = this.load(moduleId);
154
+ const result = this.resolve(sd);
155
+ this._modelCache.set(moduleId, result);
156
+ return result;
157
+ }
158
+
159
+ private _wrapNative(
160
+ moduleId: string,
161
+ inputSchema: TSchema,
162
+ outputSchema: TSchema,
163
+ ): [ResolvedSchema, ResolvedSchema] {
164
+ const inputRs: ResolvedSchema = {
165
+ jsonSchema: inputSchema as unknown as Record<string, unknown>,
166
+ schema: inputSchema,
167
+ moduleId,
168
+ direction: 'input',
169
+ };
170
+ const outputRs: ResolvedSchema = {
171
+ jsonSchema: outputSchema as unknown as Record<string, unknown>,
172
+ schema: outputSchema,
173
+ moduleId,
174
+ direction: 'output',
175
+ };
176
+ return [inputRs, outputRs];
177
+ }
178
+
179
+ clearCache(): void {
180
+ this._schemaCache.clear();
181
+ this._modelCache.clear();
182
+ this._resolver.clearCache();
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Convert a JSON Schema dict to a TypeBox TSchema.
188
+ * Since TypeBox schemas ARE JSON Schema, this wraps the raw object
189
+ * so it can be used with Value.Check/Value.Decode.
190
+ */
191
+ export function jsonSchemaToTypeBox(schema: Record<string, unknown>): TSchema {
192
+ const schemaType = schema['type'] as string | undefined;
193
+
194
+ if (schemaType === 'object') {
195
+ const properties = schema['properties'] as Record<string, Record<string, unknown>> | undefined;
196
+ const required = new Set((schema['required'] as string[]) ?? []);
197
+
198
+ if (properties) {
199
+ const typeboxProps: Record<string, TSchema> = {};
200
+ for (const [name, propSchema] of Object.entries(properties)) {
201
+ const propType = jsonSchemaToTypeBox(propSchema);
202
+ typeboxProps[name] = required.has(name) ? propType : Type.Optional(propType);
203
+ }
204
+ return Type.Object(typeboxProps);
205
+ }
206
+ return Type.Record(Type.String(), Type.Unknown());
207
+ }
208
+
209
+ if (schemaType === 'array') {
210
+ const items = schema['items'] as Record<string, unknown> | undefined;
211
+ if (items) {
212
+ return Type.Array(jsonSchemaToTypeBox(items));
213
+ }
214
+ return Type.Array(Type.Unknown());
215
+ }
216
+
217
+ if (schemaType === 'string') {
218
+ const opts: Record<string, unknown> = {};
219
+ if ('minLength' in schema) opts['minLength'] = schema['minLength'];
220
+ if ('maxLength' in schema) opts['maxLength'] = schema['maxLength'];
221
+ if ('pattern' in schema) opts['pattern'] = schema['pattern'];
222
+ if ('format' in schema) opts['format'] = schema['format'];
223
+ return Type.String(opts);
224
+ }
225
+
226
+ if (schemaType === 'integer') {
227
+ const opts: Record<string, unknown> = {};
228
+ if ('minimum' in schema) opts['minimum'] = schema['minimum'];
229
+ if ('maximum' in schema) opts['maximum'] = schema['maximum'];
230
+ if ('exclusiveMinimum' in schema) opts['exclusiveMinimum'] = schema['exclusiveMinimum'];
231
+ if ('exclusiveMaximum' in schema) opts['exclusiveMaximum'] = schema['exclusiveMaximum'];
232
+ if ('multipleOf' in schema) opts['multipleOf'] = schema['multipleOf'];
233
+ return Type.Integer(opts);
234
+ }
235
+
236
+ if (schemaType === 'number') {
237
+ const opts: Record<string, unknown> = {};
238
+ if ('minimum' in schema) opts['minimum'] = schema['minimum'];
239
+ if ('maximum' in schema) opts['maximum'] = schema['maximum'];
240
+ if ('exclusiveMinimum' in schema) opts['exclusiveMinimum'] = schema['exclusiveMinimum'];
241
+ if ('exclusiveMaximum' in schema) opts['exclusiveMaximum'] = schema['exclusiveMaximum'];
242
+ if ('multipleOf' in schema) opts['multipleOf'] = schema['multipleOf'];
243
+ return Type.Number(opts);
244
+ }
245
+
246
+ if (schemaType === 'boolean') return Type.Boolean();
247
+ if (schemaType === 'null') return Type.Null();
248
+
249
+ if ('enum' in schema) {
250
+ const values = schema['enum'] as unknown[];
251
+ return Type.Union(values.map((v) => Type.Literal(v as string | number | boolean)));
252
+ }
253
+
254
+ if ('oneOf' in schema) {
255
+ const schemas = schema['oneOf'] as Record<string, unknown>[];
256
+ return Type.Union(schemas.map((s) => jsonSchemaToTypeBox(s)));
257
+ }
258
+
259
+ if ('anyOf' in schema) {
260
+ const schemas = schema['anyOf'] as Record<string, unknown>[];
261
+ return Type.Union(schemas.map((s) => jsonSchemaToTypeBox(s)));
262
+ }
263
+
264
+ if ('allOf' in schema) {
265
+ const schemas = schema['allOf'] as Record<string, unknown>[];
266
+ return Type.Intersect(schemas.map((s) => jsonSchemaToTypeBox(s)));
267
+ }
268
+
269
+ return Type.Unknown();
270
+ }