ts-procedures 10.0.0 → 10.1.1

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 (42) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/build/adapters/hono/envelope-parity.test.js +17 -6
  3. package/build/adapters/hono/envelope-parity.test.js.map +1 -1
  4. package/build/codegen/constants.js +2 -2
  5. package/build/codegen/constants.js.map +1 -1
  6. package/build/exports.d.ts +1 -0
  7. package/build/exports.js +4 -0
  8. package/build/exports.js.map +1 -1
  9. package/build/schema/typebox.d.ts +1 -1
  10. package/build/schema/typebox.js +20 -2
  11. package/build/schema/typebox.js.map +1 -1
  12. package/build/schema/typebox.test.js +10 -0
  13. package/build/schema/typebox.test.js.map +1 -1
  14. package/build/server/doc-registry.js +3 -0
  15. package/build/server/doc-registry.js.map +1 -1
  16. package/build/server/doc-registry.test.js +31 -0
  17. package/build/server/doc-registry.test.js.map +1 -1
  18. package/build/server/index.d.ts +1 -0
  19. package/build/server/index.js +1 -0
  20. package/build/server/index.js.map +1 -1
  21. package/build/server/spec-version.d.ts +24 -0
  22. package/build/server/spec-version.js +26 -0
  23. package/build/server/spec-version.js.map +1 -0
  24. package/build/server/types.d.ts +15 -0
  25. package/build/version.d.ts +9 -0
  26. package/build/version.js +11 -0
  27. package/build/version.js.map +1 -0
  28. package/docs/doc-envelope-spec-v1.md +296 -0
  29. package/docs/doc-envelope-v1.schema.json +253 -0
  30. package/docs/http-integrations.md +27 -1
  31. package/package.json +1 -1
  32. package/src/adapters/hono/envelope-parity.test.ts +18 -6
  33. package/src/codegen/constants.ts +2 -2
  34. package/src/exports.ts +4 -0
  35. package/src/schema/typebox.test.ts +12 -0
  36. package/src/schema/typebox.ts +21 -2
  37. package/src/server/doc-registry.test.ts +34 -0
  38. package/src/server/doc-registry.ts +3 -0
  39. package/src/server/index.ts +1 -0
  40. package/src/server/spec-version.ts +27 -0
  41. package/src/server/types.ts +15 -0
  42. package/src/version.ts +11 -0
@@ -9,6 +9,18 @@ describe('typebox schema detection', () => {
9
9
  expect(isTypeboxSchema(typebox)).toBe(true)
10
10
  })
11
11
 
