alepha 0.14.4 → 0.15.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.
- package/README.md +44 -102
- package/dist/api/audits/index.d.ts +331 -443
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +2 -2
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +0 -113
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +2 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +151 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +164 -276
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +4 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +265 -377
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +1 -2
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +195 -301
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +203 -184
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +1 -2
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +5900 -165
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1481 -639
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +8 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +29 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +563 -54
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +175 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +564 -54
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +563 -54
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +4 -4
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +89 -42
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +129 -33
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +7969 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +22 -22
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +134 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +253 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +1 -2
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -5
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +19 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +28 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/chunk-DH6iiROE.js +38 -0
- package/dist/orm/index.browser.js +9 -9
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2821 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +318 -169
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2086 -1776
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/redis/index.bun.js +285 -0
- package/dist/redis/index.bun.js.map +1 -0
- package/dist/redis/index.d.ts +13 -31
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +18 -38
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +83 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +393 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.browser.js +5 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +598 -112
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1808 -97
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1200 -175
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1268 -37
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +6 -3
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +115 -13
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +321 -139
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +0 -1
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +0 -1
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/links/index.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +1 -2
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +14 -7
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4462 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +0 -1
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +1 -2
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -1
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +10 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +0 -1
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +6315 -149
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +140 -469
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +9 -9
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +28 -28
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +9 -9
- package/dist/websocket/index.js.map +1 -1
- package/package.json +13 -18
- package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
- package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
- package/src/api/users/entities/users.ts +1 -1
- package/src/api/users/index.ts +8 -8
- package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
- package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
- package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
- package/src/api/users/services/CredentialService.ts +7 -7
- package/src/api/users/services/IdentityService.ts +4 -4
- package/src/api/users/services/RegistrationService.spec.ts +25 -27
- package/src/api/users/services/RegistrationService.ts +38 -27
- package/src/api/users/services/SessionCrudService.ts +3 -3
- package/src/api/users/services/SessionService.spec.ts +3 -3
- package/src/api/users/services/SessionService.ts +27 -18
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaCli.ts +2 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
- package/src/cli/assets/apiHelloControllerTs.ts +19 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/biomeJson.ts +2 -1
- package/src/cli/assets/claudeMd.ts +308 -0
- package/src/cli/assets/dummySpecTs.ts +2 -1
- package/src/cli/assets/editorconfig.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +4 -3
- package/src/cli/assets/mainCss.ts +24 -0
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/tsconfigJson.ts +2 -1
- package/src/cli/assets/webAppRouterTs.ts +16 -0
- package/src/cli/assets/webHelloComponentTsx.ts +20 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +97 -61
- package/src/cli/commands/db.ts +21 -18
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +26 -47
- package/src/cli/commands/gen/env.ts +1 -1
- package/src/cli/commands/init.ts +79 -25
- package/src/cli/commands/lint.ts +9 -3
- package/src/cli/commands/test.ts +8 -2
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +4 -2
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +82 -0
- package/src/cli/providers/ViteDevServerProvider.ts +350 -0
- package/src/cli/providers/ViteTemplateProvider.ts +27 -0
- package/src/cli/services/AlephaCliUtils.ts +72 -602
- package/src/cli/services/PackageManagerUtils.ts +308 -0
- package/src/cli/services/ProjectScaffolder.ts +329 -0
- package/src/command/helpers/Runner.ts +15 -3
- package/src/core/Alepha.ts +2 -8
- package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/primitives/$hook.ts +6 -2
- package/src/core/primitives/$module.spec.ts +4 -0
- package/src/core/primitives/$module.ts +12 -0
- package/src/core/providers/AlsProvider.ts +1 -1
- package/src/core/providers/CodecManager.spec.ts +12 -6
- package/src/core/providers/CodecManager.ts +26 -6
- package/src/core/providers/EventManager.ts +169 -13
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
- package/src/core/providers/SchemaValidator.spec.ts +236 -0
- package/src/core/providers/StateManager.spec.ts +27 -16
- package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
- package/src/email/providers/LocalEmailProvider.ts +52 -15
- package/src/email/providers/NodemailerEmailProvider.ts +167 -56
- package/src/file/errors/FileError.ts +7 -0
- package/src/file/index.ts +9 -1
- package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
- package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/transports/SseMcpTransport.ts +16 -6
- package/src/orm/index.browser.ts +1 -19
- package/src/orm/index.bun.ts +77 -0
- package/src/orm/index.shared-server.ts +22 -0
- package/src/orm/index.shared.ts +15 -0
- package/src/orm/index.ts +19 -39
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
- package/src/orm/services/Repository.ts +19 -0
- package/src/redis/index.bun.ts +35 -0
- package/src/redis/providers/BunRedisProvider.ts +12 -43
- package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
- package/src/redis/providers/NodeRedisProvider.ts +16 -34
- package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
- package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
- package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
- package/src/security/index.browser.ts +5 -0
- package/src/security/index.ts +90 -7
- package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
- package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
- package/src/security/primitives/$role.ts +5 -5
- package/src/security/primitives/$serviceAccount.spec.ts +5 -5
- package/src/security/primitives/$serviceAccount.ts +3 -3
- package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
- package/src/server/auth/primitives/$auth.ts +10 -10
- package/src/server/auth/primitives/$authCredentials.ts +3 -3
- package/src/server/auth/primitives/$authGithub.ts +3 -3
- package/src/server/auth/primitives/$authGoogle.ts +3 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
- package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/index.ts +1 -1
- package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
- package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
- package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
- package/src/server/core/providers/ServerProvider.ts +144 -24
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +3 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/swagger/index.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +0 -1
- package/src/vite/tasks/buildServer.ts +80 -22
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- package/dist/server/security/index.browser.js +0 -13
- package/dist/server/security/index.browser.js.map +0 -1
- package/dist/server/security/index.d.ts +0 -173
- package/dist/server/security/index.d.ts.map +0 -1
- package/dist/server/security/index.js +0 -311
- package/dist/server/security/index.js.map +0 -1
- package/src/cli/assets/appRouterTs.ts +0 -9
- package/src/cli/assets/indexHtml.ts +0 -15
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/cli/commands/format.ts +0 -17
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/helpers/boot.ts +0 -106
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -69
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
import { Alepha, t } from "alepha";
|
|
2
|
+
import { describe, test } from "vitest";
|
|
3
|
+
import { KeylessJsonSchemaCodec } from "./KeylessJsonSchemaCodec.ts";
|
|
4
|
+
|
|
5
|
+
describe("KeylessJsonSchemaCodec", () => {
|
|
6
|
+
describe("Basic types", () => {
|
|
7
|
+
test("should encode and decode primitive types", async ({ expect }) => {
|
|
8
|
+
const alepha = Alepha.create();
|
|
9
|
+
|
|
10
|
+
const userSchema = t.object({
|
|
11
|
+
name: t.text(),
|
|
12
|
+
age: t.integer(),
|
|
13
|
+
active: t.boolean(),
|
|
14
|
+
score: t.number(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const data = {
|
|
18
|
+
name: "Alice",
|
|
19
|
+
age: 30,
|
|
20
|
+
active: true,
|
|
21
|
+
score: 98.5,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const encoded = alepha.codec.encode(userSchema, data, {
|
|
25
|
+
as: "string",
|
|
26
|
+
encoder: "keyless",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Keyless format is an array
|
|
30
|
+
expect(encoded).toBe('["Alice",30,true,98.5]');
|
|
31
|
+
|
|
32
|
+
const decoded = alepha.codec.decode(userSchema, encoded, {
|
|
33
|
+
encoder: "keyless",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(decoded).toEqual(data);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("should produce smaller output than JSON", async ({ expect }) => {
|
|
40
|
+
const alepha = Alepha.create();
|
|
41
|
+
|
|
42
|
+
const userSchema = t.object({
|
|
43
|
+
username: t.text(),
|
|
44
|
+
email: t.text(),
|
|
45
|
+
age: t.integer(),
|
|
46
|
+
isVerified: t.boolean(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const data = {
|
|
50
|
+
username: "john_doe",
|
|
51
|
+
email: "john@example.com",
|
|
52
|
+
age: 25,
|
|
53
|
+
isVerified: true,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const jsonEncoded = alepha.codec.encode(userSchema, data, {
|
|
57
|
+
as: "string",
|
|
58
|
+
encoder: "json",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const keylessEncoded = alepha.codec.encode(userSchema, data, {
|
|
62
|
+
as: "string",
|
|
63
|
+
encoder: "keyless",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Keyless should be smaller (no keys)
|
|
67
|
+
expect(keylessEncoded.length).toBeLessThan(jsonEncoded.length);
|
|
68
|
+
|
|
69
|
+
// Both should decode to same data
|
|
70
|
+
const jsonDecoded = alepha.codec.decode(userSchema, jsonEncoded, {
|
|
71
|
+
encoder: "json",
|
|
72
|
+
});
|
|
73
|
+
const keylessDecoded = alepha.codec.decode(userSchema, keylessEncoded, {
|
|
74
|
+
encoder: "keyless",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(jsonDecoded).toEqual(data);
|
|
78
|
+
expect(keylessDecoded).toEqual(data);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should handle bigint values as strings", async ({ expect }) => {
|
|
82
|
+
const alepha = Alepha.create();
|
|
83
|
+
|
|
84
|
+
// In Alepha, t.bigint() is a string type with format "bigint"
|
|
85
|
+
// It represents large integers as strings to avoid precision loss
|
|
86
|
+
const schema = t.object({
|
|
87
|
+
id: t.bigint(),
|
|
88
|
+
name: t.text(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const data = {
|
|
92
|
+
id: "9007199254740993",
|
|
93
|
+
name: "Test",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
97
|
+
as: "string",
|
|
98
|
+
encoder: "keyless",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// BigInt in Alepha is stored as a string
|
|
102
|
+
expect(encoded).toBe('["9007199254740993","Test"]');
|
|
103
|
+
|
|
104
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
105
|
+
encoder: "keyless",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(decoded.id).toBe("9007199254740993");
|
|
109
|
+
expect(decoded.name).toBe("Test");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("Nested objects", () => {
|
|
114
|
+
test("should handle nested objects", async ({ expect }) => {
|
|
115
|
+
const alepha = Alepha.create();
|
|
116
|
+
|
|
117
|
+
const schema = t.object({
|
|
118
|
+
user: t.object({
|
|
119
|
+
name: t.text(),
|
|
120
|
+
profile: t.object({
|
|
121
|
+
bio: t.text(),
|
|
122
|
+
age: t.integer(),
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const data = {
|
|
128
|
+
user: {
|
|
129
|
+
name: "Alice",
|
|
130
|
+
profile: {
|
|
131
|
+
bio: "Developer",
|
|
132
|
+
age: 30,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
138
|
+
as: "string",
|
|
139
|
+
encoder: "keyless",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Nested objects become nested arrays
|
|
143
|
+
expect(encoded).toBe('[["Alice",["Developer",30]]]');
|
|
144
|
+
|
|
145
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
146
|
+
encoder: "keyless",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(decoded).toEqual(data);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("Arrays", () => {
|
|
154
|
+
test("should handle arrays of primitives", async ({ expect }) => {
|
|
155
|
+
const alepha = Alepha.create();
|
|
156
|
+
|
|
157
|
+
const schema = t.object({
|
|
158
|
+
tags: t.array(t.text()),
|
|
159
|
+
scores: t.array(t.number()),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const data = {
|
|
163
|
+
tags: ["typescript", "nodejs"],
|
|
164
|
+
scores: [95.5, 88.0, 92.3],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
168
|
+
as: "string",
|
|
169
|
+
encoder: "keyless",
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
173
|
+
encoder: "keyless",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(decoded).toEqual(data);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("should handle arrays of objects", async ({ expect }) => {
|
|
180
|
+
const alepha = Alepha.create();
|
|
181
|
+
|
|
182
|
+
const schema = t.object({
|
|
183
|
+
users: t.array(
|
|
184
|
+
t.object({
|
|
185
|
+
name: t.text(),
|
|
186
|
+
age: t.integer(),
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
socialProfiles: t.array(
|
|
190
|
+
t.object({
|
|
191
|
+
platform: t.text(),
|
|
192
|
+
username: t.text(),
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const data = {
|
|
198
|
+
users: [
|
|
199
|
+
{ name: "Alice", age: 30 },
|
|
200
|
+
{ name: "Bob", age: 25 },
|
|
201
|
+
],
|
|
202
|
+
socialProfiles: [
|
|
203
|
+
{ platform: "twitter", username: "alice" },
|
|
204
|
+
{ platform: "github", username: "alice123" },
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
209
|
+
as: "string",
|
|
210
|
+
encoder: "keyless",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Arrays of objects should be encoded as nested arrays
|
|
214
|
+
expect(encoded).toBe(
|
|
215
|
+
'[[["Alice",30],["Bob",25]],[["twitter","alice"],["github","alice123"]]]',
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
219
|
+
encoder: "keyless",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(decoded).toEqual(data);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("Optional and nullable types", () => {
|
|
227
|
+
test("should handle optional fields", async ({ expect }) => {
|
|
228
|
+
const alepha = Alepha.create();
|
|
229
|
+
|
|
230
|
+
const schema = t.object({
|
|
231
|
+
name: t.text(),
|
|
232
|
+
bio: t.optional(t.text()),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const dataWithBio = {
|
|
236
|
+
name: "Alice",
|
|
237
|
+
bio: "Developer",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const dataWithoutBio = {
|
|
241
|
+
name: "Bob",
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// With optional field present
|
|
245
|
+
const encodedWith = alepha.codec.encode(schema, dataWithBio, {
|
|
246
|
+
as: "string",
|
|
247
|
+
encoder: "keyless",
|
|
248
|
+
});
|
|
249
|
+
const decodedWith = alepha.codec.decode(schema, encodedWith, {
|
|
250
|
+
encoder: "keyless",
|
|
251
|
+
});
|
|
252
|
+
expect(decodedWith).toEqual(dataWithBio);
|
|
253
|
+
|
|
254
|
+
// With optional field missing
|
|
255
|
+
const encodedWithout = alepha.codec.encode(schema, dataWithoutBio, {
|
|
256
|
+
as: "string",
|
|
257
|
+
encoder: "keyless",
|
|
258
|
+
});
|
|
259
|
+
const decodedWithout = alepha.codec.decode(schema, encodedWithout, {
|
|
260
|
+
encoder: "keyless",
|
|
261
|
+
});
|
|
262
|
+
expect(decodedWithout.name).toBe("Bob");
|
|
263
|
+
expect(decodedWithout.bio).toBeUndefined();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("should handle nullable fields", async ({ expect }) => {
|
|
267
|
+
const alepha = Alepha.create();
|
|
268
|
+
|
|
269
|
+
const schema = t.object({
|
|
270
|
+
name: t.text(),
|
|
271
|
+
deletedAt: t.nullable(t.datetime()),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const activeUser = {
|
|
275
|
+
name: "Alice",
|
|
276
|
+
deletedAt: null,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const deletedUser = {
|
|
280
|
+
name: "Bob",
|
|
281
|
+
deletedAt: "2024-01-15T10:00:00Z",
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Active user (null deletedAt)
|
|
285
|
+
const encodedActive = alepha.codec.encode(schema, activeUser, {
|
|
286
|
+
as: "string",
|
|
287
|
+
encoder: "keyless",
|
|
288
|
+
});
|
|
289
|
+
const decodedActive = alepha.codec.decode(schema, encodedActive, {
|
|
290
|
+
encoder: "keyless",
|
|
291
|
+
});
|
|
292
|
+
expect(decodedActive.name).toBe("Alice");
|
|
293
|
+
expect(decodedActive.deletedAt).toBeNull();
|
|
294
|
+
|
|
295
|
+
// Deleted user (non-null deletedAt)
|
|
296
|
+
const encodedDeleted = alepha.codec.encode(schema, deletedUser, {
|
|
297
|
+
as: "string",
|
|
298
|
+
encoder: "keyless",
|
|
299
|
+
});
|
|
300
|
+
const decodedDeleted = alepha.codec.decode(schema, encodedDeleted, {
|
|
301
|
+
encoder: "keyless",
|
|
302
|
+
});
|
|
303
|
+
expect(decodedDeleted).toEqual(deletedUser);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("Enums", () => {
|
|
308
|
+
test("should handle enum values", async ({ expect }) => {
|
|
309
|
+
const alepha = Alepha.create();
|
|
310
|
+
|
|
311
|
+
const schema = t.object({
|
|
312
|
+
status: t.enum(["ACTIVE", "INACTIVE", "PENDING"]),
|
|
313
|
+
name: t.text(),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const data = {
|
|
317
|
+
status: "ACTIVE",
|
|
318
|
+
name: "Test",
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
322
|
+
as: "string",
|
|
323
|
+
encoder: "keyless",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
327
|
+
encoder: "keyless",
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(decoded).toEqual(data);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("Binary encoding", () => {
|
|
335
|
+
test("should encode and decode binary format", async ({ expect }) => {
|
|
336
|
+
const alepha = Alepha.create();
|
|
337
|
+
|
|
338
|
+
const schema = t.object({
|
|
339
|
+
name: t.text(),
|
|
340
|
+
age: t.integer(),
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const data = {
|
|
344
|
+
name: "Alice",
|
|
345
|
+
age: 30,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const binary = alepha.codec.encode(schema, data, {
|
|
349
|
+
as: "binary",
|
|
350
|
+
encoder: "keyless",
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
expect(binary).toBeInstanceOf(Uint8Array);
|
|
354
|
+
|
|
355
|
+
const decoded = alepha.codec.decode(schema, binary, {
|
|
356
|
+
encoder: "keyless",
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(decoded).toEqual(data);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe("Complex schemas", () => {
|
|
364
|
+
test("should handle complex nested structures", async ({ expect }) => {
|
|
365
|
+
const alepha = Alepha.create();
|
|
366
|
+
|
|
367
|
+
const schema = t.object({
|
|
368
|
+
user: t.object({
|
|
369
|
+
id: t.text(),
|
|
370
|
+
profile: t.object({
|
|
371
|
+
name: t.text(),
|
|
372
|
+
age: t.nullable(t.integer()),
|
|
373
|
+
tags: t.array(t.text()),
|
|
374
|
+
}),
|
|
375
|
+
}),
|
|
376
|
+
status: t.enum(["ACTIVE", "INACTIVE"]),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const data = {
|
|
380
|
+
user: {
|
|
381
|
+
id: "123",
|
|
382
|
+
profile: {
|
|
383
|
+
name: "Alice",
|
|
384
|
+
age: 30,
|
|
385
|
+
tags: ["developer", "typescript"],
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
status: "ACTIVE",
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const encoded = alepha.codec.encode(schema, data, {
|
|
392
|
+
as: "string",
|
|
393
|
+
encoder: "keyless",
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const decoded = alepha.codec.decode(schema, encoded, {
|
|
397
|
+
encoder: "keyless",
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(decoded).toEqual(data);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("should handle comprehensive schema with all types", async ({
|
|
404
|
+
expect,
|
|
405
|
+
}) => {
|
|
406
|
+
const alepha = Alepha.create();
|
|
407
|
+
|
|
408
|
+
// Comprehensive schema with all supported types
|
|
409
|
+
const comprehensiveSchema = t.object({
|
|
410
|
+
// Primitive types
|
|
411
|
+
id: t.integer(),
|
|
412
|
+
uuid: t.uuid(),
|
|
413
|
+
name: t.text(),
|
|
414
|
+
email: t.text(),
|
|
415
|
+
score: t.number(),
|
|
416
|
+
isActive: t.boolean(),
|
|
417
|
+
bigNumber: t.bigint(),
|
|
418
|
+
|
|
419
|
+
// Date/time types (stored as strings)
|
|
420
|
+
createdAt: t.datetime(),
|
|
421
|
+
updatedAt: t.datetime(),
|
|
422
|
+
|
|
423
|
+
// Enum type
|
|
424
|
+
status: t.enum(["ACTIVE", "INACTIVE", "PENDING"]),
|
|
425
|
+
role: t.enum(["USER", "ADMIN", "MODERATOR"]),
|
|
426
|
+
|
|
427
|
+
// Optional fields
|
|
428
|
+
nickname: t.optional(t.text()),
|
|
429
|
+
bio: t.optional(t.text()),
|
|
430
|
+
|
|
431
|
+
// Nullable fields
|
|
432
|
+
deletedAt: t.nullable(t.datetime()),
|
|
433
|
+
lastLoginAt: t.nullable(t.datetime()),
|
|
434
|
+
|
|
435
|
+
// Arrays of primitives
|
|
436
|
+
tags: t.array(t.text()),
|
|
437
|
+
scores: t.array(t.number()),
|
|
438
|
+
flags: t.array(t.boolean()),
|
|
439
|
+
|
|
440
|
+
// Nested object
|
|
441
|
+
profile: t.object({
|
|
442
|
+
firstName: t.text(),
|
|
443
|
+
lastName: t.text(),
|
|
444
|
+
age: t.integer(),
|
|
445
|
+
}),
|
|
446
|
+
|
|
447
|
+
// Deeply nested objects
|
|
448
|
+
settings: t.object({
|
|
449
|
+
theme: t.text(),
|
|
450
|
+
preferences: t.object({
|
|
451
|
+
notifications: t.boolean(),
|
|
452
|
+
emailAlerts: t.boolean(),
|
|
453
|
+
language: t.text(),
|
|
454
|
+
}),
|
|
455
|
+
}),
|
|
456
|
+
|
|
457
|
+
// Arrays of objects
|
|
458
|
+
contacts: t.array(
|
|
459
|
+
t.object({
|
|
460
|
+
type: t.text(),
|
|
461
|
+
value: t.text(),
|
|
462
|
+
}),
|
|
463
|
+
),
|
|
464
|
+
|
|
465
|
+
// Nested arrays of objects
|
|
466
|
+
socialProfiles: t.array(
|
|
467
|
+
t.object({
|
|
468
|
+
platform: t.text(),
|
|
469
|
+
username: t.text(),
|
|
470
|
+
verified: t.boolean(),
|
|
471
|
+
}),
|
|
472
|
+
),
|
|
473
|
+
|
|
474
|
+
// Optional nested object
|
|
475
|
+
address: t.optional(
|
|
476
|
+
t.object({
|
|
477
|
+
street: t.text(),
|
|
478
|
+
city: t.text(),
|
|
479
|
+
country: t.text(),
|
|
480
|
+
postalCode: t.text(),
|
|
481
|
+
}),
|
|
482
|
+
),
|
|
483
|
+
|
|
484
|
+
// Nullable nested object
|
|
485
|
+
company: t.nullable(
|
|
486
|
+
t.object({
|
|
487
|
+
name: t.text(),
|
|
488
|
+
position: t.text(),
|
|
489
|
+
}),
|
|
490
|
+
),
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const fullData = {
|
|
494
|
+
id: 12345,
|
|
495
|
+
uuid: "550e8400-e29b-41d4-a716-446655440000",
|
|
496
|
+
name: "John Doe",
|
|
497
|
+
email: "john.doe@example.com",
|
|
498
|
+
score: 98.75,
|
|
499
|
+
isActive: true,
|
|
500
|
+
bigNumber: "9007199254740993",
|
|
501
|
+
createdAt: "2023-01-15T10:30:00Z",
|
|
502
|
+
updatedAt: "2024-06-20T14:45:30Z",
|
|
503
|
+
status: "ACTIVE",
|
|
504
|
+
role: "ADMIN",
|
|
505
|
+
nickname: "johnny",
|
|
506
|
+
bio: "Software developer and tech enthusiast",
|
|
507
|
+
deletedAt: null,
|
|
508
|
+
lastLoginAt: "2024-06-20T14:00:00Z",
|
|
509
|
+
tags: ["developer", "typescript", "nodejs", "premium"],
|
|
510
|
+
scores: [95.5, 88.0, 92.3, 100.0],
|
|
511
|
+
flags: [true, false, true, true],
|
|
512
|
+
profile: {
|
|
513
|
+
firstName: "John",
|
|
514
|
+
lastName: "Doe",
|
|
515
|
+
age: 35,
|
|
516
|
+
},
|
|
517
|
+
settings: {
|
|
518
|
+
theme: "dark",
|
|
519
|
+
preferences: {
|
|
520
|
+
notifications: true,
|
|
521
|
+
emailAlerts: false,
|
|
522
|
+
language: "en-US",
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
contacts: [
|
|
526
|
+
{ type: "phone", value: "+1234567890" },
|
|
527
|
+
{ type: "fax", value: "+0987654321" },
|
|
528
|
+
],
|
|
529
|
+
socialProfiles: [
|
|
530
|
+
{ platform: "twitter", username: "johndoe", verified: true },
|
|
531
|
+
{ platform: "github", username: "johndoe123", verified: false },
|
|
532
|
+
{ platform: "linkedin", username: "john-doe", verified: true },
|
|
533
|
+
],
|
|
534
|
+
address: {
|
|
535
|
+
street: "123 Main Street",
|
|
536
|
+
city: "San Francisco",
|
|
537
|
+
country: "USA",
|
|
538
|
+
postalCode: "94102",
|
|
539
|
+
},
|
|
540
|
+
company: {
|
|
541
|
+
name: "Tech Corp",
|
|
542
|
+
position: "Senior Developer",
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const encoded = alepha.codec.encode(comprehensiveSchema, fullData, {
|
|
547
|
+
as: "string",
|
|
548
|
+
encoder: "keyless",
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const decoded = alepha.codec.decode(comprehensiveSchema, encoded, {
|
|
552
|
+
encoder: "keyless",
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
expect(decoded).toEqual(fullData);
|
|
556
|
+
|
|
557
|
+
// Test with missing optional fields
|
|
558
|
+
const partialData = {
|
|
559
|
+
id: 12345,
|
|
560
|
+
uuid: "550e8400-e29b-41d4-a716-446655440000",
|
|
561
|
+
name: "Jane Doe",
|
|
562
|
+
email: "jane.doe@example.com",
|
|
563
|
+
score: 85.5,
|
|
564
|
+
isActive: false,
|
|
565
|
+
bigNumber: "123456789",
|
|
566
|
+
createdAt: "2023-02-20T08:00:00Z",
|
|
567
|
+
updatedAt: "2024-05-15T12:30:00Z",
|
|
568
|
+
status: "PENDING",
|
|
569
|
+
role: "USER",
|
|
570
|
+
// nickname and bio are omitted (optional)
|
|
571
|
+
deletedAt: "2024-01-01T00:00:00Z", // not null this time
|
|
572
|
+
lastLoginAt: null,
|
|
573
|
+
tags: [],
|
|
574
|
+
scores: [75.0],
|
|
575
|
+
flags: [false],
|
|
576
|
+
profile: {
|
|
577
|
+
firstName: "Jane",
|
|
578
|
+
lastName: "Doe",
|
|
579
|
+
age: 28,
|
|
580
|
+
},
|
|
581
|
+
settings: {
|
|
582
|
+
theme: "light",
|
|
583
|
+
preferences: {
|
|
584
|
+
notifications: false,
|
|
585
|
+
emailAlerts: true,
|
|
586
|
+
language: "fr-FR",
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
contacts: [],
|
|
590
|
+
socialProfiles: [
|
|
591
|
+
{ platform: "instagram", username: "janedoe", verified: false },
|
|
592
|
+
],
|
|
593
|
+
// address is omitted (optional)
|
|
594
|
+
company: null, // nullable field set to null
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const encodedPartial = alepha.codec.encode(
|
|
598
|
+
comprehensiveSchema,
|
|
599
|
+
partialData,
|
|
600
|
+
{
|
|
601
|
+
as: "string",
|
|
602
|
+
encoder: "keyless",
|
|
603
|
+
},
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
const decodedPartial = alepha.codec.decode(
|
|
607
|
+
comprehensiveSchema,
|
|
608
|
+
encodedPartial,
|
|
609
|
+
{
|
|
610
|
+
encoder: "keyless",
|
|
611
|
+
},
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
expect(decodedPartial).toEqual(partialData);
|
|
615
|
+
|
|
616
|
+
// Verify size reduction
|
|
617
|
+
const jsonSize = JSON.stringify(fullData).length;
|
|
618
|
+
const keylessSize = encoded.length;
|
|
619
|
+
expect(keylessSize).toBeLessThan(jsonSize);
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
describe("Safe Mode (Interpreted)", () => {
|
|
624
|
+
test("should work correctly in safe mode", async ({ expect }) => {
|
|
625
|
+
const alepha = Alepha.create();
|
|
626
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
627
|
+
|
|
628
|
+
// Force safe mode (no Function compilation)
|
|
629
|
+
codec.configure({ useFunctionCompilation: false });
|
|
630
|
+
|
|
631
|
+
const schema = t.object({
|
|
632
|
+
name: t.text(),
|
|
633
|
+
age: t.integer(),
|
|
634
|
+
active: t.boolean(),
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const data = {
|
|
638
|
+
name: "Alice",
|
|
639
|
+
age: 30,
|
|
640
|
+
active: true,
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const encoded = codec.encodeToString(schema, data);
|
|
644
|
+
expect(encoded).toBe('["Alice",30,true]');
|
|
645
|
+
|
|
646
|
+
const decoded = codec.decode(schema, encoded);
|
|
647
|
+
expect(decoded).toEqual(data);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test("should handle nested objects in safe mode", async ({ expect }) => {
|
|
651
|
+
const alepha = Alepha.create();
|
|
652
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
653
|
+
|
|
654
|
+
codec.configure({ useFunctionCompilation: false });
|
|
655
|
+
|
|
656
|
+
const schema = t.object({
|
|
657
|
+
user: t.object({
|
|
658
|
+
name: t.text(),
|
|
659
|
+
profile: t.object({
|
|
660
|
+
bio: t.text(),
|
|
661
|
+
age: t.integer(),
|
|
662
|
+
}),
|
|
663
|
+
}),
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
const data = {
|
|
667
|
+
user: {
|
|
668
|
+
name: "Alice",
|
|
669
|
+
profile: {
|
|
670
|
+
bio: "Developer",
|
|
671
|
+
age: 30,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const encoded = codec.encodeToString(schema, data);
|
|
677
|
+
expect(encoded).toBe('[["Alice",["Developer",30]]]');
|
|
678
|
+
|
|
679
|
+
const decoded = codec.decode(schema, encoded);
|
|
680
|
+
expect(decoded).toEqual(data);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("should handle arrays of objects in safe mode", async ({ expect }) => {
|
|
684
|
+
const alepha = Alepha.create();
|
|
685
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
686
|
+
|
|
687
|
+
codec.configure({ useFunctionCompilation: false });
|
|
688
|
+
|
|
689
|
+
const schema = t.object({
|
|
690
|
+
users: t.array(
|
|
691
|
+
t.object({
|
|
692
|
+
name: t.text(),
|
|
693
|
+
age: t.integer(),
|
|
694
|
+
}),
|
|
695
|
+
),
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const data = {
|
|
699
|
+
users: [
|
|
700
|
+
{ name: "Alice", age: 30 },
|
|
701
|
+
{ name: "Bob", age: 25 },
|
|
702
|
+
],
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
const encoded = codec.encodeToString(schema, data);
|
|
706
|
+
const decoded = codec.decode(schema, encoded);
|
|
707
|
+
expect(decoded).toEqual(data);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test("should handle optional fields in safe mode", async ({ expect }) => {
|
|
711
|
+
const alepha = Alepha.create();
|
|
712
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
713
|
+
|
|
714
|
+
codec.configure({ useFunctionCompilation: false });
|
|
715
|
+
|
|
716
|
+
const schema = t.object({
|
|
717
|
+
name: t.text(),
|
|
718
|
+
bio: t.optional(t.text()),
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// With optional field
|
|
722
|
+
const dataWithBio = { name: "Alice", bio: "Developer" };
|
|
723
|
+
const encodedWith = codec.encodeToString(schema, dataWithBio);
|
|
724
|
+
const decodedWith = codec.decode(schema, encodedWith);
|
|
725
|
+
expect(decodedWith).toEqual(dataWithBio);
|
|
726
|
+
|
|
727
|
+
// Without optional field
|
|
728
|
+
const dataWithoutBio = { name: "Bob" };
|
|
729
|
+
const encodedWithout = codec.encodeToString(schema, dataWithoutBio);
|
|
730
|
+
const decodedWithout = codec.decode<{ name: string; bio?: string }>(
|
|
731
|
+
schema,
|
|
732
|
+
encodedWithout,
|
|
733
|
+
);
|
|
734
|
+
expect(decodedWithout.name).toBe("Bob");
|
|
735
|
+
expect(decodedWithout.bio).toBeUndefined();
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("should handle nullable fields in safe mode", async ({ expect }) => {
|
|
739
|
+
const alepha = Alepha.create();
|
|
740
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
741
|
+
|
|
742
|
+
codec.configure({ useFunctionCompilation: false });
|
|
743
|
+
|
|
744
|
+
const schema = t.object({
|
|
745
|
+
name: t.text(),
|
|
746
|
+
deletedAt: t.nullable(t.datetime()),
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// With null value
|
|
750
|
+
const dataNull = { name: "Alice", deletedAt: null };
|
|
751
|
+
const encodedNull = codec.encodeToString(schema, dataNull);
|
|
752
|
+
const decodedNull = codec.decode<{
|
|
753
|
+
name: string;
|
|
754
|
+
deletedAt: string | null;
|
|
755
|
+
}>(schema, encodedNull);
|
|
756
|
+
expect(decodedNull.deletedAt).toBeNull();
|
|
757
|
+
|
|
758
|
+
// With non-null value
|
|
759
|
+
const dataValue = { name: "Bob", deletedAt: "2024-01-15T10:00:00Z" };
|
|
760
|
+
const encodedValue = codec.encodeToString(schema, dataValue);
|
|
761
|
+
const decodedValue = codec.decode(schema, encodedValue);
|
|
762
|
+
expect(decodedValue).toEqual(dataValue);
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
describe("Compiled Mode (Function)", () => {
|
|
767
|
+
test("should work correctly in compiled mode", async ({ expect }) => {
|
|
768
|
+
const alepha = Alepha.create();
|
|
769
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
770
|
+
|
|
771
|
+
// Force compiled mode
|
|
772
|
+
codec.configure({ useFunctionCompilation: true });
|
|
773
|
+
|
|
774
|
+
const schema = t.object({
|
|
775
|
+
name: t.text(),
|
|
776
|
+
age: t.integer(),
|
|
777
|
+
active: t.boolean(),
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
const data = {
|
|
781
|
+
name: "Alice",
|
|
782
|
+
age: 30,
|
|
783
|
+
active: true,
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const encoded = codec.encodeToString(schema, data);
|
|
787
|
+
expect(encoded).toBe('["Alice",30,true]');
|
|
788
|
+
|
|
789
|
+
const decoded = codec.decode(schema, encoded);
|
|
790
|
+
expect(decoded).toEqual(data);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test("should produce same results as safe mode", async ({ expect }) => {
|
|
794
|
+
const alepha = Alepha.create();
|
|
795
|
+
|
|
796
|
+
const codecSafe = alepha.inject(KeylessJsonSchemaCodec);
|
|
797
|
+
codecSafe.configure({ useFunctionCompilation: false });
|
|
798
|
+
|
|
799
|
+
// Create a second Alepha instance for the compiled codec
|
|
800
|
+
const alepha2 = Alepha.create();
|
|
801
|
+
const codecCompiled = alepha2.inject(KeylessJsonSchemaCodec);
|
|
802
|
+
codecCompiled.configure({ useFunctionCompilation: true });
|
|
803
|
+
|
|
804
|
+
const schema = t.object({
|
|
805
|
+
user: t.object({
|
|
806
|
+
name: t.text(),
|
|
807
|
+
age: t.integer(),
|
|
808
|
+
}),
|
|
809
|
+
tags: t.array(t.text()),
|
|
810
|
+
active: t.boolean(),
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const data = {
|
|
814
|
+
user: { name: "Alice", age: 30 },
|
|
815
|
+
tags: ["dev", "typescript"],
|
|
816
|
+
active: true,
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const encodedSafe = codecSafe.encodeToString(schema, data);
|
|
820
|
+
const encodedCompiled = codecCompiled.encodeToString(schema, data);
|
|
821
|
+
|
|
822
|
+
// Both modes should produce the same output
|
|
823
|
+
expect(encodedSafe).toBe(encodedCompiled);
|
|
824
|
+
|
|
825
|
+
const decodedSafe = codecSafe.decode(schema, encodedSafe);
|
|
826
|
+
const decodedCompiled = codecCompiled.decode(schema, encodedCompiled);
|
|
827
|
+
|
|
828
|
+
// Both modes should decode to the same result
|
|
829
|
+
expect(decodedSafe).toEqual(data);
|
|
830
|
+
expect(decodedCompiled).toEqual(data);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
describe("Configuration", () => {
|
|
835
|
+
test("should allow configuring options", async ({ expect }) => {
|
|
836
|
+
const alepha = Alepha.create();
|
|
837
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
838
|
+
|
|
839
|
+
// Configure all options
|
|
840
|
+
codec.configure({
|
|
841
|
+
useFunctionCompilation: false,
|
|
842
|
+
maxArrayLength: 100,
|
|
843
|
+
maxStringLength: 1000,
|
|
844
|
+
maxDepth: 10,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Test that configuration works by encoding/decoding
|
|
848
|
+
const schema = t.object({ name: t.text() });
|
|
849
|
+
const data = { name: "test" };
|
|
850
|
+
|
|
851
|
+
const encoded = codec.encodeToString(schema, data);
|
|
852
|
+
const decoded = codec.decode(schema, encoded);
|
|
853
|
+
|
|
854
|
+
expect(decoded).toEqual(data);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("should clear cache when compilation mode changes", async ({
|
|
858
|
+
expect,
|
|
859
|
+
}) => {
|
|
860
|
+
const alepha = Alepha.create();
|
|
861
|
+
const codec = alepha.inject(KeylessJsonSchemaCodec);
|
|
862
|
+
|
|
863
|
+
const schema = t.object({ name: t.text() });
|
|
864
|
+
const data = { name: "test" };
|
|
865
|
+
|
|
866
|
+
// Use compiled mode first
|
|
867
|
+
codec.configure({ useFunctionCompilation: true });
|
|
868
|
+
const encoded1 = codec.encodeToString(schema, data);
|
|
869
|
+
|
|
870
|
+
// Switch to safe mode (cache should be cleared)
|
|
871
|
+
codec.configure({ useFunctionCompilation: false });
|
|
872
|
+
const encoded2 = codec.encodeToString(schema, data);
|
|
873
|
+
|
|
874
|
+
// Both should produce the same result
|
|
875
|
+
expect(encoded1).toBe(encoded2);
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
});
|