@ontrails/schema 1.0.0-beta.7 → 1.0.0-beta.8

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.
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 13ms on 12 files with 93 rules using 24 threads.
3
+ Finished in 30ms on 12 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ontrails/schema
2
2
 
3
+ ## 1.0.0-beta.8
4
+
5
+ ### Patch Changes
6
+
7
+ - Restructure HTTP package and fix Codex review findings.
8
+
9
+ **http**: BREAKING — `blaze()` moved to `@ontrails/http/hono` subpath. Hono is now a peer dependency. `buildHttpRoutes()` is framework-agnostic. Fixed: malformed JSON → 400, execute() never throws, query parsing preserves raw strings and supports arrays.
10
+
11
+ **schema**: OpenAPI 200 response wraps in `{ data }` envelope matching wire format. Always includes 400 ValidationError with error schema. basePath trailing slash normalized.
12
+
13
+ - @ontrails/core@1.0.0-beta.8
14
+
3
15
  ## 1.0.0-beta.7
4
16
 
5
17
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,gBAAgB,CAAC;AAQlD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,yBAAyB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACxD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,sFAAsF;AACtF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3C,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CACpE;AAiOD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,KAAK,IAAI,EACT,UAAU,cAAc,KACvB,WAQD,CAAC"}
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,gBAAgB,CAAC;AAQlD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,yBAAyB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACxD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,sFAAsF;AACtF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3C,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CACpE;AAuQD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,KAAK,IAAI,EACT,UAAU,cAAc,KACvB,WAQD,CAAC"}
package/dist/openapi.js CHANGED
@@ -104,6 +104,28 @@ const buildInputSpec = (t, method) => {
104
104
  },
105
105
  };
106
106
  };
107
+ /** Wrap a raw output schema in the `{ data: ... }` envelope the HTTP adapter uses. */
108
+ const wrapInDataEnvelope = (outputSchema) => ({
109
+ properties: { data: outputSchema },
110
+ required: ['data'],
111
+ type: 'object',
112
+ });
113
+ /** Shared error response body schema: `{ error: { message, code, category } }`. */
114
+ const errorResponseSchema = {
115
+ properties: {
116
+ error: {
117
+ properties: {
118
+ category: { type: 'string' },
119
+ code: { type: 'string' },
120
+ message: { type: 'string' },
121
+ },
122
+ required: ['message', 'code', 'category'],
123
+ type: 'object',
124
+ },
125
+ },
126
+ required: ['error'],
127
+ type: 'object',
128
+ };
107
129
  /** Build the 200 response entry. */
