@promptbook/cli 0.112.0-97 → 0.112.0-98

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 (60) hide show
  1. package/apps/agents-server/README.md +3 -3
  2. package/apps/agents-server/src/app/admin/cli-access/CliAccessClient.tsx +99 -0
  3. package/apps/agents-server/src/app/admin/cli-access/page.tsx +14 -0
  4. package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +76 -325
  5. package/apps/agents-server/src/app/admin/database/page.tsx +1 -2
  6. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
  7. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
  8. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +7 -64
  9. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +3 -3
  10. package/apps/agents-server/src/app/api/admin/servers/route.ts +4 -4
  11. package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
  12. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
  13. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
  14. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +4 -0
  15. package/apps/agents-server/src/database/$provideClientSql.ts +17 -4
  16. package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +24 -3
  17. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -11
  18. package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +1 -20
  19. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -0
  20. package/apps/agents-server/src/languages/translations/czech.yaml +1 -0
  21. package/apps/agents-server/src/languages/translations/english.yaml +1 -0
  22. package/apps/agents-server/src/tools/$provideServer.ts +2 -2
  23. package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
  24. package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
  25. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
  26. package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +77 -237
  27. package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
  28. package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
  29. package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
  30. package/apps/agents-server/src/utils/serverRegistry.ts +4 -4
  31. package/apps/agents-server/src/utils/vpsConfiguration.ts +2 -0
  32. package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
  33. package/esm/index.es.js +2 -2
  34. package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  35. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  36. package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  37. package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  38. package/esm/src/version.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/src/book-components/Chat/Chat/Chat.tsx +2 -0
  41. package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
  42. package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
  43. package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
  44. package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
  45. package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
  46. package/src/other/templates/getTemplatesPipelineCollection.ts +721 -736
  47. package/src/version.ts +2 -2
  48. package/src/versions.txt +1 -0
  49. package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
  50. package/umd/index.umd.js +2 -2
  51. package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  52. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  53. package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  54. package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  55. package/umd/src/version.d.ts +1 -1
  56. package/apps/agents-server/src/database/$providePostgresPool.ts +0 -27
  57. package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +0 -1261
  58. package/src/conversion/validation/_importPipeline.ts +0 -88
  59. /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
  60. /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
