@falai/agent 0.3.11 → 0.3.12

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 (71) hide show
  1. package/README.md +92 -0
  2. package/dist/adapters/PrismaAdapter.d.ts +115 -0
  3. package/dist/adapters/PrismaAdapter.d.ts.map +1 -0
  4. package/dist/adapters/PrismaAdapter.js +331 -0
  5. package/dist/adapters/PrismaAdapter.js.map +1 -0
  6. package/dist/adapters/index.d.ts +6 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +5 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/cjs/adapters/PrismaAdapter.d.ts +115 -0
  11. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -0
  12. package/dist/cjs/adapters/PrismaAdapter.js +335 -0
  13. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -0
  14. package/dist/cjs/adapters/index.d.ts +6 -0
  15. package/dist/cjs/adapters/index.d.ts.map +1 -0
  16. package/dist/cjs/adapters/index.js +9 -0
  17. package/dist/cjs/adapters/index.js.map +1 -0
  18. package/dist/cjs/core/Agent.d.ts +10 -0
  19. package/dist/cjs/core/Agent.d.ts.map +1 -1
  20. package/dist/cjs/core/Agent.js +23 -0
  21. package/dist/cjs/core/Agent.js.map +1 -1
  22. package/dist/cjs/core/PersistenceManager.d.ts +77 -0
  23. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -0
  24. package/dist/cjs/core/PersistenceManager.js +153 -0
  25. package/dist/cjs/core/PersistenceManager.js.map +1 -0
  26. package/dist/cjs/index.d.ts +4 -0
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +6 -1
  29. package/dist/cjs/index.js.map +1 -1
  30. package/dist/cjs/types/agent.d.ts +3 -0
  31. package/dist/cjs/types/agent.d.ts.map +1 -1
  32. package/dist/cjs/types/agent.js.map +1 -1
  33. package/dist/cjs/types/index.d.ts +1 -0
  34. package/dist/cjs/types/index.d.ts.map +1 -1
  35. package/dist/cjs/types/persistence.d.ts +194 -0
  36. package/dist/cjs/types/persistence.d.ts.map +1 -0
  37. package/dist/cjs/types/persistence.js +7 -0
  38. package/dist/cjs/types/persistence.js.map +1 -0
  39. package/dist/core/Agent.d.ts +10 -0
  40. package/dist/core/Agent.d.ts.map +1 -1
  41. package/dist/core/Agent.js +23 -0
  42. package/dist/core/Agent.js.map +1 -1
  43. package/dist/core/PersistenceManager.d.ts +77 -0
  44. package/dist/core/PersistenceManager.d.ts.map +1 -0
  45. package/dist/core/PersistenceManager.js +149 -0
  46. package/dist/core/PersistenceManager.js.map +1 -0
  47. package/dist/index.d.ts +4 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/types/agent.d.ts +3 -0
  52. package/dist/types/agent.d.ts.map +1 -1
  53. package/dist/types/agent.js.map +1 -1
  54. package/dist/types/index.d.ts +1 -0
  55. package/dist/types/index.d.ts.map +1 -1
  56. package/dist/types/persistence.d.ts +194 -0
  57. package/dist/types/persistence.d.ts.map +1 -0
  58. package/dist/types/persistence.js +6 -0
  59. package/dist/types/persistence.js.map +1 -0
  60. package/docs/PERSISTENCE.md +419 -0
  61. package/examples/prisma-persistence.ts +313 -0
  62. package/examples/prisma-schema.example.prisma +74 -0
  63. package/package.json +9 -1
  64. package/src/adapters/PrismaAdapter.ts +510 -0
  65. package/src/adapters/index.ts +10 -0
  66. package/src/core/Agent.ts +31 -0
  67. package/src/core/PersistenceManager.ts +222 -0
  68. package/src/index.ts +21 -0
  69. package/src/types/agent.ts +3 -0
  70. package/src/types/index.ts +14 -0
  71. package/src/types/persistence.ts +234 -0
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Prisma adapter for persistence
3
+ * Simple, provider-like API with automatic schema creation
4
+ */
5
+
6
+ import type {
7
+ SessionRepository,
8
+ MessageRepository,
9
+ SessionData,
10
+ MessageData,
11
+ SessionStatus,
12
+ PersistenceAdapter,
13
+ } from "../types/persistence";
14
+
15
+ /**
16
+ * Prisma model operations
17
+ */
18
+ export interface PrismaModel {
19
+ create: (params: {
20
+ data: Record<string, unknown>;
21
+ }) => Promise<Record<string, unknown>>;
22
+ findUnique: (params: {
23
+ where: Record<string, unknown>;
24
+ }) => Promise<Record<string, unknown> | null>;
25
+ findFirst: (params: {
26
+ where: Record<string, unknown>;
27
+ orderBy?: Record<string, string>;
28
+ }) => Promise<Record<string, unknown> | null>;
29
+ findMany: (params: {
30
+ where: Record<string, unknown>;
31
+ orderBy?: Record<string, string>;
32
+ take?: number;
33
+ }) => Promise<Array<Record<string, unknown>>>;
34
+ update: (params: {
35
+ where: Record<string, unknown>;
36
+ data: Record<string, unknown>;
37
+ }) => Promise<Record<string, unknown>>;
38
+ delete: (params: {
39
+ where: Record<string, unknown>;
40
+ }) => Promise<Record<string, unknown>>;
41
+ deleteMany: (params: {
42
+ where: Record<string, unknown>;
43
+ }) => Promise<{ count: number }>;
44
+ }
45
+
46
+ /**
47
+ * Prisma client interface - matches the shape of a generated Prisma client
48
+ */
49
+ export interface PrismaClient extends Record<string, unknown> {
50
+ $queryRaw?: <T = unknown>(
51
+ query: TemplateStringsArray | string,
52
+ ...values: unknown[]
53
+ ) => Promise<T>;
54
+ $executeRaw?: (
55
+ query: TemplateStringsArray | string,
56
+ ...values: unknown[]
57
+ ) => Promise<number>;
58
+ $disconnect?: () => Promise<void>;
59
+ }
60
+
61
+ /**
62
+ * Configuration for field mappings
63
+ */
64
+ export interface FieldMappings {
65
+ sessions?: Partial<Record<keyof SessionData, string>>;
66
+ messages?: Partial<Record<keyof MessageData, string>>;
67
+ }
68
+
69
+ /**
70
+ * Options for creating a Prisma adapter
71
+ */
72
+ export interface PrismaAdapterOptions {
73
+ /**
74
+ * Prisma client instance
75
+ */
76
+ prisma: PrismaClient;
77
+
78
+ /**
79
+ * Table/model names (defaults to 'agentSession' and 'agentMessage')
80
+ */
81
+ tables?: {
82
+ sessions?: string;
83
+ messages?: string;
84
+ };
85
+
86
+ /**
87
+ * Field mappings (optional, if your schema uses different field names)
88
+ */
89
+ fieldMappings?: FieldMappings;
90
+
91
+ /**
92
+ * Whether to auto-create tables if they don't exist (default: false)
93
+ * Note: Only works if your Prisma client has $executeRaw method
94
+ */
95
+ autoMigrate?: boolean;
96
+ }
97
+
98
+ /**
99
+ * Prisma Adapter - Provider-style API for Prisma persistence
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * import { PrismaClient } from '@prisma/client';
104
+ * import { Agent, PrismaAdapter } from '@falai/agent';
105
+ *
106
+ * const prisma = new PrismaClient();
107
+ *
108
+ * const agent = new Agent({
109
+ * name: "My Agent",
110
+ * ai: provider,
111
+ * persistence: {
112
+ * adapter: new PrismaAdapter({ prisma }),
113
+ * userId: "user_123",
114
+ * },
115
+ * });
116
+ * ```
117
+ */
118
+ export class PrismaAdapter implements PersistenceAdapter {
119
+ public readonly sessionRepository: SessionRepository;
120
+ public readonly messageRepository: MessageRepository;
121
+ private prisma: PrismaClient;
122
+ private options: Required<Omit<PrismaAdapterOptions, "fieldMappings">> & {
123
+ fieldMappings?: FieldMappings;
124
+ };
125
+ private initialized = false;
126
+
127
+ constructor(options: PrismaAdapterOptions) {
128
+ this.prisma = options.prisma;
129
+ this.options = {
130
+ prisma: options.prisma,
131
+ tables: {
132
+ sessions: options.tables?.sessions || "agentSession",
133
+ messages: options.tables?.messages || "agentMessage",
134
+ },
135
+ fieldMappings: options.fieldMappings,
136
+ autoMigrate: options.autoMigrate ?? false,
137
+ };
138
+
139
+ // Initialize repositories
140
+ this.sessionRepository = new PrismaSessionRepository(
141
+ this.prisma,
142
+ this.options.tables.sessions!,
143
+ this.options.fieldMappings?.sessions
144
+ );
145
+
146
+ this.messageRepository = new PrismaMessageRepository(
147
+ this.prisma,
148
+ this.options.tables.messages!,
149
+ this.options.fieldMappings?.messages
150
+ );
151
+
152
+ // Auto-initialize if configured
153
+ if (this.options.autoMigrate) {
154
+ this.initialize().catch((error) => {
155
+ console.error("[PrismaAdapter] Auto-migration failed:", error);
156
+ });
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Initialize the adapter (check/create tables)
162
+ * Called automatically if autoMigrate is enabled
163
+ */
164
+ async initialize(): Promise<void> {
165
+ if (this.initialized) return;
166
+
167
+ if (this.options.autoMigrate && this.prisma.$executeRaw) {
168
+ // Note: This is a simplified example. In production, use Prisma Migrate.
169
+ console.warn(
170
+ "[PrismaAdapter] autoMigrate is experimental. Use Prisma Migrate for production."
171
+ );
172
+ // Table creation would go here, but it's database-specific
173
+ // Better to rely on Prisma Migrate
174
+ await Promise.resolve(); // Satisfy async requirement
175
+ }
176
+
177
+ this.initialized = true;
178
+ }
179
+
180
+ /**
181
+ * Disconnect from the database
182
+ */
183
+ async disconnect(): Promise<void> {
184
+ if (this.prisma.$disconnect) {
185
+ await this.prisma.$disconnect();
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Prisma Session Repository
192
+ * Internal implementation - users should use PrismaAdapter instead
193
+ */
194
+ class PrismaSessionRepository implements SessionRepository {
195
+ private prisma: PrismaClient;
196
+ private tableName: string;
197
+ private fieldMap: Partial<Record<keyof SessionData, string>>;
198
+
199
+ constructor(
200
+ prismaClient: PrismaClient,
201
+ tableName: string,
202
+ fieldMappings?: Partial<Record<keyof SessionData, string>>
203
+ ) {
204
+ this.prisma = prismaClient;
205
+ this.tableName = tableName;
206
+ this.fieldMap = fieldMappings || {};
207
+ }
208
+
209
+ /**
210
+ * Map our standard field names to custom schema field names
211
+ */
212
+ private mapFields(data: Record<string, unknown>): Record<string, unknown> {
213
+ const mapped: Record<string, unknown> = {};
214
+ for (const [key, value] of Object.entries(data)) {
215
+ const mappedKey = this.fieldMap[key as keyof SessionData] || key;
216
+ mapped[mappedKey] = value;
217
+ }
218
+ return mapped;
219
+ }
220
+
221
+ /**
222
+ * Map custom schema field names back to our standard field names
223
+ */
224
+ private unmapFields(data: Record<string, unknown>): SessionData {
225
+ if (!data) return data as SessionData;
226
+
227
+ const reverseMap: Record<string, string> = {};
228
+ for (const [standardKey, customKey] of Object.entries(this.fieldMap)) {
229
+ reverseMap[customKey] = standardKey;
230
+ }
231
+
232
+ const unmapped: Record<string, unknown> = {};
233
+ for (const [key, value] of Object.entries(data)) {
234
+ const unmappedKey = reverseMap[key] || key;
235
+ unmapped[unmappedKey] = value;
236
+ }
237
+
238
+ return unmapped as unknown as SessionData;
239
+ }
240
+
241
+ private getModel(): PrismaModel {
242
+ return this.prisma[this.tableName] as PrismaModel;
243
+ }
244
+
245
+ async create(
246
+ data: Omit<SessionData, "id" | "createdAt" | "updatedAt">
247
+ ): Promise<SessionData> {
248
+ const mapped = this.mapFields(data as Record<string, unknown>);
249
+ const result = await this.getModel().create({
250
+ data: mapped,
251
+ });
252
+ return this.unmapFields(result);
253
+ }
254
+
255
+ async findById(id: string): Promise<SessionData | null> {
256
+ const result = await this.getModel().findUnique({
257
+ where: { [this.fieldMap.id || "id"]: id },
258
+ });
259
+ return result ? this.unmapFields(result) : null;
260
+ }
261
+
262
+ async findActiveByUserId(userId: string): Promise<SessionData | null> {
263
+ const result = await this.getModel().findFirst({
264
+ where: {
265
+ [this.fieldMap.userId || "userId"]: userId,
266
+ [this.fieldMap.status || "status"]: "active",
267
+ },
268
+ orderBy: {
269
+ [this.fieldMap.createdAt || "createdAt"]: "desc",
270
+ },
271
+ });
272
+ return result ? this.unmapFields(result) : null;
273
+ }
274
+
275
+ async findByUserId(userId: string, limit = 100): Promise<SessionData[]> {
276
+ const results = await this.getModel().findMany({
277
+ where: {
278
+ [this.fieldMap.userId || "userId"]: userId,
279
+ },
280
+ orderBy: {
281
+ [this.fieldMap.createdAt || "createdAt"]: "desc",
282
+ },
283
+ take: limit,
284
+ });
285
+ return results.map((r) => this.unmapFields(r));
286
+ }
287
+
288
+ async update(
289
+ id: string,
290
+ data: Partial<Omit<SessionData, "id" | "createdAt">>
291
+ ): Promise<SessionData | null> {
292
+ const mapped = this.mapFields(data as Record<string, unknown>);
293
+ const result = await this.getModel().update({
294
+ where: { [this.fieldMap.id || "id"]: id },
295
+ data: {
296
+ ...mapped,
297
+ [this.fieldMap.updatedAt || "updatedAt"]: new Date(),
298
+ },
299
+ });
300
+ return this.unmapFields(result);
301
+ }
302
+
303
+ async updateStatus(
304
+ id: string,
305
+ status: SessionStatus,
306
+ completedAt?: Date
307
+ ): Promise<SessionData | null> {
308
+ const data: Record<string, unknown> = {
309
+ [this.fieldMap.status || "status"]: status,
310
+ [this.fieldMap.updatedAt || "updatedAt"]: new Date(),
311
+ };
312
+ if (completedAt) {
313
+ data[this.fieldMap.completedAt || "completedAt"] = completedAt;
314
+ }
315
+ const result = await this.getModel().update({
316
+ where: { [this.fieldMap.id || "id"]: id },
317
+ data,
318
+ });
319
+ return this.unmapFields(result);
320
+ }
321
+
322
+ async updateCollectedData(
323
+ id: string,
324
+ collectedData: Record<string, unknown>
325
+ ): Promise<SessionData | null> {
326
+ const result = await this.getModel().update({
327
+ where: { [this.fieldMap.id || "id"]: id },
328
+ data: {
329
+ [this.fieldMap.collectedData || "collectedData"]: collectedData,
330
+ [this.fieldMap.updatedAt || "updatedAt"]: new Date(),
331
+ },
332
+ });
333
+ return this.unmapFields(result);
334
+ }
335
+
336
+ async updateRouteState(
337
+ id: string,
338
+ route?: string,
339
+ state?: string
340
+ ): Promise<SessionData | null> {
341
+ const data: Record<string, unknown> = {
342
+ [this.fieldMap.updatedAt || "updatedAt"]: new Date(),
343
+ };
344
+ if (route !== undefined) {
345
+ data[this.fieldMap.currentRoute || "currentRoute"] = route;
346
+ }
347
+ if (state !== undefined) {
348
+ data[this.fieldMap.currentState || "currentState"] = state;
349
+ }
350
+ const result = await this.getModel().update({
351
+ where: { [this.fieldMap.id || "id"]: id },
352
+ data,
353
+ });
354
+ return this.unmapFields(result);
355
+ }
356
+
357
+ async incrementMessageCount(id: string): Promise<SessionData | null> {
358
+ const result = await this.getModel().update({
359
+ where: { [this.fieldMap.id || "id"]: id },
360
+ data: {
361
+ [this.fieldMap.messageCount || "messageCount"]: { increment: 1 },
362
+ [this.fieldMap.lastMessageAt || "lastMessageAt"]: new Date(),
363
+ [this.fieldMap.updatedAt || "updatedAt"]: new Date(),
364
+ },
365
+ });
366
+ return this.unmapFields(result);
367
+ }
368
+
369
+ async delete(id: string): Promise<boolean> {
370
+ try {
371
+ await this.getModel().delete({
372
+ where: { [this.fieldMap.id || "id"]: id },
373
+ });
374
+ return true;
375
+ } catch {
376
+ return false;
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Prisma Message Repository
383
+ * Internal implementation - users should use PrismaAdapter instead
384
+ */
385
+ class PrismaMessageRepository implements MessageRepository {
386
+ private prisma: PrismaClient;
387
+ private tableName: string;
388
+ private fieldMap: Partial<Record<keyof MessageData, string>>;
389
+
390
+ constructor(
391
+ prismaClient: PrismaClient,
392
+ tableName: string,
393
+ fieldMappings?: Partial<Record<keyof MessageData, string>>
394
+ ) {
395
+ this.prisma = prismaClient;
396
+ this.tableName = tableName;
397
+ this.fieldMap = fieldMappings || {};
398
+ }
399
+
400
+ /**
401
+ * Map our standard field names to custom schema field names
402
+ */
403
+ private mapFields(data: Record<string, unknown>): Record<string, unknown> {
404
+ const mapped: Record<string, unknown> = {};
405
+ for (const [key, value] of Object.entries(data)) {
406
+ const mappedKey = this.fieldMap[key as keyof MessageData] || key;
407
+ mapped[mappedKey] = value;
408
+ }
409
+ return mapped;
410
+ }
411
+
412
+ /**
413
+ * Map custom schema field names back to our standard field names
414
+ */
415
+ private unmapFields(data: Record<string, unknown>): MessageData {
416
+ if (!data) return data as MessageData;
417
+
418
+ const reverseMap: Record<string, string> = {};
419
+ for (const [standardKey, customKey] of Object.entries(this.fieldMap)) {
420
+ reverseMap[customKey] = standardKey;
421
+ }
422
+
423
+ const unmapped: Record<string, unknown> = {};
424
+ for (const [key, value] of Object.entries(data)) {
425
+ const unmappedKey = reverseMap[key] || key;
426
+ unmapped[unmappedKey] = value;
427
+ }
428
+
429
+ return unmapped as unknown as MessageData;
430
+ }
431
+
432
+ private getModel(): PrismaModel {
433
+ return this.prisma[this.tableName] as PrismaModel;
434
+ }
435
+
436
+ async create(
437
+ data: Omit<MessageData, "id" | "createdAt">
438
+ ): Promise<MessageData> {
439
+ const mapped = this.mapFields(data as Record<string, unknown>);
440
+ const result = await this.getModel().create({
441
+ data: mapped,
442
+ });
443
+ return this.unmapFields(result);
444
+ }
445
+
446
+ async findById(id: string): Promise<MessageData | null> {
447
+ const result = await this.getModel().findUnique({
448
+ where: { [this.fieldMap.id || "id"]: id },
449
+ });
450
+ return result ? this.unmapFields(result) : null;
451
+ }
452
+
453
+ async findBySessionId(
454
+ sessionId: string,
455
+ limit = 1000
456
+ ): Promise<MessageData[]> {
457
+ const results = await this.getModel().findMany({
458
+ where: {
459
+ [this.fieldMap.sessionId || "sessionId"]: sessionId,
460
+ },
461
+ orderBy: {
462
+ [this.fieldMap.createdAt || "createdAt"]: "asc",
463
+ },
464
+ take: limit,
465
+ });
466
+ return results.map((r) => this.unmapFields(r));
467
+ }
468
+
469
+ async findByUserId(userId: string, limit = 100): Promise<MessageData[]> {
470
+ const results = await this.getModel().findMany({
471
+ where: {
472
+ [this.fieldMap.userId || "userId"]: userId,
473
+ },
474
+ orderBy: {
475
+ [this.fieldMap.createdAt || "createdAt"]: "desc",
476
+ },
477
+ take: limit,
478
+ });
479
+ return results.map((r) => this.unmapFields(r));
480
+ }
481
+
482
+ async delete(id: string): Promise<boolean> {
483
+ try {
484
+ await this.getModel().delete({
485
+ where: { [this.fieldMap.id || "id"]: id },
486
+ });
487
+ return true;
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+
493
+ async deleteBySessionId(sessionId: string): Promise<number> {
494
+ const result = await this.getModel().deleteMany({
495
+ where: {
496
+ [this.fieldMap.sessionId || "sessionId"]: sessionId,
497
+ },
498
+ });
499
+ return result.count || 0;
500
+ }
501
+
502
+ async deleteByUserId(userId: string): Promise<number> {
503
+ const result = await this.getModel().deleteMany({
504
+ where: {
505
+ [this.fieldMap.userId || "userId"]: userId,
506
+ },
507
+ });
508
+ return result.count || 0;
509
+ }
510
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Database adapters for persistence
3
+ */
4
+
5
+ export { PrismaAdapter } from "./PrismaAdapter";
6
+ export type {
7
+ PrismaClient,
8
+ FieldMappings,
9
+ PrismaAdapterOptions,
10
+ } from "./PrismaAdapter";
package/src/core/Agent.ts CHANGED
@@ -16,6 +16,7 @@ import { Route } from "./Route";
16
16
  import { DomainRegistry } from "./DomainRegistry";
17
17
  import { PromptBuilder } from "./PromptBuilder";
18
18
  import { Observation } from "./Observation";
19
+ import { PersistenceManager } from "./PersistenceManager";
19
20
 
20
21
  /**
21
22
  * Main Agent class with generic context support
@@ -28,6 +29,7 @@ export class Agent<TContext = unknown> {
28
29
  private observations: Observation[] = [];
29
30
  private domainRegistry = new DomainRegistry();
30
31
  private context: TContext | undefined;
32
+ private persistenceManager: PersistenceManager | undefined;
31
33
 
32
34
  /**
33
35
  * Dynamic domain property - populated via addDomain
@@ -50,6 +52,21 @@ export class Agent<TContext = unknown> {
50
52
  // Initialize context if provided
51
53
  this.context = options.context;
52
54
 
55
+ // Initialize persistence if configured
56
+ if (options.persistence) {
57
+ this.persistenceManager = new PersistenceManager(options.persistence);
58
+
59
+ // Initialize the adapter if it has an initialize method
60
+ if (options.persistence.adapter.initialize) {
61
+ options.persistence.adapter.initialize().catch((error) => {
62
+ console.error(
63
+ "[Agent] Persistence adapter initialization failed:",
64
+ error
65
+ );
66
+ });
67
+ }
68
+ }
69
+
53
70
  // Initialize from options
54
71
  if (options.terms) {
55
72
  this.terms = [...options.terms];
@@ -598,6 +615,20 @@ export class Agent<TContext = unknown> {
598
615
  return this.domainRegistry;
599
616
  }
600
617
 
618
+ /**
619
+ * Get the persistence manager (if configured)
620
+ */
621
+ getPersistenceManager(): PersistenceManager | undefined {
622
+ return this.persistenceManager;
623
+ }
624
+
625
+ /**
626
+ * Check if persistence is enabled
627
+ */
628
+ hasPersistence(): boolean {
629
+ return this.persistenceManager !== undefined;
630
+ }
631
+
601
632
  /**
602
633
  * Get allowed domains for a specific route
603
634
  * @param routeId - Route ID to check