@prmichaelsen/agentbase-core 0.1.1 → 0.1.2

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 (143) hide show
  1. package/README.md +204 -87
  2. package/dist/client/api-types.generated.d.ts +4024 -0
  3. package/dist/client/api-types.generated.d.ts.map +1 -0
  4. package/dist/client/api-types.generated.js +6 -0
  5. package/dist/client/api-types.generated.js.map +1 -0
  6. package/dist/client/app.d.ts +126 -0
  7. package/dist/client/app.d.ts.map +1 -0
  8. package/dist/client/app.js +107 -0
  9. package/dist/client/app.js.map +1 -0
  10. package/dist/client/http-transport.d.ts +42 -0
  11. package/dist/client/http-transport.d.ts.map +1 -0
  12. package/dist/client/http-transport.js +113 -0
  13. package/dist/client/http-transport.js.map +1 -0
  14. package/dist/client/index.d.ts +6 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/index.js +9 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/client/oauth.d.ts +86 -0
  19. package/dist/client/oauth.d.ts.map +1 -0
  20. package/dist/client/oauth.js +148 -0
  21. package/dist/client/oauth.js.map +1 -0
  22. package/dist/client/svc.d.ts +511 -0
  23. package/dist/client/svc.d.ts.map +1 -0
  24. package/dist/client/svc.js +409 -0
  25. package/dist/client/svc.js.map +1 -0
  26. package/dist/config/index.d.ts +34 -0
  27. package/dist/config/index.d.ts.map +1 -0
  28. package/dist/config/index.js +50 -0
  29. package/dist/config/index.js.map +1 -0
  30. package/dist/errors/app-errors.d.ts +34 -0
  31. package/dist/errors/app-errors.d.ts.map +1 -0
  32. package/dist/errors/app-errors.js +34 -0
  33. package/dist/errors/app-errors.js.map +1 -0
  34. package/dist/errors/base.error.d.ts +8 -0
  35. package/dist/errors/base.error.d.ts.map +1 -0
  36. package/dist/errors/base.error.js +9 -0
  37. package/dist/errors/base.error.js.map +1 -0
  38. package/dist/errors/index.d.ts +6 -0
  39. package/dist/errors/index.d.ts.map +1 -0
  40. package/dist/errors/index.js +12 -0
  41. package/dist/errors/index.js.map +1 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/auth/error-handler.d.ts +6 -0
  47. package/dist/lib/auth/error-handler.d.ts.map +1 -0
  48. package/dist/lib/auth/error-handler.js +18 -0
  49. package/dist/lib/auth/error-handler.js.map +1 -0
  50. package/dist/lib/auth/guards.d.ts +5 -6
  51. package/dist/lib/auth/guards.d.ts.map +1 -1
  52. package/dist/lib/auth/guards.js +7 -18
  53. package/dist/lib/auth/guards.js.map +1 -1
  54. package/dist/lib/auth/index.d.ts +1 -0
  55. package/dist/lib/auth/index.d.ts.map +1 -1
  56. package/dist/lib/auth/index.js +1 -0
  57. package/dist/lib/auth/index.js.map +1 -1
  58. package/dist/lib/auth/session.d.ts.map +1 -1
  59. package/dist/lib/auth/session.js +8 -7
  60. package/dist/lib/auth/session.js.map +1 -1
  61. package/dist/lib/rate-limiter.d.ts.map +1 -1
  62. package/dist/lib/rate-limiter.js +2 -1
  63. package/dist/lib/rate-limiter.js.map +1 -1
  64. package/dist/services/auth.interface.d.ts +15 -0
  65. package/dist/services/auth.interface.d.ts.map +1 -0
  66. package/dist/services/auth.interface.js +2 -0
  67. package/dist/services/auth.interface.js.map +1 -0
  68. package/dist/services/base.service.d.ts +17 -1
  69. package/dist/services/base.service.d.ts.map +1 -1
  70. package/dist/services/base.service.js +26 -3
  71. package/dist/services/base.service.js.map +1 -1
  72. package/dist/services/confirmation-token.service.d.ts +5 -1
  73. package/dist/services/confirmation-token.service.d.ts.map +1 -1
  74. package/dist/services/confirmation-token.service.js.map +1 -1
  75. package/dist/services/index.d.ts +3 -2
  76. package/dist/services/index.d.ts.map +1 -1
  77. package/dist/services/index.js +1 -1
  78. package/dist/services/index.js.map +1 -1
  79. package/dist/types/branded.d.ts +24 -0
  80. package/dist/types/branded.d.ts.map +1 -0
  81. package/dist/types/branded.js +9 -0
  82. package/dist/types/branded.js.map +1 -0
  83. package/dist/types/index.d.ts +4 -0
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/index.js +3 -1
  86. package/dist/types/index.js.map +1 -1
  87. package/dist/types/result.d.ts +20 -0
  88. package/dist/types/result.d.ts.map +1 -0
  89. package/dist/types/result.js +44 -0
  90. package/dist/types/result.js.map +1 -0
  91. package/package.json +16 -2
  92. package/dist/lib/auth/guards.test.d.ts +0 -2
  93. package/dist/lib/auth/guards.test.d.ts.map +0 -1
  94. package/dist/lib/auth/guards.test.js +0 -105
  95. package/dist/lib/auth/guards.test.js.map +0 -1
  96. package/dist/lib/auth/helpers.test.d.ts +0 -2
  97. package/dist/lib/auth/helpers.test.d.ts.map +0 -1
  98. package/dist/lib/auth/helpers.test.js +0 -43
  99. package/dist/lib/auth/helpers.test.js.map +0 -1
  100. package/dist/lib/auth/session.test.d.ts +0 -2
  101. package/dist/lib/auth/session.test.d.ts.map +0 -1
  102. package/dist/lib/auth/session.test.js +0 -114
  103. package/dist/lib/auth/session.test.js.map +0 -1
  104. package/dist/lib/firebase-admin.test.d.ts +0 -2
  105. package/dist/lib/firebase-admin.test.d.ts.map +0 -1
  106. package/dist/lib/firebase-admin.test.js +0 -36
  107. package/dist/lib/firebase-admin.test.js.map +0 -1
  108. package/dist/lib/firebase-client.test.d.ts +0 -2
  109. package/dist/lib/firebase-client.test.d.ts.map +0 -1
  110. package/dist/lib/firebase-client.test.js +0 -167
  111. package/dist/lib/firebase-client.test.js.map +0 -1
  112. package/dist/lib/format-time.test.d.ts +0 -2
  113. package/dist/lib/format-time.test.d.ts.map +0 -1
  114. package/dist/lib/format-time.test.js +0 -41
  115. package/dist/lib/format-time.test.js.map +0 -1
  116. package/dist/lib/linkify.test.d.ts +0 -2
  117. package/dist/lib/linkify.test.d.ts.map +0 -1
  118. package/dist/lib/linkify.test.js +0 -40
  119. package/dist/lib/linkify.test.js.map +0 -1
  120. package/dist/lib/logger.test.d.ts +0 -2
  121. package/dist/lib/logger.test.d.ts.map +0 -1
  122. package/dist/lib/logger.test.js +0 -167
  123. package/dist/lib/logger.test.js.map +0 -1
  124. package/dist/lib/rate-limiter.test.d.ts +0 -2
  125. package/dist/lib/rate-limiter.test.d.ts.map +0 -1
  126. package/dist/lib/rate-limiter.test.js +0 -70
  127. package/dist/lib/rate-limiter.test.js.map +0 -1
  128. package/dist/lib/uuid.test.d.ts +0 -2
  129. package/dist/lib/uuid.test.d.ts.map +0 -1
  130. package/dist/lib/uuid.test.js +0 -22
  131. package/dist/lib/uuid.test.js.map +0 -1
  132. package/dist/services/base.service.test.d.ts +0 -2
  133. package/dist/services/base.service.test.d.ts.map +0 -1
  134. package/dist/services/base.service.test.js +0 -62
  135. package/dist/services/base.service.test.js.map +0 -1
  136. package/dist/services/confirmation-token.service.test.d.ts +0 -2
  137. package/dist/services/confirmation-token.service.test.d.ts.map +0 -1
  138. package/dist/services/confirmation-token.service.test.js +0 -65
  139. package/dist/services/confirmation-token.service.test.js.map +0 -1
  140. package/dist/smoke.test.d.ts +0 -2
  141. package/dist/smoke.test.d.ts.map +0 -1
  142. package/dist/smoke.test.js +0 -7
  143. package/dist/smoke.test.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @prmichaelsen/agentbase-core
