@xube/kit-aws-data-schema 0.2.12 → 0.2.14

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.
@@ -33,17 +33,6 @@ export declare const ReadingMetadataV1Schema: z.ZodObject<{
33
33
  }, z.core.$strip>;
34
34
  export type ReadingMetadataV1 = z.infer<typeof ReadingMetadataV1Schema>;
35
35
  export declare const isReadingMetadataV1: (item: unknown) => item is ReadingMetadataV1;
36
- export declare const ReadingV1Schema: z.ZodObject<{
37
- type: z.ZodString;
38
- s: z.ZodNumber;
39
- us: z.ZodOptional<z.ZodNumber>;
40
- data: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{}, z.core.$loose>]>;
41
- id: z.ZodString;
42
- component: z.ZodOptional<z.ZodString>;
43
- v: z.ZodDefault<z.ZodLiteral<1>>;
44
- }, z.core.$loose>;
45
- export type ReadingV1 = z.infer<typeof ReadingV1Schema>;
46
- export declare const isReadingV1: (item: unknown) => item is ReadingV1;
47
36
  export declare const ReadingV2MetadataSchema: z.ZodObject<{
48
37
  v: z.ZodLiteral<2>;
49
38
  tm: z.ZodNumber;
@@ -52,34 +41,47 @@ export declare const ReadingV2MetadataSchema: z.ZodObject<{
52
41
  }, z.core.$strip>;
53
42
  export type ReadingV2Metadata = z.infer<typeof ReadingV2MetadataSchema>;
54
43
  export declare const isReadingV2Metadata: (item: unknown) => item is ReadingV2Metadata;
55
- export declare const ReadingV2Schema: z.ZodObject<{
56
- data: z.ZodUnion<readonly [z.ZodCustom<Uint8Array, Uint8Array>, z.ZodString]>;
44
+ export declare const ReadingSchema: z.ZodObject<{
45
+ v: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
57
46
  id: z.ZodString;
58
47
  component: z.ZodOptional<z.ZodString>;
59
- v: z.ZodLiteral<2>;
60
- tm: z.ZodNumber;
48
+ type: z.ZodString;
49
+ s: z.ZodNumber;
50
+ us: z.ZodOptional<z.ZodNumber>;
51
+ data: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{}, z.core.$loose>]>;
61
52
  bo: z.ZodOptional<z.ZodBoolean>;
62
53
  so: z.ZodOptional<z.ZodBoolean>;
63
54
  }, z.core.$loose>;
64
- export type ReadingV2 = z.infer<typeof ReadingV2Schema>;
65
- export declare const isReadingV2: (item: unknown) => item is ReadingV2;
66
- export declare const ReadingSchema: z.ZodPreprocess<z.ZodDiscriminatedUnion<[z.ZodObject<{
55
+ export type Reading = z.infer<typeof ReadingSchema>;
56
+ export declare const isReading: (item: unknown) => item is Reading;
57
+ export declare const isReadingV1: (item: unknown) => item is Reading & {
58
+ v: typeof READING_VERSION_1;
59
+ };
60
+ export declare const isReadingV2: (item: unknown) => item is Reading & {
61
+ v: typeof READING_VERSION_2;
62
+ };
63
+ export declare const ReadingV1Schema: z.ZodObject<{
64
+ v: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
65
+ id: z.ZodString;
66
+ component: z.ZodOptional<z.ZodString>;
67
67
  type: z.ZodString;
68
68
  s: z.ZodNumber;
69
69
  us: z.ZodOptional<z.ZodNumber>;
70
70
  data: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{}, z.core.$loose>]>;
71
+ bo: z.ZodOptional<z.ZodBoolean>;
72
+ so: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$loose>;
74
+ export type ReadingV1 = Reading;
75
+ export declare const ReadingV2Schema: z.ZodObject<{
76
+ v: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
71
77
  id: z.ZodString;
72
78
  component: z.ZodOptional<z.ZodString>;
73
- v: z.ZodDefault<z.ZodLiteral<1>>;
74
- }, z.core.$loose>, z.ZodObject<{
75
- data: z.ZodUnion<readonly [z.ZodCustom<Uint8Array, Uint8Array>, z.ZodString]>;
76
- id: z.ZodString;
77
- component: z.ZodOptional<z.ZodString>;
78
- v: z.ZodLiteral<2>;
79
- tm: z.ZodNumber;
79
+ type: z.ZodString;
80
+ s: z.ZodNumber;
81
+ us: z.ZodOptional<z.ZodNumber>;
82
+ data: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{}, z.core.$loose>]>;
80
83
  bo: z.ZodOptional<z.ZodBoolean>;
81
84
  so: z.ZodOptional<z.ZodBoolean>;
82
- }, z.core.$loose>], "v">>;
83
- export type Reading = z.infer<typeof ReadingSchema>;
84
- export declare const isReading: (item: unknown) => item is Reading;
85
+ }, z.core.$loose>;
86
+ export type ReadingV2 = Reading;
85
87
  //# sourceMappingURL=reading.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/decode/reading.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,eAAO,MAAM,uBAAuB,IAAoB,CAAC;AAEzD,eAAO,MAAM,sBAAsB,+FAAyB,CAAC;AAE7D,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AACF,eAAO,MAAM,yBAAyB,SAC9B,OAAO,KACZ,IAAI,IAAI,uBAC4C,CAAC;AAExD,eAAO,MAAM,qBAAqB;;;;;;iBAGhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,eAAO,MAAM,iBAAiB,SAAU,OAAO,KAAG,IAAI,IAAI,eACX,CAAC;AAEhD,eAAO,MAAM,oBAAoB;;;;;;iBAAwB,CAAC;AAC1D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACtE,eAAO,MAAM,oBAAoB,SACzB,OAAO,KACZ,IAAI,IAAI,kBAAkE,CAAC;AAE9E,eAAO,MAAM,uBAAuB;;;iBAAgC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,mBAAmB,SAAU,OAAO,KAAG,IAAI,IAAI,iBACX,CAAC;AAOlD,eAAO,MAAM,eAAe;;;;;;;;iBAS1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACxD,eAAO,MAAM,WAAW,SAAU,OAAO,KAAG,IAAI,IAAI,SACX,CAAC;AAE1C,eAAO,MAAM,uBAAuB;;;;;iBAKlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,mBAAmB,SACxB,OAAO,KACZ,IAAI,IAAI,iBACsC,CAAC;AAElD,eAAO,MAAM,eAAe;;;;;;;;iBAI1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AACxD,eAAO,MAAM,WAAW,SAAU,OAAO,KAAG,IAAI,IAAI,SACX,CAAC;AAiB1C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;yBAMzB,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AACpD,eAAO,MAAM,SAAS,SAAU,OAAO,KAAG,IAAI,IAAI,OACX,CAAC"}
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/decode/reading.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,eAAO,MAAM,uBAAuB,IAAoB,CAAC;AAEzD,eAAO,MAAM,sBAAsB,+FAAyB,CAAC;AAE7D,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AACF,eAAO,MAAM,yBAAyB,SAC9B,OAAO,KACZ,IAAI,IAAI,uBAC4C,CAAC;AAExD,eAAO,MAAM,qBAAqB;;;;;;iBAGhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,eAAO,MAAM,iBAAiB,SAAU,OAAO,KAAG,IAAI,IAAI,eACX,CAAC;AAEhD,eAAO,MAAM,oBAAoB;;;;;;iBAAwB,CAAC;AAC1D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACtE,eAAO,MAAM,oBAAoB,SACzB,OAAO,KACZ,IAAI,IAAI,kBAAkE,CAAC;AAE9E,eAAO,MAAM,uBAAuB;;;iBAAgC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,mBAAmB,SAAU,OAAO,KAAG,IAAI,IAAI,iBACX,CAAC;AAElD,eAAO,MAAM,uBAAuB;;;;;iBAKlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,mBAAmB,SACxB,OAAO,KACZ,IAAI,IAAI,iBACsC,CAAC;AAElD,eAAO,MAAM,aAAa;;;;;;;;;;iBAYxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AACpD,eAAO,MAAM,SAAS,SAAU,OAAO,KAAG,IAAI,IAAI,OACX,CAAC;AAExC,eAAO,MAAM,WAAW,SAChB,OAAO,KACZ,IAAI,IAAI,OAAO,GAAG;IAAE,CAAC,EAAE,OAAO,iBAAiB,CAAA;CAGjD,CAAC;AAEF,eAAO,MAAM,WAAW,SAChB,OAAO,KACZ,IAAI,IAAI,OAAO,GAAG;IAAE,CAAC,EAAE,OAAO,iBAAiB,CAAA;CAGjD,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;iBAAgB,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC;AAChC,eAAO,MAAM,eAAe;;;;;;;;;;iBAAgB,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isReading = exports.ReadingSchema = exports.isReadingV2 = exports.ReadingV2Schema = exports.isReadingV2Metadata = exports.ReadingV2MetadataSchema = exports.isReadingV1 = exports.ReadingV1Schema = exports.isReadingMetadataV1 = exports.ReadingMetadataV1Schema = exports.isDeviceReadingTypes = exports.DeviceReadingSchemas = exports.isDeviceReadingV1 = exports.DeviceReadingV1Schema = exports.isDeviceReadingMetadataV1 = exports.DeviceReadingMetadataV1Schema = exports.ReadingDataTypesSchema = exports.DEFAULT_READING_VERSION = exports.READING_VERSION_2 = exports.READING_VERSION_1 = void 0;
3
+ exports.ReadingV2Schema = exports.ReadingV1Schema = exports.isReadingV2 = exports.isReadingV1 = exports.isReading = exports.ReadingSchema = exports.isReadingV2Metadata = exports.ReadingV2MetadataSchema = exports.isReadingMetadataV1 = exports.ReadingMetadataV1Schema = exports.isDeviceReadingTypes = exports.DeviceReadingSchemas = exports.isDeviceReadingV1 = exports.DeviceReadingV1Schema = exports.isDeviceReadingMetadataV1 = exports.DeviceReadingMetadataV1Schema = exports.ReadingDataTypesSchema = exports.DEFAULT_READING_VERSION = exports.READING_VERSION_2 = exports.READING_VERSION_1 = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const kit_schema_1 = require("@xube/kit-schema");
6
6
  const constants_1 = require("../constants");