12
+ test('it recognizes a top-level Type.Unsafe schema (~unsafe marker, no ~kind)', () => {
13
+ // typebox >= 1.2.0: Type.Unsafe carries `~unsafe` INSTEAD of `~kind`.
14
+ expect(isTypeboxSchema(Type.Unsafe({ type: 'string' }))).toBe(true)
15
+ })
16
+
17
+ test('it recognizes any future ~-prefixed marker (convention, not a name list)', () => {
18
+ // Detection keys off TypeBox's `~` namespace convention, so a marker that
19
+ // doesn't exist yet is still recognized without a code change.
20
+ const futureMarker = { '~somethingNew': true, type: 'object' }
21
+ expect(isTypeboxSchema(futureMarker)).toBe(true)
22
+ })
23
+
12
24
  test('it rejects non-TypeBox values', () => {
13
25
  expect(isTypeboxSchema({ type: 'object' })).toBe(false)
14
26
  expect(isTypeboxSchema('string value')).toBe(false)
@@ -3,10 +3,29 @@ import type { TJSONSchema } from './json-schema.js'
3
3
 
4
4
  const TYPEBOX_KIND = Symbol.for('TypeBox.Kind')
5
5
 
6
+ /** TypeBox v1 prefixes every internal marker key with `~`. */
7
+ const TILDE = '~'.charCodeAt(0)
8
+
9
+ /**
10
+ * TypeBox v1 namespaces all of its internal markers under a `~` prefix
11
+ * (`~kind`, `~unsafe`, `~optional`, ...) — non-enumerable own properties that
12
+ * stay out of the serialized JSON Schema. Detecting the *convention* rather
13
+ * than specific marker names means new markers (e.g. `~unsafe`, added in
14
+ * typebox 1.2.0 and used by `Type.Unsafe(...)` *instead of* `~kind`) are
15
+ * recognized without a code change. Reflect.ownKeys is required because these
16
+ * markers are non-enumerable.
17
+ */
18
+ function hasTypeBoxV1Marker(schema: object): boolean {
19
+ for (const key of Reflect.ownKeys(schema)) {
20
+ if (typeof key === 'string' && key.charCodeAt(0) === TILDE) return true
21
+ }
22
+ return false
23
+ }
24
+
6
25
  /**
7
26
  * Built-in adapter for TypeBox (`import { Type } from 'typebox'`).
8
27
  *
9
- * Detection covers both typebox v1 (`~kind` property) and legacy
28
+ * Detection covers both typebox v1 (any `~`-prefixed marker) and legacy
10
29
  * `@sinclair/typebox` 0.3x (`Symbol.for('TypeBox.Kind')`). TypeBox schemas
11
30
  * already ARE JSON Schema, so conversion is the identity.
12
31
  */
@@ -14,7 +33,7 @@ export const typeboxAdapter: SchemaAdapter = {
14
33
  name: 'typebox',
15
34
  detect(schema: unknown): boolean {
16
35
  if (typeof schema !== 'object' || schema === null) return false
17
- return '~kind' in schema || TYPEBOX_KIND in schema
36
+ return hasTypeBoxV1Marker(schema) || TYPEBOX_KIND in schema
18
37
  },
19
38
  toJsonSchema(schema: unknown): TJSONSchema {
20
39
  return schema as TJSONSchema
@@ -5,7 +5,9 @@ import type {
5
5
  THttpStreamProcedureRegistration,
6
6
  TProcedureRegistration,
7
7
  } from '../core/types.js'
8
+ import pkg from '../../package.json' with { type: 'json' }
8
9
  import { DocRegistry } from './doc-registry.js'
10
+ import { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
9
11
  import { defineErrorTaxonomy } from './errors/taxonomy.js'
10
12
  import { buildRpcRouteDoc } from './docs/rpc-doc.js'
11
13
  import { buildHttpStreamRouteDoc } from './docs/http-stream-doc.js'
@@ -201,6 +203,38 @@ describe('DocRegistry', () => {
201
203
  })
202
204
  })
203
205
 
206
+ // --------------------------------------------------------------------------
207
+ // toJSON() spec metadata
208
+ // --------------------------------------------------------------------------
209
+ describe('toJSON() spec metadata', () => {
210
+ test('emits specVersion (the constant, a number) and generatorVersion (the package version)', () => {
211
+ const out = new DocRegistry().toJSON()
212
+ expect(out.specVersion).toBe(DOC_ENVELOPE_SPEC_VERSION)
213
+ expect(typeof out.specVersion).toBe('number')
214
+ expect(out.generatorVersion).toBe(pkg.version)
215
+ // the exported constant must track the real package version
216
+ expect(DOC_ENVELOPE_GENERATOR_VERSION).toBe(pkg.version)
217
+ })
218
+
219
+ test('metadata is present even on an otherwise-empty envelope', () => {
220
+ const out = new DocRegistry({ includeDefaults: false }).toJSON()
221
+ expect(out).toMatchObject({
222
+ specVersion: DOC_ENVELOPE_SPEC_VERSION,
223
+ generatorVersion: pkg.version,
224
+ })
225
+ })
226
+
227
+ test('transform receives the metadata on its envelope argument', () => {
228
+ const seen = new DocRegistry().toJSON({
229
+ transform: ({ specVersion, generatorVersion }) => ({ specVersion, generatorVersion }),
230
+ })
231
+ expect(seen).toEqual({
232
+ specVersion: DOC_ENVELOPE_SPEC_VERSION,
233
+ generatorVersion: pkg.version,
234
+ })
235
+ })
236
+ })
237
+
204
238
  // --------------------------------------------------------------------------
205
239
  // toJSON() filter
206
240
  // --------------------------------------------------------------------------
@@ -13,6 +13,7 @@ import {
13
13
  taxonomyToErrorDocs,
14
14
  type ErrorTaxonomy,
15
15
  } from './errors/taxonomy.js'
16
+ import { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
16
17
 
17
18
  export type {
18
19
  AnyHttpRouteDoc,
@@ -112,6 +113,8 @@ export class DocRegistry {
112
113
  }
113
114
 
114
115
  const envelope: DocEnvelope = {
116
+ specVersion: DOC_ENVELOPE_SPEC_VERSION,
117
+ generatorVersion: DOC_ENVELOPE_GENERATOR_VERSION,
115
118
  basePath: this.basePath,
116
119
  headers: [...this.headers],
117
120
  errors: [...this.errors],
@@ -25,5 +25,6 @@ export { buildStreamRouteDoc } from './docs/stream-doc.js'
25
25
  export { buildHttpRouteDoc } from './docs/http-doc.js'
26
26
  export { buildHttpStreamRouteDoc } from './docs/http-stream-doc.js'
27
27
  export { DocRegistry } from './doc-registry.js'
28
+ export { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
28
29
  export { writeDocEnvelope } from './doc-envelope.js'
29
30
  export type { DocEnvelopeSource } from './doc-envelope.js'
@@ -0,0 +1,27 @@
1
+ import { PACKAGE_VERSION } from '../version.js'
2
+
3
+ /**
4
+ * Spec version of the {@link DocEnvelope} wire format.
5
+ *
6
+ * Bump this ONLY when the envelope shape changes in a way downstream parsers
7
+ * must react to — field renames, nesting changes, or semantic changes. Do NOT
8
+ * bump it on ordinary package releases (dependency upgrades, bug fixes, new
9
+ * optional fields that older parsers can safely ignore). It is decoupled from
10
+ * the npm package version on purpose, so consumers get a small monotonic
11
+ * integer to branch on instead of having to interpret semver.
12
+ *
13
+ * Consumer contract: an envelope with no `specVersion` field predates this
14
+ * field and MUST be treated as spec version 1.
15
+ */
16
+ export const DOC_ENVELOPE_SPEC_VERSION = 1
17
+
18
+ /**
19
+ * The ts-procedures package version that produced an envelope (e.g. `"10.0.0"`).
20
+ * Populates the envelope's `generatorVersion` field.
21
+ *
22
+ * Informational only — for telemetry, debugging, and "which release served
23
+ * this?" questions. Do NOT use it to decide parse compatibility; it churns on
24
+ * every release regardless of whether the envelope format changed. Use
25
+ * {@link DOC_ENVELOPE_SPEC_VERSION} for compatibility decisions.
26
+ */
27
+ export const DOC_ENVELOPE_GENERATOR_VERSION: string = PACKAGE_VERSION
@@ -250,6 +250,21 @@ export interface DocRegistryOutputOptions<TEnvelope = DocEnvelope> {
250
250
  }
251
251
 
252
252
  export interface DocEnvelope {
253
+ /**
254
+ * Wire-format version of this envelope. Every envelope produced by
255
+ * `DocRegistry.toJSON()` / `builder.toDocEnvelope()` carries it. Downstream
256
+ * consumers branch on it to pick a parser; bumped only on envelope format
257
+ * changes, NOT on package releases. Optional on the type so hand-built
258
+ * envelopes and pre-existing documents remain valid — an absent value MUST be
259
+ * treated as spec version 1. See {@link DOC_ENVELOPE_SPEC_VERSION}.
260
+ */
261
+ specVersion?: number
262
+ /**
263
+ * ts-procedures package version that produced this envelope (e.g. `"10.0.0"`).
264
+ * Informational only — do not use for parse-compatibility decisions (use
265
+ * {@link specVersion} for that). See {@link DOC_ENVELOPE_GENERATOR_VERSION}.
266
+ */
267
+ generatorVersion?: string
253
268
  basePath: string
254
269
  headers: HeaderDoc[]
255
270
  errors: ErrorDoc[]
package/src/version.ts ADDED
@@ -0,0 +1,11 @@
1
+ import pkg from '../package.json' with { type: 'json' }
2
+
3
+ /**
4
+ * The ts-procedures package version (e.g. `"10.0.0"`), read from `package.json`.
5
+ *
6
+ * Single source of truth for "which version is this build?" — consumed by the
7
+ * codegen file header and the doc-envelope `generatorVersion`. Import this
8
+ * rather than re-importing `package.json`, so there's one place the version
9
+ * enters the source tree.
10
+ */
11
+ export const PACKAGE_VERSION: string = pkg.version