2
2
 
3
- Shared service infrastructure for agentbase projects — BaseService, auth, Firebase wrappers, logging, and common utilities.
3
+ Shared service infrastructure for agentbase projects — BaseService, auth, Firebase wrappers, logging, typed errors, Result type, client SDK, and common utilities.
4
4
 
5
5
  ## Installation
6
6
 
@@ -25,33 +25,40 @@ import { ... } from '@prmichaelsen/agentbase-core' // everything
25
25
  import { ... } from '@prmichaelsen/agentbase-core/services' // BaseService, ConfirmationTokenService
26
26
  import { ... } from '@prmichaelsen/agentbase-core/lib' // logger, firebase, utilities
27
27
  import { ... } from '@prmichaelsen/agentbase-core/lib/auth' // server auth guards
28
- import type { ... } from '@prmichaelsen/agentbase-core/types' // AuthUser, ServerSession, AuthResult
28
+ import { ... } from '@prmichaelsen/agentbase-core/types' // AuthUser, Result, branded types
29
+ import { ... } from '@prmichaelsen/agentbase-core/client' // HTTP client, SvcClient, AppClient, OAuth
30
+ import { ... } from '@prmichaelsen/agentbase-core/config' // loadConfig, validateConfig
29
31
  ```
30
32
 
31
33
  ## Services
32
34
 
33
35
  ### BaseService
34
36
 
35
- Abstract base class for all services. Provides config injection, structured logging, and lifecycle hooks.
37
+ Abstract base class with config injection, structured logging, lifecycle hooks, and state tracking.
36
38
 
37
39
  ```ts
