@celom/prose 0.1.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 +433 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# @celom/prose
|
|
2
|
+
|
|
3
|
+
Declarative workflow DSL for orchestrating complex business operations in Node.js.
|
|
4
|
+
|
|
5
|
+
Define multi-step business logic as type-safe pipelines with built-in retries, timeouts, transactions, event publishing, and observability — using plain async/await.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createFlow, ValidationError } from '@celom/prose';
|
|
9
|
+
|
|
10
|
+
const onboardUser = createFlow<{ email: string; name: string }>('onboard-user')
|
|
11
|
+
.validate('checkEmail', (ctx) => {
|
|
12
|
+
if (!ctx.input.email.includes('@'))
|
|
13
|
+
throw ValidationError.single('email', 'Invalid email');
|
|
14
|
+
})
|
|
15
|
+
.step('createAccount', async (ctx) => {
|
|
16
|
+
const user = await db.createUser(ctx.input);
|
|
17
|
+
return { user };
|
|
18
|
+
})
|
|
19
|
+
.withRetry({ maxAttempts: 3, delayMs: 200, backoffMultiplier: 2 })
|
|
20
|
+
.step('sendWelcome', async (ctx) => {
|
|
21
|
+
await mailer.send(ctx.state.user.email, 'Welcome!');
|
|
22
|
+
})
|
|
23
|
+
.event('users', (ctx) => ({
|
|
24
|
+
eventType: 'user.onboarded',
|
|
25
|
+
userId: ctx.state.user.id,
|
|
26
|
+
}))
|
|
27
|
+
.build();
|
|
28
|
+
|
|
29
|
+
const result = await onboardUser.execute(
|
|
30
|
+
{ email: 'alice@example.com', name: 'Alice' },
|
|
31
|
+
{ db, eventPublisher }
|
|
32
|
+
);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @celom/prose
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Type-safe state threading** — each step's return type merges into `ctx.state`, giving you full autocomplete and compile-time checks across the entire pipeline
|
|
44
|
+
- **Retries with exponential backoff** — per-step retry policies with configurable delays, backoff multipliers, caps, and conditional retry predicates
|
|
45
|
+
- **Timeouts** — flow-level and step-level timeouts backed by `AbortSignal`, with actual interruption of async operations
|
|
46
|
+
- **Cooperative cancellation** — pass an external `AbortSignal` to cancel a running flow
|
|
47
|
+
- **Database transactions** — wrap steps in `db.transaction()` with any ORM (Drizzle, Knex, Prisma)
|
|
48
|
+
- **Event publishing** — emit domain events to named channels with automatic correlation IDs
|
|
49
|
+
- **Parallel execution** — run independent steps concurrently with configurable merge strategies
|
|
50
|
+
- **Conditional steps & early exit** — skip steps based on runtime conditions or short-circuit the flow entirely
|
|
51
|
+
- **Composable sub-flows** — extract and reuse step sequences via `.pipe()`
|
|
52
|
+
- **Observability hooks** — plug in logging, metrics, or tracing through the observer interface
|
|
53
|
+
- **Zero dependencies** — runs in-process with no external infrastructure
|
|
54
|
+
|
|
55
|
+
## Guide
|
|
56
|
+
|
|
57
|
+
### Creating a flow
|
|
58
|
+
|
|
59
|
+
`createFlow` returns a builder. Chain steps onto it and call `.build()` to get an executable flow.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { createFlow } from '@celom/prose';
|
|
63
|
+
|
|
64
|
+
const flow = createFlow<{ orderId: string }>('process-order')
|
|
65
|
+
.step('fetch', async (ctx) => {
|
|
66
|
+
const order = await db.getOrder(ctx.input.orderId);
|
|
67
|
+
return { order };
|
|
68
|
+
})
|
|
69
|
+
.step('charge', async (ctx) => {
|
|
70
|
+
const receipt = await payments.charge(ctx.state.order.total);
|
|
71
|
+
return { receipt };
|
|
72
|
+
})
|
|
73
|
+
.build();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The generic parameter defines the input shape. TypeScript infers the state type as steps accumulate — after the `fetch` step, `ctx.state.order` is available with full type information.
|
|
77
|
+
|
|
78
|
+
### Running a flow
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await flow.execute(
|
|
82
|
+
{ orderId: 'ord_123' }, // input
|
|
83
|
+
{ db, eventPublisher }, // dependencies
|
|
84
|
+
{ timeout: 30_000 } // options (optional)
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Execution options:**
|
|
89
|
+
|
|
90
|
+
| Option | Type | Description |
|
|
91
|
+
|--------|------|-------------|
|
|
92
|
+
| `timeout` | `number` | Max duration for the entire flow (ms) |
|
|
93
|
+
| `stepTimeout` | `number` | Default max duration per step (ms) |
|
|
94
|
+
| `signal` | `AbortSignal` | External signal for cancellation |
|
|
95
|
+
| `observer` | `FlowObserver` | Lifecycle hooks for logging/metrics |
|
|
96
|
+
| `throwOnError` | `boolean` | `false` returns partial state instead of throwing |
|
|
97
|
+
| `correlationId` | `string` | Custom ID propagated to events and observers |
|
|
98
|
+
| `errorHandling` | `object` | Control behavior for missing deps (see below) |
|
|
99
|
+
|
|
100
|
+
### Validation
|
|
101
|
+
|
|
102
|
+
Validation steps run before processing and are never retried. Throw `ValidationError` to fail fast.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { ValidationError } from '@celom/prose';
|
|
106
|
+
|
|
107
|
+
flow.validate('checkInput', (ctx) => {
|
|
108
|
+
if (ctx.input.amount <= 0)
|
|
109
|
+
throw ValidationError.single('amount', 'Must be positive');
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`ValidationError` accepts an optional array of issues for multi-field validation:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
throw new ValidationError('Validation failed', [
|
|
117
|
+
{ field: 'email', message: 'Required' },
|
|
118
|
+
{ field: 'age', message: 'Must be at least 18' },
|
|
119
|
+
]);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Retries
|
|
123
|
+
|
|
124
|
+
Chain `.withRetry()` after any step to add a retry policy.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
flow
|
|
128
|
+
.step('callExternalApi', async (ctx) => {
|
|
129
|
+
const data = await api.fetch(ctx.input.url);
|
|
130
|
+
return { data };
|
|
131
|
+
})
|
|
132
|
+
.withRetry({
|
|
133
|
+
maxAttempts: 5,
|
|
134
|
+
delayMs: 100,
|
|
135
|
+
backoffMultiplier: 2,
|
|
136
|
+
maxDelayMs: 5_000,
|
|
137
|
+
shouldRetry: (err) => err.status !== 400,
|
|
138
|
+
stepTimeout: 10_000, // override the flow-level stepTimeout for this step
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Option | Type | Default | Description |
|
|
143
|
+
|--------|------|---------|-------------|
|
|
144
|
+
| `maxAttempts` | `number` | — | Total attempts (including the first) |
|
|
145
|
+
| `delayMs` | `number` | — | Initial delay between retries |
|
|
146
|
+
| `backoffMultiplier` | `number` | `1` | Multiplier applied to delay after each retry |
|
|
147
|
+
| `maxDelayMs` | `number` | `Infinity` | Upper bound on delay |
|
|
148
|
+
| `shouldRetry` | `(error) => boolean` | — | Predicate to conditionally retry |
|
|
149
|
+
| `stepTimeout` | `number` | — | Timeout override for this step |
|
|
150
|
+
|
|
151
|
+
### Timeouts & cancellation
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
|
|
156
|
+
const result = await flow.execute(input, deps, {
|
|
157
|
+
timeout: 30_000, // abort if the flow exceeds 30s
|
|
158
|
+
stepTimeout: 5_000, // abort any step that exceeds 5s
|
|
159
|
+
signal: controller.signal, // cancel from outside
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// later, to cancel:
|
|
163
|
+
controller.abort();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Inside step handlers, `ctx.signal` exposes the combined signal so you can pass it to fetch, database calls, or check `ctx.signal.aborted` for cooperative cancellation.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
flow.step('longOperation', async (ctx) => {
|
|
170
|
+
const resp = await fetch(url, { signal: ctx.signal });
|
|
171
|
+
return { data: await resp.json() };
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Conditional steps
|
|
176
|
+
|
|
177
|
+
`stepIf` runs the handler only when the condition returns `true`. Skipped steps don't affect state and don't consume retry attempts.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
flow
|
|
181
|
+
.step('checkCache', (ctx) => {
|
|
182
|
+
return { cached: cache.has(ctx.input.key) };
|
|
183
|
+
})
|
|
184
|
+
.stepIf('fromCache', (ctx) => ctx.state.cached, (ctx) => {
|
|
185
|
+
return { value: cache.get(ctx.input.key) };
|
|
186
|
+
})
|
|
187
|
+
.stepIf('fromDb', (ctx) => !ctx.state.cached, async (ctx) => {
|
|
188
|
+
return { value: await db.get(ctx.input.key) };
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Early exit with breakIf
|
|
193
|
+
|
|
194
|
+
`breakIf` short-circuits the flow, skipping all remaining steps **and** the `.map()` transformer. An optional second argument defines the return value.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
flow
|
|
198
|
+
.step('findUser', async (ctx) => {
|
|
199
|
+
const existing = await db.findByEmail(ctx.input.email);
|
|
200
|
+
return { existing };
|
|
201
|
+
})
|
|
202
|
+
.breakIf(
|
|
203
|
+
(ctx) => ctx.state.existing != null,
|
|
204
|
+
(ctx) => ({ user: ctx.state.existing, created: false })
|
|
205
|
+
)
|
|
206
|
+
.step('createUser', async (ctx) => {
|
|
207
|
+
const user = await db.createUser(ctx.input);
|
|
208
|
+
return { user };
|
|
209
|
+
})
|
|
210
|
+
.map((input, state) => ({ user: state.user, created: true }))
|
|
211
|
+
.build();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Database transactions
|
|
215
|
+
|
|
216
|
+
Use `.transaction()` to wrap a step in `db.transaction()`. The transaction client is passed as the second argument.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
flow.transaction('persist', async (ctx, tx) => {
|
|
220
|
+
const id = await tx.insert('users', { name: ctx.input.name });
|
|
221
|
+
return { userId: id };
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Requires a `db` dependency conforming to:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
interface DatabaseClient {
|
|
229
|
+
transaction<T>(fn: (tx: TransactionClient) => Promise<T>): Promise<T>;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Works with Drizzle, Knex, Prisma, or any ORM exposing a `transaction()` method.
|
|
234
|
+
|
|
235
|
+
### Event publishing
|
|
236
|
+
|
|
237
|
+
Emit domain events to named channels. Events are automatically enriched with `correlationId`.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// single event
|
|
241
|
+
flow.event('orders', (ctx) => ({
|
|
242
|
+
eventType: 'order.created',
|
|
243
|
+
orderId: ctx.state.orderId,
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
// multiple events on the same channel
|
|
247
|
+
flow.events('notifications', [
|
|
248
|
+
(ctx) => ({ eventType: 'email.send', to: ctx.input.email }),
|
|
249
|
+
(ctx) => ({ eventType: 'sms.send', to: ctx.input.phone }),
|
|
250
|
+
]);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Requires an `eventPublisher` dependency conforming to:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
interface FlowEventPublisher {
|
|
257
|
+
publish(channel: string, event: FlowEvent): Promise<void> | void;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Parallel execution
|
|
262
|
+
|
|
263
|
+
Run independent handlers concurrently and merge results into state.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
flow.parallel('fetchAll', 'deep',
|
|
267
|
+
async (ctx) => ({ users: await fetchUsers() }),
|
|
268
|
+
async (ctx) => ({ posts: await fetchPosts() }),
|
|
269
|
+
);
|
|
270
|
+
// ctx.state now has both `users` and `posts`
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Merge strategies:**
|
|
274
|
+
|
|
275
|
+
| Strategy | Behavior |
|
|
276
|
+
|----------|----------|
|
|
277
|
+
| `'shallow'` | `Object.assign()` — later results override earlier ones |
|
|
278
|
+
| `'error-on-conflict'` | Throws if any keys overlap between results |
|
|
279
|
+
| `'deep'` | Recursive merge; arrays are concatenated |
|
|
280
|
+
|
|
281
|
+
### Output transformation
|
|
282
|
+
|
|
283
|
+
`.map()` transforms the accumulated state into a custom output shape.
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
flow
|
|
287
|
+
.step('fetch', async (ctx) => {
|
|
288
|
+
const user = await db.getUser(ctx.input.id);
|
|
289
|
+
return { user };
|
|
290
|
+
})
|
|
291
|
+
.map((input, state) => ({
|
|
292
|
+
id: state.user.id,
|
|
293
|
+
displayName: state.user.name,
|
|
294
|
+
}))
|
|
295
|
+
.build();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Composable sub-flows with .pipe()
|
|
299
|
+
|
|
300
|
+
Extract reusable step sequences as functions and compose them with `.pipe()`.
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
function withAuth(builder) {
|
|
304
|
+
return builder
|
|
305
|
+
.step('validateToken', async (ctx) => {
|
|
306
|
+
const session = await auth.verify(ctx.input.token);
|
|
307
|
+
return { session };
|
|
308
|
+
})
|
|
309
|
+
.step('loadUser', async (ctx) => {
|
|
310
|
+
const user = await db.getUser(ctx.state.session.userId);
|
|
311
|
+
return { user };
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const flow = createFlow<{ token: string }>('protected-action')
|
|
316
|
+
.pipe(withAuth)
|
|
317
|
+
.step('doAction', (ctx) => {
|
|
318
|
+
// ctx.state.user is fully typed here
|
|
319
|
+
return { result: `Hello, ${ctx.state.user.name}` };
|
|
320
|
+
})
|
|
321
|
+
.build();
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Observability
|
|
325
|
+
|
|
326
|
+
Pass an observer to hook into flow and step lifecycle events.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { PinoFlowObserver } from '@celom/prose';
|
|
330
|
+
import pino from 'pino';
|
|
331
|
+
|
|
332
|
+
const logger = pino();
|
|
333
|
+
const observer = new PinoFlowObserver(logger);
|
|
334
|
+
|
|
335
|
+
await flow.execute(input, deps, { observer });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Observer hooks:**
|
|
339
|
+
|
|
340
|
+
| Hook | Called when |
|
|
341
|
+
|------|------------|
|
|
342
|
+
| `onFlowStart` | Flow begins |
|
|
343
|
+
| `onFlowComplete` | Flow finishes successfully |
|
|
344
|
+
| `onFlowError` | Flow fails |
|
|
345
|
+
| `onFlowBreak` | Flow exits early via `breakIf` |
|
|
346
|
+
| `onStepStart` | Step begins |
|
|
347
|
+
| `onStepComplete` | Step finishes |
|
|
348
|
+
| `onStepError` | Step fails (after exhausting retries) |
|
|
349
|
+
| `onStepRetry` | Step is about to be retried |
|
|
350
|
+
| `onStepSkipped` | Conditional step is skipped |
|
|
351
|
+
|
|
352
|
+
All hooks are optional — implement only what you need:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
await flow.execute(input, deps, {
|
|
356
|
+
observer: {
|
|
357
|
+
onStepComplete: (name, _result, duration) =>
|
|
358
|
+
console.log(`${name} took ${duration}ms`),
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Built-in observers:** `DefaultObserver` (console), `NoOpObserver` (silent), `PinoFlowObserver` (structured logging).
|
|
364
|
+
|
|
365
|
+
### Error handling
|
|
366
|
+
|
|
367
|
+
By default, step errors are wrapped in `FlowExecutionError` and thrown.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { FlowExecutionError, ValidationError, TimeoutError } from '@celom/prose';
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
await flow.execute(input, deps);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (err instanceof ValidationError) {
|
|
376
|
+
// fail-fast validation — err.issues has field-level details
|
|
377
|
+
} else if (err instanceof TimeoutError) {
|
|
378
|
+
// flow or step exceeded its timeout
|
|
379
|
+
} else if (err instanceof FlowExecutionError) {
|
|
380
|
+
// step execution failure — err.stepName, err.originalError
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Set `throwOnError: false` to return partial state instead of throwing:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
const result = await flow.execute(input, deps, { throwOnError: false });
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Control behavior when optional dependencies are missing:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
await flow.execute(input, deps, {
|
|
395
|
+
errorHandling: {
|
|
396
|
+
throwOnMissingDatabase: false, // warn instead of throwing
|
|
397
|
+
throwOnMissingEventPublisher: false, // warn instead of throwing
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Flow metadata
|
|
403
|
+
|
|
404
|
+
Every step handler receives `ctx.meta` with runtime metadata:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
flow.step('example', (ctx) => {
|
|
408
|
+
ctx.meta.flowName; // 'process-order'
|
|
409
|
+
ctx.meta.currentStep; // 'example'
|
|
410
|
+
ctx.meta.startedAt; // Date
|
|
411
|
+
ctx.meta.correlationId; // auto-generated or custom
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## What this isn't
|
|
416
|
+
|
|
417
|
+
Prose is an **in-process** workflow orchestration library. It runs inside your existing Node.js process with zero external dependencies. Before adopting it, it's worth understanding what it does _not_ try to be:
|
|
418
|
+
|
|
419
|
+
**Not a durable execution engine.** If you need workflows that survive process restarts, resume after hours or days, or coordinate across distributed services, look at [Temporal](https://temporal.io), [Inngest](https://www.inngest.com), or [Trigger.dev](https://trigger.dev). These require infrastructure (servers, queues, databases) but give you persistence and replay guarantees that an in-process library fundamentally cannot.
|
|
420
|
+
|
|
421
|
+
**Not a full effect system.** [Effect-TS](https://effect.website) is more powerful in every technical dimension — typed errors in the return signature, type-level dependency injection via Layers, fibers, streams, and a massive standard library. If your team can invest in learning its functional programming model, Effect is the more capable choice. Prose trades that power for simplicity: pure async/await, no monads, no new paradigms to learn.
|
|
422
|
+
|
|
423
|
+
**Not a state machine.** [XState](https://stately.ai/docs/xstate) models workflows as finite state machines with explicit states, transitions, and guards — ideal for complex non-linear flows with many possible state transitions. Prose is designed for sequential (or branching) business logic pipelines where a state machine's verbosity would be overhead.
|
|
424
|
+
|
|
425
|
+
**Not a result type library.** Libraries like [neverthrow](https://github.com/supermacro/neverthrow) or [fp-ts](https://github.com/gcanti/fp-ts) encode errors in return types (`Result<T, E>`, `Either<E, A>`). Prose does not — steps throw, and failures are wrapped in `FlowExecutionError`. If typed error channels are critical to you, Effect or neverthrow are better fits.
|
|
426
|
+
|
|
427
|
+
### Where Prose fits
|
|
428
|
+
|
|
429
|
+
Prose is for teams building backend services with multi-step business logic (process an order, onboard a user, handle a payment) who want structured retries, timeouts, transactions, observability, and type-safe state threading — without adopting new infrastructure or a new programming paradigm.
|
|
430
|
+
|
|
431
|
+
## License
|
|
432
|
+
|
|
433
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@celom/prose",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"author": "Carlos Mimoso",
|
|
5
|
+
"description": "Declarative workflow DSL for orchestrating complex business operations in Javascript/Typescript.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"workflow",
|
|
8
|
+
"typescript",
|
|
9
|
+
"pipeline",
|
|
10
|
+
"async",
|
|
11
|
+
"dsl",
|
|
12
|
+
"workflow-engine",
|
|
13
|
+
"orchestration",
|
|
14
|
+
"type-safe",
|
|
15
|
+
"retry",
|
|
16
|
+
"business-logic",
|
|
17
|
+
"observability"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/celom/prose.git"
|
|
22
|
+
},
|
|
23
|
+
"private": false,
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
"./package.json": "./package.json",
|
|
33
|
+
".": {
|
|
34
|
+
"@celom/source": "./src/index.ts",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {}
|
|
45
|
+
}
|