UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

876 lines (728 loc) 23.7 kB
import { Alepha, z } from "alepha"; import { describe, test } from "vitest"; import { KeylessJsonSchemaCodec } from "../providers/KeylessJsonSchemaCodec.ts"; describe("KeylessJsonSchemaCodec", () => { describe("Basic types", () => { test("should encode and decode primitive types", async ({ expect }) => { const alepha = Alepha.create(); const userSchema = z.object({ name: z.text(), age: z.integer(), active: z.boolean(), score: z.number(), }); const data = { name: "Alice", age: 30, active: true, score: 98.5, }; const encoded = alepha.codec.encode(userSchema, data, { as: "string", encoder: "keyless", }); // Keyless format is an array expect(encoded).toBe('["Alice",30,true,98.5]'); const decoded = alepha.codec.decode(userSchema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); test("should produce smaller output than JSON", async ({ expect }) => { const alepha = Alepha.create(); const userSchema = z.object({ username: z.text(), email: z.text(), age: z.integer(), isVerified: z.boolean(), }); const data = { username: "john_doe", email: "john@example.com", age: 25, isVerified: true, }; const jsonEncoded = alepha.codec.encode(userSchema, data, { as: "string", encoder: "json", }); const keylessEncoded = alepha.codec.encode(userSchema, data, { as: "string", encoder: "keyless", }); // Keyless should be smaller (no keys) expect(keylessEncoded.length).toBeLessThan(jsonEncoded.length); // Both should decode to same data const jsonDecoded = alepha.codec.decode(userSchema, jsonEncoded, { encoder: "json", }); const keylessDecoded = alepha.codec.decode(userSchema, keylessEncoded, { encoder: "keyless", }); expect(jsonDecoded).toEqual(data); expect(keylessDecoded).toEqual(data); }); test("should handle bigint values as strings", async ({ expect }) => { const alepha = Alepha.create(); // In Alepha, z.bigint() is a string type with format "bigint" // It represents large integers as strings to avoid precision loss const schema = z.object({ id: z.bigint(), name: z.text(), }); const data = { id: "9007199254740993", name: "Test", }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); // BigInt in Alepha is stored as a string expect(encoded).toBe('["9007199254740993","Test"]'); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded.id).toBe("9007199254740993"); expect(decoded.name).toBe("Test"); }); }); describe("Nested objects", () => { test("should handle nested objects", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ user: z.object({ name: z.text(), profile: z.object({ bio: z.text(), age: z.integer(), }), }), }); const data = { user: { name: "Alice", profile: { bio: "Developer", age: 30, }, }, }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); // Nested objects become nested arrays expect(encoded).toBe('[["Alice",["Developer",30]]]'); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); }); describe("Arrays", () => { test("should handle arrays of primitives", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ tags: z.array(z.text()), scores: z.array(z.number()), }); const data = { tags: ["typescript", "nodejs"], scores: [95.5, 88.0, 92.3], }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); test("should handle arrays of objects", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ users: z.array( z.object({ name: z.text(), age: z.integer(), }), ), socialProfiles: z.array( z.object({ platform: z.text(), username: z.text(), }), ), }); const data = { users: [ { name: "Alice", age: 30 }, { name: "Bob", age: 25 }, ], socialProfiles: [ { platform: "twitter", username: "alice" }, { platform: "github", username: "alice123" }, ], }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); // Arrays of objects should be encoded as nested arrays expect(encoded).toBe( '[[["Alice",30],["Bob",25]],[["twitter","alice"],["github","alice123"]]]', ); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); }); describe("Optional and nullable types", () => { test("should handle optional fields", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ name: z.text(), bio: z.text().optional(), }); const dataWithBio = { name: "Alice", bio: "Developer", }; const dataWithoutBio = { name: "Bob", }; // With optional field present const encodedWith = alepha.codec.encode(schema, dataWithBio, { as: "string", encoder: "keyless", }); const decodedWith = alepha.codec.decode(schema, encodedWith, { encoder: "keyless", }); expect(decodedWith).toEqual(dataWithBio); // With optional field missing const encodedWithout = alepha.codec.encode(schema, dataWithoutBio, { as: "string", encoder: "keyless", }); const decodedWithout = alepha.codec.decode(schema, encodedWithout, { encoder: "keyless", }); expect(decodedWithout.name).toBe("Bob"); expect(decodedWithout.bio).toBeUndefined(); }); test("should handle nullable fields", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ name: z.text(), deletedAt: z.datetime().nullable(), }); const activeUser = { name: "Alice", deletedAt: null, }; const deletedUser = { name: "Bob", deletedAt: "2024-01-15T10:00:00Z", }; // Active user (null deletedAt) const encodedActive = alepha.codec.encode(schema, activeUser, { as: "string", encoder: "keyless", }); const decodedActive = alepha.codec.decode(schema, encodedActive, { encoder: "keyless", }); expect(decodedActive.name).toBe("Alice"); expect(decodedActive.deletedAt).toBeNull(); // Deleted user (non-null deletedAt) const encodedDeleted = alepha.codec.encode(schema, deletedUser, { as: "string", encoder: "keyless", }); const decodedDeleted = alepha.codec.decode(schema, encodedDeleted, { encoder: "keyless", }); expect(decodedDeleted).toEqual(deletedUser); }); }); describe("Enums", () => { test("should handle enum values", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ status: z.enum(["ACTIVE", "INACTIVE", "PENDING"]), name: z.text(), }); const data = { status: "ACTIVE", name: "Test", }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); }); describe("Binary encoding", () => { test("should encode and decode binary format", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ name: z.text(), age: z.integer(), }); const data = { name: "Alice", age: 30, }; const binary = alepha.codec.encode(schema, data, { as: "binary", encoder: "keyless", }); expect(binary).toBeInstanceOf(Uint8Array); const decoded = alepha.codec.decode(schema, binary, { encoder: "keyless", }); expect(decoded).toEqual(data); }); }); describe("Complex schemas", () => { test("should handle complex nested structures", async ({ expect }) => { const alepha = Alepha.create(); const schema = z.object({ user: z.object({ id: z.text(), profile: z.object({ name: z.text(), age: z.integer().nullable(), tags: z.array(z.text()), }), }), status: z.enum(["ACTIVE", "INACTIVE"]), }); const data = { user: { id: "123", profile: { name: "Alice", age: 30, tags: ["developer", "typescript"], }, }, status: "ACTIVE", }; const encoded = alepha.codec.encode(schema, data, { as: "string", encoder: "keyless", }); const decoded = alepha.codec.decode(schema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(data); }); test("should handle comprehensive schema with all types", async ({ expect, }) => { const alepha = Alepha.create(); // Comprehensive schema with all supported types const comprehensiveSchema = z.object({ // Primitive types id: z.integer(), uuid: z.uuid(), name: z.text(), email: z.text(), score: z.number(), isActive: z.boolean(), bigNumber: z.bigint(), // Date/time types (stored as strings) createdAt: z.datetime(), updatedAt: z.datetime(), // Enum type status: z.enum(["ACTIVE", "INACTIVE", "PENDING"]), role: z.enum(["USER", "ADMIN", "MODERATOR"]), // Optional fields nickname: z.text().optional(), bio: z.text().optional(), // Nullable fields deletedAt: z.datetime().nullable(), lastLoginAt: z.datetime().nullable(), // Arrays of primitives tags: z.array(z.text()), scores: z.array(z.number()), flags: z.array(z.boolean()), // Nested object profile: z.object({ firstName: z.text(), lastName: z.text(), age: z.integer(), }), // Deeply nested objects settings: z.object({ theme: z.text(), preferences: z.object({ notifications: z.boolean(), emailAlerts: z.boolean(), language: z.text(), }), }), // Arrays of objects contacts: z.array( z.object({ type: z.text(), value: z.text(), }), ), // Nested arrays of objects socialProfiles: z.array( z.object({ platform: z.text(), username: z.text(), verified: z.boolean(), }), ), // Optional nested object address: z .object({ street: z.text(), city: z.text(), country: z.text(), postalCode: z.text(), }) .optional(), // Nullable nested object company: z .object({ name: z.text(), position: z.text(), }) .nullable(), }); const fullData = { id: 12345, uuid: "550e8400-e29b-41d4-a716-446655440000", name: "John Doe", email: "john.doe@example.com", score: 98.75, isActive: true, bigNumber: "9007199254740993", createdAt: "2023-01-15T10:30:00Z", updatedAt: "2024-06-20T14:45:30Z", status: "ACTIVE", role: "ADMIN", nickname: "johnny", bio: "Software developer and tech enthusiast", deletedAt: null, lastLoginAt: "2024-06-20T14:00:00Z", tags: ["developer", "typescript", "nodejs", "premium"], scores: [95.5, 88.0, 92.3, 100.0], flags: [true, false, true, true], profile: { firstName: "John", lastName: "Doe", age: 35, }, settings: { theme: "dark", preferences: { notifications: true, emailAlerts: false, language: "en-US", }, }, contacts: [ { type: "phone", value: "+1234567890" }, { type: "fax", value: "+0987654321" }, ], socialProfiles: [ { platform: "twitter", username: "johndoe", verified: true }, { platform: "github", username: "johndoe123", verified: false }, { platform: "linkedin", username: "john-doe", verified: true }, ], address: { street: "123 Main Street", city: "San Francisco", country: "USA", postalCode: "94102", }, company: { name: "Tech Corp", position: "Senior Developer", }, }; const encoded = alepha.codec.encode(comprehensiveSchema, fullData, { as: "string", encoder: "keyless", }); const decoded = alepha.codec.decode(comprehensiveSchema, encoded, { encoder: "keyless", }); expect(decoded).toEqual(fullData); // Test with missing optional fields const partialData = { id: 12345, uuid: "550e8400-e29b-41d4-a716-446655440000", name: "Jane Doe", email: "jane.doe@example.com", score: 85.5, isActive: false, bigNumber: "123456789", createdAt: "2023-02-20T08:00:00Z", updatedAt: "2024-05-15T12:30:00Z", status: "PENDING", role: "USER", // nickname and bio are omitted (optional) deletedAt: "2024-01-01T00:00:00Z", // not null this time lastLoginAt: null, tags: [], scores: [75.0], flags: [false], profile: { firstName: "Jane", lastName: "Doe", age: 28, }, settings: { theme: "light", preferences: { notifications: false, emailAlerts: true, language: "fr-FR", }, }, contacts: [], socialProfiles: [ { platform: "instagram", username: "janedoe", verified: false }, ], // address is omitted (optional) company: null, // nullable field set to null }; const encodedPartial = alepha.codec.encode( comprehensiveSchema, partialData, { as: "string", encoder: "keyless", }, ); const decodedPartial = alepha.codec.decode( comprehensiveSchema, encodedPartial, { encoder: "keyless", }, ); expect(decodedPartial).toEqual(partialData); // Verify size reduction const jsonSize = JSON.stringify(fullData).length; const keylessSize = encoded.length; expect(keylessSize).toBeLessThan(jsonSize); }); }); describe("Safe Mode (Interpreted)", () => { test("should work correctly in safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); // Force safe mode (no Function compilation) codec.configure({ useFunctionCompilation: false }); const schema = z.object({ name: z.text(), age: z.integer(), active: z.boolean(), }); const data = { name: "Alice", age: 30, active: true, }; const encoded = codec.encodeToString(schema, data); expect(encoded).toBe('["Alice",30,true]'); const decoded = codec.decode(schema, encoded); expect(decoded).toEqual(data); }); test("should handle nested objects in safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); codec.configure({ useFunctionCompilation: false }); const schema = z.object({ user: z.object({ name: z.text(), profile: z.object({ bio: z.text(), age: z.integer(), }), }), }); const data = { user: { name: "Alice", profile: { bio: "Developer", age: 30, }, }, }; const encoded = codec.encodeToString(schema, data); expect(encoded).toBe('[["Alice",["Developer",30]]]'); const decoded = codec.decode(schema, encoded); expect(decoded).toEqual(data); }); test("should handle arrays of objects in safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); codec.configure({ useFunctionCompilation: false }); const schema = z.object({ users: z.array( z.object({ name: z.text(), age: z.integer(), }), ), }); const data = { users: [ { name: "Alice", age: 30 }, { name: "Bob", age: 25 }, ], }; const encoded = codec.encodeToString(schema, data); const decoded = codec.decode(schema, encoded); expect(decoded).toEqual(data); }); test("should handle optional fields in safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); codec.configure({ useFunctionCompilation: false }); const schema = z.object({ name: z.text(), bio: z.text().optional(), }); // With optional field const dataWithBio = { name: "Alice", bio: "Developer" }; const encodedWith = codec.encodeToString(schema, dataWithBio); const decodedWith = codec.decode(schema, encodedWith); expect(decodedWith).toEqual(dataWithBio); // Without optional field const dataWithoutBio = { name: "Bob" }; const encodedWithout = codec.encodeToString(schema, dataWithoutBio); const decodedWithout = codec.decode<{ name: string; bio?: string }>( schema, encodedWithout, ); expect(decodedWithout.name).toBe("Bob"); expect(decodedWithout.bio).toBeUndefined(); }); test("should handle nullable fields in safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); codec.configure({ useFunctionCompilation: false }); const schema = z.object({ name: z.text(), deletedAt: z.datetime().nullable(), }); // With null value const dataNull = { name: "Alice", deletedAt: null }; const encodedNull = codec.encodeToString(schema, dataNull); const decodedNull = codec.decode<{ name: string; deletedAt: string | null; }>(schema, encodedNull); expect(decodedNull.deletedAt).toBeNull(); // With non-null value const dataValue = { name: "Bob", deletedAt: "2024-01-15T10:00:00Z" }; const encodedValue = codec.encodeToString(schema, dataValue); const decodedValue = codec.decode(schema, encodedValue); expect(decodedValue).toEqual(dataValue); }); }); describe("Compiled Mode (Function)", () => { test("should work correctly in compiled mode", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); // Force compiled mode codec.configure({ useFunctionCompilation: true }); const schema = z.object({ name: z.text(), age: z.integer(), active: z.boolean(), }); const data = { name: "Alice", age: 30, active: true, }; const encoded = codec.encodeToString(schema, data); expect(encoded).toBe('["Alice",30,true]'); const decoded = codec.decode(schema, encoded); expect(decoded).toEqual(data); }); test("should produce same results as safe mode", async ({ expect }) => { const alepha = Alepha.create(); const codecSafe = alepha.inject(KeylessJsonSchemaCodec); codecSafe.configure({ useFunctionCompilation: false }); // Create a second Alepha instance for the compiled codec const alepha2 = Alepha.create(); const codecCompiled = alepha2.inject(KeylessJsonSchemaCodec); codecCompiled.configure({ useFunctionCompilation: true }); const schema = z.object({ user: z.object({ name: z.text(), age: z.integer(), }), tags: z.array(z.text()), active: z.boolean(), }); const data = { user: { name: "Alice", age: 30 }, tags: ["dev", "typescript"], active: true, }; const encodedSafe = codecSafe.encodeToString(schema, data); const encodedCompiled = codecCompiled.encodeToString(schema, data); // Both modes should produce the same output expect(encodedSafe).toBe(encodedCompiled); const decodedSafe = codecSafe.decode(schema, encodedSafe); const decodedCompiled = codecCompiled.decode(schema, encodedCompiled); // Both modes should decode to the same result expect(decodedSafe).toEqual(data); expect(decodedCompiled).toEqual(data); }); }); describe("Configuration", () => { test("should allow configuring options", async ({ expect }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); // Configure options codec.configure({ useFunctionCompilation: false, }); // Test that configuration works by encoding/decoding const schema = z.object({ name: z.text() }); const data = { name: "test" }; const encoded = codec.encodeToString(schema, data); const decoded = codec.decode(schema, encoded); expect(decoded).toEqual(data); }); test("should clear cache when compilation mode changes", async ({ expect, }) => { const alepha = Alepha.create(); const codec = alepha.inject(KeylessJsonSchemaCodec); const schema = z.object({ name: z.text() }); const data = { name: "test" }; // Use compiled mode first codec.configure({ useFunctionCompilation: true }); const encoded1 = codec.encodeToString(schema, data); // Switch to safe mode (cache should be cleared) codec.configure({ useFunctionCompilation: false }); const encoded2 = codec.encodeToString(schema, data); // Both should produce the same result expect(encoded1).toBe(encoded2); }); }); });