@@ -26,52 +26,38 @@ exports.isDeviceReadingTypes = isDeviceReadingTypes;
26
26
  exports.ReadingMetadataV1Schema = exports.DeviceReadingMetadataV1Schema;
27
27
  const isReadingMetadataV1 = (item) => exports.ReadingMetadataV1Schema.safeParse(item).success;
28
28
  exports.isReadingMetadataV1 = isReadingMetadataV1;
29
- const ReadingCommonShape = {
29
+ exports.ReadingV2MetadataSchema = zod_1.z.object({
30
+ [constants_1.READING_VERSION_IDENTIFIER]: zod_1.z.literal(exports.READING_VERSION_2),
31
+ tm: zod_1.z.number().refine((tm) => tm > 0, { message: "tm must be positive" }),
32
+ bo: zod_1.z.boolean().optional(),
33
+ so: zod_1.z.boolean().optional(),
34
+ });
35
+ const isReadingV2Metadata = (item) => exports.ReadingV2MetadataSchema.safeParse(item).success;
36
+ exports.isReadingV2Metadata = isReadingV2Metadata;
37
+ exports.ReadingSchema = zod_1.z.looseObject({
38
+ [constants_1.READING_VERSION_IDENTIFIER]: zod_1.z
39
+ .union([zod_1.z.literal(exports.READING_VERSION_1), zod_1.z.literal(exports.READING_VERSION_2)])
40
+ .default(exports.DEFAULT_READING_VERSION),
30
41
  id: zod_1.z.string(),
31
42
  component: zod_1.z.string().optional(),
32
- };
33
- exports.ReadingV1Schema = zod_1.z.looseObject({
34
- [constants_1.READING_VERSION_IDENTIFIER]: zod_1.z
35
- .literal(exports.READING_VERSION_1)
36
- .default(exports.READING_VERSION_1),
37
- ...ReadingCommonShape,
38
43
  type: zod_1.z.string(),
39
44
  s: zod_1.z.number(),
40
45
  us: zod_1.z.number().optional(),
41
46
  data: exports.ReadingDataTypesSchema,
42
- });
43
- const isReadingV1 = (item) => exports.ReadingV1Schema.safeParse(item).success;
44
- exports.isReadingV1 = isReadingV1;
45
- exports.ReadingV2MetadataSchema = zod_1.z.object({
46
- [constants_1.READING_VERSION_IDENTIFIER]: zod_1.z.literal(exports.READING_VERSION_2),
47
- tm: zod_1.z.number(),
48
47
  bo: zod_1.z.boolean().optional(),
49
48
  so: zod_1.z.boolean().optional(),
50
49
  });
51
- const isReadingV2Metadata = (item) => exports.ReadingV2MetadataSchema.safeParse(item).success;
52
- exports.isReadingV2Metadata = isReadingV2Metadata;
53
- exports.ReadingV2Schema = zod_1.z.looseObject({
54
- ...exports.ReadingV2MetadataSchema.shape,
55
- ...ReadingCommonShape,
56
- data: zod_1.z.union([zod_1.z.instanceof(Uint8Array), zod_1.z.string()]),
57
- });
58
- const isReadingV2 = (item) => exports.ReadingV2Schema.safeParse(item).success;
59
- exports.isReadingV2 = isReadingV2;
60
- const injectDefaultVersionIfMissing = (item) => {
61
- if (item === null ||
62
- typeof item !== "object" ||
63
- Array.isArray(item) ||
64
- constants_1.READING_VERSION_IDENTIFIER in item) {
65
- return item;
66
- }
67
- return {
68
- ...item,
69
- [constants_1.READING_VERSION_IDENTIFIER]: exports.DEFAULT_READING_VERSION,
70
- };
71
- };
72
- exports.ReadingSchema = zod_1.z.preprocess(injectDefaultVersionIfMissing, zod_1.z.discriminatedUnion(constants_1.READING_VERSION_IDENTIFIER, [
73
- exports.ReadingV1Schema,
74
- exports.ReadingV2Schema,
75
- ]));
76
50
  const isReading = (item) => exports.ReadingSchema.safeParse(item).success;
77
51
  exports.isReading = isReading;
52
+ const isReadingV1 = (item) => {
53
+ const result = exports.ReadingSchema.safeParse(item);
54
+ return result.success && result.data.v === exports.READING_VERSION_1;
55
+ };
56
+ exports.isReadingV1 = isReadingV1;
57
+ const isReadingV2 = (item) => {
58
+ const result = exports.ReadingSchema.safeParse(item);
59
+ return result.success && result.data.v === exports.READING_VERSION_2;
60
+ };
61
+ exports.isReadingV2 = isReadingV2;
62
+ exports.ReadingV1Schema = exports.ReadingSchema;
63
+ exports.ReadingV2Schema = exports.ReadingSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xube/kit-aws-data-schema",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -18,14 +18,14 @@
18
18
  },
19
19
  "homepage": "https://github.com/XubeLtd/dev-kit#readme",
20
20
  "devDependencies": {
21
- "@xube/kit-build": "^0.2.12"
21
+ "@xube/kit-build": "^0.2.14"
22
22
  },
23
23
  "dependencies": {
24
- "@xube/kit-aws-schema": "^0.2.12",
25
- "@xube/kit-constants": "^0.2.12",
26
- "@xube/kit-generator": "^0.2.12",
27
- "@xube/kit-log": "^0.2.12",
28
- "@xube/kit-schema": "^0.2.12",
24
+ "@xube/kit-aws-schema": "^0.2.14",
25
+ "@xube/kit-constants": "^0.2.14",
26
+ "@xube/kit-generator": "^0.2.14",
27
+ "@xube/kit-log": "^0.2.14",
28
+ "@xube/kit-schema": "^0.2.14",
29
29
  "zod": "^4.4.3",
30
30
  "zod-validation-error": "^5.0.0"
31
31
  }
@@ -1,109 +1,133 @@
1
- import { Buffer } from "buffer";
2
1
  import {
3
2
  DEFAULT_READING_VERSION,
4
3
  READING_VERSION_1,
5
4
  READING_VERSION_2,
5
+ Reading,
6
6
  ReadingSchema,
7
- ReadingV1,
8
- ReadingV1Schema,
9
- ReadingV2,
10
- ReadingV2Schema,
7
+ ReadingV2Metadata,
8
+ ReadingV2MetadataSchema,
11
9
  isReading,
12
10
  isReadingV1,
13
11
  isReadingV2,
12
+ isReadingV2Metadata,
14
13
  } from "./reading";
15
14
 
16
- const legacyV1 = {
17
- data: "S,+000.30,+001.74,M,00,33",
15
+ const baseV1 = {
16
+ v: READING_VERSION_1,
17
+ id: "dev1",
18
+ component: "componentA",
19
+ type: "data",
18
20
  s: 1697578841,
19
21
  us: 214981,
20
- type: "data",
21
- id: "dev1",
22
- component: "c",
23
- };
24
- const explicitV1 = { ...legacyV1, v: READING_VERSION_1 };
25
- const blobV2Binary = {
26
- v: READING_VERSION_2,
27
- id: "dev1",
28
- component: "c",
29
- tm: 1780859506366,
30
- bo: false,
31
- so: false,
32
- data: Buffer.from([0x01, 0x02, 0x03]),
22
+ data: "S,+000.30,+001.74,M,00,33",
33
23
  };
34
- const blobV2String = {
24
+
25
+ const baseV2 = {
35
26
  v: READING_VERSION_2,
36
27
  id: "dev1",
37
- tm: 1,
38
- bo: false,
39
- so: false,
40
- data: "blob-as-string",
28
+ component: "componentA",
29
+ type: "data",
30
+ s: 1780859506,
31
+ us: 366000,
32
+ data: "AQIDBA==",
41
33
  };
42
34
 
43
- describe("ReadingSchema (discriminated union)", () => {
44
- describe("v1 (timestamped)", () => {
45
- it("parses a legacy record missing `v` and injects DEFAULT_READING_VERSION", () => {
46
- const parsed = ReadingSchema.parse(legacyV1) as ReadingV1;
47
- expect(parsed.v).toBe(DEFAULT_READING_VERSION);
35
+ describe("ReadingSchema (unified storage shape)", () => {
36
+ describe("v1 rows", () => {
37
+ it("parses a v1 row with neither bo nor so", () => {
38
+ const parsed = ReadingSchema.parse(baseV1) as Reading;
48
39
  expect(parsed.v).toBe(READING_VERSION_1);
49
- expect(parsed.data).toBe(legacyV1.data);
50
- expect(parsed.id).toBe(legacyV1.id);
40
+ expect(parsed.s).toBe(baseV1.s);
41
+ expect(parsed.us).toBe(baseV1.us);
42
+ expect(parsed.bo).toBeUndefined();
43
+ expect(parsed.so).toBeUndefined();
51
44
  });
52
45
 
53
- it("parses an explicit v=1 record without mutating other fields", () => {
54
- const parsed = ReadingSchema.parse(explicitV1) as ReadingV1;
55
- expect(parsed).toEqual(explicitV1);
46
+ it("parses a v1 row with only bo", () => {
47
+ const parsed = ReadingSchema.parse({ ...baseV1, bo: true }) as Reading;
48
+ expect(parsed.v).toBe(READING_VERSION_1);
49
+ expect(parsed.bo).toBe(true);
50
+ expect(parsed.so).toBeUndefined();
56
51
  });
57
52
 
58
- it("preserves passthrough fields", () => {
59
- const parsed = ReadingSchema.parse({
60
- ...explicitV1,
61
- extra: "passthrough",
62
- }) as ReadingV1 & { extra: string };
63
- expect(parsed.extra).toBe("passthrough");
53
+ it("parses a v1 row with only so", () => {
54
+ const parsed = ReadingSchema.parse({ ...baseV1, so: false }) as Reading;
55
+ expect(parsed.v).toBe(READING_VERSION_1);
56
+ expect(parsed.bo).toBeUndefined();
57
+ expect(parsed.so).toBe(false);
64
58
  });
65
59
 
66
- it("does not overwrite an explicit v on a record that already has one", () => {
67
- // Preprocess only injects v when missing — must not clobber existing v.
68
- const parsed = ReadingSchema.parse(explicitV1) as ReadingV1;
60
+ it("parses a v1 row with both bo and so", () => {
61
+ const parsed = ReadingSchema.parse({
62
+ ...baseV1,
63
+ bo: false,
64
+ so: true,
65
+ }) as Reading;
69
66
  expect(parsed.v).toBe(READING_VERSION_1);
67
+ expect(parsed.bo).toBe(false);
68
+ expect(parsed.so).toBe(true);
70
69
  });
71
70
  });
72
71
 
73
- describe("v2 (blob)", () => {
74
- it("parses a v=2 record with a Uint8Array data payload", () => {
75
- const parsed = ReadingSchema.parse(blobV2Binary) as ReadingV2;
72
+ describe("v2 rows", () => {
73
+ it("parses a v2 row with neither bo nor so", () => {
74
+ const parsed = ReadingSchema.parse(baseV2) as Reading;
76
75
  expect(parsed.v).toBe(READING_VERSION_2);
77
- expect(parsed.tm).toBe(blobV2Binary.tm);
78
- expect(parsed.data).toBeInstanceOf(Uint8Array);
79
- expect((parsed.data as Uint8Array).length).toBe(3);
76
+ expect(parsed.s).toBe(baseV2.s);
77
+ expect(parsed.us).toBe(baseV2.us);
78
+ expect(parsed.bo).toBeUndefined();
79
+ expect(parsed.so).toBeUndefined();
80
80
  });
81
81
 
82
- it("parses a v=2 record with a string data payload", () => {
83
- expect(ReadingSchema.parse(blobV2String)).toEqual(blobV2String);
82
+ it("parses a v2 row with only bo", () => {
83
+ const parsed = ReadingSchema.parse({ ...baseV2, bo: true }) as Reading;
84
+ expect(parsed.v).toBe(READING_VERSION_2);
85
+ expect(parsed.bo).toBe(true);
86
+ expect(parsed.so).toBeUndefined();
84
87
  });
85
88
 
86
- it("rejects a v=2 record missing the required tm field", () => {
87
- const { tm: _tm, ...withoutTm } = blobV2String;
88
- expect(ReadingSchema.safeParse(withoutTm).success).toBe(false);
89
+ it("parses a v2 row with only so", () => {
90
+ const parsed = ReadingSchema.parse({ ...baseV2, so: false }) as Reading;
91
+ expect(parsed.v).toBe(READING_VERSION_2);
92
+ expect(parsed.bo).toBeUndefined();
93
+ expect(parsed.so).toBe(false);
89
94
  });
90
95
 
91
- it("rejects a v=2 record missing the required data field", () => {
92
- const { data: _data, ...withoutData } = blobV2String;
93
- expect(ReadingSchema.safeParse(withoutData).success).toBe(false);
96
+ it("parses a v2 row with both bo and so", () => {
97
+ const parsed = ReadingSchema.parse({
98
+ ...baseV2,
99
+ bo: false,
100
+ so: true,
101
+ }) as Reading;
102
+ expect(parsed.v).toBe(READING_VERSION_2);
103
+ expect(parsed.bo).toBe(false);
104
+ expect(parsed.so).toBe(true);
94
105
  });
106
+ });
95
107
 
96
- it("rejects a v=2 record missing the required id field", () => {
97
- const { id: _id, ...withoutId } = blobV2String;
98
- expect(ReadingSchema.safeParse(withoutId).success).toBe(false);
108
+ describe("legacy + default behaviour", () => {
109
+ it("parses a legacy row (no v field) and defaults to v1", () => {
110
+ const { v: _omit, ...legacy } = baseV1;
111
+ const parsed = ReadingSchema.parse(legacy) as Reading;
112
+ expect(parsed.v).toBe(DEFAULT_READING_VERSION);
113
+ expect(parsed.v).toBe(READING_VERSION_1);
114
+ });
115
+
116
+ it("preserves passthrough fields (looseObject)", () => {
117
+ const parsed = ReadingSchema.parse({
118
+ ...baseV1,
119
+ extra: "passthrough",
120
+ }) as Reading & { extra: string };
121
+ expect(parsed.extra).toBe("passthrough");
99
122
  });
100
- });
101
123
 
102
- describe("invalid input", () => {
103
124
  it("rejects an unknown v value", () => {
104
- expect(ReadingSchema.safeParse({ v: 99, foo: "bar" }).success).toBe(
105
- false,
106
- );
125
+ expect(ReadingSchema.safeParse({ ...baseV1, v: 99 }).success).toBe(false);
126
+ });
127
+
128
+ it("rejects a row missing required core fields", () => {
129
+ const { id: _id, ...withoutId } = baseV1;
130
+ expect(ReadingSchema.safeParse(withoutId).success).toBe(false);
107
131
  });
108
132
 
109
133
  it.each([
@@ -116,66 +140,92 @@ describe("ReadingSchema (discriminated union)", () => {
116
140
  ])("rejects %s input without crashing", (_label, input) => {
117
141
  expect(ReadingSchema.safeParse(input).success).toBe(false);
118
142
  });
143
+ });
144
+ });
119
145
 
120
- it("rejects an empty object (preprocess injects v=1, then required v1 fields fail)", () => {
121
- expect(ReadingSchema.safeParse({}).success).toBe(false);
122
- });
146
+ describe("type-narrowing predicates", () => {
147
+ const v1Both = { ...baseV1, bo: true, so: true };
148
+ const v2Both = { ...baseV2, bo: true, so: true };
149
+
150
+ it("isReading accepts any valid reading", () => {
151
+ expect(isReading(baseV1)).toBe(true);
152
+ expect(isReading(v1Both)).toBe(true);
153
+ expect(isReading(baseV2)).toBe(true);
154
+ expect(isReading(v2Both)).toBe(true);
155
+ expect(isReading({ foo: "bar" })).toBe(false);
156
+ expect(isReading(null)).toBe(false);
157
+ });
158
+
159
+ it("isReadingV1 narrows to v=1 only", () => {
160
+ expect(isReadingV1(baseV1)).toBe(true);
161
+ expect(isReadingV1(v1Both)).toBe(true);
162
+ expect(isReadingV1(baseV2)).toBe(false);
163
+ expect(isReadingV1(v2Both)).toBe(false);
164
+ });
165
+
166
+ it("isReadingV2 narrows to v=2 only", () => {
167
+ expect(isReadingV2(baseV2)).toBe(true);
168
+ expect(isReadingV2(v2Both)).toBe(true);
169
+ expect(isReadingV2(baseV1)).toBe(false);
170
+ expect(isReadingV2(v1Both)).toBe(false);
123
171
  });
124
172
  });
125
173
 
126
- describe("ReadingV1Schema (standalone)", () => {
127
- it("defaults v to READING_VERSION_1 when missing", () => {
128
- const parsed = ReadingV1Schema.parse(legacyV1) as ReadingV1;
129
- expect(parsed.v).toBe(READING_VERSION_1);
174
+ describe("ReadingV2MetadataSchema (wire-format header)", () => {
175
+ const minimal: ReadingV2Metadata = {
176
+ v: READING_VERSION_2,
177
+ tm: 1780859506366,
178
+ };
179
+
180
+ it("accepts minimal v2 metadata (no bo, no so)", () => {
181
+ expect(ReadingV2MetadataSchema.safeParse(minimal).success).toBe(true);
130
182
  });
131
183
 
132
- it("rejects an explicit v=2 (literal check)", () => {
184
+ it("accepts v2 metadata with only bo", () => {
133
185
  expect(
134
- ReadingV1Schema.safeParse({ ...legacyV1, v: READING_VERSION_2 }).success,
135
- ).toBe(false);
186
+ ReadingV2MetadataSchema.safeParse({ ...minimal, bo: true }).success,
187
+ ).toBe(true);
136
188
  });
137
- });
138
189
 
139
- describe("ReadingV2Schema (standalone)", () => {
140
- it("requires v=2 explicitly (no default)", () => {
190
+ it("accepts v2 metadata with only so", () => {
141
191
  expect(
142
- ReadingV2Schema.safeParse({ tm: 1, payload: "x" }).success,
143
- ).toBe(false);
192
+ ReadingV2MetadataSchema.safeParse({ ...minimal, so: false }).success,
193
+ ).toBe(true);
144
194
  });
145
195
 
146
- it("accepts a valid v=2 record", () => {
147
- expect(ReadingV2Schema.safeParse(blobV2String).success).toBe(true);
196
+ it("accepts v2 metadata with both bo and so", () => {
197
+ expect(
198
+ ReadingV2MetadataSchema.safeParse({
199
+ ...minimal,
200
+ bo: false,
201
+ so: true,
202
+ }).success,
203
+ ).toBe(true);
148
204
  });
149
- });
150
205
 
151
- describe("type-narrowing predicates", () => {
152
- it("isReadingV1 accepts legacy and explicit v1, rejects v2", () => {
153
- expect(isReadingV1(legacyV1)).toBe(true);
154
- expect(isReadingV1(explicitV1)).toBe(true);
155
- expect(isReadingV1(blobV2Binary)).toBe(false);
206
+ it("rejects v2 metadata with tm = 0 or negative", () => {
207
+ expect(ReadingV2MetadataSchema.safeParse({ ...minimal, tm: 0 }).success).toBe(
208
+ false,
209
+ );
210
+ expect(
211
+ ReadingV2MetadataSchema.safeParse({ ...minimal, tm: -1 }).success,
212
+ ).toBe(false);
156
213
  });
157
214
 
158
- it("isReadingV2 accepts v2, rejects v1 and legacy", () => {
159
- expect(isReadingV2(blobV2Binary)).toBe(true);
160
- expect(isReadingV2(blobV2String)).toBe(true);
161
- expect(isReadingV2(legacyV1)).toBe(false);
162
- expect(isReadingV2(explicitV1)).toBe(false);
215
+ it("rejects v2 metadata missing tm", () => {
216
+ const { tm: _tm, ...noTm } = minimal;
217
+ expect(ReadingV2MetadataSchema.safeParse(noTm).success).toBe(false);
163
218
  });
164
219
 
165
- it("isReading accepts both kinds, rejects garbage", () => {
166
- expect(isReading(legacyV1)).toBe(true);
167
- expect(isReading(explicitV1)).toBe(true);
168
- expect(isReading(blobV2Binary)).toBe(true);
169
- expect(isReading(blobV2String)).toBe(true);
170
- expect(isReading({ foo: "bar" })).toBe(false);
171
- expect(isReading(null)).toBe(false);
220
+ it("isReadingV2Metadata matches the schema", () => {
221
+ expect(isReadingV2Metadata(minimal)).toBe(true);
222
+ expect(isReadingV2Metadata({ ...minimal, bo: true, so: false })).toBe(true);
223
+ expect(isReadingV2Metadata({ v: 1, tm: 1 })).toBe(false);
172
224
  });
173
225
  });
174
226
 
175
227
  describe("DEFAULT_READING_VERSION", () => {
176
228
  it("currently equals READING_VERSION_1", () => {
177
- // If this fails, the preprocess no longer assumes legacy-records-are-v1 —
178
- // make sure all consumers and the migration story are aware before flipping.
179
229
  expect(DEFAULT_READING_VERSION).toBe(READING_VERSION_1);
180
230
  });
181
231
  });
@@ -40,28 +40,9 @@ export type ReadingMetadataV1 = z.infer<typeof ReadingMetadataV1Schema>;
40
40
  export const isReadingMetadataV1 = (item: unknown): item is ReadingMetadataV1 =>
41
41
  ReadingMetadataV1Schema.safeParse(item).success;
42
42
 
43
- const ReadingCommonShape = {
44
- id: z.string(),
45
- component: z.string().optional(),
46
- };
47
-
48
- export const ReadingV1Schema = z.looseObject({
49
- [READING_VERSION_IDENTIFIER]: z
50
- .literal(READING_VERSION_1)
51
- .default(READING_VERSION_1),
52
- ...ReadingCommonShape,
53
- type: z.string(),
54
- s: z.number(),
55
- us: z.number().optional(),
56
- data: ReadingDataTypesSchema,
57
- });
58
- export type ReadingV1 = z.infer<typeof ReadingV1Schema>;
59
- export const isReadingV1 = (item: unknown): item is ReadingV1 =>
60
- ReadingV1Schema.safeParse(item).success;
61
-
62
43
  export const ReadingV2MetadataSchema = z.object({
63
44
  [READING_VERSION_IDENTIFIER]: z.literal(READING_VERSION_2),
64
- tm: z.number(),
45
+ tm: z.number().refine((tm) => tm > 0, { message: "tm must be positive" }),
65
46
  bo: z.boolean().optional(),
66
47
  so: z.boolean().optional(),
67
48
  });
@@ -71,37 +52,38 @@ export const isReadingV2Metadata = (
71
52
  ): item is ReadingV2Metadata =>
72
53
  ReadingV2MetadataSchema.safeParse(item).success;
73
54
 
74
- export const ReadingV2Schema = z.looseObject({
75
- ...ReadingV2MetadataSchema.shape,
76
- ...ReadingCommonShape,
77
- data: z.union([z.instanceof(Uint8Array), z.string()]),
55
+ export const ReadingSchema = z.looseObject({
56
+ [READING_VERSION_IDENTIFIER]: z
57
+ .union([z.literal(READING_VERSION_1), z.literal(READING_VERSION_2)])
58
+ .default(DEFAULT_READING_VERSION),
59
+ id: z.string(),
60
+ component: z.string().optional(),
61
+ type: z.string(),
62
+ s: z.number(),
63
+ us: z.number().optional(),
64
+ data: ReadingDataTypesSchema,
65
+ bo: z.boolean().optional(),
66
+ so: z.boolean().optional(),
78
67
  });
79
- export type ReadingV2 = z.infer<typeof ReadingV2Schema>;
80
- export const isReadingV2 = (item: unknown): item is ReadingV2 =>
81
- ReadingV2Schema.safeParse(item).success;
82
-
83
- const injectDefaultVersionIfMissing = (item: unknown): unknown => {
84
- if (
85
- item === null ||
86
- typeof item !== "object" ||
87
- Array.isArray(item) ||
88
- READING_VERSION_IDENTIFIER in item
89
- ) {
90
- return item;
91
- }
92
- return {
93
- ...item,
94
- [READING_VERSION_IDENTIFIER]: DEFAULT_READING_VERSION,
95
- };
96
- };
97
-
98
- export const ReadingSchema = z.preprocess(
99
- injectDefaultVersionIfMissing,
100
- z.discriminatedUnion(READING_VERSION_IDENTIFIER, [
101
- ReadingV1Schema,
102
- ReadingV2Schema,
103
- ]),
104
- );
105
68
  export type Reading = z.infer<typeof ReadingSchema>;
106
69
  export const isReading = (item: unknown): item is Reading =>
107
70
  ReadingSchema.safeParse(item).success;
71
+
72
+ export const isReadingV1 = (
73
+ item: unknown,
74
+ ): item is Reading & { v: typeof READING_VERSION_1 } => {
75
+ const result = ReadingSchema.safeParse(item);
76
+ return result.success && result.data.v === READING_VERSION_1;
77
+ };
78
+
79
+ export const isReadingV2 = (
80
+ item: unknown,
81
+ ): item is Reading & { v: typeof READING_VERSION_2 } => {
82
+ const result = ReadingSchema.safeParse(item);
83
+ return result.success && result.data.v === READING_VERSION_2;
84
+ };
85
+
86
+ export const ReadingV1Schema = ReadingSchema;
87
+ export type ReadingV1 = Reading;
88
+ export const ReadingV2Schema = ReadingSchema;
89
+ export type ReadingV2 = Reading;