@rkt-tools/contracts 0.1.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/README.md +39 -0
- package/dist/envelope.d.ts +204 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +106 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @rkt-tools/contracts
|
|
2
|
+
|
|
3
|
+
The pure, shared **ingestion-envelope contract** for the `rkt-comms` monorepo: the Zod schemas and inferred types that describe the `POST /ingest/event` payloads. Both the Core (`packages/crm`) and the Mac bridge (`packages/imap-mcp`) import it, so the wire contract is authored **once** instead of hand-mirrored across the two packages.
|
|
4
|
+
|
|
5
|
+
- **Pure.** Types/schemas only — no I/O, no Node API, the sole runtime dependency is `zod`. It is a leaf in the dependency graph (it must import neither consumer; `pnpm depcheck` enforces this).
|
|
6
|
+
- **Zod v4.** Authored in v4 idiom (`z.strictObject`, `z.iso.datetime`, `z.uuid`).
|
|
7
|
+
|
|
8
|
+
## Exports
|
|
9
|
+
|
|
10
|
+
| Export | What |
|
|
11
|
+
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
12
|
+
| `bridgeEnvelopeSchema` | discriminated union over `eventKind` — what the Core parses |
|
|
13
|
+
| `imessageEnvelopeSchema` / `callEnvelopeSchema` | the two standalone members — what the bridge validates against |
|
|
14
|
+
| `participantSchema`, `imessageSafeMetadataSchema`, `callSafeMetadataSchema`, `SAFE_METADATA_KEYS` | building blocks |
|
|
15
|
+
| `parseBridgeEnvelope(value)` | `bridgeEnvelopeSchema.parse` |
|
|
16
|
+
| types: `BridgeEnvelope` (union), `CoreEnvelope` (**iMessage member only**, not the union), `CallEnvelope`, `SafeMetadata` | inferred |
|
|
17
|
+
|
|
18
|
+
## Build & test
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm --filter @rkt-tools/contracts build # tsc -p tsconfig.build.json → dist/ (emits .d.ts)
|
|
22
|
+
pnpm --filter @rkt-tools/contracts test # golden-fixture contract guard
|
|
23
|
+
pnpm --filter @rkt-tools/contracts typecheck
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Consumers build it deps-first on every build/deploy path via the trailing-`...` filter, e.g. `pnpm --filter "rkt-crm..." build`.
|
|
27
|
+
|
|
28
|
+
## Changing the wire contract
|
|
29
|
+
|
|
30
|
+
Edit `src/envelope.ts` **and extend the golden fixtures in `test/envelope.test.ts` in the same commit.** The fixtures (known-good must-parse, known-bad must-reject) are the regression guard that the contract can't silently loosen or tighten. The build must keep `declaration: true` (in `tsconfig.build.json`) or NodeNext consumers fail type resolution.
|
|
31
|
+
|
|
32
|
+
## Consumption
|
|
33
|
+
|
|
34
|
+
| Consumer | How |
|
|
35
|
+
| ----------------------------------- | ---------------------------------------------------------------------------------- |
|
|
36
|
+
| **rkt-comms** (`crm`, `imap-mcp`) | `"@rkt-tools/contracts": "workspace:*"` — no npm auth needed for local/Railway builds |
|
|
37
|
+
| **rkt-ship** (and other cross-repo) | `"@rkt-tools/contracts": "^0.1.0"` from public npm (`publishConfig.access: public`) |
|
|
38
|
+
|
|
39
|
+
Release and registry runbook: [`docs/contracts-registry.md`](../../docs/contracts-registry.md). ADR: [`0033`](../../packages/crm/docs/adr/0033-rkt-contracts-npm-registry-consumption.md).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const participantSchema: z.ZodObject<{
|
|
3
|
+
externalIdentityRef: z.ZodString;
|
|
4
|
+
role: z.ZodEnum<{
|
|
5
|
+
sender: "sender";
|
|
6
|
+
recipient: "recipient";
|
|
7
|
+
participant: "participant";
|
|
8
|
+
owner: "owner";
|
|
9
|
+
}>;
|
|
10
|
+
}, z.core.$strict>;
|
|
11
|
+
export declare const SAFE_METADATA_KEYS: readonly ["message_count", "inbound_count", "outbound_count", "participant_count", "service", "chat_kind", "has_attachments", "attachment_count", "reaction_only_count", "session_candidate_id", "source_schema_version"];
|
|
12
|
+
export declare const imessageSafeMetadataSchema: z.ZodObject<{
|
|
13
|
+
message_count: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
inbound_count: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
outbound_count: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
participant_count: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
service: z.ZodOptional<z.ZodEnum<{
|
|
18
|
+
imessage: "imessage";
|
|
19
|
+
sms: "sms";
|
|
20
|
+
}>>;
|
|
21
|
+
chat_kind: z.ZodOptional<z.ZodEnum<{
|
|
22
|
+
dm: "dm";
|
|
23
|
+
group: "group";
|
|
24
|
+
}>>;
|
|
25
|
+
has_attachments: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
attachment_count: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
reaction_only_count: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
session_candidate_id: z.ZodOptional<z.ZodString>;
|
|
29
|
+
source_schema_version: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
export declare const callSafeMetadataSchema: z.ZodObject<{
|
|
32
|
+
call_seconds: z.ZodNumber;
|
|
33
|
+
call_service: z.ZodEnum<{
|
|
34
|
+
cellular: "cellular";
|
|
35
|
+
facetime_audio: "facetime_audio";
|
|
36
|
+
facetime_video: "facetime_video";
|
|
37
|
+
}>;
|
|
38
|
+
answered: z.ZodBoolean;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export declare const imessageEnvelopeSchema: z.ZodObject<{
|
|
41
|
+
bridgeId: z.ZodString;
|
|
42
|
+
sourceEventId: z.ZodString;
|
|
43
|
+
occurredAt: z.ZodISODateTime;
|
|
44
|
+
direction: z.ZodEnum<{
|
|
45
|
+
inbound: "inbound";
|
|
46
|
+
outbound: "outbound";
|
|
47
|
+
mixed: "mixed";
|
|
48
|
+
unknown: "unknown";
|
|
49
|
+
}>;
|
|
50
|
+
isGroup: z.ZodBoolean;
|
|
51
|
+
payloadHash: z.ZodString;
|
|
52
|
+
backfillBatchId: z.ZodOptional<z.ZodUUID>;
|
|
53
|
+
watermarkRef: z.ZodOptional<z.ZodString>;
|
|
54
|
+
eventKind: z.ZodLiteral<"imessage.message">;
|
|
55
|
+
source: z.ZodLiteral<"imessage">;
|
|
56
|
+
conversationRef: z.ZodString;
|
|
57
|
+
participants: z.ZodArray<z.ZodObject<{
|
|
58
|
+
externalIdentityRef: z.ZodString;
|
|
59
|
+
role: z.ZodEnum<{
|
|
60
|
+
sender: "sender";
|
|
61
|
+
recipient: "recipient";
|
|
62
|
+
participant: "participant";
|
|
63
|
+
owner: "owner";
|
|
64
|
+
}>;
|
|
65
|
+
}, z.core.$strict>>;
|
|
66
|
+
safeMetadata: z.ZodDefault<z.ZodObject<{
|
|
67
|
+
message_count: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
inbound_count: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
outbound_count: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
participant_count: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
service: z.ZodOptional<z.ZodEnum<{
|
|
72
|
+
imessage: "imessage";
|
|
73
|
+
sms: "sms";
|
|
74
|
+
}>>;
|
|
75
|
+
chat_kind: z.ZodOptional<z.ZodEnum<{
|
|
76
|
+
dm: "dm";
|
|
77
|
+
group: "group";
|
|
78
|
+
}>>;
|
|
79
|
+
has_attachments: z.ZodOptional<z.ZodBoolean>;
|
|
80
|
+
attachment_count: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
reaction_only_count: z.ZodOptional<z.ZodNumber>;
|
|
82
|
+
session_candidate_id: z.ZodOptional<z.ZodString>;
|
|
83
|
+
source_schema_version: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, z.core.$strict>>;
|
|
85
|
+
}, z.core.$strict>;
|
|
86
|
+
export declare const callEnvelopeSchema: z.ZodObject<{
|
|
87
|
+
direction: z.ZodEnum<{
|
|
88
|
+
inbound: "inbound";
|
|
89
|
+
outbound: "outbound";
|
|
90
|
+
}>;
|
|
91
|
+
bridgeId: z.ZodString;
|
|
92
|
+
sourceEventId: z.ZodString;
|
|
93
|
+
occurredAt: z.ZodISODateTime;
|
|
94
|
+
isGroup: z.ZodBoolean;
|
|
95
|
+
payloadHash: z.ZodString;
|
|
96
|
+
backfillBatchId: z.ZodOptional<z.ZodUUID>;
|
|
97
|
+
watermarkRef: z.ZodOptional<z.ZodString>;
|
|
98
|
+
eventKind: z.ZodLiteral<"call.event">;
|
|
99
|
+
source: z.ZodLiteral<"call_history">;
|
|
100
|
+
conversationRef: z.ZodString;
|
|
101
|
+
participants: z.ZodArray<z.ZodObject<{
|
|
102
|
+
externalIdentityRef: z.ZodString;
|
|
103
|
+
role: z.ZodEnum<{
|
|
104
|
+
sender: "sender";
|
|
105
|
+
recipient: "recipient";
|
|
106
|
+
participant: "participant";
|
|
107
|
+
owner: "owner";
|
|
108
|
+
}>;
|
|
109
|
+
}, z.core.$strict>>;
|
|
110
|
+
safeMetadata: z.ZodObject<{
|
|
111
|
+
call_seconds: z.ZodNumber;
|
|
112
|
+
call_service: z.ZodEnum<{
|
|
113
|
+
cellular: "cellular";
|
|
114
|
+
facetime_audio: "facetime_audio";
|
|
115
|
+
facetime_video: "facetime_video";
|
|
116
|
+
}>;
|
|
117
|
+
answered: z.ZodBoolean;
|
|
118
|
+
}, z.core.$strict>;
|
|
119
|
+
}, z.core.$strict>;
|
|
120
|
+
export declare const bridgeEnvelopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
121
|
+
bridgeId: z.ZodString;
|
|
122
|
+
sourceEventId: z.ZodString;
|
|
123
|
+
occurredAt: z.ZodISODateTime;
|
|
124
|
+
direction: z.ZodEnum<{
|
|
125
|
+
inbound: "inbound";
|
|
126
|
+
outbound: "outbound";
|
|
127
|
+
mixed: "mixed";
|
|
128
|
+
unknown: "unknown";
|
|
129
|
+
}>;
|
|
130
|
+
isGroup: z.ZodBoolean;
|
|
131
|
+
payloadHash: z.ZodString;
|
|
132
|
+
backfillBatchId: z.ZodOptional<z.ZodUUID>;
|
|
133
|
+
watermarkRef: z.ZodOptional<z.ZodString>;
|
|
134
|
+
eventKind: z.ZodLiteral<"imessage.message">;
|
|
135
|
+
source: z.ZodLiteral<"imessage">;
|
|
136
|
+
conversationRef: z.ZodString;
|
|
137
|
+
participants: z.ZodArray<z.ZodObject<{
|
|
138
|
+
externalIdentityRef: z.ZodString;
|
|
139
|
+
role: z.ZodEnum<{
|
|
140
|
+
sender: "sender";
|
|
141
|
+
recipient: "recipient";
|
|
142
|
+
participant: "participant";
|
|
143
|
+
owner: "owner";
|
|
144
|
+
}>;
|
|
145
|
+
}, z.core.$strict>>;
|
|
146
|
+
safeMetadata: z.ZodDefault<z.ZodObject<{
|
|
147
|
+
message_count: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
inbound_count: z.ZodOptional<z.ZodNumber>;
|
|
149
|
+
outbound_count: z.ZodOptional<z.ZodNumber>;
|
|
150
|
+
participant_count: z.ZodOptional<z.ZodNumber>;
|
|
151
|
+
service: z.ZodOptional<z.ZodEnum<{
|
|
152
|
+
imessage: "imessage";
|
|
153
|
+
sms: "sms";
|
|
154
|
+
}>>;
|
|
155
|
+
chat_kind: z.ZodOptional<z.ZodEnum<{
|
|
156
|
+
dm: "dm";
|
|
157
|
+
group: "group";
|
|
158
|
+
}>>;
|
|
159
|
+
has_attachments: z.ZodOptional<z.ZodBoolean>;
|
|
160
|
+
attachment_count: z.ZodOptional<z.ZodNumber>;
|
|
161
|
+
reaction_only_count: z.ZodOptional<z.ZodNumber>;
|
|
162
|
+
session_candidate_id: z.ZodOptional<z.ZodString>;
|
|
163
|
+
source_schema_version: z.ZodOptional<z.ZodString>;
|
|
164
|
+
}, z.core.$strict>>;
|
|
165
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
166
|
+
direction: z.ZodEnum<{
|
|
167
|
+
inbound: "inbound";
|
|
168
|
+
outbound: "outbound";
|
|
169
|
+
}>;
|
|
170
|
+
bridgeId: z.ZodString;
|
|
171
|
+
sourceEventId: z.ZodString;
|
|
172
|
+
occurredAt: z.ZodISODateTime;
|
|
173
|
+
isGroup: z.ZodBoolean;
|
|
174
|
+
payloadHash: z.ZodString;
|
|
175
|
+
backfillBatchId: z.ZodOptional<z.ZodUUID>;
|
|
176
|
+
watermarkRef: z.ZodOptional<z.ZodString>;
|
|
177
|
+
eventKind: z.ZodLiteral<"call.event">;
|
|
178
|
+
source: z.ZodLiteral<"call_history">;
|
|
179
|
+
conversationRef: z.ZodString;
|
|
180
|
+
participants: z.ZodArray<z.ZodObject<{
|
|
181
|
+
externalIdentityRef: z.ZodString;
|
|
182
|
+
role: z.ZodEnum<{
|
|
183
|
+
sender: "sender";
|
|
184
|
+
recipient: "recipient";
|
|
185
|
+
participant: "participant";
|
|
186
|
+
owner: "owner";
|
|
187
|
+
}>;
|
|
188
|
+
}, z.core.$strict>>;
|
|
189
|
+
safeMetadata: z.ZodObject<{
|
|
190
|
+
call_seconds: z.ZodNumber;
|
|
191
|
+
call_service: z.ZodEnum<{
|
|
192
|
+
cellular: "cellular";
|
|
193
|
+
facetime_audio: "facetime_audio";
|
|
194
|
+
facetime_video: "facetime_video";
|
|
195
|
+
}>;
|
|
196
|
+
answered: z.ZodBoolean;
|
|
197
|
+
}, z.core.$strict>;
|
|
198
|
+
}, z.core.$strict>], "eventKind">;
|
|
199
|
+
export type BridgeEnvelope = z.infer<typeof bridgeEnvelopeSchema>;
|
|
200
|
+
export type CoreEnvelope = z.infer<typeof imessageEnvelopeSchema>;
|
|
201
|
+
export type CallEnvelope = z.infer<typeof callEnvelopeSchema>;
|
|
202
|
+
export type SafeMetadata = z.infer<typeof imessageSafeMetadataSchema>;
|
|
203
|
+
export declare function parseBridgeEnvelope(value: unknown): BridgeEnvelope;
|
|
204
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,iBAAiB;;;;;;;;kBAG5B,CAAC;AAKH,eAAO,MAAM,kBAAkB,2NAYrB,CAAC;AAEX,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;kBAYrC,CAAC;AAKH,eAAO,MAAM,sBAAsB;;;;;;;;kBAIjC,CAAC;AAsBH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAQjC,CAAC;AAOH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkB7B,CAAC;AAKH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAG/B,CAAC;AAOH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAElE"}
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Canonical ingestion-envelope contract shared by the Core (crm) and the Mac bridge
|
|
2
|
+
// (imap-mcp). Single source of truth — was hand-mirrored across the two packages.
|
|
3
|
+
// Pure types/schemas, no I/O. Authored in Zod v4 idiom.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Participant — shared by both envelope variants.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const participantSchema = z.strictObject({
|
|
9
|
+
externalIdentityRef: z.string().min(1),
|
|
10
|
+
role: z.enum(['sender', 'recipient', 'participant', 'owner']),
|
|
11
|
+
});
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// iMessage safe-metadata allowlist (key list kept for consumers that enumerate it).
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const SAFE_METADATA_KEYS = [
|
|
16
|
+
'message_count',
|
|
17
|
+
'inbound_count',
|
|
18
|
+
'outbound_count',
|
|
19
|
+
'participant_count',
|
|
20
|
+
'service',
|
|
21
|
+
'chat_kind',
|
|
22
|
+
'has_attachments',
|
|
23
|
+
'attachment_count',
|
|
24
|
+
'reaction_only_count',
|
|
25
|
+
'session_candidate_id',
|
|
26
|
+
'source_schema_version',
|
|
27
|
+
];
|
|
28
|
+
export const imessageSafeMetadataSchema = z.strictObject({
|
|
29
|
+
message_count: z.number().int().nonnegative().optional(),
|
|
30
|
+
inbound_count: z.number().int().nonnegative().optional(),
|
|
31
|
+
outbound_count: z.number().int().nonnegative().optional(),
|
|
32
|
+
participant_count: z.number().int().positive().optional(),
|
|
33
|
+
service: z.enum(['imessage', 'sms']).optional(),
|
|
34
|
+
chat_kind: z.enum(['dm', 'group']).optional(),
|
|
35
|
+
has_attachments: z.boolean().optional(),
|
|
36
|
+
attachment_count: z.number().int().nonnegative().optional(),
|
|
37
|
+
reaction_only_count: z.number().int().nonnegative().optional(),
|
|
38
|
+
session_candidate_id: z.string().min(1).optional(),
|
|
39
|
+
source_schema_version: z.string().min(1).optional(),
|
|
40
|
+
});
|
|
41
|
+
// Call safe metadata — all three REQUIRED and load-bearing at promotion (a missing
|
|
42
|
+
// `answered` would silently drop a connected inbound call; a missing `call_service`
|
|
43
|
+
// would mislabel a cellular call as FaceTime), so the Core fails closed.
|
|
44
|
+
export const callSafeMetadataSchema = z.strictObject({
|
|
45
|
+
call_seconds: z.number().int().nonnegative(),
|
|
46
|
+
call_service: z.enum(['cellular', 'facetime_audio', 'facetime_video']),
|
|
47
|
+
answered: z.boolean(),
|
|
48
|
+
});
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Common fields shared by every envelope variant. Defined as a plain object (not
|
|
51
|
+
// z.object) so it can be spread into each strictObject variant.
|
|
52
|
+
// `occurredAt` accepts ISO 8601 with Z or ±HH:MM offset; `backfillBatchId` is an
|
|
53
|
+
// RFC-9562 UUID. (Zod v4 enforces both more strictly than v3 did — RFC-conformant.)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const commonFields = {
|
|
56
|
+
bridgeId: z.string().min(1),
|
|
57
|
+
sourceEventId: z.string().min(1),
|
|
58
|
+
occurredAt: z.iso.datetime({ offset: true }),
|
|
59
|
+
direction: z.enum(['inbound', 'outbound', 'mixed', 'unknown']),
|
|
60
|
+
isGroup: z.boolean(),
|
|
61
|
+
payloadHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
62
|
+
backfillBatchId: z.uuid().optional(),
|
|
63
|
+
watermarkRef: z.string().min(1).optional(),
|
|
64
|
+
};
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// iMessage envelope member.
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
export const imessageEnvelopeSchema = z.strictObject({
|
|
69
|
+
eventKind: z.literal('imessage.message'),
|
|
70
|
+
source: z.literal('imessage'),
|
|
71
|
+
conversationRef: z.string().min(1),
|
|
72
|
+
participants: z.array(participantSchema).min(1),
|
|
73
|
+
// Optional at parse time (the builder always emits it); defaults to {}.
|
|
74
|
+
safeMetadata: imessageSafeMetadataSchema.default({}),
|
|
75
|
+
...commonFields,
|
|
76
|
+
});
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Call envelope member. The "exactly one owner + one participant" constraint is a
|
|
79
|
+
// .refine() on the `participants` FIELD (not the object), so the member stays a
|
|
80
|
+
// ZodObject and is valid inside z.discriminatedUnion.
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export const callEnvelopeSchema = z.strictObject({
|
|
83
|
+
eventKind: z.literal('call.event'),
|
|
84
|
+
source: z.literal('call_history'),
|
|
85
|
+
conversationRef: z.string().regex(/^call:/),
|
|
86
|
+
participants: z
|
|
87
|
+
.array(participantSchema)
|
|
88
|
+
.refine((arr) => arr.length === 2 &&
|
|
89
|
+
arr.filter((p) => p.role === 'owner').length === 1 &&
|
|
90
|
+
arr.filter((p) => p.role === 'participant').length === 1, { message: 'call envelope must be 1:1 — exactly one owner and one participant' }),
|
|
91
|
+
safeMetadata: callSafeMetadataSchema,
|
|
92
|
+
...commonFields,
|
|
93
|
+
// A call is one-directional; narrow the shared 4-value direction to the two values a
|
|
94
|
+
// call can carry. Placed after the spread so it overrides commonFields.direction.
|
|
95
|
+
direction: z.enum(['inbound', 'outbound']),
|
|
96
|
+
});
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Discriminated union — the single schema the Core parses for all variants.
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
export const bridgeEnvelopeSchema = z.discriminatedUnion('eventKind', [
|
|
101
|
+
imessageEnvelopeSchema,
|
|
102
|
+
callEnvelopeSchema,
|
|
103
|
+
]);
|
|
104
|
+
export function parseBridgeEnvelope(value) {
|
|
105
|
+
return bridgeEnvelopeSchema.parse(value);
|
|
106
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './envelope.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rkt-tools/contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared ingestion-envelope Zod schemas for the rkt stack",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"prepublishOnly": "pnpm run build && pnpm run test"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"zod": "^4.4.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^9.39.4",
|
|
36
|
+
"@types/node": "^22.10.2",
|
|
37
|
+
"eslint": "^9.39.4",
|
|
38
|
+
"eslint-config-prettier": "^9.1.2",
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"typescript-eslint": "^8.61.1",
|
|
41
|
+
"vitest": "^4.1.6"
|
|
42
|
+
}
|
|
43
|
+
}
|