38
- import { BaseService, type Logger } from '@prmichaelsen/agentbase-core/services'
40
+ import { BaseService, ServiceState, type Logger } from '@prmichaelsen/agentbase-core/services'
39
41
 
40
- interface MyConfig { dbUrl: string }
41
-
42
- class UserService extends BaseService<MyConfig> {
43
- constructor(config: MyConfig, logger: Logger) {
42
+ class UserService extends BaseService<{ dbUrl: string }> {
43
+ constructor(config: { dbUrl: string }, logger: Logger) {
44
44
  super(config, logger)
45
45
  }
46
46
 
47
47
  async initialize() {
48
- this.logger.info('Connecting to database', { url: this.config.dbUrl })
48
+ await super.initialize() // sets state to Initialized
49
+ this.logger.info('Connected', { url: this.config.dbUrl })
49
50
  }
50
51
 
51
- async shutdown() {
52
- this.logger.info('Closing connections')
52
+ async findUser(id: string) {
53
+ this.ensureInitialized() // throws if not initialized
54
+ // ...
53
55
  }
54
56
  }
57
+
58
+ const svc = new UserService({ dbUrl: '...' }, logger)
59
+ svc.getState() // ServiceState.Uninitialized
60
+ await svc.initialize()
61
+ svc.getState() // ServiceState.Initialized
55
62
  ```
56
63
 
57
64
  ### ConfirmationTokenService
@@ -74,9 +81,76 @@ const token = confirmations.generateToken({
74
81
 
75
82
  // Step 2: Consume token (single-use)
76
83
  const action = confirmations.consumeToken(token, 'user-123')
77
- if (action) {
78
- // Execute the confirmed action
84
+ if (action) { /* execute */ }
85
+ ```
86
+
87
+ ## Error Types
88
+
89
+ Typed error hierarchy with discriminated `kind` field and HTTP status mapping.
90
+
91
+ ```ts
92
+ import {
93
+ UnauthorizedError, ForbiddenError, ValidationError, NotFoundError,
94
+ ConflictError, RateLimitError, ExternalError, InternalError,
95
+ isAppError, errorToStatusCode,
96
+ } from '@prmichaelsen/agentbase-core'
97
+
98
+ // Throw typed errors
99
+ throw new ValidationError('Email is required', { field: 'email' })
100
+ throw new NotFoundError('User not found')
101
+
102
+ // Type guard
103
+ try {
104
+ await doSomething()
105
+ } catch (err) {
106
+ if (isAppError(err)) {
107
+ console.log(err.kind) // 'VALIDATION' | 'NOT_FOUND' | ...
108
+ console.log(err.statusCode) // 400, 404, ...
109
+ console.log(err.context) // { field: 'email' }
110
+ }
79
111
  }
112
+
113
+ // Map to HTTP status
114
+ const status = errorToStatusCode(err) // 400 for ValidationError, 500 for unknown
115
+ ```
116
+
117
+ | Error | Kind | Status |
118
+ |-------|------|--------|
119
+ | `ValidationError` | `VALIDATION` | 400 |
120
+ | `UnauthorizedError` | `UNAUTHORIZED` | 401 |
121
+ | `ForbiddenError` | `FORBIDDEN` | 403 |
122
+ | `NotFoundError` | `NOT_FOUND` | 404 |
123
+ | `ConflictError` | `CONFLICT` | 409 |
124
+ | `RateLimitError` | `RATE_LIMIT` | 429 |
125
+ | `ExternalError` | `EXTERNAL` | 502 |
126
+ | `InternalError` | `INTERNAL` | 500 |
127
+
128
+ ## Result Type
129
+
130
+ Discriminated `Result<T, E>` for operations where failure is expected.
131
+
132
+ ```ts
133
+ import { ok, err, isOk, isErr, mapOk, andThen, getOrElse, tryCatchAsync } from '@prmichaelsen/agentbase-core'
134
+ import type { Result } from '@prmichaelsen/agentbase-core'
135
+
136
+ // Create results
137
+ const success: Result<number> = ok(42)
138
+ const failure: Result<number, string> = err('not found')
139
+
140
+ // Type guards
141
+ if (isOk(success)) console.log(success.value) // 42
142
+ if (isErr(failure)) console.log(failure.error) // 'not found'
143
+
144
+ // Combinators
145
+ const doubled = mapOk(ok(5), x => x * 2) // Ok(10)
146
+ const chained = andThen(ok(5), x => x > 0 ? ok(x) : err('negative'))
147
+ const value = getOrElse(err('fail'), 0) // 0
148
+
149
+ // Wrap async functions
150
+ const result = await tryCatchAsync(
151
+ () => fetchUser(id),
152
+ (e) => new NotFoundError('User not found')
153
+ )
80
154
  ```
81
155
 
82
156
  ## Auth
@@ -84,20 +158,23 @@ if (action) {
84
158
  ### Server-Side (requires `@prmichaelsen/firebase-admin-sdk-v8`)
85
159
 
86
160
  ```ts
87
- import { getServerSession, requireAuth, requireAdmin, isAdmin } from '@prmichaelsen/agentbase-core/lib/auth'
161
+ import {
162
+ getServerSession, requireAuth, requireAdmin, isAdmin,
163
+ handleAuthError,
164
+ } from '@prmichaelsen/agentbase-core/lib/auth'
88
165
 
89
166
  // Get session from request cookies
90
167
  const session = await getServerSession(request)
91
168
 
92
- // Guard: returns null if authorized, Response(401) if not
93
- const authError = await requireAuth(request)
94
- if (authError) return authError
95
-
96
- // Guard: returns null if admin, Response(401/403) if not
97
- const adminError = await requireAdmin(request, 'admin@example.com,other@example.com')
98
- if (adminError) return adminError
169
+ // Guards throw typed errors use try-catch
170
+ try {
171
+ await requireAuth(request) // throws UnauthorizedError
172
+ await requireAdmin(request, 'admin@example.com') // throws Unauthorized or Forbidden
173
+ } catch (error) {
174
+ return handleAuthError(error) // converts to Response(401/403/500)
175
+ }
99
176
 
100
- // Check admin status
177
+ // Boolean check (does not throw)
101
178
  const admin = await isAdmin(request, 'admin@example.com')
102
179
  ```
103
180
 
@@ -109,22 +186,15 @@ import {
109
186
  upgradeAnonymousAccount, logout, onAuthChange, getIdToken,
110
187
  } from '@prmichaelsen/agentbase-core/lib'
111
188
 
112
- // Initialize once
113
189
  initializeFirebase({ apiKey: '...', authDomain: '...', projectId: '...' })
114
190
 
115
- // Auth flows
116
191
  await signIn('user@example.com', 'password')
117
192
  await signUp('user@example.com', 'password')
118
193
  await signInAnonymously()
119
194
  await upgradeAnonymousAccount('user@example.com', 'password')
120
195
  await logout()
121
196
 
122
- // Listen for auth changes
123
- const unsubscribe = onAuthChange((user) => {
124
- console.log(user ? `Signed in: ${user.uid}` : 'Signed out')
125
- })
126
-
127
- // Get ID token for API calls
197
+ const unsubscribe = onAuthChange((user) => { /* ... */ })
128
198
  const token = await getIdToken()
129
199
  ```
130
200
 
@@ -133,100 +203,147 @@ const token = await getIdToken()
133
203
  ```ts
134
204
  import { isRealUser, isRealUserServer } from '@prmichaelsen/agentbase-core/lib/auth'
135
205
 
136
- // Client-side: works with any { isAnonymous?: boolean }
137
206
  if (isRealUser(user)) { /* non-anonymous */ }
138
-
139
- // Server-side: typed for AuthUser
140
- if (isRealUserServer(authUser)) { /* non-anonymous */ }
207
+ if (isRealUserServer(authUser)) { /* non-anonymous, typed for AuthUser */ }
141
208
  ```
142
209
 
143
- ## Logger
210
+ ## Client SDK
144
211
 
145
- Structured logger with automatic sensitive data redaction.
212
+ Full REST client for agentbase.me with 17 service classes, compound workflows, and OAuth support.
213
+
214
+ ### Setup
146
215
 
147
216
  ```ts
148
- import { createLogger, authLogger, apiLogger, dbLogger, chatLogger } from '@prmichaelsen/agentbase-core/lib'
217
+ import { HttpClient, AuthSvc, MemoriesSvc, OAuthClient } from '@prmichaelsen/agentbase-core/client'
149
218
 
150
- const logger = createLogger('MyService')
151
- logger.info('Processing request', { userId: 'abc', apiKey: 'secret' })
152
- // Output: [MyService] Processing request { userId: 'abc', apiKey: '[REDACTED]' }
219
+ // With API key
220
+ const http = new HttpClient({
221
+ baseUrl: 'https://agentbase.me',
222
+ auth: { type: 'apiKey', key: 'your-api-key' },
223
+ })
153
224
 
154
- logger.debug('Only in development') // Only logs when NODE_ENV=development
155
- logger.error('Failed', new Error('boom'))
225
+ // With OAuth bearer token
226
+ const http = new HttpClient({
227
+ baseUrl: 'https://agentbase.me',
228
+ auth: { type: 'bearer', token: () => oauthClient.getValidToken() },
229
+ })
230
+
231
+ // With session cookie (browser)
232
+ const http = new HttpClient({
233
+ baseUrl: 'https://agentbase.me',
234
+ auth: { type: 'cookie' },
235
+ })
156
236
  ```
157
237
 
158
- ### Sanitization Utilities
238
+ ### SvcClient (1:1 REST mirror)
159
239
 
160
240
  ```ts
161
- import { sanitizeToken, sanitizeEmail, sanitizeUserId, sanitizeObject } from '@prmichaelsen/agentbase-core/lib'
241
+ const memories = new MemoriesSvc(http)
242
+
243
+ // All methods return SdkResponse<T> — never throws
244
+ const result = await memories.search({ query: 'vacation photos', mode: 'semantic' })
245
+ if (result.error) {
246
+ console.error(result.error.message)
247
+ } else {
248
+ console.log(result.data) // { data: Memory[], total: number }
249
+ }
162
250
 
163
- sanitizeToken('abc123...') // "a1b2c3d4..."
164
- sanitizeEmail('john@x.com') // "jo***@x.com"
165
- sanitizeUserId('user-12345') // "user_user-123"
166
- sanitizeObject({ password: 'secret', name: 'ok' }) // { password: '[REDACTED]', name: 'ok' }
251
+ // Or use throwOnError() for cleaner async/await
252
+ const { data } = await memories.feed({ limit: 10 }).then(r => r.throwOnError())
253
+ ```
254
+
255
+ Available services: `AuthSvc`, `OAuthSvc`, `MemoriesSvc`, `ConversationsSvc`, `ProfilesSvc`, `GroupsSvc`, `DmsSvc`, `SearchSvc`, `NotificationsSvc`, `RelationshipsSvc`, `BoardsSvc`, `TokensSvc`, `IntegrationsSvc`, `SettingsSvc`, `PaymentsSvc`, `UsageSvc`, `JobsSvc`
256
+
257
+ ### AppClient (compound workflows)
258
+
259
+ ```ts
260
+ import { AppClient } from '@prmichaelsen/agentbase-core/client'
261
+
262
+ const app = new AppClient({ auth, memories, conversations, profiles, groups, dms, search, notifications })
263
+
264
+ await app.loginAndGetSession(idToken)
265
+ await app.searchAndFetchMemories('vacation', 5)
266
+ await app.createGroupAndInvite('Book Club', 'Monthly reads', ['user-1', 'user-2'])
267
+ await app.getFullProfile('user-123') // profile + memory count
268
+ await app.startDm('user-456') // find existing or create new
167
269
  ```
168
270
 
169
- ## Firebase Admin
271
+ ### OAuth Integration
170
272
 
171
273
  ```ts
172
- import { initFirebaseAdmin } from '@prmichaelsen/agentbase-core/lib'
274
+ import {
275
+ OAuthClient, generateCodeVerifier, generateCodeChallenge,
276
+ buildAuthorizationUrl,
277
+ } from '@prmichaelsen/agentbase-core/client'
278
+
279
+ // 1. Generate PKCE params
280
+ const verifier = generateCodeVerifier()
281
+ const challenge = await generateCodeChallenge(verifier)
282
+
283
+ // 2. Build authorization URL
284
+ const url = buildAuthorizationUrl('https://agentbase.me', {
285
+ clientId: 'your-client-id',
286
+ redirectUri: 'http://localhost:3000/callback',
287
+ scope: 'read write',
288
+ codeChallenge: challenge,
289
+ })
173
290
 
174
- // Reads FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY and FIREBASE_PROJECT_ID from env
175
- initFirebaseAdmin()
291
+ // 3. After redirect, exchange code for tokens
292
+ const oauth = new OAuthClient(http, { clientId: 'your-client-id' })
293
+ await oauth.exchangeCode(code, redirectUri, verifier)
294
+
295
+ // 4. Get valid token (auto-refreshes if expired)
296
+ const token = await oauth.getValidToken()
297
+
298
+ // API token shortcut
299
+ await oauth.exchangeApiToken('your-api-token')
176
300
  ```
177
301
 
178
- ## Rate Limiter
302
+ ## Configuration
179
303
 
180
- Works with Cloudflare Workers Rate Limiting API.
304
+ Centralized environment variable loading and validation.
181
305
 
182
306
  ```ts
183
- import { checkRateLimit, createRateLimitResponse, getRateLimitIdentifier } from '@prmichaelsen/agentbase-core/lib'
307
+ import { loadConfig, validateConfig } from '@prmichaelsen/agentbase-core/config'
184
308
 
185
- const identifier = getRateLimitIdentifier(request, userId)
186
- const result = await checkRateLimit(rateLimiter, identifier, {
187
- limit: 100,
188
- period: 60,
189
- keyPrefix: 'api',
190
- })
309
+ const config = loadConfig() // reads from process.env
310
+ validateConfig(config, { requireFirebaseAdmin: true }) // throws ValidationError if missing
191
311
 
192
- if (!result.success) {
193
- return createRateLimitResponse(result) // 429 with proper headers
194
- }
312
+ // Or pass custom env
313
+ const config = loadConfig({ FIREBASE_PROJECT_ID: 'my-project', ... })
195
314
  ```
196
315
 
197
- ## Utilities
316
+ ## Logger
317
+
318
+ Structured logger with automatic sensitive data redaction.
198
319
 
199
320
  ```ts
200
- import { formatExactTime, getRelativeTime, linkifyText, generateUUID } from '@prmichaelsen/agentbase-core/lib'
321
+ import { createLogger, authLogger, apiLogger } from '@prmichaelsen/agentbase-core/lib'
201
322
 
202
- formatExactTime('2026-03-15T14:30:00Z') // "2:30 PM Sat 3/15/26"
203
- getRelativeTime('2026-03-15T14:00:00Z') // "30m ago"
204
- linkifyText('Visit https://example.com') // "Visit [https://example.com](https://example.com)"
205
- generateUUID() // "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
323
+ const logger = createLogger('MyService')
324
+ logger.info('Request', { userId: 'abc', apiKey: 'secret' })
325
+ // Output: [MyService] Request { userId: 'abc', apiKey: '[REDACTED]' }
206
326
  ```
207
327
 
208
- ## Types
328
+ ### Sanitization Utilities
209
329
 
210
330
  ```ts
211
- import type { AuthUser, ServerSession, AuthResult } from '@prmichaelsen/agentbase-core/types'
331
+ import { sanitizeToken, sanitizeEmail, sanitizeUserId, sanitizeObject } from '@prmichaelsen/agentbase-core/lib'
212
332
 
213
- interface AuthUser {
214
- uid: string
215
- email: string | null
216
- displayName: string | null
217
- photoURL: string | null
218
- emailVerified: boolean
219
- isAnonymous: boolean
220
- }
333
+ sanitizeToken('abc123...') // "a1b2c3d4..."
334
+ sanitizeEmail('john@x.com') // "jo***@x.com"
335
+ sanitizeObject({ password: 'secret', name: 'ok' }) // { password: '[REDACTED]', name: 'ok' }
336
+ ```
221
337
 
222
- interface ServerSession {
223
- user: AuthUser
224
- }
338
+ ## Types
225
339
 
226
- interface AuthResult {
227
- success: boolean
228
- error?: string
229
- }
340
+ ```ts
341
+ import type { AuthUser, ServerSession, Result, UserId, EmailAddress } from '@prmichaelsen/agentbase-core/types'
342
+ import { toUserId, toEmailAddress, toTimestamp } from '@prmichaelsen/agentbase-core/types'
343
+
344
+ // Branded primitives prevent mixing IDs
345
+ const uid: UserId = toUserId('firebase-uid')
346
+ const email: EmailAddress = toEmailAddress('user@example.com')
230
347
  ```
231
348
 
232
349
  ## License