@company-semantics/contracts 0.42.0 → 0.43.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/package.json +5 -1
- package/src/email/README.md +35 -0
- package/src/email/index.ts +30 -0
- package/src/email/registry.ts +105 -0
- package/src/email/types.ts +74 -0
- package/src/index.ts +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"types": "./src/execution/index.ts",
|
|
33
33
|
"default": "./src/execution/index.ts"
|
|
34
34
|
},
|
|
35
|
+
"./email": {
|
|
36
|
+
"types": "./src/email/index.ts",
|
|
37
|
+
"default": "./src/email/index.ts"
|
|
38
|
+
},
|
|
35
39
|
"./schemas/guard-result.schema.json": "./schemas/guard-result.schema.json"
|
|
36
40
|
},
|
|
37
41
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# email/
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
TypeScript types and registry for transactional email kinds and their metadata.
|
|
6
|
+
|
|
7
|
+
## Invariants
|
|
8
|
+
|
|
9
|
+
- Types only; minimal runtime code (pure functions for registry lookup)
|
|
10
|
+
- No external dependencies (contracts policy)
|
|
11
|
+
- `EmailKind` union MUST match keys in `EMAIL_KINDS` registry
|
|
12
|
+
- `EMAIL_KINDS` is the single source of truth for subjects and rendering rules
|
|
13
|
+
- OTP values MUST NEVER appear in contracts (security: logged if leaked)
|
|
14
|
+
- Subjects are owned by the registry, never duplicated in templates
|
|
15
|
+
- Unknown email kinds MUST be rejected at API boundaries via `isValidEmailKind()`
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
### Types
|
|
20
|
+
|
|
21
|
+
- `EmailKind` — Union of valid email kind identifiers
|
|
22
|
+
- `EmailPayloads` — Type-safe payload mapping for each kind
|
|
23
|
+
- `SendEmailInput<K>` — Type-safe input for sending emails
|
|
24
|
+
- `EmailKindDefinition` — Full definition schema for a kind
|
|
25
|
+
|
|
26
|
+
### Registry & Functions
|
|
27
|
+
|
|
28
|
+
- `EMAIL_KINDS` — Registry of all email kinds
|
|
29
|
+
- `getEmailKindDefinition(kind)` — Type-safe registry lookup
|
|
30
|
+
- `isValidEmailKind(kind)` — Validate string is EmailKind
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
**Imports from:** (none — leaf module)
|
|
35
|
+
**Imported by:** company-semantics-backend (email service, templates)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Domain Barrel
|
|
3
|
+
*
|
|
4
|
+
* Re-exports email kind vocabulary types and registry.
|
|
5
|
+
* Import from '@company-semantics/contracts/email'.
|
|
6
|
+
*
|
|
7
|
+
* @see ADR-CONT-034 for design rationale
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Kind Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
export type { EmailKind, EmailPayloads, SendEmailInput } from './types'
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Definition Types
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
export type { EmailKindDefinition } from './registry'
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Registry
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
EMAIL_KINDS,
|
|
28
|
+
getEmailKindDefinition,
|
|
29
|
+
isValidEmailKind,
|
|
30
|
+
} from './registry'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Kind Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry of all email kinds and their definitions.
|
|
5
|
+
* This is the single source of truth for email metadata.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - Every EmailKind MUST have an entry in EMAIL_KINDS
|
|
9
|
+
* - Registry keys MUST match definition.kind
|
|
10
|
+
* - Registry is exhaustive (satisfies Record<EmailKind, ...>)
|
|
11
|
+
* - Subjects are owned here, not duplicated in templates
|
|
12
|
+
*
|
|
13
|
+
* @see ADR-CONT-034 for design rationale
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { EmailKind } from './types'
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Email Kind Definition
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Complete definition for an email kind.
|
|
24
|
+
*
|
|
25
|
+
* This interface is the schema for entries in EMAIL_KINDS registry.
|
|
26
|
+
* It captures subject, rendering requirements, and domain metadata.
|
|
27
|
+
*
|
|
28
|
+
* Invariants:
|
|
29
|
+
* - kind field MUST match the registry key
|
|
30
|
+
* - subject is the authoritative source (templates import from here)
|
|
31
|
+
*/
|
|
32
|
+
export interface EmailKindDefinition {
|
|
33
|
+
/** The email kind this definition describes */
|
|
34
|
+
kind: EmailKind
|
|
35
|
+
/** Email subject line (single source of truth) */
|
|
36
|
+
subject: string
|
|
37
|
+
/** Whether plain text body is required */
|
|
38
|
+
plainTextRequired: boolean
|
|
39
|
+
/** Whether HTML body is supported */
|
|
40
|
+
htmlSupported: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Registry
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* EMAIL_KINDS is the authoritative registry.
|
|
49
|
+
*
|
|
50
|
+
* All subjects, rendering rules, and domain metadata are derived from here.
|
|
51
|
+
* Backend email templates MUST use this registry for subjects
|
|
52
|
+
* rather than hardcoding values.
|
|
53
|
+
*
|
|
54
|
+
* To add a new kind:
|
|
55
|
+
* 1. Add to EmailKind union in types.ts
|
|
56
|
+
* 2. Add entry to this registry
|
|
57
|
+
* 3. Add payload to EmailPayloads if needed
|
|
58
|
+
* 4. Implement template in backend
|
|
59
|
+
*/
|
|
60
|
+
export const EMAIL_KINDS = {
|
|
61
|
+
'auth.otp': {
|
|
62
|
+
kind: 'auth.otp',
|
|
63
|
+
subject: 'Your login code',
|
|
64
|
+
plainTextRequired: true,
|
|
65
|
+
htmlSupported: false,
|
|
66
|
+
},
|
|
67
|
+
'auth.magic_link': {
|
|
68
|
+
kind: 'auth.magic_link',
|
|
69
|
+
subject: 'Your login link',
|
|
70
|
+
plainTextRequired: true,
|
|
71
|
+
htmlSupported: false,
|
|
72
|
+
},
|
|
73
|
+
'org.invite': {
|
|
74
|
+
kind: 'org.invite',
|
|
75
|
+
subject: 'You have been invited to join a workspace',
|
|
76
|
+
plainTextRequired: true,
|
|
77
|
+
htmlSupported: true,
|
|
78
|
+
},
|
|
79
|
+
'security.alert': {
|
|
80
|
+
kind: 'security.alert',
|
|
81
|
+
subject: 'Security alert for your account',
|
|
82
|
+
plainTextRequired: true,
|
|
83
|
+
htmlSupported: false,
|
|
84
|
+
},
|
|
85
|
+
} as const satisfies Record<EmailKind, EmailKindDefinition>
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Registry Helpers
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Type-safe registry lookup.
|
|
93
|
+
* Returns the definition for a given email kind.
|
|
94
|
+
*/
|
|
95
|
+
export function getEmailKindDefinition(kind: EmailKind): EmailKindDefinition {
|
|
96
|
+
return EMAIL_KINDS[kind]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if a string is a valid EmailKind.
|
|
101
|
+
* Use at API boundaries to reject unknown kinds.
|
|
102
|
+
*/
|
|
103
|
+
export function isValidEmailKind(kind: string): kind is EmailKind {
|
|
104
|
+
return kind in EMAIL_KINDS
|
|
105
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Domain Types
|
|
3
|
+
*
|
|
4
|
+
* Shared types for transactional email across Company Semantics codebases.
|
|
5
|
+
* Types only - no runtime code, no business logic.
|
|
6
|
+
*
|
|
7
|
+
* @see ADR-CONT-034 for design rationale
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// EmailKind Union
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* EmailKind identifies the type of transactional email.
|
|
16
|
+
*
|
|
17
|
+
* Naming convention: `{domain}.{type}`
|
|
18
|
+
* - domain: auth, org, security
|
|
19
|
+
* - type: specific email variant
|
|
20
|
+
*
|
|
21
|
+
* New kinds MUST be added to:
|
|
22
|
+
* 1. This union type
|
|
23
|
+
* 2. EMAIL_KINDS registry in registry.ts
|
|
24
|
+
* 3. EmailPayloads interface (if kind has payload)
|
|
25
|
+
*/
|
|
26
|
+
export type EmailKind =
|
|
27
|
+
| 'auth.otp'
|
|
28
|
+
| 'auth.magic_link' // future
|
|
29
|
+
| 'org.invite' // future
|
|
30
|
+
| 'security.alert' // future
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// Email Payloads
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Type-safe payload mapping for each email kind.
|
|
38
|
+
*
|
|
39
|
+
* Each key is an EmailKind, and the value is the required payload shape.
|
|
40
|
+
* Kinds without entries here have empty payloads.
|
|
41
|
+
*/
|
|
42
|
+
export interface EmailPayloads {
|
|
43
|
+
'auth.otp': {
|
|
44
|
+
/** The 6-digit OTP code */
|
|
45
|
+
otp: string
|
|
46
|
+
/** How long until the code expires */
|
|
47
|
+
expiresInMinutes: number
|
|
48
|
+
/** IP address of the request (for security context) */
|
|
49
|
+
requestIp?: string
|
|
50
|
+
/** User agent of the request (for security context) */
|
|
51
|
+
userAgent?: string
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Send Email Input
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Type-safe input for sending emails.
|
|
61
|
+
*
|
|
62
|
+
* The payload type is inferred from the kind.
|
|
63
|
+
* If the kind is in EmailPayloads, that payload is required.
|
|
64
|
+
*/
|
|
65
|
+
export interface SendEmailInput<K extends EmailKind> {
|
|
66
|
+
/** The type of email to send */
|
|
67
|
+
kind: K
|
|
68
|
+
/** Recipient email address (will be normalized) */
|
|
69
|
+
to: string
|
|
70
|
+
/** Type-safe payload for this email kind */
|
|
71
|
+
payload: K extends keyof EmailPayloads ? EmailPayloads[K] : never
|
|
72
|
+
/** Idempotency key to prevent duplicate sends */
|
|
73
|
+
idempotencyKey: string
|
|
74
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -112,6 +112,21 @@ export { extractFirstWord, resolveDisplayName, deriveFullName } from './identity
|
|
|
112
112
|
// Auth domain types
|
|
113
113
|
export { OTPErrorCode } from './auth/index'
|
|
114
114
|
|
|
115
|
+
// Email domain types
|
|
116
|
+
// @see ADR-CONT-034 for design rationale
|
|
117
|
+
export type {
|
|
118
|
+
EmailKind,
|
|
119
|
+
EmailPayloads,
|
|
120
|
+
SendEmailInput,
|
|
121
|
+
EmailKindDefinition,
|
|
122
|
+
} from './email/index'
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
EMAIL_KINDS,
|
|
126
|
+
getEmailKindDefinition,
|
|
127
|
+
isValidEmailKind,
|
|
128
|
+
} from './email/index'
|
|
129
|
+
|
|
115
130
|
// Organization domain types
|
|
116
131
|
// @see ADR-BE-XXX for design rationale (Personal vs Shared Organization Model)
|
|
117
132
|
export type {
|