@pumped-fn/lite 1.7.0 → 1.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @pumped-fn/lite
2
2
 
3
+ ## 1.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9e1f827: Add `name` property to ExecutionContext for extension visibility
8
+
9
+ - ExecutionContext now exposes `name: string | undefined` (lazy-computed)
10
+ - Name resolution: exec name > flow name > undefined
11
+ - OTEL extension uses `ctx.name` with configurable `defaultFlowName` fallback
12
+
13
+ ## 1.8.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 36105b0: Add `seek()` and `seekTag()` methods to `ContextData` for hierarchical data lookup across ExecutionContext parent chain. Also add PATTERNS.md architectural documentation and include MIGRATION.md in package.
18
+
3
19
  ## 1.7.0
4
20
 
5
21
  ### Minor Changes
package/MIGRATION.md ADDED
@@ -0,0 +1,422 @@
1
+ # Migration Guide: @pumped-fn/core-next → @pumped-fn/lite
2
+
3
+ This guide helps AI agents migrate code from `@pumped-fn/core-next` to `@pumped-fn/lite`.
4
+
5
+ ## Quick Reference
6
+
7
+ | core-next | lite | Notes |
8
+ |-----------|------|-------|
9
+ | `provide(factory)` | `atom({ factory })` | No deps |
10
+ | `derive(deps, factory)` | `atom({ deps, factory })` | With deps |
11
+ | `Core.Executor<T>` | `Lite.Atom<T>` | Type alias |
12
+ | `Core.Accessor<T>` | `Lite.Controller<T>` | Renamed + reactive |
13
+ | `Core.Controller` | `ResolveContext` | Factory context |
14
+ | `scope.accessor(exec)` | `scope.controller(atom)` | Get controller |
15
+ | `accessor.on(fn)` | `ctrl.on(event, fn)` | Event filtering: `'resolved'`, `'resolving'`, `'*'` |
16
+ | `Promised<T>` | `Promise<T>` | Use native |
17
+ | `multi()` | ❌ Not available | Use Map pattern |
18
+ | `standardSchema` | ❌ Not available | Validate manually |
19
+ | `errors.*` | `Error` | Simple errors |
20
+
21
+ ## Step-by-Step Migration
22
+
23
+ ### 1. Update Imports
24
+
25
+ ```typescript
26
+ // BEFORE (core-next)
27
+ import {
28
+ provide,
29
+ derive,
30
+ preset,
31
+ createScope,
32
+ flow,
33
+ tag,
34
+ tags,
35
+ resolves,
36
+ extension,
37
+ Promised,
38
+ multi,
39
+ standardSchema,
40
+ } from '@pumped-fn/core-next'
41
+ import type { Core, Flow, Tag } from '@pumped-fn/core-next'
42
+
43
+ // AFTER (lite)
44
+ import {
45
+ atom,
46
+ preset,
47
+ createScope,
48
+ flow,
49
+ tag,
50
+ tags,
51
+ controller,
52
+ } from '@pumped-fn/lite'
53
+ import type { Lite } from '@pumped-fn/lite'
54
+ ```
55
+
56
+ ### 2. Migrate Executors to Atoms
57
+
58
+ #### Simple Executor (no dependencies)
59
+
60
+ ```typescript
61
+ // BEFORE (core-next)
62
+ const configExecutor = provide((ctrl) => {
63
+ ctrl.cleanup(() => console.log('cleanup'))
64
+ return { port: 3000 }
65
+ })
66
+
67
+ // AFTER (lite)
68
+ const configAtom = atom({
69
+ factory: (ctx) => {
70
+ ctx.cleanup(() => console.log('cleanup'))
71
+ return { port: 3000 }
72
+ }
73
+ })
74
+ ```
75
+
76
+ #### Executor with Dependencies
77
+
78
+ ```typescript
79
+ // BEFORE (core-next)
80
+ const serverExecutor = derive(
81
+ { config: configExecutor },
82
+ (ctrl, { config }) => {
83
+ return createServer(config.port)
84
+ }
85
+ )
86
+
87
+ // AFTER (lite)
88
+ const serverAtom = atom({
89
+ deps: { config: configAtom },
90
+ factory: (ctx, { config }) => {
91
+ return createServer(config.port)
92
+ }
93
+ })
94
+ ```
95
+
96
+ #### Lazy/Accessor Dependencies
97
+
98
+ ```typescript
99
+ // BEFORE (core-next)
100
+ const dbExecutor = derive(
101
+ { config: configExecutor.lazy },
102
+ (ctrl, { config }) => {
103
+ return connectDb(config.get().connectionString)
104
+ }
105
+ )
106
+
107
+ // AFTER (lite)
108
+ const dbAtom = atom({
109
+ deps: { config: controller(configAtom) },
110
+ factory: (ctx, { config }) => {
111
+ return connectDb(config.get().connectionString)
112
+ }
113
+ })
114
+ ```
115
+
116
+ ### 3. Migrate Types
117
+
118
+ ```typescript
119
+ // BEFORE (core-next)
120
+ const myExecutor: Core.Executor<Config> = provide(...)
121
+ const myAccessor: Core.Accessor<Config> = scope.accessor(myExecutor)
122
+ type MyOutput = Core.InferOutput<typeof myExecutor>
123
+
124
+ // AFTER (lite)
125
+ const myAtom: Lite.Atom<Config> = atom(...)
126
+ const myController: Lite.Controller<Config> = scope.controller(myAtom)
127
+ ```
128
+
129
+ ### 4. Migrate Scope Usage
130
+
131
+ ```typescript
132
+ // BEFORE (core-next)
133
+ const scope = createScope({
134
+ presets: [preset(configExecutor, mockConfig)],
135
+ extensions: [loggingExtension],
136
+ })
137
+ const config = await scope.resolve(configExecutor)
138
+ const accessor = scope.accessor(configExecutor)
139
+
140
+ // AFTER (lite)
141
+ const scope = createScope({
142
+ presets: [preset(configAtom, mockConfig)],
143
+ extensions: [loggingExtension],
144
+ })
145
+ const config = await scope.resolve(configAtom)
146
+ const ctrl = scope.controller(configAtom)
147
+ ```
148
+
149
+ ### 5. Migrate Flows
150
+
151
+ ```typescript
152
+ // BEFORE (core-next)
153
+ const handleRequest = flow(
154
+ {
155
+ input: requestSchema, // StandardSchema validation
156
+ output: responseSchema,
157
+ deps: { db: dbExecutor },
158
+ tags: [apiTag('users')],
159
+ },
160
+ async (ctx, input, { db }) => {
161
+ return db.query(input.userId)
162
+ }
163
+ )
164
+
165
+ // Execution
166
+ const result = await scope.exec(handleRequest, { userId: '123' })
167
+
168
+ // AFTER (lite) - Optional parse validation
169
+ const handleRequest = flow({
170
+ name: 'handleRequest',
171
+ deps: { db: dbAtom },
172
+ tags: [apiTag('users')],
173
+ parse: (raw) => {
174
+ const obj = raw as Record<string, unknown>
175
+ if (typeof obj.userId !== 'string') throw new Error('userId required')
176
+ return { userId: obj.userId }
177
+ },
178
+ factory: async (ctx, { db }) => {
179
+ // ctx.input is typed as { userId: string }
180
+ return db.query(ctx.input.userId)
181
+ }
182
+ })
183
+
184
+ // Execution via context
185
+ const context = scope.createContext()
186
+ const result = await context.exec({
187
+ flow: handleRequest,
188
+ input: { userId: '123' }
189
+ })
190
+ await context.close()
191
+ ```
192
+
193
+ ### 6. Migrate Tags
194
+
195
+ Tags work the same in both packages, with lite adding optional `parse` for validation:
196
+
197
+ ```typescript
198
+ // Same in both
199
+ const tenantId = tag<string>({ label: 'tenantId' })
200
+
201
+ // lite-only: tag with parse validation
202
+ const userId = tag({
203
+ label: 'userId',
204
+ parse: (raw) => {
205
+ if (typeof raw !== 'string') throw new Error('Must be string')
206
+ if (raw.length < 1) throw new Error('Cannot be empty')
207
+ return raw
208
+ }
209
+ })
210
+
211
+ userId('abc-123') // OK - returns Tagged<string>
212
+ userId(123) // Throws ParseError
213
+
214
+ const myAtom = atom({
215
+ deps: { tenant: tags.required(tenantId) },
216
+ factory: (ctx, { tenant }) => {
217
+ console.log('Tenant:', tenant)
218
+ }
219
+ })
220
+ ```
221
+
222
+ ### 7. Migrate Extensions
223
+
224
+ ```typescript
225
+ // BEFORE (core-next) - Full lifecycle with wrap()
226
+ const loggingExt = extension({
227
+ name: 'logging',
228
+ wrap(scope, next, operation) {
229
+ if (operation.kind === 'resolve') {
230
+ console.log('Resolving:', operation.executor)
231
+ }
232
+ return next()
233
+ }
234
+ })
235
+
236
+ // AFTER (lite) - Simplified 4-hook interface
237
+ const loggingExt: Lite.Extension = {
238
+ name: 'logging',
239
+ wrapResolve: async (next, atom, scope) => {
240
+ console.log('Resolving atom...')
241
+ const result = await next()
242
+ console.log('Resolved:', result)
243
+ return result
244
+ },
245
+ wrapExec: async (next, target, ctx) => {
246
+ console.log('Executing...')
247
+ return next()
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### 8. Handle Removed Features
253
+
254
+ #### Multi-Executor Pools
255
+
256
+ ```typescript
257
+ // BEFORE (core-next)
258
+ const connectionPool = multi.provide({
259
+ key: z.string(),
260
+ factory: (ctrl, key) => createConnection(key)
261
+ })
262
+ const conn = await scope.resolve(connectionPool('db-primary'))
263
+
264
+ // AFTER (lite) - Use Map pattern
265
+ const connections = new Map<string, Connection>()
266
+ const connectionAtom = atom({
267
+ factory: async (ctx) => {
268
+ const key = tags.required(connectionKeyTag).get(ctx.scope)
269
+ if (!connections.has(key)) {
270
+ connections.set(key, createConnection(key))
271
+ }
272
+ return connections.get(key)!
273
+ }
274
+ })
275
+ ```
276
+
277
+ #### StandardSchema Validation
278
+
279
+ ```typescript
280
+ // BEFORE (core-next)
281
+ const userFlow = flow({
282
+ input: z.object({ id: z.string() }), // Auto-validates
283
+ ...
284
+ })
285
+
286
+ // AFTER (lite) - Use parse function
287
+ import { z } from 'zod'
288
+
289
+ const userSchema = z.object({ id: z.string() })
290
+
291
+ const userFlow = flow({
292
+ name: 'userFlow',
293
+ parse: (raw) => userSchema.parse(raw), // Validates before factory
294
+ factory: async (ctx) => {
295
+ // ctx.input is typed as { id: string }
296
+ return ctx.input.id
297
+ }
298
+ })
299
+ ```
300
+
301
+ #### Promised Class
302
+
303
+ ```typescript
304
+ // BEFORE (core-next)
305
+ const promised = scope.exec(myFlow, input)
306
+ promised.finally(() => console.log('done'))
307
+ const result = await promised
308
+
309
+ // AFTER (lite)
310
+ const context = scope.createContext()
311
+ const result = await context.exec({ flow: myFlow, input })
312
+ .finally(() => console.log('done'))
313
+ await context.close()
314
+ ```
315
+
316
+ #### resolves() Helper
317
+
318
+ ```typescript
319
+ // BEFORE (core-next)
320
+ const { config, db, cache } = await resolves(scope, {
321
+ config: configExecutor,
322
+ db: dbExecutor,
323
+ cache: cacheExecutor,
324
+ })
325
+
326
+ // AFTER (lite) - Parallel resolution
327
+ const [config, db, cache] = await Promise.all([
328
+ scope.resolve(configAtom),
329
+ scope.resolve(dbAtom),
330
+ scope.resolve(cacheAtom),
331
+ ])
332
+ ```
333
+
334
+ ### 9. Migrate Reactivity
335
+
336
+ Lite has built-in reactivity via Controller that core-next lacks:
337
+
338
+ ```typescript
339
+ // lite-only feature: self-invalidation
340
+ const configAtom = atom({
341
+ factory: async (ctx) => {
342
+ const config = await fetchConfig()
343
+
344
+ const interval = setInterval(() => ctx.invalidate(), 30_000)
345
+ ctx.cleanup(() => clearInterval(interval))
346
+
347
+ return config
348
+ }
349
+ })
350
+
351
+ // lite-only: subscribe to changes with state filtering
352
+ const ctrl = scope.controller(configAtom)
353
+
354
+ // Subscribe to specific events
355
+ ctrl.on('resolved', () => {
356
+ console.log('Config resolved:', ctrl.get())
357
+ })
358
+
359
+ ctrl.on('resolving', () => {
360
+ console.log('Config is re-resolving...')
361
+ })
362
+
363
+ // Subscribe to all state changes
364
+ ctrl.on('*', () => {
365
+ console.log('Config state changed:', ctrl.state)
366
+ })
367
+
368
+ // Fine-grained subscriptions with select()
369
+ const portSelect = scope.select(configAtom, (config) => config.port)
370
+ portSelect.subscribe(() => {
371
+ console.log('Port changed:', portSelect.get())
372
+ })
373
+ ```
374
+
375
+ ## Migration Checklist
376
+
377
+ - [ ] Update package.json dependency
378
+ - [ ] Change import statements
379
+ - [ ] Rename `provide()` → `atom({ factory })`
380
+ - [ ] Rename `derive()` → `atom({ deps, factory })`
381
+ - [ ] Rename `.lazy` → `controller()`
382
+ - [ ] Rename `Core.*` types → `Lite.*` types
383
+ - [ ] Rename `scope.accessor()` → `scope.controller()`
384
+ - [ ] Update flow execution to use `context.exec()`
385
+ - [ ] Add manual validation if using StandardSchema
386
+ - [ ] Replace `multi()` with Map-based patterns
387
+ - [ ] Replace `Promised` with native Promise
388
+ - [ ] Replace `resolves()` with `Promise.all()`
389
+ - [ ] Update extension `wrap()` to `wrapResolve()`/`wrapExec()`
390
+ - [ ] Run type checker: `pnpm -F @pumped-fn/lite typecheck`
391
+ - [ ] Run tests
392
+
393
+ ## When NOT to Migrate
394
+
395
+ Keep using `@pumped-fn/core-next` if you need:
396
+
397
+ - StandardSchema validation (automatic flow input/output validation)
398
+ - Multi-executor pools (`multi()`)
399
+ - Journaling/debugging features
400
+ - Rich error hierarchy with context
401
+ - O(1) tag lookup (lite uses O(n))
402
+ - `Promised` class utilities
403
+
404
+ ## Feature Comparison
405
+
406
+ | Feature | lite | core-next |
407
+ |---------|------|-----------|
408
+ | Atoms/Executors | ✅ | ✅ |
409
+ | Flows | ✅ | ✅ |
410
+ | Tags | ✅ | ✅ |
411
+ | Extensions | ✅ (4 hooks) | ✅ (full) |
412
+ | Schema validation | ❌ | ✅ |
413
+ | Journaling | ❌ | ✅ |
414
+ | Multi-executor | ❌ | ✅ |
415
+ | Promised class | ❌ | ✅ |
416
+ | Rich errors | ❌ | ✅ |
417
+ | Controller reactivity | ✅ | ❌ |
418
+ | Self-invalidation | ✅ | ❌ |
419
+ | Fine-grained select() | ✅ | ❌ |
420
+ | Tag/Flow parse functions | ✅ | ❌ |
421
+ | Bundle size | <17KB | ~75KB |
422
+ | Dependencies | 0 | 0 |