@@ -1,1261 +0,0 @@
1
- import type { TODO_any } from '@promptbook-local/types';
2
- import type { SupabaseClient } from '@supabase/supabase-js';
3
- import { type Pool } from 'pg';
4
- import { $providePostgresPool } from '../$providePostgresPool';
5
-
6
- /**
7
- * Minimal query result shape consumed by Agents Server Supabase call sites.
8
- */
9
- type LocalPostgresQueryResult<TData = TODO_any> = {
10
- readonly data: TData | null;
11
- readonly error: LocalPostgresError | null;
12
- readonly count?: number | null;
13
- readonly status?: number;
14
- readonly statusText?: string;
15
- };
16
-
17
- /**
18
- * Supabase-like error shape returned by the local PostgreSQL adapter.
19
- */
20
- type LocalPostgresError = {
21
- readonly code?: string;
22
- readonly message: string;
23
- readonly details?: string;
24
- readonly hint?: string;
25
- };
26
-
27
- /**
28
- * Supported query operation kinds.
29
- */
30
- type LocalPostgresOperation = 'select' | 'insert' | 'update' | 'delete' | 'upsert';
31
-
32
- /**
33
- * Query filter captured from Supabase-like fluent calls.
34
- */
35
- type LocalPostgresFilter = {
36
- readonly column: string;
37
- readonly operator: 'eq' | 'neq' | 'is' | 'not-is' | 'in' | 'lt' | 'lte' | 'gt' | 'gte' | 'like' | 'ilike';
38
- readonly value: unknown;
39
- };
40
-
41
- /**
42
- * Query order captured from Supabase-like fluent calls.
43
- */
44
- type LocalPostgresOrder = {
45
- readonly column: string;
46
- readonly ascending: boolean;
47
- readonly nullsFirst?: boolean;
48
- };
49
-
50
- /**
51
- * Select options supported by Supabase and used by this app.
52
- */
53
- type LocalPostgresSelectOptions = {
54
- readonly count?: 'exact';
55
- readonly head?: boolean;
56
- };
57
-
58
- /**
59
- * Upsert options supported by Supabase and used by this app.
60
- */
61
- type LocalPostgresUpsertOptions = {
62
- readonly onConflict?: string;
63
- };
64
-
65
- /**
66
- * Columns whose values are persisted as JSON in PostgreSQL.
67
- */
68
- const JSON_COLUMNS_BY_TABLE = new Map<string, ReadonlySet<string>>([
69
- ['Agent', new Set(['agentProfile', 'usage', 'preparedModelRequirements', 'preparedExternals'])],
70
- ['AgentHistory', new Set([])],
71
- ['ChatHistory', new Set(['message', 'usage'])],
72
- ['LlmCache', new Set(['value'])],
73
- ['VectorStoreKnowledgeSourceHashes', new Set([])],
74
- ['Image', new Set([])],
75
- ['File', new Set(['securityResult'])],
76
- ['Message', new Set(['sender', 'recipients', 'metadata'])],
77
- ['MessageSendAttempt', new Set(['raw'])],
78
- ['UserChat', new Set(['messages'])],
79
- ['UserChatJob', new Set(['parameters'])],
80
- ['UserChatTimeout', new Set(['parameters'])],
81
- ['UserData', new Set(['value'])],
82
- ['Wallet', new Set(['jsonSchema'])],
83
- ['ShareTargetPayload', new Set(['attachments'])],
84
- ['CalendarConnection', new Set(['scopes'])],
85
- ['CalendarActivity', new Set(['details'])],
86
- ]);
87
-
88
- /**
89
- * Boolean columns stored and restored as booleans.
90
- */
91
- const BOOLEAN_COLUMNS = new Set([
92
- 'isAdmin',
93
- 'isRevoked',
94
- 'isGlobal',
95
- 'isUserScoped',
96
- 'isSuccessful',
97
- 'isChatFocused',
98
- ]);
99
-
100
- /**
101
- * Unique constraints required by common Supabase upsert and duplicate-detection flows.
102
- */
103
- const UNIQUE_INDEX_COLUMNS_BY_TABLE = new Map<string, ReadonlyArray<ReadonlyArray<string>>>([
104
- ['_Server', [['name'], ['domain']]],
105
- ['Metadata', [['key']]],
106
- ['ServerLimit', [['key']]],
107
- ['Agent', [['permanentId']]],
108
- ['AgentExternals', [['type', 'hash']]],
109
- ['VectorStoreKnowledgeSourceHashes', [['source']]],
110
- ['User', [['username']]],
111
- ['UserChatJob', [['chatId', 'clientMessageId']]],
112
- ['LlmCache', [['hash']]],
113
- ['OpenAiAssistantCache', [['agentHash']]],
114
- ['ApiTokens', [['token']]],
115
- ['GenerationLock', [['lockKey']]],
116
- ['CustomStylesheet', [['scope']]],
117
- ['CustomJavascript', [['scope']]],
118
- ['Wallet', [['userId', 'agentPermanentId', 'service', 'key']]],
119
- ['UserData', [['userId', 'key']]],
120
- ['UserPushSubscription', [['endpoint']]],
121
- ]);
122
-
123
- /**
124
- * Known unique conflict columns used when `.upsert` omits `onConflict`.
125
- */
126
- const DEFAULT_UPSERT_CONFLICT_COLUMNS_BY_TABLE = new Map<string, ReadonlyArray<string>>([
127
- ['AgentExternals', ['type', 'hash']],
128
- ['LlmCache', ['hash']],
129
- ['VectorStoreKnowledgeSourceHashes', ['source']],
130
- ['Metadata', ['key']],
131
- ['ServerLimit', ['key']],
132
- ]);
133
-
134
- /**
135
- * Cached Supabase-shaped local client.
136
- */
137
- let localPostgresSupabase: SupabaseClient | null = null;
138
-
139
- /**
140
- * Provides a Supabase-shaped client backed by a local PostgreSQL database.
141
- */
142
- export function $provideLocalPostgresSupabase(): SupabaseClient {
143
- if (localPostgresSupabase) {
144
- return localPostgresSupabase;
145
- }
146
-
147
- localPostgresSupabase = new LocalPostgresSupabaseClient($providePostgresPool()) as unknown as SupabaseClient;
148
- return localPostgresSupabase;
149
- }
150
-
151
- /**
152
- * Resets the cached adapter for isolated tests.
153
- */
154
- export function $resetLocalPostgresSupabaseForTests(): void {
155
- localPostgresSupabase = null;
156
- }
157
-
158
- /**
159
- * Supabase-shaped client with only the table query surface used by Agents Server.
160
- */
161
- class LocalPostgresSupabaseClient {
162
- public constructor(private readonly pool: Pool) {}
163
-
164
- /**
165
- * Starts a query for one PostgreSQL table.
166
- */
167
- public from(tableName: string): LocalPostgresTable {
168
- return new LocalPostgresTable(this.pool, tableName);
169
- }
170
- }
171
-
172
- /**
173
- * Supabase-shaped table entry point. Every operation starts a fresh query builder.
174
- */
175
- class LocalPostgresTable {
176
- public constructor(
177
- private readonly pool: Pool,
178
- private readonly tableName: string,
179
- ) {}
180
-
181
- /**
182
- * Starts a select query.
183
- */
184
- public select(columns = '*', options?: LocalPostgresSelectOptions): LocalPostgresQueryBuilder {
185
- return new LocalPostgresQueryBuilder(this.pool, this.tableName).select(columns, options);
186
- }
187
-
188
- /**
189
- * Starts an insert query.
190
- */
191
- public insert(values: TODO_any): LocalPostgresQueryBuilder {
192
- return new LocalPostgresQueryBuilder(this.pool, this.tableName).insert(values);
193
- }
194
-
195
- /**
196
- * Starts an update query.
197
- */
198
- public update(values: Record<string, unknown>): LocalPostgresQueryBuilder {
199
- return new LocalPostgresQueryBuilder(this.pool, this.tableName).update(values);
200
- }
201
-
202
- /**
203
- * Starts a delete query.
204
- */
205
- public delete(): LocalPostgresQueryBuilder {
206
- return new LocalPostgresQueryBuilder(this.pool, this.tableName).delete();
207
- }
208
-
209
- /**
210
- * Starts an upsert query.
211
- */
212
- public upsert(values: TODO_any, options?: LocalPostgresUpsertOptions): LocalPostgresQueryBuilder {
213
- return new LocalPostgresQueryBuilder(this.pool, this.tableName).upsert(values, options);
214
- }
215
- }
216
-
217
- /**
218
- * Supabase-shaped thenable query builder executed by `await`.
219
- */
220
- class LocalPostgresQueryBuilder implements PromiseLike<LocalPostgresQueryResult> {
221
- private operation: LocalPostgresOperation = 'select';
222
- private selectedColumns = '*';
223
- private selectOptions: LocalPostgresSelectOptions = {};
224
- private filters: Array<LocalPostgresFilter> = [];
225
- private orFilters: Array<string> = [];
226
- private orders: Array<LocalPostgresOrder> = [];
227
- private limitCount: number | null = null;
228
- private offsetCount: number | null = null;
229
- private singleMode: 'single' | 'maybeSingle' | null = null;
230
- private mutationRows: Array<Record<string, unknown>> = [];
231
- private mutationValues: Record<string, unknown> = {};
232
- private upsertOptions: LocalPostgresUpsertOptions = {};
233
- private signal: AbortSignal | null = null;
234
- private isReturningSelection = false;
235
-
236
- public constructor(
237
- private readonly pool: Pool,
238
- private readonly tableName: string,
239
- ) {}
240
-
241
- /**
242
- * Configures selected columns or mutation return columns.
243
- */
244
- public select(columns = '*', options: LocalPostgresSelectOptions = {}): this {
245
- if (this.operation !== 'select') {
246
- this.isReturningSelection = true;
247
- }
248
-
249
- this.selectedColumns = columns || '*';
250
- this.selectOptions = options;
251
- return this;
252
- }
253
-
254
- /**
255
- * Configures inserted rows.
256
- */
257
- public insert(values: TODO_any): this {
258
- this.operation = 'insert';
259
- this.mutationRows = normalizeMutationRows(values);
260
- return this;
261
- }
262
-
263
- /**
264
- * Configures updated values.
265
- */
266
- public update(values: Record<string, unknown>): this {
267
- this.operation = 'update';
268
- this.mutationValues = stripUndefinedValues(values);
269
- return this;
270
- }
271
-
272
- /**
273
- * Configures row deletion.
274
- */
275
- public delete(): this {
276
- this.operation = 'delete';
277
- return this;
278
- }
279
-
280
- /**
281
- * Configures inserted-or-updated rows.
282
- */
283
- public upsert(values: TODO_any, options: LocalPostgresUpsertOptions = {}): this {
284
- this.operation = 'upsert';
285
- this.mutationRows = normalizeMutationRows(values);
286
- this.upsertOptions = options;
287
- return this;
288
- }
289
-
290
- /**
291
- * Adds equality filter.
292
- */
293
- public eq(column: string, value: unknown): this {
294
- this.filters.push({ column, operator: 'eq', value });
295
- return this;
296
- }
297
-
298
- /**
299
- * Adds inequality filter.
300
- */
301
- public neq(column: string, value: unknown): this {
302
- this.filters.push({ column, operator: 'neq', value });
303
- return this;
304
- }
305
-
306
- /**
307
- * Adds nullability filter.
308
- */
309
- public is(column: string, value: unknown): this {
310
- this.filters.push({ column, operator: 'is', value });
311
- return this;
312
- }
313
-
314
- /**
315
- * Adds negative filter for supported operators.
316
- */
317
- public not(column: string, operator: string, value: unknown): this {
318
- if (operator === 'is') {
319
- this.filters.push({ column, operator: 'not-is', value });
320
- } else if (operator === 'eq') {
321
- this.filters.push({ column, operator: 'neq', value });
322
- }
323
- return this;
324
- }
325
-
326
- /**
327
- * Adds `IN` filter.
328
- */
329
- public in(column: string, value: ReadonlyArray<unknown>): this {
330
- this.filters.push({ column, operator: 'in', value });
331
- return this;
332
- }
333
-
334
- /**
335
- * Adds less-than filter.
336
- */
337
- public lt(column: string, value: unknown): this {
338
- this.filters.push({ column, operator: 'lt', value });
339
- return this;
340
- }
341
-
342
- /**
343
- * Adds less-than-or-equal filter.
344
- */
345
- public lte(column: string, value: unknown): this {
346
- this.filters.push({ column, operator: 'lte', value });
347
- return this;
348
- }
349
-
350
- /**
351
- * Adds greater-than filter.
352
- */
353
- public gt(column: string, value: unknown): this {
354
- this.filters.push({ column, operator: 'gt', value });
355
- return this;
356
- }
357
-
358
- /**
359
- * Adds greater-than-or-equal filter.
360
- */
361
- public gte(column: string, value: unknown): this {
362
- this.filters.push({ column, operator: 'gte', value });
363
- return this;
364
- }
365
-
366
- /**
367
- * Adds SQL LIKE filter.
368
- */
369
- public like(column: string, value: string): this {
370
- this.filters.push({ column, operator: 'like', value });
371
- return this;
372
- }
373
-
374
- /**
375
- * Adds case-insensitive LIKE filter.
376
- */
377
- public ilike(column: string, value: string): this {
378
- this.filters.push({ column, operator: 'ilike', value });
379
- return this;
380
- }
381
-
382
- /**
383
- * Adds an OR filter in the PostgREST format used by Supabase.
384
- */
385
- public or(filter: string): this {
386
- this.orFilters.push(filter);
387
- return this;
388
- }
389
-
390
- /**
391
- * Adds ordering.
392
- */
393
- public order(column: string, options: { ascending?: boolean; nullsFirst?: boolean } = {}): this {
394
- this.orders.push({
395
- column,
396
- ascending: options.ascending !== false,
397
- nullsFirst: options.nullsFirst,
398
- });
399
- return this;
400
- }
401
-
402
- /**
403
- * Adds a limit.
404
- */
405
- public limit(count: number): this {
406
- this.limitCount = count;
407
- return this;
408
- }
409
-
410
- /**
411
- * Adds inclusive range pagination.
412
- */
413
- public range(from: number, to: number): this {
414
- this.offsetCount = from;
415
- this.limitCount = Math.max(0, to - from + 1);
416
- return this;
417
- }
418
-
419
- /**
420
- * Marks the query as requiring exactly one row.
421
- */
422
- public single(): Promise<LocalPostgresQueryResult> {
423
- this.singleMode = 'single';
424
- return this.execute();
425
- }
426
-
427
- /**
428
- * Marks the query as requiring at most one row.
429
- */
430
- public maybeSingle(): Promise<LocalPostgresQueryResult> {
431
- this.singleMode = 'maybeSingle';
432
- return this.execute();
433
- }
434
-
435
- /**
436
- * Accepts an abort signal for API compatibility.
437
- */
438
- public abortSignal(signal: AbortSignal): this {
439
- this.signal = signal;
440
- return this;
441
- }
442
-
443
- /**
444
- * Makes the query builder awaitable.
445
- */
446
- public then<TResult1 = LocalPostgresQueryResult, TResult2 = never>(
447
- onfulfilled?:
448
- | ((value: LocalPostgresQueryResult) => TResult1 | PromiseLike<TResult1>)
449
- | null,
450
- onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
451
- ): Promise<TResult1 | TResult2> {
452
- return this.execute().then(onfulfilled, onrejected);
453
- }
454
-
455
- /**
456
- * Executes the configured query.
457
- */
458
- private async execute(): Promise<LocalPostgresQueryResult> {
459
- try {
460
- if (this.signal?.aborted) {
461
- throw new Error('The operation was aborted.');
462
- }
463
-
464
- switch (this.operation) {
465
- case 'insert':
466
- return this.executeInsert();
467
- case 'update':
468
- return this.executeUpdate();
469
- case 'delete':
470
- return this.executeDelete();
471
- case 'upsert':
472
- return this.executeUpsert();
473
- case 'select':
474
- default:
475
- return this.executeSelect();
476
- }
477
- } catch (error) {
478
- return {
479
- data: null,
480
- error: normalizePostgresError(error),
481
- status: 400,
482
- statusText: 'Bad Request',
483
- };
484
- }
485
- }
486
-
487
- /**
488
- * Executes a select query.
489
- */
490
- private async executeSelect(): Promise<LocalPostgresQueryResult> {
491
- const selectedColumns = parseSelectedColumns(this.selectedColumns);
492
- const where = this.createWhereClause();
493
- const orderBy = this.createOrderByClause();
494
- const limit = this.createLimitClause(where.values.length + 1);
495
- const count = this.selectOptions.count === 'exact' ? await this.executeCount(where) : null;
496
-
497
- if (this.selectOptions.head) {
498
- return {
499
- data: null,
500
- error: null,
501
- count,
502
- status: 200,
503
- statusText: 'OK',
504
- };
505
- }
506
-
507
- const sql = [
508
- `SELECT ${createSelectExpression(selectedColumns)} FROM ${quoteIdentifier(this.tableName)}`,
509
- where.sql,
510
- orderBy,
511
- limit.sql,
512
- ]
513
- .filter(Boolean)
514
- .join(' ');
515
- const rows = (await this.pool.query(sql, [...where.values, ...limit.values])).rows as Array<Record<string, unknown>>;
516
- const data = rows.map((row) => deserializeRow(this.tableName, row));
517
-
518
- return this.finalizeDataResponse(data, count);
519
- }
520
-
521
- /**
522
- * Executes an insert query.
523
- */
524
- private async executeInsert(): Promise<LocalPostgresQueryResult> {
525
- const returnedRows: Array<Record<string, unknown>> = [];
526
- const tableBaseName = resolveTableBaseName(this.tableName);
527
- const returningClause = this.isReturningSelection ? ` RETURNING ${createSelectExpression(parseSelectedColumns(this.selectedColumns))}` : '';
528
-
529
- for (const rawRow of this.mutationRows) {
530
- const row = withInsertDefaults(tableBaseName, rawRow);
531
- const columns = Object.keys(row).filter((column) => row[column] !== undefined);
532
- const values = columns.map((column) => serializeValue(this.tableName, column, row[column]));
533
- const columnSql = columns.map(quoteIdentifier).join(', ');
534
- const placeholderSql = values.map((_, index) => `$${index + 1}`).join(', ');
535
- const sql =
536
- columns.length === 0
537
- ? `INSERT INTO ${quoteIdentifier(this.tableName)} DEFAULT VALUES${returningClause}`
538
- : `INSERT INTO ${quoteIdentifier(this.tableName)} (${columnSql}) VALUES (${placeholderSql})${returningClause}`;
539
- const result = await this.pool.query(sql, values);
540
-
541
- if (this.isReturningSelection) {
542
- returnedRows.push(...(result.rows as Array<Record<string, unknown>>).map((rowItem) => deserializeRow(this.tableName, rowItem)));
543
- }
544
- }
545
-
546
- return this.createMutationResponse(returnedRows);
547
- }
548
-
549
- /**
550
- * Executes an update query.
551
- */
552
- private async executeUpdate(): Promise<LocalPostgresQueryResult> {
553
- const updateColumns = Object.keys(this.mutationValues);
554
- if (updateColumns.length === 0) {
555
- return this.createMutationResponse([]);
556
- }
557
-
558
- const values: Array<unknown> = [];
559
- const assignments = updateColumns
560
- .map((column) => {
561
- values.push(serializeValue(this.tableName, column, this.mutationValues[column]));
562
- return `${quoteIdentifier(column)} = $${values.length}`;
563
- })
564
- .join(', ');
565
- const where = this.createWhereClause(values.length + 1);
566
- const returningClause = this.isReturningSelection
567
- ? ` RETURNING ${createSelectExpression(parseSelectedColumns(this.selectedColumns))}`
568
- : '';
569
- const sql = [
570
- `UPDATE ${quoteIdentifier(this.tableName)} SET ${assignments}`,
571
- where.sql,
572
- returningClause,
573
- ]
574
- .filter(Boolean)
575
- .join(' ');
576
- const result = await this.pool.query(sql, [...values, ...where.values]);
577
-
578
- return this.createMutationResponse(
579
- (result.rows as Array<Record<string, unknown>>).map((row) => deserializeRow(this.tableName, row)),
580
- );
581
- }
582
-
583
- /**
584
- * Executes a delete query.
585
- */
586
- private async executeDelete(): Promise<LocalPostgresQueryResult> {
587
- const where = this.createWhereClause();
588
- const returningClause = this.isReturningSelection
589
- ? ` RETURNING ${createSelectExpression(parseSelectedColumns(this.selectedColumns))}`
590
- : '';
591
- const sql = [
592
- `DELETE FROM ${quoteIdentifier(this.tableName)}`,
593
- where.sql,
594
- returningClause,
595
- ]
596
- .filter(Boolean)
597
- .join(' ');
598
- const result = await this.pool.query(sql, [...where.values]);
599
-
600
- return this.createMutationResponse(
601
- (result.rows as Array<Record<string, unknown>>).map((row) => deserializeRow(this.tableName, row)),
602
- );
603
- }
604
-
605
- /**
606
- * Executes an upsert query.
607
- */
608
- private async executeUpsert(): Promise<LocalPostgresQueryResult> {
609
- const returnedRows: Array<Record<string, unknown>> = [];
610
- const tableBaseName = resolveTableBaseName(this.tableName);
611
- const conflictColumns = resolveUpsertConflictColumns(tableBaseName, this.upsertOptions);
612
- const returningClause = this.isReturningSelection ? ` RETURNING ${createSelectExpression(parseSelectedColumns(this.selectedColumns))}` : '';
613
-
614
- for (const rawRow of this.mutationRows) {
615
- const row = withInsertDefaults(tableBaseName, rawRow);
616
- const columns = Object.keys(row).filter((column) => row[column] !== undefined);
617
- const values = columns.map((column) => serializeValue(this.tableName, column, row[column]));
618
- const columnSql = columns.map(quoteIdentifier).join(', ');
619
- const placeholderSql = values.map((_, index) => `$${index + 1}`).join(', ');
620
- const sql = buildUpsertSql({
621
- tableName: this.tableName,
622
- columnSql,
623
- columns,
624
- conflictColumns,
625
- placeholderSql,
626
- returningClause,
627
- });
628
- const result = await this.pool.query(sql, values);
629
-
630
- if (this.isReturningSelection) {
631
- returnedRows.push(...(result.rows as Array<Record<string, unknown>>).map((rowItem) => deserializeRow(this.tableName, rowItem)));
632
- }
633
- }
634
-
635
- return this.createMutationResponse(returnedRows);
636
- }
637
-
638
- /**
639
- * Creates a mutation response, optionally returning changed rows.
640
- */
641
- private createMutationResponse(rows: ReadonlyArray<Record<string, unknown>>): LocalPostgresQueryResult {
642
- if (!this.isReturningSelection) {
643
- return {
644
- data: null,
645
- error: null,
646
- status: 201,
647
- statusText: 'Created',
648
- };
649
- }
650
-
651
- return this.finalizeDataResponse([...rows], null);
652
- }
653
-
654
- /**
655
- * Applies single/maybeSingle response semantics.
656
- */
657
- private finalizeDataResponse(data: Array<Record<string, unknown>>, count: number | null): LocalPostgresQueryResult {
658
- if (this.singleMode === 'single') {
659
- if (data.length !== 1) {
660
- return {
661
- data: null,
662
- error: {
663
- code: 'PGRST116',
664
- message: `Expected exactly one row, received ${data.length}.`,
665
- },
666
- count,
667
- status: 406,
668
- statusText: 'Not Acceptable',
669
- };
670
- }
671
-
672
- return { data: data[0], error: null, count, status: 200, statusText: 'OK' };
673
- }
674
-
675
- if (this.singleMode === 'maybeSingle') {
676
- if (data.length > 1) {
677
- return {
678
- data: null,
679
- error: {
680
- code: 'PGRST116',
681
- message: `Expected zero or one row, received ${data.length}.`,
682
- },
683
- count,
684
- status: 406,
685
- statusText: 'Not Acceptable',
686
- };
687
- }
688
-
689
- return { data: data[0] || null, error: null, count, status: 200, statusText: 'OK' };
690
- }
691
-
692
- return { data, error: null, count, status: 200, statusText: 'OK' };
693
- }
694
-
695
- /**
696
- * Counts rows matching the current filters.
697
- */
698
- private async executeCount(where: { readonly sql: string; readonly values: ReadonlyArray<unknown> }): Promise<number> {
699
- const row = (
700
- await this.pool.query<{ count: string }>(
701
- `SELECT COUNT(*)::text AS "count" FROM ${quoteIdentifier(this.tableName)} ${where.sql}`,
702
- [...where.values],
703
- )
704
- ).rows[0];
705
-
706
- return Number(row?.count || 0);
707
- }
708
-
709
- /**
710
- * Creates the SQL WHERE clause.
711
- */
712
- private createWhereClause(startingIndex = 1): { readonly sql: string; readonly values: ReadonlyArray<unknown> } {
713
- const parts: Array<string> = [];
714
- const values: Array<unknown> = [];
715
-
716
- for (const filter of this.filters) {
717
- const condition = createFilterCondition(this.tableName, filter, startingIndex + values.length);
718
- parts.push(condition.sql);
719
- values.push(...condition.values);
720
- }
721
-
722
- for (const filter of this.orFilters) {
723
- const condition = createOrFilterCondition(this.tableName, filter, startingIndex + values.length);
724
- if (!condition) {
725
- continue;
726
- }
727
-
728
- parts.push(condition.sql);
729
- values.push(...condition.values);
730
- }
731
-
732
- return {
733
- sql: parts.length > 0 ? `WHERE ${parts.join(' AND ')}` : '',
734
- values,
735
- };
736
- }
737
-
738
- /**
739
- * Creates the SQL ORDER BY clause.
740
- */
741
- private createOrderByClause(): string {
742
- if (this.orders.length === 0) {
743
- return '';
744
- }
745
-
746
- const orderParts: Array<string> = [];
747
- for (const order of this.orders) {
748
- const quotedColumn = quoteIdentifier(order.column);
749
- const direction = order.ascending ? 'ASC' : 'DESC';
750
-
751
- if (order.nullsFirst === true) {
752
- orderParts.push(`${quotedColumn} ${direction} NULLS FIRST`);
753
- } else if (order.nullsFirst === false) {
754
- orderParts.push(`${quotedColumn} ${direction} NULLS LAST`);
755
- } else {
756
- orderParts.push(`${quotedColumn} ${direction}`);
757
- }
758
- }
759
-
760
- return `ORDER BY ${orderParts.join(', ')}`;
761
- }
762
-
763
- /**
764
- * Creates the SQL LIMIT/OFFSET clause.
765
- */
766
- private createLimitClause(startingIndex: number): { readonly sql: string; readonly values: ReadonlyArray<unknown> } {
767
- if (this.limitCount === null) {
768
- return { sql: '', values: [] };
769
- }
770
-
771
- if (this.offsetCount === null) {
772
- return { sql: `LIMIT $${startingIndex}`, values: [this.limitCount] };
773
- }
774
-
775
- return {
776
- sql: `LIMIT $${startingIndex} OFFSET $${startingIndex + 1}`,
777
- values: [this.limitCount, this.offsetCount],
778
- };
779
- }
780
- }
781
-
782
- /**
783
- * Builds one PostgreSQL upsert statement.
784
- */
785
- function buildUpsertSql(options: {
786
- readonly tableName: string;
787
- readonly columnSql: string;
788
- readonly columns: ReadonlyArray<string>;
789
- readonly conflictColumns: ReadonlyArray<string>;
790
- readonly placeholderSql: string;
791
- readonly returningClause: string;
792
- }): string {
793
- const insertSql = `INSERT INTO ${quoteIdentifier(options.tableName)} (${options.columnSql}) VALUES (${options.placeholderSql})`;
794
-
795
- if (options.conflictColumns.length === 0) {
796
- return `${insertSql}${options.returningClause}`;
797
- }
798
-
799
- const updateColumns = options.columns.filter((column) => column !== 'id');
800
- const updateAssignments =
801
- updateColumns.length > 0
802
- ? updateColumns.map((column) => `${quoteIdentifier(column)} = EXCLUDED.${quoteIdentifier(column)}`).join(', ')
803
- : `${quoteIdentifier(options.conflictColumns[0]!)} = EXCLUDED.${quoteIdentifier(options.conflictColumns[0]!)} `;
804
-
805
- return `${insertSql} ON CONFLICT (${options.conflictColumns.map(quoteIdentifier).join(', ')}) DO UPDATE SET ${updateAssignments}${options.returningClause}`;
806
- }
807
-
808
- /**
809
- * Parsed PostgREST filter expression.
810
- */
811
- type ParsedPostgrestFilter = {
812
- readonly column: string;
813
- readonly operator: string;
814
- readonly value: string;
815
- };
816
-
817
- /**
818
- * Creates SQL for one simple filter.
819
- */
820
- function createFilterCondition(
821
- tableName: string,
822
- filter: LocalPostgresFilter,
823
- startingIndex: number,
824
- ): { readonly sql: string; readonly values: ReadonlyArray<unknown> } {
825
- const column = quoteIdentifier(filter.column);
826
- const value = serializeValue(tableName, filter.column, filter.value);
827
-
828
- switch (filter.operator) {
829
- case 'eq':
830
- return value === null
831
- ? { sql: `${column} IS NULL`, values: [] }
832
- : { sql: `${column} = $${startingIndex}`, values: [value] };
833
- case 'neq':
834
- return value === null
835
- ? { sql: `${column} IS NOT NULL`, values: [] }
836
- : { sql: `${column} <> $${startingIndex}`, values: [value] };
837
- case 'is':
838
- return filter.value === null
839
- ? { sql: `${column} IS NULL`, values: [] }
840
- : { sql: `${column} IS NOT DISTINCT FROM $${startingIndex}`, values: [value] };
841
- case 'not-is':
842
- return filter.value === null
843
- ? { sql: `${column} IS NOT NULL`, values: [] }
844
- : { sql: `${column} IS DISTINCT FROM $${startingIndex}`, values: [value] };
845
- case 'in': {
846
- const values = Array.isArray(filter.value)
847
- ? filter.value.map((item) => serializeValue(tableName, filter.column, item))
848
- : [];
849
- if (values.length === 0) {
850
- return { sql: '0 = 1', values: [] };
851
- }
852
-
853
- const placeholders = values.map((_, index) => `$${startingIndex + index}`).join(', ');
854
- return { sql: `${column} IN (${placeholders})`, values };
855
- }
856
- case 'lt':
857
- return { sql: `${column} < $${startingIndex}`, values: [value] };
858
- case 'lte':
859
- return { sql: `${column} <= $${startingIndex}`, values: [value] };
860
- case 'gt':
861
- return { sql: `${column} > $${startingIndex}`, values: [value] };
862
- case 'gte':
863
- return { sql: `${column} >= $${startingIndex}`, values: [value] };
864
- case 'like':
865
- return { sql: `${column} LIKE $${startingIndex}`, values: [value] };
866
- case 'ilike':
867
- return { sql: `${column} ILIKE $${startingIndex}`, values: [value] };
868
- default:
869
- return { sql: '1 = 1', values: [] };
870
- }
871
- }
872
-
873
- /**
874
- * Creates SQL for one PostgREST `.or(...)` filter.
875
- */
876
- function createOrFilterCondition(
877
- tableName: string,
878
- filter: string,
879
- startingIndex: number,
880
- ): { readonly sql: string; readonly values: ReadonlyArray<unknown> } | null {
881
- const conditions: Array<string> = [];
882
- const values: Array<unknown> = [];
883
-
884
- for (const part of splitPostgrestOrFilter(filter)) {
885
- const parsedFilter = parsePostgrestFilter(part);
886
- if (!parsedFilter) {
887
- continue;
888
- }
889
-
890
- if (parsedFilter.operator === 'cs') {
891
- conditions.push('0 = 1');
892
- continue;
893
- }
894
-
895
- const condition = createFilterCondition(
896
- tableName,
897
- {
898
- column: parsedFilter.column,
899
- operator: normalizePostgrestOperator(parsedFilter.operator),
900
- value: decodePostgrestFilterValue(parsedFilter.value),
901
- },
902
- startingIndex + values.length,
903
- );
904
- conditions.push(condition.sql);
905
- values.push(...condition.values);
906
- }
907
-
908
- if (conditions.length === 0) {
909
- return null;
910
- }
911
-
912
- return {
913
- sql: `(${conditions.join(' OR ')})`,
914
- values,
915
- };
916
- }
917
-
918
- /**
919
- * Splits a PostgREST OR filter while keeping JSON literals intact.
920
- */
921
- function splitPostgrestOrFilter(filter: string): Array<string> {
922
- const parts: Array<string> = [];
923
- let current = '';
924
- let depth = 0;
925
- let isInsideString = false;
926
-
927
- for (let index = 0; index < filter.length; index++) {
928
- const character = filter[index]!;
929
- const previousCharacter = filter[index - 1];
930
-
931
- if (character === '"' && previousCharacter !== '\\') {
932
- isInsideString = !isInsideString;
933
- } else if (!isInsideString && (character === '{' || character === '[')) {
934
- depth++;
935
- } else if (!isInsideString && (character === '}' || character === ']')) {
936
- depth--;
937
- }
938
-
939
- if (character === ',' && depth === 0 && !isInsideString) {
940
- parts.push(current);
941
- current = '';
942
- continue;
943
- }
944
-
945
- current += character;
946
- }
947
-
948
- if (current) {
949
- parts.push(current);
950
- }
951
-
952
- return parts.map((part) => part.trim()).filter(Boolean);
953
- }
954
-
955
- /**
956
- * Parses one PostgREST filter expression.
957
- */
958
- function parsePostgrestFilter(filter: string): ParsedPostgrestFilter | null {
959
- const match = /^([^.]*)\.([a-z]+)\.([\s\S]*)$/iu.exec(filter.trim());
960
- if (!match) {
961
- return null;
962
- }
963
-
964
- return {
965
- column: match[1]!,
966
- operator: match[2]!.toLowerCase(),
967
- value: match[3]!,
968
- };
969
- }
970
-
971
- /**
972
- * Converts PostgREST operators into internal filter operators.
973
- */
974
- function normalizePostgrestOperator(operator: string): LocalPostgresFilter['operator'] {
975
- switch (operator) {
976
- case 'neq':
977
- return 'neq';
978
- case 'ilike':
979
- return 'ilike';
980
- case 'like':
981
- return 'like';
982
- case 'lt':
983
- return 'lt';
984
- case 'lte':
985
- return 'lte';
986
- case 'gt':
987
- return 'gt';
988
- case 'gte':
989
- return 'gte';
990
- case 'eq':
991
- default:
992
- return 'eq';
993
- }
994
- }
995
-
996
- /**
997
- * Decodes a PostgREST filter value when URL-encoded by callers.
998
- */
999
- function decodePostgrestFilterValue(value: string): string {
1000
- try {
1001
- return decodeURIComponent(value);
1002
- } catch {
1003
- return value;
1004
- }
1005
- }
1006
-
1007
- /**
1008
- * Creates a select expression from parsed columns.
1009
- */
1010
- function createSelectExpression(columns: ReadonlyArray<string>): string {
1011
- if (columns.length === 0 || columns.includes('*')) {
1012
- return '*';
1013
- }
1014
-
1015
- return columns.map(quoteIdentifier).join(', ');
1016
- }
1017
-
1018
- /**
1019
- * Parses a simple Supabase select column list.
1020
- */
1021
- function parseSelectedColumns(columns: string): Array<string> {
1022
- const trimmedColumns = columns.trim();
1023
- if (!trimmedColumns || trimmedColumns === '*') {
1024
- return ['*'];
1025
- }
1026
-
1027
- return trimmedColumns
1028
- .split(',')
1029
- .map((column) => column.trim())
1030
- .filter(Boolean)
1031
- .map((column) => column.split(':').pop() || column)
1032
- .map((column) => column.replace(/\s+/g, ''))
1033
- .filter((column) => /^[A-Za-z_][A-Za-z0-9_]*$/u.test(column));
1034
- }
1035
-
1036
- /**
1037
- * Converts mutation payloads into a uniform array of records.
1038
- */
1039
- function normalizeMutationRows(values: TODO_any): Array<Record<string, unknown>> {
1040
- if (Array.isArray(values)) {
1041
- return values.map((row) => stripUndefinedValues(row || {}));
1042
- }
1043
-
1044
- return [stripUndefinedValues(values || {})];
1045
- }
1046
-
1047
- /**
1048
- * Removes undefined values because Supabase omits them from mutation payloads.
1049
- */
1050
- function stripUndefinedValues(values: Record<string, unknown>): Record<string, unknown> {
1051
- const result: Record<string, unknown> = {};
1052
-
1053
- for (const [key, value] of Object.entries(values)) {
1054
- if (value !== undefined) {
1055
- result[key] = value;
1056
- }
1057
- }
1058
-
1059
- return result;
1060
- }
1061
-
1062
- /**
1063
- * Adds application-side defaults matching the existing standalone database behavior.
1064
- */
1065
- function withInsertDefaults(tableBaseName: string, row: Record<string, unknown>): Record<string, unknown> {
1066
- const nowIso = new Date().toISOString();
1067
- const result = { ...row };
1068
-
1069
- if (result.createdAt === undefined) {
1070
- result.createdAt = nowIso;
1071
- }
1072
- if (result.updatedAt === undefined && tableBaseName !== 'AgentHistory' && tableBaseName !== 'ChatHistory') {
1073
- result.updatedAt = nowIso;
1074
- }
1075
-
1076
- switch (tableBaseName) {
1077
- case 'Agent':
1078
- result.visibility ??= 'PRIVATE';
1079
- result.folderId ??= null;
1080
- result.sortOrder ??= Date.now();
1081
- result.deletedAt ??= null;
1082
- result.preparedModelRequirements ??= null;
1083
- break;
1084
- case 'AgentFolder':
1085
- result.parentId ??= null;
1086
- result.sortOrder ??= Date.now();
1087
- result.deletedAt ??= null;
1088
- result.icon ??= null;
1089
- result.color ??= null;
1090
- break;
1091
- case 'User':
1092
- result.isAdmin ??= false;
1093
- result.profileImageUrl ??= null;
1094
- break;
1095
- case 'UserChat':
1096
- result.messages ??= [];
1097
- result.source ??= 'WEB_UI';
1098
- result.title ??= null;
1099
- result.draftMessage ??= null;
1100
- break;
1101
- case 'UserChatJob':
1102
- result.parameters ??= {};
1103
- result.queuedAt ??= nowIso;
1104
- result.attemptCount ??= 0;
1105
- break;
1106
- case 'UserChatTimeout':
1107
- result.parameters ??= {};
1108
- result.queuedAt ??= nowIso;
1109
- result.attemptCount ??= 0;
1110
- result.runCount ??= 0;
1111
- break;
1112
- case 'ApiTokens':
1113
- result.isRevoked ??= false;
1114
- break;
1115
- case 'Wallet':
1116
- result.isUserScoped ??= true;
1117
- result.isGlobal ??= false;
1118
- result.deletedAt ??= null;
1119
- break;
1120
- case 'UserMemory':
1121
- result.isGlobal ??= false;
1122
- result.deletedAt ??= null;
1123
- break;
1124
- case 'ShareTargetPayload':
1125
- result.attachments ??= [];
1126
- result.consumedAt ??= null;
1127
- break;
1128
- case 'UserPushSubscription':
1129
- result.isChatFocused ??= false;
1130
- break;
1131
- }
1132
-
1133
- return result;
1134
- }
1135
-
1136
- /**
1137
- * Serializes one value for PostgreSQL storage.
1138
- */
1139
- function serializeValue(tableName: string, column: string, value: unknown): unknown {
1140
- if (value === undefined) {
1141
- return undefined;
1142
- }
1143
- if (value === null) {
1144
- return null;
1145
- }
1146
- if (isJsonColumn(tableName, column) && typeof value !== 'string') {
1147
- return JSON.stringify(value);
1148
- }
1149
- if (BOOLEAN_COLUMNS.has(column)) {
1150
- return Boolean(value);
1151
- }
1152
- return value;
1153
- }
1154
-
1155
- /**
1156
- * Deserializes one PostgreSQL row into Supabase-like row values.
1157
- */
1158
- function deserializeRow(tableName: string, row: Record<string, unknown>): Record<string, unknown> {
1159
- const result: Record<string, unknown> = {};
1160
-
1161
- for (const [column, value] of Object.entries(row)) {
1162
- if (value === null || value === undefined) {
1163
- result[column] = null;
1164
- } else if (isJsonColumn(tableName, column) && typeof value === 'string') {
1165
- result[column] = parseJsonValue(value);
1166
- } else if (BOOLEAN_COLUMNS.has(column)) {
1167
- result[column] = Boolean(value);
1168
- } else {
1169
- result[column] = value;
1170
- }
1171
- }
1172
-
1173
- return result;
1174
- }
1175
-
1176
- /**
1177
- * Parses JSON while preserving invalid strings.
1178
- */
1179
- function parseJsonValue(value: string): unknown {
1180
- try {
1181
- return JSON.parse(value);
1182
- } catch {
1183
- return value;
1184
- }
1185
- }
1186
-
1187
- /**
1188
- * Resolves whether a column is JSON for a specific table.
1189
- */
1190
- function isJsonColumn(tableName: string, column: string): boolean {
1191
- return JSON_COLUMNS_BY_TABLE.get(resolveTableBaseName(tableName))?.has(column) || false;
1192
- }
1193
-
1194
- /**
1195
- * Resolves a table base name from the actual prefixed table name.
1196
- */
1197
- function resolveTableBaseName(tableName: string): string {
1198
- const knownTableNames = uniqueStrings([
1199
- '_Server',
1200
- ...Array.from(JSON_COLUMNS_BY_TABLE.keys()),
1201
- ...Array.from(UNIQUE_INDEX_COLUMNS_BY_TABLE.keys()),
1202
- 'AgentFolder',
1203
- 'CustomStylesheet',
1204
- 'CustomJavascript',
1205
- 'CalendarActivity',
1206
- ]).sort((left, right) => right.length - left.length);
1207
-
1208
- return knownTableNames.find((knownTableName) => tableName.endsWith(knownTableName)) || tableName;
1209
- }
1210
-
1211
- /**
1212
- * Resolves upsert conflict columns.
1213
- */
1214
- function resolveUpsertConflictColumns(
1215
- tableBaseName: string,
1216
- options: LocalPostgresUpsertOptions,
1217
- ): ReadonlyArray<string> {
1218
- if (options.onConflict) {
1219
- return options.onConflict
1220
- .split(',')
1221
- .map((column) => column.trim())
1222
- .filter(Boolean);
1223
- }
1224
-
1225
- return DEFAULT_UPSERT_CONFLICT_COLUMNS_BY_TABLE.get(tableBaseName) || [];
1226
- }
1227
-
1228
- /**
1229
- * Converts PostgreSQL errors into Supabase-like errors.
1230
- */
1231
- function normalizePostgresError(error: unknown): LocalPostgresError {
1232
- if (typeof error === 'object' && error !== null) {
1233
- const postgresLikeError = error as Partial<LocalPostgresError>;
1234
- if (typeof postgresLikeError.message === 'string') {
1235
- return {
1236
- code: postgresLikeError.code,
1237
- message: postgresLikeError.message,
1238
- details: postgresLikeError.details,
1239
- hint: postgresLikeError.hint,
1240
- };
1241
- }
1242
- }
1243
-
1244
- return {
1245
- message: error instanceof Error ? error.message : String(error),
1246
- };
1247
- }
1248
-
1249
- /**
1250
- * Quotes one PostgreSQL identifier.
1251
- */
1252
- function quoteIdentifier(identifier: string): string {
1253
- return `"${identifier.replace(/"/g, '""')}"`;
1254
- }
1255
-
1256
- /**
1257
- * Deduplicates strings while preserving order.
1258
- */
1259
- function uniqueStrings(values: ReadonlyArray<string>): Array<string> {
1260
- return Array.from(new Set(values.filter(Boolean)));
1261
- }