stitchkit 0.71.0 → 0.72.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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/application/admission.d.ts +22 -2
  3. package/dist/application/admission.d.ts.map +1 -1
  4. package/dist/application/channel.d.ts +28 -0
  5. package/dist/application/channel.d.ts.map +1 -1
  6. package/dist/application/diagnostic-journal-contract.d.ts +20 -0
  7. package/dist/application/diagnostic-journal-contract.d.ts.map +1 -1
  8. package/dist/application/diagnostic-journal-lock.d.ts +17 -0
  9. package/dist/application/diagnostic-journal-lock.d.ts.map +1 -0
  10. package/dist/application/diagnostic-journal-manager.d.ts +2 -1
  11. package/dist/application/diagnostic-journal-manager.d.ts.map +1 -1
  12. package/dist/application/diagnostic-journal-storage.d.ts +4 -1
  13. package/dist/application/diagnostic-journal-storage.d.ts.map +1 -1
  14. package/dist/application/diagnostic-journal.d.ts +4 -2
  15. package/dist/application/diagnostic-journal.d.ts.map +1 -1
  16. package/dist/application-opentelemetry.js +1 -1
  17. package/dist/application.d.ts +3 -3
  18. package/dist/application.d.ts.map +1 -1
  19. package/dist/application.js +134 -21
  20. package/dist/browser/resumable.d.ts +56 -0
  21. package/dist/browser/resumable.d.ts.map +1 -0
  22. package/dist/{index-zpyj7hsv.js → index-3cwck0rm.js} +98 -33
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +103 -14
  26. package/dist/primitives/audit.d.ts +52 -0
  27. package/dist/primitives/audit.d.ts.map +1 -0
  28. package/dist/primitives/deadline.d.ts +39 -0
  29. package/dist/primitives/deadline.d.ts.map +1 -0
  30. package/dist/primitives/decimal.d.ts +10 -0
  31. package/dist/primitives/decimal.d.ts.map +1 -0
  32. package/dist/primitives/delivery.d.ts +96 -0
  33. package/dist/primitives/delivery.d.ts.map +1 -0
  34. package/dist/primitives/event.d.ts +42 -0
  35. package/dist/primitives/event.d.ts.map +1 -0
  36. package/dist/primitives/export-operation.d.ts +89 -0
  37. package/dist/primitives/export-operation.d.ts.map +1 -0
  38. package/dist/primitives/index.d.ts +12 -0
  39. package/dist/primitives/index.d.ts.map +1 -0
  40. package/dist/primitives/lifecycle.d.ts +90 -0
  41. package/dist/primitives/lifecycle.d.ts.map +1 -0
  42. package/dist/primitives/migration-checks.d.ts +15 -0
  43. package/dist/primitives/migration-checks.d.ts.map +1 -0
  44. package/dist/primitives/money.d.ts +43 -0
  45. package/dist/primitives/money.d.ts.map +1 -0
  46. package/dist/primitives/owner-scope.d.ts +30 -0
  47. package/dist/primitives/owner-scope.d.ts.map +1 -0
  48. package/dist/primitives/permission.d.ts +28 -0
  49. package/dist/primitives/permission.d.ts.map +1 -0
  50. package/dist/primitives/quantity.d.ts +57 -0
  51. package/dist/primitives/quantity.d.ts.map +1 -0
  52. package/dist/primitives.d.ts +3 -0
  53. package/dist/primitives.d.ts.map +1 -0
  54. package/dist/primitives.js +750 -0
  55. package/llms-full.txt +246 -3
  56. package/llms.txt +1 -0
  57. package/package.json +6 -2