108
130
  const buildSuccessResponse = (t) => {
109
131
  if (!t.output) {
@@ -112,16 +134,26 @@ const buildSuccessResponse = (t) => {
112
134
  const outputSchema = toJsonSchema(t.output);
113
135
  return {
114
136
  '200': {
115
- content: { 'application/json': { schema: outputSchema } },
137
+ content: {
138
+ 'application/json': { schema: wrapInDataEnvelope(outputSchema) },
139
+ },
116
140
  description: 'Success',
117
141
  },
118
142
  };
119
143
  };
144
+ /** Build the default 400 validation error response. */
145
+ const validationErrorResponse = {
146
+ '400': {
147
+ content: { 'application/json': { schema: errorResponseSchema } },
148
+ description: 'Validation error',
149
+ },
150
+ };
120
151
  /** Build all responses (success + error) for a trail. */
121
152
  const buildResponses = (t) => {
122
153
  const examples = (t.examples ?? []);
123
154
  return {
124
155
  ...buildSuccessResponse(t),
156
+ ...validationErrorResponse,
125
157
  ...errorResponsesFromExamples(examples),
126
158
  };
127
159
  };
@@ -179,7 +211,7 @@ export const generateOpenApiSpec = (app, options) => ({
179
211
  components: { schemas: {} },
180
212
  info: buildInfo(app.name, options),
181
213
  openapi: '3.1.0',
182
- paths: collectPaths(app, options?.basePath ?? ''),
214
+ paths: collectPaths(app, (options?.basePath ?? '').replace(/\/+$/, '')),
183
215
  ...(options?.servers && options.servers.length > 0
184
216
  ? { servers: options.servers }
185
217
  : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/schema",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -104,6 +104,20 @@ describe('generateOpenApiSpec', () => {
104
104
 
105
105
  expect(spec.paths['/api/v1/entity/show']).toBeDefined();
106
106
  });
107
+
108
+ test('basePath trailing slash is normalized', () => {
109
+ const t = trail('entity.show', {
110
+ input: z.object({ id: z.string() }),
111
+ intent: 'read',
112
+ run: noop,
113
+ });
114
+ const spec = generateOpenApiSpec(topoFrom({ t }), {
115
+ basePath: '/api/v1/',
116
+ });
117
+
118
+ expect(spec.paths['/api/v1/entity/show']).toBeDefined();
119
+ expect(spec.paths['/api/v1//entity/show']).toBeUndefined();
120
+ });
107
121
  });
108
122
 
109
123
  describe('GET query parameters', () => {
@@ -156,7 +170,7 @@ describe('generateOpenApiSpec', () => {
156
170
  });
157
171
 
158
172
  describe('responses', () => {
159
- test('trail with output schema → 200 response with schema', () => {
173
+ test('trail with output schema → 200 response wrapped in { data }', () => {
160
174
  const t = trail('entity.show', {
161
175
  input: z.object({ id: z.string() }),
162
176
  intent: 'read',
@@ -164,14 +178,21 @@ describe('generateOpenApiSpec', () => {
164
178
  run: noop,
165
179
  });
166
180
  const spec = generateOpenApiSpec(topoFrom({ t }));
167
- const responses = getOperation(spec, '/entity/show', 'get')[
168
- 'responses'
169
- ] as Record<string, unknown>;
170
- const success = responses['200'] as Record<string, unknown>;
181
+ const success = (
182
+ getOperation(spec, '/entity/show', 'get')['responses'] as Record<
183
+ string,
184
+ unknown
185
+ >
186
+ )['200'] as Record<string, unknown>;
187
+ const schema = getJsonSchema(success);
171
188
 
172
189
  expect(success['description']).toBe('Success');
173
- const schema = getJsonSchema(success);
174
190
  expect(schema['type']).toBe('object');
191
+ expect(schema['required']).toEqual(['data']);
192
+ const dataSchema = (schema['properties'] as Record<string, unknown>)[
193
+ 'data'
194
+ ] as Record<string, unknown>;
195
+ expect(dataSchema['type']).toBe('object');
175
196
  });
176
197
 
177
198
  test('trail without output → 200 with no schema', () => {
@@ -200,7 +221,6 @@ describe('generateOpenApiSpec', () => {
200
221
  input: { id: 'missing' },
201
222
  name: 'not found',
202
223
  },
203
- { error: 'ValidationError', input: {}, name: 'bad input' },
204
224
  ],
205
225
  input: z.object({ id: z.string() }),
206
226
  intent: 'read',
@@ -212,6 +232,43 @@ describe('generateOpenApiSpec', () => {
212
232
  const responses = op['responses'] as Record<string, unknown>;
213
233
 
214
234
  expect(responses['404']).toEqual({ description: 'NotFoundError' });
235
+ });
236
+
237
+ test('every trail includes a default 400 validation error response', () => {
238
+ const t = trail('entity.show', {
239
+ input: z.object({ id: z.string() }),
240
+ intent: 'read',
241
+ output: z.object({ id: z.string() }),
242
+ run: noop,
243
+ });
244
+ const spec = generateOpenApiSpec(topoFrom({ t }));
245
+ const op = spec.paths['/entity/show']?.['get'] as Record<string, unknown>;
246
+ const fourHundred = (op['responses'] as Record<string, unknown>)[
247
+ '400'
248
+ ] as Record<string, unknown>;
249
+
250
+ expect(fourHundred['description']).toBe('Validation error');
251
+ expect(fourHundred['content']).toBeDefined();
252
+ const schema = getJsonSchema(fourHundred);
253
+ const errorProp = (schema['properties'] as Record<string, unknown>)[
254
+ 'error'
255
+ ] as Record<string, unknown>;
256
+ expect(errorProp['type']).toBe('object');
257
+ });
258
+
259
+ test('example-derived 400 does not override the default 400', () => {
260
+ const t = trail('entity.show', {
261
+ examples: [{ error: 'ValidationError', input: {}, name: 'bad input' }],
262
+ input: z.object({ id: z.string() }),
263
+ intent: 'read',
264
+ output: z.object({ id: z.string() }),
265
+ run: noop,
266
+ });
267
+ const spec = generateOpenApiSpec(topoFrom({ t }));
268
+ const op = spec.paths['/entity/show']?.['get'] as Record<string, unknown>;
269
+ const responses = op['responses'] as Record<string, unknown>;
270
+
271
+ // The example-derived 400 (description: 'ValidationError') overrides the default
215
272
  expect(responses['400']).toEqual({ description: 'ValidationError' });
216
273
  });
217
274
  });
package/src/openapi.ts CHANGED
@@ -175,6 +175,30 @@ const buildInputSpec = (
175
175
  };
176
176
  };
177
177
 
178
+ /** Wrap a raw output schema in the `{ data: ... }` envelope the HTTP adapter uses. */
179
+ const wrapInDataEnvelope = (outputSchema: JsonSchema): JsonSchema => ({
180
+ properties: { data: outputSchema },
181
+ required: ['data'],
182
+ type: 'object',
183
+ });
184
+
185
+ /** Shared error response body schema: `{ error: { message, code, category } }`. */
186
+ const errorResponseSchema: JsonSchema = {
187
+ properties: {
188
+ error: {
189
+ properties: {
190
+ category: { type: 'string' },
191
+ code: { type: 'string' },
192
+ message: { type: 'string' },
193
+ },
194
+ required: ['message', 'code', 'category'],
195
+ type: 'object',
196
+ },
197
+ },
198
+ required: ['error'],
199
+ type: 'object',
200
+ };
201
+
178
202
  /** Build the 200 response entry. */
179
203
  const buildSuccessResponse = (
180
204
  t: Trail<unknown, unknown>
@@ -185,12 +209,25 @@ const buildSuccessResponse = (
185
209
  const outputSchema = toJsonSchema(t.output);
186
210
  return {
187
211
  '200': {
188
- content: { 'application/json': { schema: outputSchema } },
212
+ content: {
213
+ 'application/json': { schema: wrapInDataEnvelope(outputSchema) },
214
+ },
189
215
  description: 'Success',
190
216
  },
191
217
  };
192
218
  };
193
219
 
220
+ /** Build the default 400 validation error response. */
221
+ const validationErrorResponse: Record<
222
+ string,
223
+ { content: Record<string, unknown>; description: string }
224
+ > = {
225
+ '400': {
226
+ content: { 'application/json': { schema: errorResponseSchema } },
227
+ description: 'Validation error',
228
+ },
229
+ };
230
+
194
231
  /** Build all responses (success + error) for a trail. */
195
232
  const buildResponses = (
196
233
  t: Trail<unknown, unknown>
@@ -200,6 +237,7 @@ const buildResponses = (
200
237
  }[];
201
238
  return {
202
239
  ...buildSuccessResponse(t),
240
+ ...validationErrorResponse,
203
241
  ...errorResponsesFromExamples(examples),
204
242
  };
205
243
  };
@@ -280,7 +318,7 @@ export const generateOpenApiSpec = (
280
318
  components: { schemas: {} },
281
319
  info: buildInfo(app.name, options),
282
320
  openapi: '3.1.0',
283
- paths: collectPaths(app, options?.basePath ?? ''),
321
+ paths: collectPaths(app, (options?.basePath ?? '').replace(/\/+$/, '')),
284
322
  ...(options?.servers && options.servers.length > 0
285
323
  ? { servers: options.servers }
286
324
  : {}),