@planet-matrix/mobius-model 0.4.0 → 0.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planet-matrix/mobius-model",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Mobius model.",
5
5
  "keywords": [
6
6
  "mobius",
@@ -9,22 +9,23 @@ This module provides runtime utilities organized by domain so you can quickly lo
9
9
  ### 1. Domain Areas
10
10
 
11
11
  1. Helper: Shared runtime helpers used across other domains.
12
- 2. Is: Predicates and type guards for runtime checks.
13
- 3. String: String manipulation, slicing, and formatting helpers.
14
- 4. Number: Numeric checks, parsing, and math-oriented helpers.
15
- 5. Boolean: Boolean helpers and predicates.
16
- 6. BigInt: BigInt checks and helpers.
17
- 7. Symbol: Symbol checks and helpers.
18
- 8. Array: Array construction, slicing, and transformation helpers.
19
- 9. Object: Object predicates and object utility helpers.
20
- 10. Function: Function predicates and invocation-related helpers.
21
- 11. Temporal: Temporal/date-time predicates and helpers.
22
- 12. Error: Error predicates and helpers.
23
- 13. RegExp: RegExp predicates and helpers.
24
- 14. Promise: Promise predicates and async-related helpers.
25
- 15. Stream: Stream predicates and stream-related helpers.
26
-
27
- For a full list of exports, see the domain files (Is, String, Number, Temporal, Promise, Stream, etc.).
12
+ 2. Is: Runtime predicates and type guards (primitives, objects, iterables, DOM/browser targets, and more).
13
+ 3. String: String generation, casing conversion, slicing, splitting, and truncation helpers.
14
+ 4. Number: Numeric normalization, range constraint, parity checks, and randomization helpers.
15
+ 5. Boolean: Boolean conversion and logical operation helpers.
16
+ 6. BigInt: BigInt-oriented helpers.
17
+ 7. Symbol: Symbol creation, inspection, and conversion helpers.
18
+ 8. Array: Array-oriented helpers and transformations.
19
+ 9. Object: Object field selection/exclusion, Date-field timestamp conversion, and object utility helpers.
20
+ 10. Function: Function composition and execution-control helpers (once, debounce, throttle, memoize, etc.).
21
+ 11. Temporal: Date/time formatting, relative-time, and humanization helpers.
22
+ 12. Error: Error detection and exception-stringify helpers.
23
+ 13. RegExp: RegExp-based validation helpers.
24
+ 14. Promise: Promise control-flow, queueing, retry, interval, and forever-loop helpers.
25
+ 15. Stream: ReadableStream construction, consumption, and transform helpers.
26
+ 16. Enhance: Runtime enhancement helpers (for example, BigInt JSON serialization support).
27
+
28
+ For a full list of exports, see the domain files (Helper, Is, String, Number, Boolean, BigInt, Symbol, Array, Object, Function, Temporal, Error, RegExp, Promise, Stream, Enhance).
28
29
 
29
30
  ## For Contributors
30
31
 
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @see {@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt#%E5%9C%A8_json_%E4%B8%AD%E4%BD%BF%E7%94%A8}
3
+ */
4
+ export const enhanceBigInt = (): void => {
5
+ // @ts-expect-error - extend BigInt toJSON
6
+ // oxlint-disable-next-line no-extend-native
7
+ BigInt.prototype.toJSON = function toJSON(): number {
8
+ return Number(this.toString())
9
+ }
10
+ }
@@ -13,3 +13,5 @@ export * from "./error.ts"
13
13
  export * from "./regexp.ts"
14
14
  export * from "./promise.ts"
15
15
  export * from "./stream.ts"
16
+
17
+ export * from "./enhance.ts"
@@ -1,4 +1,5 @@
1
1
  import type { AnyRecord } from "../type/index.ts"
2
+ import { isDate } from "./is.ts"
2
3
 
3
4
  /**
4
5
  * Return a new object that includes only the specified keys from the source object.
@@ -56,3 +57,84 @@ export const excludeFields = <T extends AnyRecord, K extends keyof T>(
56
57
  // oxlint-disable-next-line no-unsafe-type-assertion
57
58
  return newObject as Omit<T, K> // 使用Omit类型确保返回的类型反映了被排除的键
58
59
  }
60
+
61
+ /**
62
+ * Convert all Date fields in an object to number.
63
+ *
64
+ * @example
65
+ * ```before
66
+ * type Obj = {
67
+ * createdAt: Date,
68
+ * }
69
+ * ```
70
+ * ```after
71
+ * type Obj = {
72
+ * createdAt: number,
73
+ * }
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```before
78
+ * type Obj = {
79
+ * createdAt: Date | undefined,
80
+ * }
81
+ * ```
82
+ * ```after
83
+ * type Obj = {
84
+ * createdAt: number | undefined,
85
+ * }
86
+ * ```
87
+ *
88
+ * @example
89
+ * ```before
90
+ * type Obj = {
91
+ * createdAt: Date | null,
92
+ * }
93
+ * ```
94
+ * ```after
95
+ * type Obj = {
96
+ * createdAt: number | null,
97
+ * }
98
+ * ```
99
+ */
100
+ export type ObjectDateFieldsToNumber<O extends object> = {
101
+ [K in keyof O]: O[K] extends Date
102
+ ? number
103
+ : (
104
+ O[K] extends Date | undefined
105
+ ? number | undefined
106
+ :
107
+ (O[K] extends Date | null
108
+ ? number | null
109
+ : (
110
+ O[K] extends object
111
+ ? ObjectDateFieldsToNumber<O[K]>
112
+ : O[K]
113
+ )
114
+ )
115
+ )
116
+ }
117
+ /**
118
+ * Convert all Date fields in an object to number.
119
+ *
120
+ * @example
121
+ * ```
122
+ * const obj = { createdAt: new Date() };
123
+ * const result = objectDateFieldsToNumber(obj);
124
+ * // Expect: { createdAt: 1712345678901 }
125
+ * ```
126
+ */
127
+ export const objectDateFieldsToNumber = <O extends Record<string | number | symbol, unknown>>(
128
+ obj: O
129
+ ): ObjectDateFieldsToNumber<O> => {
130
+ // oxlint-disable-next-line guard-for-in
131
+ for (const key in obj) {
132
+ const value = obj[key];
133
+ if (isDate(value)) {
134
+ // oxlint-disable-next-line no-unsafe-type-assertion
135
+ obj[key] = value.getTime() as O[Extract<keyof O, string>]
136
+ }
137
+ }
138
+ // oxlint-disable-next-line no-unsafe-type-assertion
139
+ return obj as ObjectDateFieldsToNumber<O>
140
+ }
@@ -0,0 +1,105 @@
1
+ # Encoding
2
+
3
+ Runtime utilities for encoding and decoding text. This module currently focuses on Base64 conversions with predictable UTF-8 behavior.
4
+
5
+ ## For Users
6
+
7
+ This module provides lightweight encoding helpers for common text <-> Base64 scenarios.
8
+
9
+ ### 1. Domain Areas
10
+
11
+ 1. Base64: Convert UTF-8 strings to Base64, decode Base64 back to UTF-8 strings, and validate Base64 input.
12
+
13
+ Current public exports:
14
+
15
+ - `isBase64(input: string): boolean`
16
+ - `assertBase64(input: string): void`
17
+ - `stringToBase64(input: string): string`
18
+ - `base64ToString(input: string): string`
19
+
20
+ ## For Contributors
21
+
22
+ This guide documents conventions and best practices for implementing encoding utilities in this module.
23
+
24
+ ### 1. Documentation and Comments
25
+
26
+ #### 1.1 JSDoc Comment Format
27
+
28
+ Every exported function should include JSDoc in this form:
29
+
30
+ ```typescript
31
+ /**
32
+ * Brief one-line description of what the function does.
33
+ *
34
+ * @example
35
+ * ```
36
+ * // Expect: "aGVsbG8="
37
+ * const example1 = stringToBase64("hello")
38
+ * // Expect: "hello"
39
+ * const example2 = base64ToString("aGVsbG8=")
40
+ * ```
41
+ */
42
+ export const stringToBase64 = (input: string): string => {
43
+ ...
44
+ }
45
+ ```
46
+
47
+ **Documentation Rules:**
48
+ - First line: Clear, concise description starting with a verb (Check, Get, Convert, etc.)
49
+ - Add a blank line after the description
50
+ - Use `@example` tag followed by triple backticks
51
+ - Include multiple cases showing different scenarios
52
+ - Use comment format: `// Expect: <result>`
53
+ - Assign example results to variables like `example1`, `example2` to keep examples readable
54
+ - Place `@see`(if has) after the `@example` block, separated by a blank line
55
+ - Prefer deterministic examples; avoid randomness or time-dependent output in docs
56
+ - If a function returns a non-scalar, show the expected shape or key properties
57
+
58
+ ### 2. Runtime Implementation Patterns
59
+
60
+ #### 2.1 Input and Output Rules
61
+
62
+ - Text conversion utilities should accept explicit `string` input.
63
+ - Behavior should be deterministic and side-effect free.
64
+ - Keep UTF-8 semantics explicit in implementation.
65
+
66
+ #### 2.2 Helper Placement
67
+
68
+ - Place local helper constants/functions immediately before the utility they support.
69
+ - Prefix non-exported helpers with `internal`.
70
+ - Never export internal helpers.
71
+
72
+ #### 2.3 Spacing
73
+
74
+ - Separate different utility functions with a single blank line.
75
+
76
+ ### 3. Naming Conventions
77
+
78
+ #### 3.1 Function Name Format
79
+
80
+ Use clear operation-oriented names in the `encoding` domain:
81
+
82
+ - `isBase64` for Base64 validation checks returning boolean
83
+ - `assertBase64` for Base64 validation that throws on malformed input
84
+ - `stringToBase64` for UTF-8 string to Base64 conversion
85
+ - `base64ToString` for Base64 to UTF-8 string conversion
86
+
87
+ Prefer names that encode both source and target representations.
88
+
89
+ ### 4. Export Strategy
90
+
91
+ - Export all public utilities from domain files.
92
+ - Re-export them through `index.ts`.
93
+ - Do not export internal helpers.
94
+
95
+ ### 5. Common Pitfalls to Avoid
96
+
97
+ 1. Do not assume all Base64 input is valid without documenting behavior.
98
+ 2. Do not mix encodings implicitly (keep UTF-8 explicit).
99
+ 3. Do not add environment-specific APIs unless compatibility is documented.
100
+ 4. Do not mutate shared state.
101
+
102
+ ### 6. Testing Requirements
103
+
104
+ - Write one test per function.
105
+ - If multiple cases are needed, include them within the same test.
@@ -0,0 +1,98 @@
1
+ const internalBase64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
2
+ const internalNormalizeBase64 = (input: string): string => {
3
+ return input.replaceAll(/\s+/g, "")
4
+ }
5
+ /**
6
+ * Check whether input is a valid Base64 string.
7
+ *
8
+ * @example
9
+ * ```
10
+ * // Expect: true
11
+ * const example1 = isBase64("aGVsbG8=")
12
+ * // Expect: false
13
+ * const example2 = isBase64("abc")
14
+ * ```
15
+ */
16
+ export const isBase64 = (input: string): boolean => {
17
+ const normalizedInput = internalNormalizeBase64(input)
18
+ return internalBase64Pattern.test(normalizedInput)
19
+ }
20
+
21
+ const internalAssertValidBase64 = (input: string): void => {
22
+ if (internalBase64Pattern.test(input) === false) {
23
+ throw new TypeError("Invalid Base64 input")
24
+ }
25
+ }
26
+ /**
27
+ * Assert input is a valid Base64 string.
28
+ *
29
+ * @throws {TypeError} when input is not valid Base64
30
+ */
31
+ export const assertBase64 = (input: string): void => {
32
+ const normalizedInput = internalNormalizeBase64(input)
33
+ internalAssertValidBase64(normalizedInput)
34
+ }
35
+
36
+ const internalStringToBase64ByBrowserApi = (input: string): string => {
37
+ const bytes = new TextEncoder().encode(input)
38
+ let binaryString = ""
39
+
40
+ bytes.forEach((byte) => {
41
+ binaryString = binaryString + String.fromCodePoint(byte)
42
+ })
43
+
44
+ return btoa(binaryString)
45
+ }
46
+ /**
47
+ * Convert a UTF-8 string into a Base64 string.
48
+ *
49
+ * @example
50
+ * ```
51
+ * // Expect: "aGVsbG8="
52
+ * const example1 = stringToBase64("hello")
53
+ * // Expect: "5L2g5aW9"
54
+ * const example2 = stringToBase64("你好")
55
+ * ```
56
+ */
57
+ export const stringToBase64 = (input: string): string => {
58
+ if (typeof Buffer !== "undefined") {
59
+ return Buffer.from(input, "utf8").toString("base64")
60
+ }
61
+
62
+ if (typeof btoa !== "undefined") {
63
+ return internalStringToBase64ByBrowserApi(input)
64
+ }
65
+
66
+ throw new Error("No Base64 runtime support found")
67
+ }
68
+
69
+ const internalBase64ToStringByBrowserApi = (input: string): string => {
70
+ const binaryString = atob(input)
71
+ const bytes = Uint8Array.from(binaryString, char => char.codePointAt(0) ?? 0)
72
+ return new TextDecoder().decode(bytes)
73
+ }
74
+ /**
75
+ * Convert a valid Base64 string into a UTF-8 string.
76
+ *
77
+ * @example
78
+ * ```
79
+ * // Expect: "hello"
80
+ * const example1 = base64ToString("aGVsbG8=")
81
+ * // Expect: "你好"
82
+ * const example2 = base64ToString("5L2g5aW9")
83
+ * ```
84
+ */
85
+ export const base64ToString = (input: string): string => {
86
+ const normalizedInput = internalNormalizeBase64(input)
87
+ internalAssertValidBase64(normalizedInput)
88
+
89
+ if (typeof Buffer !== "undefined") {
90
+ return Buffer.from(normalizedInput, "base64").toString("utf8")
91
+ }
92
+
93
+ if (typeof atob !== "undefined") {
94
+ return internalBase64ToStringByBrowserApi(normalizedInput)
95
+ }
96
+
97
+ throw new Error("No Base64 runtime support found")
98
+ }
@@ -0,0 +1 @@
1
+ export * from "./base64.ts"
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * as Basic from "./basic/index.ts"
2
2
  export * as Type from "./type/index.ts"
3
3
  export * as Reactor from "./reactor/index.ts"
4
+ export * as Encoding from "./encoding/index.ts"
5
+ export * as Random from "./random/index.ts"
@@ -0,0 +1,109 @@
1
+ # Random
2
+
3
+ Runtime utilities for generating random-oriented values with cross-runtime compatibility. This module currently focuses on UUID generation.
4
+
5
+ ## For Users
6
+
7
+ This module provides lightweight helpers for random value generation scenarios.
8
+
9
+ ### 1. Domain Areas
10
+
11
+ 1. UUID: Generate RFC 4122 version-4 UUID strings with runtime-aware fallback behavior.
12
+
13
+ Current public exports:
14
+
15
+ - `isUuid(input: string): boolean`
16
+ - `assertUuid(input: string): void`
17
+ - `getUuidVersion(input: string): number`
18
+ - `generateUuid(): string`
19
+
20
+ ## For Contributors
21
+
22
+ This guide documents conventions and best practices for implementing random utilities in this module.
23
+
24
+ ### 1. Documentation and Comments
25
+
26
+ #### 1.1 JSDoc Comment Format
27
+
28
+ Every exported function should include JSDoc in this form:
29
+
30
+ ```typescript
31
+ /**
32
+ * Brief one-line description of what the function does.
33
+ *
34
+ * @example
35
+ * ```
36
+ * const example1 = generateUuid()
37
+ * // Expect: true
38
+ * const example2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(example1)
39
+ * ```
40
+ */
41
+ export const generateUuid = (): string => {
42
+ ...
43
+ }
44
+ ```
45
+
46
+ **Documentation Rules:**
47
+ - First line: Clear, concise description starting with a verb (Generate, Check, Convert, etc.)
48
+ - Add a blank line after the description
49
+ - Use `@example` tag followed by triple backticks
50
+ - Include multiple cases showing different scenarios
51
+ - Use comment format: `// Expect: <result>`
52
+ - Assign example results to variables like `example1`, `example2` to keep examples readable
53
+ - Place `@see`(if has) after the `@example` block, separated by a blank line
54
+ - Prefer deterministic examples; avoid asserting exact random outputs
55
+ - If a function returns a structured string, show expected format characteristics
56
+
57
+ ### 2. Runtime Implementation Patterns
58
+
59
+ #### 2.1 Compatibility First
60
+
61
+ - Prefer standard runtime APIs when available (for example, `crypto.randomUUID`).
62
+ - Keep fallback logic for environments where modern APIs are unavailable.
63
+
64
+ #### 2.2 Deterministic Interface
65
+
66
+ - Public API shape should remain deterministic even if output values are random.
67
+ - Return values should always conform to the documented output format.
68
+
69
+ #### 2.3 Helper Placement
70
+
71
+ - Place local helper constants/functions immediately before the utility they support.
72
+ - Prefix non-exported helpers with `internal`.
73
+ - Never export internal helpers.
74
+
75
+ #### 2.4 Spacing
76
+
77
+ - Separate different utility functions with a single blank line.
78
+
79
+ ### 3. Naming Conventions
80
+
81
+ #### 3.1 Function Name Format
82
+
83
+ Use clear operation-oriented names in the `random` domain:
84
+
85
+ - `isUuid` for UUID format validation checks
86
+ - `assertUuid` for UUID validation that throws on malformed input
87
+ - `getUuidVersion` for extracting UUID version numbers
88
+ - `generateUuid` for UUID creation helpers
89
+
90
+ Prefer names that clearly indicate output format and intent.
91
+
92
+ ### 4. Export Strategy
93
+
94
+ - Export all public utilities from domain files.
95
+ - Re-export them through `index.ts`.
96
+ - Do not export internal helpers.
97
+
98
+ ### 5. Common Pitfalls to Avoid
99
+
100
+ 1. Do not assume browser-only APIs are always available.
101
+ 2. Do not couple random utilities to a specific runtime unless documented.
102
+ 3. Do not assert exact random results in tests.
103
+ 4. Do not mutate shared/global state permanently.
104
+
105
+ ### 6. Testing Requirements
106
+
107
+ - Write one test per function.
108
+ - If multiple cases are needed, include them within the same test.
109
+ - For random outputs, validate shape/constraints instead of exact value.
@@ -0,0 +1 @@
1
+ export * from "./uuid.ts"
@@ -0,0 +1,103 @@
1
+
2
+ const internalUUID_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
3
+ /**
4
+ * Check whether input is a valid UUID string.
5
+ *
6
+ * @example
7
+ * ```
8
+ * // Expect: true
9
+ * const example1 = isUuid("550e8400-e29b-41d4-a716-446655440000")
10
+ * // Expect: false
11
+ * const example2 = isUuid("not-a-uuid")
12
+ * ```
13
+ */
14
+ export const isUuid = (input: string): boolean => {
15
+ return internalUUID_REGEXP.test(input)
16
+ }
17
+
18
+ /**
19
+ * Assert input is a valid UUID string.
20
+ *
21
+ * @example
22
+ * ```
23
+ * // Expect: no throw
24
+ * const example1 = assertUuid("550e8400-e29b-41d4-a716-446655440000")
25
+ * // Expect: throws TypeError
26
+ * const example2 = () => assertUuid("not-a-uuid")
27
+ * ```
28
+ *
29
+ * @throws {TypeError} when input is not a valid UUID
30
+ */
31
+ export const assertUuid = (input: string): void => {
32
+ if (isUuid(input) === false) {
33
+ throw new TypeError(`Expected a valid UUID string, got: ${input}`)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get the version number from a valid UUID string.
39
+ *
40
+ * @example
41
+ * ```
42
+ * // Expect: 4
43
+ * const example1 = getUuidVersion("550e8400-e29b-41d4-a716-446655440000")
44
+ * // Expect: 1
45
+ * const example2 = getUuidVersion("123e4567-e89b-12d3-a456-426614174000")
46
+ * ```
47
+ *
48
+ * @throws {TypeError} when input is not a valid UUID
49
+ */
50
+ export const getUuidVersion = (input: string): number => {
51
+ // 1) Ensure the input is a syntactically valid UUID string.
52
+ // If invalid, assertUuid throws TypeError and prevents unsafe parsing.
53
+ assertUuid(input)
54
+
55
+ // 2) Per UUID canonical format xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,
56
+ // the version nibble is the first hex digit of the 3rd group.
57
+ // In a 36-char UUID string, that position is index 14.
58
+
59
+ // 3) Convert the single hex character (for example "4") to a base-10 number.
60
+ // parseInt("4", 16) -> 4, parseInt("a", 16) -> 10.
61
+ return Number.parseInt(input[14]!, 16)
62
+ }
63
+
64
+ /**
65
+ * Generate a RFC 4122 version-4 UUID string.
66
+ *
67
+ * @example
68
+ * ```
69
+ * const example1 = generateUuid()
70
+ * // Expect: true
71
+ * const example2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(example1)
72
+ * ```
73
+ */
74
+ export const generateUuid = (): string => {
75
+ if (typeof crypto === "object") {
76
+ if (typeof crypto.randomUUID === "function") {
77
+ return crypto.randomUUID()
78
+ }
79
+
80
+ if (typeof crypto.getRandomValues === "function" && typeof Uint8Array === "function") {
81
+ const buffer = new Uint8Array(16)
82
+ crypto.getRandomValues(buffer)
83
+
84
+ // Per RFC 4122, set bits for version and `clock_seq_hi_and_reserved`
85
+ buffer[6] = (buffer[6]! & 0x0F) | 0x40 // version 4
86
+ buffer[8] = (buffer[8]! & 0x3F) | 0x80 // variant 1
87
+
88
+ // Convert buffer to UUID string format
89
+ const hex = [...buffer].map(b => b.toString(16).padStart(2, "0")).join("")
90
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
91
+ }
92
+ }
93
+
94
+ // Fallback for environments without crypto support
95
+ const fallbackUUID = (): string => {
96
+ const random = (a: number): string => {
97
+ return ((a ^ ((Math.random() * 16) >> (a / 4))) & 15).toString(16)
98
+ }
99
+ return "10000000-1000-4000-8000-100000000000".replaceAll(/[018]/g, (char: string) => random(Number(char)))
100
+ }
101
+
102
+ return fallbackUUID()
103
+ }
@@ -1,6 +1,6 @@
1
1
  import { expect, test } from "vitest"
2
2
 
3
- import { excludeFields, includeFields } from "#Source/basic/object.ts"
3
+ import { excludeFields, includeFields, objectDateFieldsToNumber } from "#Source/basic/object.ts"
4
4
 
5
5
  test("includeFields picks specified keys", () => {
6
6
  expect(includeFields({ a: 1, b: 2, c: 3 }, ["a", "c"])).toEqual({ a: 1, c: 3 })
@@ -13,3 +13,34 @@ test("excludeFields omits specified keys", () => {
13
13
  // @ts-expect-error - Testing behavior with undefined input
14
14
  expect(excludeFields(undefined, ["a"])).toEqual({})
15
15
  })
16
+
17
+ test("objectDateFieldsToNumber converts top-level Date fields", () => {
18
+ const createdAt = new Date("2024-01-02T03:04:05.678Z")
19
+ const updatedAt = new Date("2024-02-03T04:05:06.789Z")
20
+ const nestedDate = new Date("2024-03-04T05:06:07.890Z")
21
+
22
+ const source = {
23
+ id: "x-1",
24
+ createdAt,
25
+ updatedAt,
26
+ nested: {
27
+ occurredAt: nestedDate,
28
+ },
29
+ optional: undefined as Date | undefined,
30
+ nullable: null as Date | null,
31
+ }
32
+
33
+ const result = objectDateFieldsToNumber(source)
34
+
35
+ expect(result).toEqual({
36
+ id: "x-1",
37
+ createdAt: createdAt.getTime(),
38
+ updatedAt: updatedAt.getTime(),
39
+ nested: {
40
+ occurredAt: nestedDate,
41
+ },
42
+ optional: undefined,
43
+ nullable: null,
44
+ })
45
+ expect(result).toBe(source)
46
+ })
@@ -0,0 +1,40 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ assertBase64,
5
+ base64ToString,
6
+ isBase64,
7
+ stringToBase64,
8
+ } from "#Source/encoding/index.ts"
9
+
10
+ test("isBase64 validates Base64 input", () => {
11
+ expect(isBase64("aGVsbG8=")).toBe(true)
12
+ expect(isBase64("5L2g5aW9")).toBe(true)
13
+ expect(isBase64("8J+Ri/CfjI0=")).toBe(true)
14
+ expect(isBase64("aGVs\n bG8=")).toBe(true)
15
+
16
+ expect(isBase64("%%%")).toBe(false)
17
+ expect(isBase64("abc")).toBe(false)
18
+ })
19
+
20
+ test("assertBase64 throws on malformed input", () => {
21
+ expect(() => assertBase64("aGVsbG8=")).not.toThrow()
22
+ expect(() => assertBase64("aGVs\n bG8=")).not.toThrow()
23
+ expect(() => assertBase64("%%%")).toThrow(TypeError)
24
+ expect(() => assertBase64("abc")).toThrow(TypeError)
25
+ })
26
+
27
+ test("stringToBase64 converts UTF-8 text to Base64", () => {
28
+ expect(stringToBase64("hello")).toBe("aGVsbG8=")
29
+ expect(stringToBase64("你好")).toBe("5L2g5aW9")
30
+ expect(stringToBase64("👋🌍")).toBe("8J+Ri/CfjI0=")
31
+ })
32
+
33
+ test("base64ToString converts Base64 to UTF-8 text and validates malformed input", () => {
34
+ expect(base64ToString("aGVsbG8=")).toBe("hello")
35
+ expect(base64ToString("5L2g5aW9")).toBe("你好")
36
+ expect(base64ToString("8J+Ri/CfjI0=")).toBe("👋🌍")
37
+
38
+ expect(() => base64ToString("%%%")).toThrow(TypeError)
39
+ expect(() => base64ToString("abc")).toThrow(TypeError)
40
+ })