@@ -0,0 +1,750 @@
1
+ import {
2
+ ManagedFileRefSchema
3
+ } from "./index-f6n5n7nz.js";
4
+ import"./index-ksp6e2ye.js";
5
+
6
+ // src/primitives/audit.ts
7
+ import { z as z2 } from "zod";
8
+
9
+ // src/primitives/event.ts
10
+ import { z } from "zod";
11
+ var DomainEventActorSchema = z.object({
12
+ id: z.string().min(1),
13
+ role: z.string().min(1)
14
+ });
15
+ var DomainEventSubjectSchema = z.object({
16
+ type: z.string().min(1),
17
+ id: z.string().min(1)
18
+ });
19
+ function createDomainEventSchema(payload) {
20
+ return z.object({
21
+ id: z.string().min(1),
22
+ type: z.string().min(1),
23
+ occurredAt: z.iso.datetime({ offset: true }),
24
+ actor: DomainEventActorSchema.optional(),
25
+ subject: DomainEventSubjectSchema,
26
+ payload
27
+ });
28
+ }
29
+ var DomainEventSchema = createDomainEventSchema(z.unknown());
30
+
31
+ // src/primitives/audit.ts
32
+ var AuditChangeSchema = z2.object({
33
+ operation: z2.string().min(1),
34
+ change: z2.unknown()
35
+ });
36
+ var AuditRecordSchema = createDomainEventSchema(AuditChangeSchema);
37
+ var audit = Object.freeze({
38
+ record(change) {
39
+ return Object.freeze({ mode: "record", change });
40
+ },
41
+ omit(reason) {
42
+ if (reason.trim() === "")
43
+ throw new Error("[stitchkit] audit omission requires a reason");
44
+ return Object.freeze({ mode: "omit", reason });
45
+ }
46
+ });
47
+ function isZodType(value) {
48
+ return typeof value === "object" && value !== null && "safeParse" in value && typeof value.safeParse === "function";
49
+ }
50
+ function endpointAuditPolicy(endpoint) {
51
+ const policy = endpoint.meta?.audit;
52
+ if (!policy || typeof policy !== "object" || !("mode" in policy))
53
+ return;
54
+ if (policy.mode === "omit" && "reason" in policy && typeof policy.reason === "string") {
55
+ return audit.omit(policy.reason);
56
+ }
57
+ if (policy.mode === "record" && "change" in policy && isZodType(policy.change)) {
58
+ return { mode: "record", change: policy.change };
59
+ }
60
+ return;
61
+ }
62
+ function assertAuditDeclared(contract) {
63
+ for (const [operation, endpoint] of Object.entries(contract.endpoints)) {
64
+ if (!endpointAuditPolicy(endpoint)) {
65
+ throw new Error(`[stitchkit] contract "${contract.meta.prefix}" operation "${operation}" must declare meta.audit`);
66
+ }
67
+ }
68
+ }
69
+ function createAuditRecord(input) {
70
+ const change = input.policy.change.parse(input.change);
71
+ return AuditRecordSchema.parse({
72
+ id: input.id,
73
+ type: "audit.recorded",
74
+ occurredAt: input.occurredAt,
75
+ actor: input.actor,
76
+ subject: input.subject,
77
+ payload: { operation: input.operation, change }
78
+ });
79
+ }
80
+ // src/primitives/deadline.ts
81
+ import { z as z3 } from "zod";
82
+ var DAY_MS = 86400000;
83
+ var DeadlineResultSchema = z3.object({
84
+ dueAt: z3.iso.datetime({ offset: true }),
85
+ remainingDays: z3.number().int(),
86
+ overdueDays: z3.number().int().nonnegative(),
87
+ category: z3.string().min(1)
88
+ });
89
+ function partsInZone(value, timeZone) {
90
+ const parts = new Intl.DateTimeFormat("en-CA", {
91
+ timeZone,
92
+ year: "numeric",
93
+ month: "2-digit",
94
+ day: "2-digit",
95
+ hour: "2-digit",
96
+ minute: "2-digit",
97
+ second: "2-digit",
98
+ hourCycle: "h23"
99
+ }).formatToParts(value);
100
+ const read = (type) => {
101
+ const part = parts.find((candidate) => candidate.type === type)?.value;
102
+ if (!part)
103
+ throw new Error(`[stitchkit] timezone projection omitted ${type}`);
104
+ return Number(part);
105
+ };
106
+ return {
107
+ year: read("year"),
108
+ month: read("month"),
109
+ day: read("day"),
110
+ hour: read("hour"),
111
+ minute: read("minute"),
112
+ second: read("second")
113
+ };
114
+ }
115
+ function partsEpoch(parts) {
116
+ return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
117
+ }
118
+ function localPartsToEpoch(parts, timeZone) {
119
+ const desired = partsEpoch(parts);
120
+ let candidate = desired;
121
+ for (let attempt = 0;attempt < 3; attempt += 1) {
122
+ const projected = partsEpoch(partsInZone(new Date(candidate), timeZone));
123
+ candidate += desired - projected;
124
+ }
125
+ return candidate;
126
+ }
127
+ function addCalendarDays(anchor, days, timeZone) {
128
+ const parts = partsInZone(anchor, timeZone);
129
+ const shifted = new Date(Date.UTC(parts.year, parts.month - 1, parts.day + days, parts.hour, parts.minute, parts.second));
130
+ return new Date(localPartsToEpoch(partsInZone(shifted, "UTC"), timeZone));
131
+ }
132
+ function calendarDayKey(value, timeZone) {
133
+ const parts = partsInZone(value, timeZone);
134
+ return Date.UTC(parts.year, parts.month - 1, parts.day) / DAY_MS;
135
+ }
136
+ function defineDeadlinePolicy(config) {
137
+ new Intl.DateTimeFormat("en", { timeZone: config.timeZone }).format(new Date(0));
138
+ if (!Number.isSafeInteger(config.warningDays) || config.warningDays < 0) {
139
+ throw new RangeError("warningDays must be a non-negative safe integer");
140
+ }
141
+ const addDays = (value, days) => config.boundary === "calendar-day" ? addCalendarDays(value, days, config.timeZone) : new Date(value.getTime() + days * DAY_MS);
142
+ return Object.freeze({
143
+ definition: config,
144
+ evaluate(input) {
145
+ if (!Number.isSafeInteger(input.durationDays) || input.durationDays < 0) {
146
+ throw new RangeError("durationDays must be a non-negative safe integer");
147
+ }
148
+ const dueAt = addDays(input.anchorAt, input.durationDays);
149
+ const remainingDays = config.boundary === "calendar-day" ? calendarDayKey(dueAt, config.timeZone) - calendarDayKey(input.now, config.timeZone) : Math.ceil((dueAt.getTime() - input.now.getTime()) / DAY_MS);
150
+ const category = remainingDays < 0 ? config.categories.overdue : remainingDays <= config.warningDays ? config.categories.warning : config.categories.onTrack;
151
+ return DeadlineResultSchema.parse({
152
+ dueAt: dueAt.toISOString(),
153
+ remainingDays,
154
+ overdueDays: Math.max(0, -remainingDays),
155
+ category
156
+ });
157
+ },
158
+ queryBoundary(now) {
159
+ return Object.freeze({
160
+ overdueBefore: now.toISOString(),
161
+ warningBefore: addDays(now, config.warningDays).toISOString()
162
+ });
163
+ }
164
+ });
165
+ }
166
+ // src/primitives/delivery.ts
167
+ import { z as z4 } from "zod";
168
+ var DomainEventDestinationSchema = z4.object({
169
+ id: z4.string().min(1),
170
+ transport: z4.string().min(1),
171
+ address: z4.string().min(1)
172
+ });
173
+ var DomainEventDeliveryOutcomeSchema = z4.discriminatedUnion("outcome", [
174
+ z4.object({ outcome: z4.literal("delivered"), receipt: z4.string().min(1).optional() }),
175
+ z4.object({
176
+ outcome: z4.literal("retryable"),
177
+ code: z4.string().min(1),
178
+ retryAt: z4.iso.datetime({ offset: true })
179
+ }),
180
+ z4.object({ outcome: z4.literal("terminal"), code: z4.string().min(1) }),
181
+ z4.object({ outcome: z4.literal("unknown"), code: z4.string().min(1) })
182
+ ]);
183
+ var DomainEventDeliveryClaimSchema = z4.object({
184
+ event: DomainEventSchema,
185
+ destination: DomainEventDestinationSchema,
186
+ attempt: z4.number().int().positive()
187
+ });
188
+ function defineDomainEventDelivery(config) {
189
+ const maxClaims = config.maxClaimsPerDispatch ?? 100;
190
+ if (!Number.isSafeInteger(maxClaims) || maxClaims <= 0) {
191
+ throw new RangeError("maxClaimsPerDispatch must be a positive safe integer");
192
+ }
193
+ return Object.freeze({
194
+ plan(eventInput) {
195
+ const event = DomainEventSchema.parse(eventInput);
196
+ const destinations = config.routes.filter((route) => route.type === event.type).flatMap((route) => route.destinations(event)).map((destination) => DomainEventDestinationSchema.parse(destination));
197
+ const ids = new Set;
198
+ for (const destination of destinations) {
199
+ if (ids.has(destination.id)) {
200
+ throw new Error(`[stitchkit] duplicate destination id "${destination.id}"`);
201
+ }
202
+ ids.add(destination.id);
203
+ }
204
+ return Object.freeze({ event, destinations: Object.freeze(destinations) });
205
+ },
206
+ async dispatch(eventId) {
207
+ let attempts = 0;
208
+ while (attempts < maxClaims) {
209
+ const claim = await config.outbox.claim(eventId);
210
+ if (!claim)
211
+ return { eventId, attempts, exhausted: false };
212
+ const parsedClaim = DomainEventDeliveryClaimSchema.parse(claim);
213
+ if (parsedClaim.event.id !== eventId) {
214
+ throw new Error("[stitchkit] outbox claim event id does not match dispatch request");
215
+ }
216
+ const transport = config.transports[parsedClaim.destination.transport];
217
+ let outcome;
218
+ if (!transport) {
219
+ outcome = { outcome: "unknown", code: "TRANSPORT_NOT_DECLARED" };
220
+ } else {
221
+ try {
222
+ outcome = DomainEventDeliveryOutcomeSchema.parse(await transport.send(parsedClaim.event, parsedClaim.destination));
223
+ } catch {
224
+ outcome = { outcome: "unknown", code: "TRANSPORT_FAILED" };
225
+ }
226
+ }
227
+ switch (outcome.outcome) {
228
+ case "delivered":
229
+ await config.outbox.delivered(parsedClaim, outcome);
230
+ break;
231
+ case "retryable":
232
+ await config.outbox.retry(parsedClaim, outcome);
233
+ break;
234
+ case "terminal":
235
+ await config.outbox.terminal(parsedClaim, outcome);
236
+ break;
237
+ case "unknown":
238
+ await config.outbox.unknown(parsedClaim, outcome);
239
+ break;
240
+ }
241
+ attempts += 1;
242
+ }
243
+ return { eventId, attempts, exhausted: true };
244
+ }
245
+ });
246
+ }
247
+ // src/primitives/export-operation.ts
248
+ import { z as z5 } from "zod";
249
+ function createExportResultSchema(operationId) {
250
+ return z5.discriminatedUnion("state", [
251
+ z5.object({
252
+ state: z5.literal("ready"),
253
+ file: ManagedFileRefSchema.extend({
254
+ mediaType: z5.string().min(1),
255
+ name: z5.string().min(1)
256
+ })
257
+ }),
258
+ z5.object({ state: z5.literal("pending"), operationId })
259
+ ]);
260
+ }
261
+ function defineExportOperation(config) {
262
+ if (config.mediaType.trim() === "")
263
+ throw new Error("[stitchkit] export mediaType is required");
264
+ const result = createExportResultSchema(config.operationId);
265
+ return Object.freeze({
266
+ input: config.input,
267
+ result,
268
+ ready(inputValue, file) {
269
+ const input = config.input.parse(inputValue);
270
+ return result.parse({
271
+ state: "ready",
272
+ file: {
273
+ ...file,
274
+ mediaType: config.mediaType,
275
+ name: config.filename(input)
276
+ }
277
+ });
278
+ },
279
+ pending(operationId) {
280
+ return result.parse({ state: "pending", operationId });
281
+ },
282
+ endpoint(definition) {
283
+ return {
284
+ ...definition,
285
+ input: config.input,
286
+ output: result
287
+ };
288
+ }
289
+ });
290
+ }
291
+ // src/primitives/lifecycle.ts
292
+ import { z as z6 } from "zod";
293
+ var lifecycleStateBrand = Symbol("stitchkit.lifecycle.state");
294
+ var LifecycleTransitionEventSchema = z6.object({
295
+ id: z6.string().min(1),
296
+ type: z6.literal("lifecycle.transitioned"),
297
+ occurredAt: z6.iso.datetime({ offset: true }),
298
+ actor: z6.object({ id: z6.string().min(1), role: z6.string().min(1) }),
299
+ subject: z6.object({ type: z6.string().min(1), id: z6.string().min(1) }),
300
+ payload: z6.object({
301
+ lifecycle: z6.string().min(1),
302
+ transition: z6.string().min(1),
303
+ from: z6.string().min(1),
304
+ to: z6.string().min(1),
305
+ data: z6.unknown()
306
+ })
307
+ });
308
+ function stateMatches(expected, actual) {
309
+ return Array.isArray(expected) ? expected.includes(actual) : expected === actual;
310
+ }
311
+ function lifecycleState(value) {
312
+ const state = { value, [lifecycleStateBrand]: true };
313
+ return Object.freeze(state);
314
+ }
315
+ function defineLifecycle(config) {
316
+ const definition = config;
317
+ return Object.freeze({
318
+ definition,
319
+ state(value) {
320
+ if (!config.states.includes(value)) {
321
+ throw new Error(`[stitchkit] lifecycle "${config.name}" received an unknown state`);
322
+ }
323
+ return lifecycleState(value);
324
+ },
325
+ availableTransitions(state, role) {
326
+ return Object.entries(config.transitions).filter(([, transition]) => stateMatches(transition.from, state.value) && transition.by.includes(role)).map(([name]) => name);
327
+ },
328
+ transition(input) {
329
+ const transition = config.transitions[input.transition];
330
+ if (!transition) {
331
+ return {
332
+ outcome: "transition_not_allowed",
333
+ transition: input.transition,
334
+ state: input.state.value
335
+ };
336
+ }
337
+ if (!stateMatches(transition.from, input.state.value)) {
338
+ return {
339
+ outcome: "transition_not_allowed",
340
+ transition: input.transition,
341
+ state: input.state.value
342
+ };
343
+ }
344
+ if (!transition.by.includes(input.role)) {
345
+ return {
346
+ outcome: "role_not_allowed",
347
+ transition: input.transition,
348
+ role: input.role
349
+ };
350
+ }
351
+ const parsedPayload = transition.payload?.safeParse(input.payload);
352
+ if (parsedPayload && !parsedPayload.success) {
353
+ return {
354
+ outcome: "invalid_payload",
355
+ transition: input.transition,
356
+ issues: parsedPayload.error.issues.map((issue) => issue.message)
357
+ };
358
+ }
359
+ const actor = { id: input.actorId, role: input.role };
360
+ const event = LifecycleTransitionEventSchema.parse({
361
+ id: input.eventId,
362
+ type: "lifecycle.transitioned",
363
+ occurredAt: input.occurredAt,
364
+ actor,
365
+ subject: input.subject,
366
+ payload: {
367
+ lifecycle: config.name,
368
+ transition: input.transition,
369
+ from: input.state.value,
370
+ to: transition.to,
371
+ data: parsedPayload?.data ?? input.payload
372
+ }
373
+ });
374
+ return {
375
+ outcome: "transitioned",
376
+ state: lifecycleState(transition.to),
377
+ event
378
+ };
379
+ }
380
+ });
381
+ }
382
+ // src/primitives/migration-checks.ts
383
+ function lineNumber(text, offset) {
384
+ return text.slice(0, offset).split(`
385
+ `).length;
386
+ }
387
+ function excerptAt(text, offset) {
388
+ const start = text.lastIndexOf(`
389
+ `, offset) + 1;
390
+ const end = text.indexOf(`
391
+ `, offset);
392
+ return text.slice(start, end === -1 ? undefined : end).trim();
393
+ }
394
+ function escapeRegExp(value) {
395
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
396
+ }
397
+ function scanMoneyNumberRisks(sources, identifiers = ["amount", "money", "price", "total"]) {
398
+ const risks = [];
399
+ for (const source of sources) {
400
+ for (const identifier of identifiers) {
401
+ const pattern = new RegExp(`\\b${escapeRegExp(identifier)}\\b[^\\n;]{0,120}\\.toFixed\\(\\s*2\\s*\\)`, "gi");
402
+ for (const match of source.text.matchAll(pattern)) {
403
+ if (match.index === undefined)
404
+ continue;
405
+ risks.push({
406
+ path: source.path,
407
+ line: lineNumber(source.text, match.index),
408
+ kind: "money-number",
409
+ excerpt: excerptAt(source.text, match.index)
410
+ });
411
+ }
412
+ }
413
+ }
414
+ return risks;
415
+ }
416
+ function scanOwnerFilterRisks(sources, ownerKeys = ["ownerId"]) {
417
+ const risks = [];
418
+ for (const source of sources) {
419
+ for (const ownerKey of ownerKeys) {
420
+ const key = escapeRegExp(ownerKey);
421
+ const pattern = new RegExp(`\\.(?:findMany|findFirst|count|aggregate|groupBy)\\s*\\([^;]{0,500}?\\b${key}\\s*(?::|[,}])`, "g");
422
+ for (const match of source.text.matchAll(pattern)) {
423
+ if (match.index === undefined)
424
+ continue;
425
+ risks.push({
426
+ path: source.path,
427
+ line: lineNumber(source.text, match.index),
428
+ kind: "manual-owner-filter",
429
+ excerpt: excerptAt(source.text, match.index)
430
+ });
431
+ }
432
+ }
433
+ }
434
+ return risks;
435
+ }
436
+ // src/primitives/money.ts
437
+ import { z as z7 } from "zod";
438
+ var IntegerStringSchema = z7.string().regex(/^-?(?:0|[1-9]\d*)$/);
439
+ function createMoneySchema(currency) {
440
+ return z7.object({
441
+ minor: IntegerStringSchema,
442
+ currency: z7.literal(currency)
443
+ });
444
+ }
445
+ function parseMinor(value) {
446
+ return BigInt(IntegerStringSchema.parse(value.minor));
447
+ }
448
+ function assertSameCurrency(left, right) {
449
+ if (left.currency !== right.currency) {
450
+ throw new TypeError(`cannot combine "${left.currency}" and "${right.currency}"`);
451
+ }
452
+ }
453
+ function money(minor, currency) {
454
+ return Object.freeze({ minor: minor.toString(), currency });
455
+ }
456
+ function addMoney(left, right) {
457
+ assertSameCurrency(left, right);
458
+ return money(parseMinor(left) + parseMinor(right), left.currency);
459
+ }
460
+ function subtractMoney(left, right) {
461
+ assertSameCurrency(left, right);
462
+ return money(parseMinor(left) - parseMinor(right), left.currency);
463
+ }
464
+ function multiplyMoney(value, quantity) {
465
+ if (typeof quantity === "number" && !Number.isSafeInteger(quantity)) {
466
+ throw new RangeError("quantity must be a safe integer");
467
+ }
468
+ const multiplier = typeof quantity === "bigint" ? quantity : BigInt(quantity);
469
+ return money(parseMinor(value) * multiplier, value.currency);
470
+ }
471
+ function shareMoney(value, numerator, denominator) {
472
+ if (denominator <= 0n)
473
+ throw new RangeError("denominator must be positive");
474
+ if (numerator < 0n)
475
+ throw new RangeError("numerator must be non-negative");
476
+ const scaled = parseMinor(value) * numerator;
477
+ return Object.freeze({
478
+ amount: money(scaled / denominator, value.currency),
479
+ remainder: Object.freeze({
480
+ numerator: (scaled % denominator).toString(),
481
+ denominator: denominator.toString(),
482
+ currency: value.currency
483
+ })
484
+ });
485
+ }
486
+ function splitMoney(value, count) {
487
+ if (!Number.isSafeInteger(count) || count <= 0) {
488
+ throw new RangeError("count must be a positive safe integer");
489
+ }
490
+ const divisor = BigInt(count);
491
+ const total = parseMinor(value);
492
+ return Object.freeze({
493
+ part: money(total / divisor, value.currency),
494
+ count,
495
+ remainder: money(total % divisor, value.currency)
496
+ });
497
+ }
498
+ function defineMoney(currency) {
499
+ const schema = createMoneySchema(currency);
500
+ return Object.freeze({
501
+ currency,
502
+ schema,
503
+ create(minor) {
504
+ const parsed = schema.parse({ minor: minor.toString(), currency });
505
+ return Object.freeze(parsed);
506
+ },
507
+ add: addMoney,
508
+ subtract: subtractMoney,
509
+ multiply: multiplyMoney,
510
+ share: shareMoney,
511
+ split: splitMoney
512
+ });
513
+ }
514
+ // src/primitives/owner-scope.ts
515
+ var ownerScopeBrand = Symbol("stitchkit.owner.scope");
516
+ function scopedOwner(ownerId) {
517
+ const scope = { kind: "owner", ownerId, [ownerScopeBrand]: true };
518
+ return Object.freeze(scope);
519
+ }
520
+ function allOwners() {
521
+ const scope = {
522
+ kind: "all",
523
+ permission: "acrossAllOwners",
524
+ [ownerScopeBrand]: true
525
+ };
526
+ return Object.freeze(scope);
527
+ }
528
+ function defineOwnerScope(definition) {
529
+ return Object.freeze({
530
+ definition,
531
+ forIdentity(identity) {
532
+ const ownerId = definition.ownerId(identity);
533
+ return ownerId ? { outcome: "resolved", scope: scopedOwner(ownerId) } : { outcome: "owner_missing" };
534
+ },
535
+ acrossAllOwners(identity) {
536
+ return definition.canAccessAll(identity) ? { outcome: "resolved", scope: allOwners() } : { outcome: "across_all_forbidden" };
537
+ }
538
+ });
539
+ }
540
+ // src/primitives/permission.ts
541
+ function definePermissionMatrix(config) {
542
+ if (new Set(config.roles).size !== config.roles.length) {
543
+ throw new Error("[stitchkit] permission matrix roles must be unique");
544
+ }
545
+ if (new Set(config.operations).size !== config.operations.length) {
546
+ throw new Error("[stitchkit] permission matrix operations must be unique");
547
+ }
548
+ for (const role of config.roles) {
549
+ const declared = Object.keys(config.grants[role]).sort();
550
+ const expected = [...config.operations].sort();
551
+ if (declared.length !== expected.length || declared.some((value, index) => value !== expected[index])) {
552
+ throw new Error(`[stitchkit] permission matrix role "${role}" must decide every operation`);
553
+ }
554
+ }
555
+ const roleSet = new Set(config.roles);
556
+ const operationSet = new Set(config.operations);
557
+ const decisions = new Map;
558
+ for (const role of config.roles) {
559
+ for (const operation of config.operations) {
560
+ decisions.set(`${role}\x00${operation}`, config.grants[role][operation]);
561
+ }
562
+ }
563
+ return Object.freeze({
564
+ definition: config,
565
+ allows(role, operation) {
566
+ return config.grants[role][operation];
567
+ },
568
+ capabilities(role) {
569
+ return config.operations.filter((operation) => config.grants[role][operation]);
570
+ },
571
+ check(role, operation) {
572
+ if (!roleSet.has(role))
573
+ return { outcome: "unknown_role", role };
574
+ if (!operationSet.has(operation))
575
+ return { outcome: "unknown_operation", operation };
576
+ return decisions.get(`${role}\x00${operation}`) ? { outcome: "allowed" } : { outcome: "denied" };
577
+ }
578
+ });
579
+ }
580
+ // src/primitives/quantity.ts
581
+ import { z as z8 } from "zod";
582
+
583
+ // src/primitives/decimal.ts
584
+ var DECIMAL_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
585
+ function parseDecimal(value) {
586
+ if (!DECIMAL_PATTERN.test(value))
587
+ throw new TypeError(`invalid decimal value "${value}"`);
588
+ const negative = value.startsWith("-");
589
+ const unsigned = negative ? value.slice(1) : value;
590
+ const [whole = "0", fraction = ""] = unsigned.split(".");
591
+ const coefficient = BigInt(`${negative ? "-" : ""}${whole}${fraction}`);
592
+ return normalizeDecimal({ coefficient, scale: fraction.length });
593
+ }
594
+ function normalizeDecimal(parts) {
595
+ let coefficient = parts.coefficient;
596
+ let scale = parts.scale;
597
+ while (scale > 0 && coefficient % 10n === 0n) {
598
+ coefficient /= 10n;
599
+ scale -= 1;
600
+ }
601
+ return { coefficient, scale };
602
+ }
603
+ function formatDecimal(parts) {
604
+ const normalized = normalizeDecimal(parts);
605
+ const negative = normalized.coefficient < 0n;
606
+ const digits = (negative ? -normalized.coefficient : normalized.coefficient).toString();
607
+ if (normalized.scale === 0)
608
+ return `${negative ? "-" : ""}${digits}`;
609
+ const padded = digits.padStart(normalized.scale + 1, "0");
610
+ const point = padded.length - normalized.scale;
611
+ return `${negative ? "-" : ""}${padded.slice(0, point)}.${padded.slice(point)}`;
612
+ }
613
+ function addDecimal(left, right) {
614
+ const scale = Math.max(left.scale, right.scale);
615
+ const leftCoefficient = left.coefficient * 10n ** BigInt(scale - left.scale);
616
+ const rightCoefficient = right.coefficient * 10n ** BigInt(scale - right.scale);
617
+ return normalizeDecimal({ coefficient: leftCoefficient + rightCoefficient, scale });
618
+ }
619
+ function multiplyDecimalRatio(value, numerator, denominator) {
620
+ if (denominator <= 0n)
621
+ throw new RangeError("conversion denominator must be positive");
622
+ let coefficient = value.coefficient * numerator;
623
+ let scale = value.scale;
624
+ for (let extraScale = 0;extraScale <= 18; extraScale += 1) {
625
+ if (coefficient % denominator === 0n) {
626
+ return normalizeDecimal({ coefficient: coefficient / denominator, scale });
627
+ }
628
+ coefficient *= 10n;
629
+ scale += 1;
630
+ }
631
+ throw new RangeError("conversion does not have a finite decimal representation");
632
+ }
633
+
634
+ // src/primitives/quantity.ts
635
+ var DecimalStringSchema = z8.string().refine((value) => {
636
+ try {
637
+ return formatDecimal(parseDecimal(value)) === value;
638
+ } catch {
639
+ return false;
640
+ }
641
+ }, "expected a canonical decimal string");
642
+ function createQuantitySchema(unit) {
643
+ return z8.object({ value: DecimalStringSchema, unit: z8.literal(unit) });
644
+ }
645
+ function quantity(value, unit) {
646
+ return Object.freeze({ value: formatDecimal(parseDecimal(value)), unit });
647
+ }
648
+ function sameUnit(left, right) {
649
+ return left === right;
650
+ }
651
+ function addQuantity(left, right) {
652
+ if (!sameUnit(left.unit, right.unit)) {
653
+ throw new TypeError(`cannot combine "${left.unit}" and "${right.unit}"`);
654
+ }
655
+ return quantity(formatDecimal(addDecimal(parseDecimal(left.value), parseDecimal(right.value))), left.unit);
656
+ }
657
+ var QuantityProjectionSchema = z8.discriminatedUnion("kind", [
658
+ z8.object({
659
+ kind: z8.literal("recorded"),
660
+ quantity: z8.object({ value: DecimalStringSchema, unit: z8.string().min(1) })
661
+ }),
662
+ z8.object({
663
+ kind: z8.literal("derived"),
664
+ quantity: z8.object({ value: DecimalStringSchema, unit: z8.string().min(1) }),
665
+ source: z8.object({ value: DecimalStringSchema, unit: z8.string().min(1) }),
666
+ conversionId: z8.string().min(1)
667
+ })
668
+ ]);
669
+ function defineUnitSystem(config) {
670
+ const ids = new Set;
671
+ for (const conversion of config.conversions) {
672
+ if (ids.has(conversion.id)) {
673
+ throw new Error(`[stitchkit] duplicate conversion id "${conversion.id}"`);
674
+ }
675
+ ids.add(conversion.id);
676
+ if (!/^-?(?:0|[1-9]\d*)$/.test(conversion.numerator)) {
677
+ throw new Error(`[stitchkit] conversion "${conversion.id}" numerator must be an integer`);
678
+ }
679
+ if (!/^(?:[1-9]\d*)$/.test(conversion.denominator)) {
680
+ throw new Error(`[stitchkit] conversion "${conversion.id}" denominator must be positive`);
681
+ }
682
+ }
683
+ return Object.freeze({
684
+ definition: config,
685
+ create(value, unit) {
686
+ if (!config.units.includes(unit))
687
+ throw new Error(`[stitchkit] unknown unit "${unit}"`);
688
+ return quantity(value, unit);
689
+ },
690
+ recorded(value) {
691
+ return Object.freeze({ kind: "recorded", quantity: value });
692
+ },
693
+ convert(source, to) {
694
+ if (sameUnit(source.unit, to)) {
695
+ return Object.freeze({
696
+ kind: "derived",
697
+ quantity: quantity(source.value, to),
698
+ source,
699
+ conversionId: "identity"
700
+ });
701
+ }
702
+ const conversion = config.conversions.find((candidate) => candidate.from === source.unit && candidate.to === to);
703
+ if (!conversion) {
704
+ throw new Error(`[stitchkit] no conversion from "${source.unit}" to "${to}"`);
705
+ }
706
+ const converted = multiplyDecimalRatio(parseDecimal(source.value), BigInt(conversion.numerator), BigInt(conversion.denominator));
707
+ return Object.freeze({
708
+ kind: "derived",
709
+ quantity: quantity(formatDecimal(converted), to),
710
+ source,
711
+ conversionId: conversion.id
712
+ });
713
+ }
714
+ });
715
+ }
716
+ export {
717
+ subtractMoney,
718
+ splitMoney,
719
+ shareMoney,
720
+ scanOwnerFilterRisks,
721
+ scanMoneyNumberRisks,
722
+ multiplyMoney,
723
+ defineUnitSystem,
724
+ definePermissionMatrix,
725
+ defineOwnerScope,
726
+ defineMoney,
727
+ defineLifecycle,
728
+ defineExportOperation,
729
+ defineDomainEventDelivery,
730
+ defineDeadlinePolicy,
731
+ createQuantitySchema,
732
+ createMoneySchema,
733
+ createExportResultSchema,
734
+ createDomainEventSchema,
735
+ createAuditRecord,
736
+ audit,
737
+ assertAuditDeclared,
738
+ addQuantity,
739
+ addMoney,
740
+ QuantityProjectionSchema,
741
+ LifecycleTransitionEventSchema,
742
+ DomainEventSubjectSchema,
743
+ DomainEventSchema,
744
+ DomainEventDestinationSchema,
745
+ DomainEventDeliveryOutcomeSchema,
746
+ DomainEventDeliveryClaimSchema,
747
+ DomainEventActorSchema,
748
+ DeadlineResultSchema,
749
+ AuditRecordSchema
750
+ };