ai-functions 2.1.3 → 2.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +90 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +51 -858
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +8 -0
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +176 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +685 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/sandbox.d.ts +36 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +44 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +155 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +29 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +71 -1176
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +874 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +122 -21
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +144 -18
- package/src/sandbox.ts +52 -0
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- package/src/type-guards.ts +31 -0
- package/src/types.ts +186 -27
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/fill-template.test.ts +89 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/sandbox-execution.test.ts +155 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for embeddingCacheMiddleware — embedding-side analogue of
|
|
3
|
+
* cacheMiddleware for `wrapEmbeddingModel`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
7
|
+
import { wrapEmbeddingModel } from 'ai'
|
|
8
|
+
import { MockEmbeddingModelV3 } from 'ai/test'
|
|
9
|
+
import { embeddingCacheMiddleware } from '../src/index.js'
|
|
10
|
+
|
|
11
|
+
describe('embeddingCacheMiddleware', () => {
|
|
12
|
+
const originalGate = process.env['V3_EVAL_CACHE']
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
process.env['V3_EVAL_CACHE'] = '1'
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (originalGate === undefined) {
|
|
20
|
+
delete process.env['V3_EVAL_CACHE']
|
|
21
|
+
} else {
|
|
22
|
+
process.env['V3_EVAL_CACHE'] = originalGate
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('returns cached embeddings on second call with same values', async () => {
|
|
27
|
+
let callCount = 0
|
|
28
|
+
const upstream = new MockEmbeddingModelV3({
|
|
29
|
+
modelId: 'test-embed',
|
|
30
|
+
doEmbed: async () => {
|
|
31
|
+
callCount++
|
|
32
|
+
return {
|
|
33
|
+
embeddings: [
|
|
34
|
+
[0.1, 0.2, 0.3],
|
|
35
|
+
[0.4, 0.5, 0.6],
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const wrapped = wrapEmbeddingModel({
|
|
42
|
+
model: upstream,
|
|
43
|
+
middleware: embeddingCacheMiddleware({ enabled: true }),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const r1 = await wrapped.doEmbed({ values: ['a', 'b'] })
|
|
47
|
+
expect(r1.embeddings).toEqual([
|
|
48
|
+
[0.1, 0.2, 0.3],
|
|
49
|
+
[0.4, 0.5, 0.6],
|
|
50
|
+
])
|
|
51
|
+
expect(callCount).toBe(1)
|
|
52
|
+
|
|
53
|
+
const r2 = await wrapped.doEmbed({ values: ['a', 'b'] })
|
|
54
|
+
expect(r2.embeddings).toEqual([
|
|
55
|
+
[0.1, 0.2, 0.3],
|
|
56
|
+
[0.4, 0.5, 0.6],
|
|
57
|
+
])
|
|
58
|
+
expect(callCount).toBe(1) // cache hit — no second upstream call
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('treats different value batches as separate keys', async () => {
|
|
62
|
+
let callCount = 0
|
|
63
|
+
const upstream = new MockEmbeddingModelV3({
|
|
64
|
+
modelId: 'test-embed',
|
|
65
|
+
doEmbed: async ({ values }) => {
|
|
66
|
+
callCount++
|
|
67
|
+
return {
|
|
68
|
+
embeddings: values.map((_, i) => [i, i + 1]),
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
const wrapped = wrapEmbeddingModel({
|
|
73
|
+
model: upstream,
|
|
74
|
+
middleware: embeddingCacheMiddleware({ enabled: true }),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await wrapped.doEmbed({ values: ['a'] })
|
|
78
|
+
await wrapped.doEmbed({ values: ['b'] })
|
|
79
|
+
expect(callCount).toBe(2)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('falls through to upstream when env gate is unset', async () => {
|
|
83
|
+
delete process.env['V3_EVAL_CACHE']
|
|
84
|
+
let callCount = 0
|
|
85
|
+
const upstream = new MockEmbeddingModelV3({
|
|
86
|
+
modelId: 'test-embed',
|
|
87
|
+
doEmbed: async () => {
|
|
88
|
+
callCount++
|
|
89
|
+
return { embeddings: [[1, 2, 3]] }
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
const wrapped = wrapEmbeddingModel({
|
|
93
|
+
model: upstream,
|
|
94
|
+
middleware: embeddingCacheMiddleware(),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
98
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
99
|
+
expect(callCount).toBe(2) // no caching when gate is off
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('respects explicit enabled: false override', async () => {
|
|
103
|
+
let callCount = 0
|
|
104
|
+
const upstream = new MockEmbeddingModelV3({
|
|
105
|
+
modelId: 'test-embed',
|
|
106
|
+
doEmbed: async () => {
|
|
107
|
+
callCount++
|
|
108
|
+
return { embeddings: [[1, 2, 3]] }
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
const wrapped = wrapEmbeddingModel({
|
|
112
|
+
model: upstream,
|
|
113
|
+
middleware: embeddingCacheMiddleware({ enabled: false }),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
117
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
118
|
+
expect(callCount).toBe(2)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('evicts entries past TTL and re-fetches', async () => {
|
|
122
|
+
let callCount = 0
|
|
123
|
+
const upstream = new MockEmbeddingModelV3({
|
|
124
|
+
modelId: 'test-embed',
|
|
125
|
+
doEmbed: async () => {
|
|
126
|
+
callCount++
|
|
127
|
+
return { embeddings: [[callCount]] }
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
const wrapped = wrapEmbeddingModel({
|
|
131
|
+
model: upstream,
|
|
132
|
+
middleware: embeddingCacheMiddleware({ enabled: true, ttlMs: -1 }),
|
|
133
|
+
// TTL = -1 → every entry is "older than -1 ms" → always evicted on access
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
137
|
+
await wrapped.doEmbed({ values: ['x'] })
|
|
138
|
+
expect(callCount).toBe(2) // TTL expired, re-fetch
|
|
139
|
+
})
|
|
140
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for fillTemplate in function-registry.ts
|
|
3
|
+
*
|
|
4
|
+
* fillTemplate replaces {{key}} placeholders in a template string with values
|
|
5
|
+
* from an args record. Non-primitive values (objects/arrays) must serialize
|
|
6
|
+
* via JSON.stringify, not String(), to avoid "[object Object]" corruption.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest'
|
|
10
|
+
import { fillTemplate } from '../src/function-registry.js'
|
|
11
|
+
|
|
12
|
+
describe('fillTemplate', () => {
|
|
13
|
+
describe('primitive values', () => {
|
|
14
|
+
it('interpolates a string value', () => {
|
|
15
|
+
expect(fillTemplate('Hello {{name}}!', { name: 'world' })).toBe('Hello world!')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('interpolates a number value', () => {
|
|
19
|
+
expect(fillTemplate('Count: {{n}}', { n: 42 })).toBe('Count: 42')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('interpolates a boolean value', () => {
|
|
23
|
+
expect(fillTemplate('Active: {{flag}}', { flag: true })).toBe('Active: true')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('interpolates zero without stripping it', () => {
|
|
27
|
+
expect(fillTemplate('Value: {{v}}', { v: 0 })).toBe('Value: 0')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('leaves placeholder empty when key is missing', () => {
|
|
31
|
+
expect(fillTemplate('{{missing}} value', {})).toBe(' value')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('replaces multiple distinct placeholders', () => {
|
|
35
|
+
expect(fillTemplate('{{a}} + {{b}} = {{c}}', { a: 1, b: 2, c: 3 })).toBe('1 + 2 = 3')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('object and array values — must NOT produce [object Object]', () => {
|
|
40
|
+
it('serializes a plain object via JSON.stringify', () => {
|
|
41
|
+
const result = fillTemplate('Data: {{obj}}', { obj: { foo: 'bar', n: 1 } })
|
|
42
|
+
expect(result).not.toContain('[object Object]')
|
|
43
|
+
expect(result).toBe('Data: {"foo":"bar","n":1}')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('serializes an array via JSON.stringify', () => {
|
|
47
|
+
const result = fillTemplate('Items: {{list}}', { list: ['a', 'b', 'c'] })
|
|
48
|
+
expect(result).not.toContain('[object Object]')
|
|
49
|
+
expect(result).toBe('Items: ["a","b","c"]')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('serializes a nested object', () => {
|
|
53
|
+
const payload = { subject: 'AI', stats: { count: 5, tags: ['fast', 'smart'] } }
|
|
54
|
+
const result = fillTemplate('Payload: {{payload}}', { payload })
|
|
55
|
+
expect(result).not.toContain('[object Object]')
|
|
56
|
+
const parsed = JSON.parse(result.replace('Payload: ', ''))
|
|
57
|
+
expect(parsed.subject).toBe('AI')
|
|
58
|
+
expect(parsed.stats.count).toBe(5)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('serializes an array of objects (upstream cascade step output pattern)', () => {
|
|
62
|
+
const steps = [{ id: 1, label: 'Subject' }, { id: 2, label: 'Problem' }]
|
|
63
|
+
const result = fillTemplate('Steps: {{steps}}', { steps })
|
|
64
|
+
expect(result).not.toContain('[object Object]')
|
|
65
|
+
const parsed = JSON.parse(result.replace('Steps: ', ''))
|
|
66
|
+
expect(parsed).toHaveLength(2)
|
|
67
|
+
expect(parsed[0].label).toBe('Subject')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('edge cases', () => {
|
|
72
|
+
it('keeps null as empty string (null coalesces to empty via ?? fallback)', () => {
|
|
73
|
+
// null ?? '' → '' → String('') → ''
|
|
74
|
+
expect(fillTemplate('{{v}}', { v: null as unknown as string })).toBe('')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('keeps undefined key as empty string', () => {
|
|
78
|
+
expect(fillTemplate('{{v}}', {})).toBe('')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('handles template with no placeholders', () => {
|
|
82
|
+
expect(fillTemplate('no placeholders here', { x: 1 })).toBe('no placeholders here')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('handles empty template', () => {
|
|
86
|
+
expect(fillTemplate('', { x: 1 })).toBe('')
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
})
|