json.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { deepFreeze, snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
  2. import { SessionFormatError } from './error.ts'
  3. import type {
  4. SessionFormatHeader,
  5. SessionFormatJsonValue,
  6. } from './types.ts'
  7. /**
  8. * Test whether a value is a non-null, non-array object.
  9. * @param value - candidate value.
  10. * @returns whether the value is an object record.
  11. */
  12. export function isSessionFormatJsonObject(value: unknown): value is Record<string, unknown> {
  13. return typeof value === 'object' && value !== null && !Array.isArray(value)
  14. }
  15. /**
  16. * Require a non-negative safe integer without the JSON-unstable negative zero.
  17. * @param value - candidate count.
  18. * @param label - diagnostic subject.
  19. * @returns validated count.
  20. */
  21. export function sessionFormatCount(value: unknown, label: string): number {
  22. if (!Number.isSafeInteger(value) || (value as number) < 0 || Object.is(value, -0)) {
  23. throw new SessionFormatError(`${label} must be a non-negative safe integer`)
  24. }
  25. return value as number
  26. }
  27. /**
  28. * Require a safe integer without the JSON-unstable negative zero.
  29. * @param value - candidate integer.
  30. * @param label - diagnostic subject.
  31. * @returns validated integer.
  32. */
  33. export function sessionFormatSafeInteger(value: unknown, label: string): number {
  34. if (!Number.isSafeInteger(value) || Object.is(value, -0)) {
  35. throw new SessionFormatError(`${label} must be a safe integer`)
  36. }
  37. return value as number
  38. }
  39. /**
  40. * Require a non-negative integral format version.
  41. * @param value - candidate version.
  42. * @param label - diagnostic subject.
  43. * @returns validated version.
  44. */
  45. export function sessionFormatVersion(value: unknown, label = 'Session format version'): number {
  46. return sessionFormatCount(value, label)
  47. }
  48. /**
  49. * Read only the version required for directional dispatch.
  50. * @param headerValue - untrusted physical header value.
  51. * @returns validated stored version.
  52. */
  53. export function inspectSessionFormatVersion(headerValue: unknown): number {
  54. if (!isSessionFormatJsonObject(headerValue)) {
  55. throw new SessionFormatError('Session header must be a JSON object')
  56. }
  57. return sessionFormatVersion(headerValue['version'])
  58. }
  59. /**
  60. * Detach and deeply freeze a caller-supplied lossless JSON value.
  61. * @param value - borrowed candidate.
  62. * @param label - diagnostic subject.
  63. * @returns an immutable detached JSON snapshot.
  64. */
  65. export function snapshotSessionFormatJson(value: unknown, label = 'Session value'): SessionFormatJsonValue {
  66. const snapshot = snapshotJsonValue(value)
  67. if (snapshot === undefined) {
  68. throw new SessionFormatError(`${label} is not lossless JSON`)
  69. }
  70. return deepFreeze(snapshot) as SessionFormatJsonValue
  71. }
  72. /**
  73. * Snapshot one logical header without inspecting an event body.
  74. * @param header - borrowed logical header.
  75. * @param label - diagnostic subject.
  76. * @returns immutable detached header.
  77. */
  78. export function snapshotSessionFormatHeader(header: SessionFormatHeader, label = 'Session header'): SessionFormatHeader {
  79. const snapshot = snapshotSessionFormatJson(header, label)
  80. if (!isSessionFormatJsonObject(snapshot)) throw new SessionFormatError(`${label} must be a JSON object`)
  81. inspectSessionFormatVersion(snapshot)
  82. if (typeof snapshot['id'] !== 'string') throw new SessionFormatError(`${label} id must be a string`)
  83. sessionFormatCount(snapshot['createdAt'], `${label} createdAt`)
  84. if (typeof snapshot['isSeeded'] !== 'boolean') throw new SessionFormatError(`${label} isSeeded must be a boolean`)
  85. sessionFormatCount(snapshot['delegationDepth'], `${label} delegationDepth`)
  86. return snapshot as unknown as SessionFormatHeader
  87. }