@topgunbuild/server 0.7.0 → 0.8.1
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/dist/index.d.mts +303 -2
- package/dist/index.d.ts +303 -2
- package/dist/index.js +843 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +770 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
- package/LICENSE +0 -97
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _topgunbuild_core from '@topgunbuild/core';
|
|
2
|
-
import { Timestamp, LWWRecord, ORMapRecord, Principal, EventJournalImpl, EventJournalConfig, JournalEvent, PermissionPolicy, ConsistencyLevel, ReplicationConfig, LWWMap, ORMap, PermissionType, MigrationConfig, MigrationStatus, MigrationMetrics, PartitionMap, PartitionInfo, PartitionChange, ReplicationLag, ReplicationHealth, ReplicationResult, ClusterReadOptions, MerkleTree, EntryProcessorDef, EntryProcessorResult, HLC, MergeRejection, ConflictResolverDef, MergeContext, MergeResult, IndexedLWWMap, IndexedORMap } from '@topgunbuild/core';
|
|
2
|
+
import { Timestamp, LWWRecord, ORMapRecord, Principal, EventJournalImpl, EventJournalConfig, JournalEvent, PermissionPolicy, ConsistencyLevel, ReplicationConfig, FullTextIndexConfig, LWWMap, ORMap, PermissionType, MigrationConfig, MigrationStatus, MigrationMetrics, PartitionMap, PartitionInfo, PartitionChange, ReplicationLag, ReplicationHealth, ReplicationResult, ClusterReadOptions, MerkleTree, EntryProcessorDef, EntryProcessorResult, HLC, MergeRejection, ConflictResolverDef, MergeContext, MergeResult, IndexedLWWMap, IndexedORMap, SearchUpdateType, FTSSearchOptions, SearchRespPayload, SearchOptions } from '@topgunbuild/core';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { Pool, PoolConfig } from 'pg';
|
|
5
5
|
import pino from 'pino';
|
|
@@ -1858,6 +1858,8 @@ interface ServerCoordinatorConfig {
|
|
|
1858
1858
|
eventJournalEnabled?: boolean;
|
|
1859
1859
|
/** Event journal configuration */
|
|
1860
1860
|
eventJournalConfig?: Partial<Omit<EventJournalServiceConfig, 'pool'>>;
|
|
1861
|
+
/** Enable full-text search for specific maps */
|
|
1862
|
+
fullTextSearch?: Record<string, FullTextIndexConfig>;
|
|
1861
1863
|
}
|
|
1862
1864
|
declare class ServerCoordinator {
|
|
1863
1865
|
private httpServer;
|
|
@@ -1906,12 +1908,18 @@ declare class ServerCoordinator {
|
|
|
1906
1908
|
private readReplicaHandler?;
|
|
1907
1909
|
private merkleTreeManager?;
|
|
1908
1910
|
private repairScheduler?;
|
|
1911
|
+
private searchCoordinator;
|
|
1909
1912
|
private readonly _nodeId;
|
|
1910
1913
|
private _actualPort;
|
|
1911
1914
|
private _actualClusterPort;
|
|
1912
1915
|
private _readyPromise;
|
|
1913
1916
|
private _readyResolve;
|
|
1914
1917
|
constructor(config: ServerCoordinatorConfig);
|
|
1918
|
+
/**
|
|
1919
|
+
* Populate FTS indexes from existing map data.
|
|
1920
|
+
* Called after storage initialization.
|
|
1921
|
+
*/
|
|
1922
|
+
private backfillSearchIndexes;
|
|
1915
1923
|
/** Wait for server to be fully ready (ports assigned) */
|
|
1916
1924
|
ready(): Promise<void>;
|
|
1917
1925
|
/**
|
|
@@ -1947,6 +1955,37 @@ declare class ServerCoordinator {
|
|
|
1947
1955
|
getTaskletSchedulerStats(): TaskletSchedulerStats;
|
|
1948
1956
|
/** Get tasklet scheduler for scheduling long-running operations */
|
|
1949
1957
|
getTaskletScheduler(): TaskletScheduler;
|
|
1958
|
+
/**
|
|
1959
|
+
* Enable full-text search for a map.
|
|
1960
|
+
* Can be called at runtime to enable FTS dynamically.
|
|
1961
|
+
*
|
|
1962
|
+
* @param mapName - Name of the map to enable FTS for
|
|
1963
|
+
* @param config - FTS configuration (fields, tokenizer, bm25 options)
|
|
1964
|
+
*/
|
|
1965
|
+
enableFullTextSearch(mapName: string, config: FullTextIndexConfig): void;
|
|
1966
|
+
/**
|
|
1967
|
+
* Disable full-text search for a map.
|
|
1968
|
+
*
|
|
1969
|
+
* @param mapName - Name of the map to disable FTS for
|
|
1970
|
+
*/
|
|
1971
|
+
disableFullTextSearch(mapName: string): void;
|
|
1972
|
+
/**
|
|
1973
|
+
* Check if full-text search is enabled for a map.
|
|
1974
|
+
*
|
|
1975
|
+
* @param mapName - Name of the map to check
|
|
1976
|
+
* @returns True if FTS is enabled
|
|
1977
|
+
*/
|
|
1978
|
+
isFullTextSearchEnabled(mapName: string): boolean;
|
|
1979
|
+
/**
|
|
1980
|
+
* Get FTS index statistics for a map.
|
|
1981
|
+
*
|
|
1982
|
+
* @param mapName - Name of the map
|
|
1983
|
+
* @returns Index stats or null if FTS not enabled
|
|
1984
|
+
*/
|
|
1985
|
+
getFullTextSearchStats(mapName: string): {
|
|
1986
|
+
documentCount: number;
|
|
1987
|
+
fields: string[];
|
|
1988
|
+
} | null;
|
|
1950
1989
|
/**
|
|
1951
1990
|
* Phase 10.02: Graceful cluster departure
|
|
1952
1991
|
*
|
|
@@ -4442,4 +4481,266 @@ declare class MapFactory {
|
|
|
4442
4481
|
getConfig(): ServerIndexConfig;
|
|
4443
4482
|
}
|
|
4444
4483
|
|
|
4445
|
-
|
|
4484
|
+
/**
|
|
4485
|
+
* SearchCoordinator - Server-side Full-Text Search Handler
|
|
4486
|
+
*
|
|
4487
|
+
* Manages FullTextIndex instances per map and handles search requests.
|
|
4488
|
+
* Part of Phase 11.1a: Server-side BM25 Search.
|
|
4489
|
+
* Phase 11.1b: Live Search Subscriptions with delta updates.
|
|
4490
|
+
*
|
|
4491
|
+
* @module search/SearchCoordinator
|
|
4492
|
+
*/
|
|
4493
|
+
|
|
4494
|
+
/**
|
|
4495
|
+
* Result item returned from server search.
|
|
4496
|
+
*/
|
|
4497
|
+
interface ServerSearchResult {
|
|
4498
|
+
key: string;
|
|
4499
|
+
value: unknown;
|
|
4500
|
+
score: number;
|
|
4501
|
+
matchedTerms: string[];
|
|
4502
|
+
}
|
|
4503
|
+
/**
|
|
4504
|
+
* Configuration for enabling search on a map.
|
|
4505
|
+
*/
|
|
4506
|
+
interface SearchConfig extends FullTextIndexConfig {
|
|
4507
|
+
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Callback type for sending updates to clients.
|
|
4510
|
+
*/
|
|
4511
|
+
type SendUpdateCallback = (clientId: string, subscriptionId: string, key: string, value: unknown, score: number, matchedTerms: string[], type: SearchUpdateType) => void;
|
|
4512
|
+
/**
|
|
4513
|
+
* Batched update for a single document change.
|
|
4514
|
+
*/
|
|
4515
|
+
interface BatchedUpdate {
|
|
4516
|
+
key: string;
|
|
4517
|
+
value: unknown;
|
|
4518
|
+
score: number;
|
|
4519
|
+
matchedTerms: string[];
|
|
4520
|
+
type: SearchUpdateType;
|
|
4521
|
+
}
|
|
4522
|
+
/**
|
|
4523
|
+
* Callback type for sending batched updates to clients.
|
|
4524
|
+
*/
|
|
4525
|
+
type SendBatchUpdateCallback = (clientId: string, subscriptionId: string, updates: BatchedUpdate[]) => void;
|
|
4526
|
+
/**
|
|
4527
|
+
* SearchCoordinator manages full-text search indexes for the server.
|
|
4528
|
+
*
|
|
4529
|
+
* Responsibilities:
|
|
4530
|
+
* - Maintain FullTextIndex per enabled map
|
|
4531
|
+
* - Execute one-shot search queries
|
|
4532
|
+
* - Update indexes when data changes
|
|
4533
|
+
*
|
|
4534
|
+
* @example
|
|
4535
|
+
* ```typescript
|
|
4536
|
+
* const searchCoordinator = new SearchCoordinator();
|
|
4537
|
+
*
|
|
4538
|
+
* // Enable FTS for a map
|
|
4539
|
+
* searchCoordinator.enableSearch('articles', {
|
|
4540
|
+
* fields: ['title', 'body'],
|
|
4541
|
+
* tokenizer: { minLength: 2 },
|
|
4542
|
+
* bm25: { k1: 1.2, b: 0.75 }
|
|
4543
|
+
* });
|
|
4544
|
+
*
|
|
4545
|
+
* // Search
|
|
4546
|
+
* const results = searchCoordinator.search('articles', 'machine learning', {
|
|
4547
|
+
* limit: 20,
|
|
4548
|
+
* boost: { title: 2.0 }
|
|
4549
|
+
* });
|
|
4550
|
+
* ```
|
|
4551
|
+
*/
|
|
4552
|
+
declare class SearchCoordinator {
|
|
4553
|
+
/** Map name → FullTextIndex */
|
|
4554
|
+
private readonly indexes;
|
|
4555
|
+
/** Map name → FullTextIndexConfig (for reference) */
|
|
4556
|
+
private readonly configs;
|
|
4557
|
+
/** Callback to get document value by key (injected by ServerCoordinator) */
|
|
4558
|
+
private getDocumentValue?;
|
|
4559
|
+
/** Subscription ID → SearchSubscription */
|
|
4560
|
+
private readonly subscriptions;
|
|
4561
|
+
/** Map name → Set of subscription IDs */
|
|
4562
|
+
private readonly subscriptionsByMap;
|
|
4563
|
+
/** Client ID → Set of subscription IDs */
|
|
4564
|
+
private readonly subscriptionsByClient;
|
|
4565
|
+
/** Callback for sending updates to clients */
|
|
4566
|
+
private sendUpdate?;
|
|
4567
|
+
/** Callback for sending batched updates to clients */
|
|
4568
|
+
private sendBatchUpdate?;
|
|
4569
|
+
/** Queue of pending notifications per map */
|
|
4570
|
+
private readonly pendingNotifications;
|
|
4571
|
+
/** Timer for batching notifications */
|
|
4572
|
+
private notificationTimer;
|
|
4573
|
+
/** Batch interval in milliseconds (~1 frame at 60fps) */
|
|
4574
|
+
private readonly BATCH_INTERVAL;
|
|
4575
|
+
constructor();
|
|
4576
|
+
/**
|
|
4577
|
+
* Set the callback for sending updates to clients.
|
|
4578
|
+
* Called by ServerCoordinator during initialization.
|
|
4579
|
+
*/
|
|
4580
|
+
setSendUpdateCallback(callback: SendUpdateCallback): void;
|
|
4581
|
+
/**
|
|
4582
|
+
* Set the callback for sending batched updates to clients.
|
|
4583
|
+
* When set, notifications are batched within BATCH_INTERVAL (16ms) window.
|
|
4584
|
+
* Called by ServerCoordinator during initialization.
|
|
4585
|
+
*
|
|
4586
|
+
* @param callback - Function to call with batched updates
|
|
4587
|
+
*/
|
|
4588
|
+
setSendBatchUpdateCallback(callback: SendBatchUpdateCallback): void;
|
|
4589
|
+
/**
|
|
4590
|
+
* Set the callback for retrieving document values.
|
|
4591
|
+
* Called by ServerCoordinator during initialization.
|
|
4592
|
+
*/
|
|
4593
|
+
setDocumentValueGetter(getter: (mapName: string, key: string) => unknown | undefined): void;
|
|
4594
|
+
/**
|
|
4595
|
+
* Enable full-text search for a map.
|
|
4596
|
+
*
|
|
4597
|
+
* @param mapName - Name of the map to enable FTS for
|
|
4598
|
+
* @param config - FTS configuration (fields, tokenizer, bm25 options)
|
|
4599
|
+
*/
|
|
4600
|
+
enableSearch(mapName: string, config: SearchConfig): void;
|
|
4601
|
+
/**
|
|
4602
|
+
* Disable full-text search for a map.
|
|
4603
|
+
*
|
|
4604
|
+
* @param mapName - Name of the map to disable FTS for
|
|
4605
|
+
*/
|
|
4606
|
+
disableSearch(mapName: string): void;
|
|
4607
|
+
/**
|
|
4608
|
+
* Check if FTS is enabled for a map.
|
|
4609
|
+
*/
|
|
4610
|
+
isSearchEnabled(mapName: string): boolean;
|
|
4611
|
+
/**
|
|
4612
|
+
* Get enabled map names.
|
|
4613
|
+
*/
|
|
4614
|
+
getEnabledMaps(): string[];
|
|
4615
|
+
/**
|
|
4616
|
+
* Execute a one-shot search query.
|
|
4617
|
+
*
|
|
4618
|
+
* @param mapName - Name of the map to search
|
|
4619
|
+
* @param query - Search query text
|
|
4620
|
+
* @param options - Search options (limit, minScore, boost)
|
|
4621
|
+
* @returns Search response payload
|
|
4622
|
+
*/
|
|
4623
|
+
search(mapName: string, query: string, options?: FTSSearchOptions): SearchRespPayload;
|
|
4624
|
+
/**
|
|
4625
|
+
* Handle document set/update.
|
|
4626
|
+
* Called by ServerCoordinator when data changes.
|
|
4627
|
+
*
|
|
4628
|
+
* @param mapName - Name of the map
|
|
4629
|
+
* @param key - Document key
|
|
4630
|
+
* @param value - Document value
|
|
4631
|
+
*/
|
|
4632
|
+
onDataChange(mapName: string, key: string, value: Record<string, unknown> | null | undefined, changeType: 'add' | 'update' | 'remove'): void;
|
|
4633
|
+
/**
|
|
4634
|
+
* Build index from existing map entries.
|
|
4635
|
+
* Called when FTS is enabled for a map that already has data.
|
|
4636
|
+
*
|
|
4637
|
+
* @param mapName - Name of the map
|
|
4638
|
+
* @param entries - Iterator of [key, value] tuples
|
|
4639
|
+
*/
|
|
4640
|
+
buildIndexFromEntries(mapName: string, entries: Iterable<[string, Record<string, unknown> | null]>): void;
|
|
4641
|
+
/**
|
|
4642
|
+
* Get index statistics for monitoring.
|
|
4643
|
+
*/
|
|
4644
|
+
getIndexStats(mapName: string): {
|
|
4645
|
+
documentCount: number;
|
|
4646
|
+
fields: string[];
|
|
4647
|
+
} | null;
|
|
4648
|
+
/**
|
|
4649
|
+
* Clear all indexes (for testing or shutdown).
|
|
4650
|
+
*/
|
|
4651
|
+
clear(): void;
|
|
4652
|
+
/**
|
|
4653
|
+
* Subscribe to live search results.
|
|
4654
|
+
* Returns initial results and tracks the subscription for delta updates.
|
|
4655
|
+
*
|
|
4656
|
+
* @param clientId - ID of the subscribing client
|
|
4657
|
+
* @param subscriptionId - Unique subscription identifier
|
|
4658
|
+
* @param mapName - Name of the map to search
|
|
4659
|
+
* @param query - Search query text
|
|
4660
|
+
* @param options - Search options (limit, minScore, boost)
|
|
4661
|
+
* @returns Initial search results
|
|
4662
|
+
*/
|
|
4663
|
+
subscribe(clientId: string, subscriptionId: string, mapName: string, query: string, options?: SearchOptions): ServerSearchResult[];
|
|
4664
|
+
/**
|
|
4665
|
+
* Unsubscribe from a live search.
|
|
4666
|
+
*
|
|
4667
|
+
* @param subscriptionId - Subscription to remove
|
|
4668
|
+
*/
|
|
4669
|
+
unsubscribe(subscriptionId: string): void;
|
|
4670
|
+
/**
|
|
4671
|
+
* Unsubscribe all subscriptions for a client.
|
|
4672
|
+
* Called when a client disconnects.
|
|
4673
|
+
*
|
|
4674
|
+
* @param clientId - ID of the disconnected client
|
|
4675
|
+
*/
|
|
4676
|
+
unsubscribeClient(clientId: string): void;
|
|
4677
|
+
/**
|
|
4678
|
+
* Get the number of active subscriptions.
|
|
4679
|
+
*/
|
|
4680
|
+
getSubscriptionCount(): number;
|
|
4681
|
+
/**
|
|
4682
|
+
* Notify subscribers about a document change.
|
|
4683
|
+
* Computes delta (ENTER/UPDATE/LEAVE) for each affected subscription.
|
|
4684
|
+
*
|
|
4685
|
+
* @param mapName - Name of the map that changed
|
|
4686
|
+
* @param key - Document key that changed
|
|
4687
|
+
* @param value - New document value (null if removed)
|
|
4688
|
+
* @param changeType - Type of change
|
|
4689
|
+
*/
|
|
4690
|
+
private notifySubscribers;
|
|
4691
|
+
/**
|
|
4692
|
+
* Score a single document against a subscription's query.
|
|
4693
|
+
*
|
|
4694
|
+
* OPTIMIZED: O(Q × D) complexity instead of O(N) full index scan.
|
|
4695
|
+
* Uses pre-tokenized queryTerms and FullTextIndex.scoreSingleDocument().
|
|
4696
|
+
*
|
|
4697
|
+
* @param subscription - The subscription containing query and cached queryTerms
|
|
4698
|
+
* @param key - Document key
|
|
4699
|
+
* @param value - Document value
|
|
4700
|
+
* @param index - The FullTextIndex for this map
|
|
4701
|
+
* @returns Scored result or null if document doesn't match
|
|
4702
|
+
*/
|
|
4703
|
+
private scoreDocument;
|
|
4704
|
+
/**
|
|
4705
|
+
* Queue a notification for batched processing.
|
|
4706
|
+
* Notifications are collected and processed together after BATCH_INTERVAL.
|
|
4707
|
+
*
|
|
4708
|
+
* @param mapName - Name of the map that changed
|
|
4709
|
+
* @param key - Document key that changed
|
|
4710
|
+
* @param value - New document value (null if removed)
|
|
4711
|
+
* @param changeType - Type of change
|
|
4712
|
+
*/
|
|
4713
|
+
queueNotification(mapName: string, key: string, value: Record<string, unknown> | null, changeType: 'add' | 'update' | 'remove'): void;
|
|
4714
|
+
/**
|
|
4715
|
+
* Schedule a flush of pending notifications.
|
|
4716
|
+
* Uses setTimeout to batch notifications within BATCH_INTERVAL window.
|
|
4717
|
+
*/
|
|
4718
|
+
private scheduleNotificationFlush;
|
|
4719
|
+
/**
|
|
4720
|
+
* Flush all pending notifications.
|
|
4721
|
+
* Processes each map's notifications and sends batched updates.
|
|
4722
|
+
*/
|
|
4723
|
+
flushNotifications(): void;
|
|
4724
|
+
/**
|
|
4725
|
+
* Process batched notifications for a single map.
|
|
4726
|
+
* Computes updates for each subscription and sends as a batch.
|
|
4727
|
+
*
|
|
4728
|
+
* @param mapName - Name of the map
|
|
4729
|
+
* @param notifications - Array of pending notifications
|
|
4730
|
+
*/
|
|
4731
|
+
private processBatchedNotifications;
|
|
4732
|
+
/**
|
|
4733
|
+
* Compute the update for a single document change against a subscription.
|
|
4734
|
+
* Returns null if no update is needed.
|
|
4735
|
+
*
|
|
4736
|
+
* @param subscription - The subscription to check
|
|
4737
|
+
* @param key - Document key
|
|
4738
|
+
* @param value - Document value (null if removed)
|
|
4739
|
+
* @param changeType - Type of change
|
|
4740
|
+
* @param index - The FullTextIndex for this map
|
|
4741
|
+
* @returns BatchedUpdate or null
|
|
4742
|
+
*/
|
|
4743
|
+
private computeSubscriptionUpdate;
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
export { BufferPool, type BufferPoolConfig, type BufferPoolStats, type ClusterConfig, ClusterCoordinator, type ClusterCoordinatorConfig, type ClusterCoordinatorEvents, ClusterManager, type ClusterMember, type ClusterMessage, type CoalescingPreset, type CoalescingWriterMetrics, type CoalescingWriterOptions, ConflictResolverHandler, type ConflictResolverHandlerConfig, ConflictResolverService, type ConflictResolverServiceConfig, type ConnectionContext, ConnectionRateLimiter, DEFAULT_CLUSTER_COORDINATOR_CONFIG, DEFAULT_CONFLICT_RESOLVER_CONFIG, DEFAULT_FAILURE_DETECTOR_CONFIG, DEFAULT_INDEX_CONFIG, DEFAULT_JOURNAL_SERVICE_CONFIG, DEFAULT_LAG_TRACKER_CONFIG, DEFAULT_MERKLE_TREE_CONFIG, DEFAULT_READ_REPLICA_CONFIG, DEFAULT_REASSIGNER_CONFIG, DEFAULT_REPAIR_CONFIG, DEFAULT_SANDBOX_CONFIG, EntryProcessorHandler, type EntryProcessorHandlerConfig, EventJournalService, type EventJournalServiceConfig, type ExportOptions, type FailoverStatus, FailureDetector, type FailureDetectorConfig, type FailureDetectorEvents, FilterTasklet, ForEachTasklet, type IInterceptor, type IServerStorage, type IndexDefinition, IteratorTasklet, type IteratorTaskletConfig, type LagInfo, LagTracker, type LagTrackerConfig, LockManager, type Logger, MapFactory, type MapIndexConfig, MapTasklet, MapWithResolver, type MapWithResolverConfig, MemoryServerAdapter, type MergeWithResolverResult, type MerkleComparisonResult, MerkleTreeManager, type MerkleTreeManagerConfig, MigrationManager, type NativeModuleStatus, type NativeStats, type NodeState, type ORMapTombstones, type ORMapValue, ObjectPool, type ObjectPoolConfig, type ObjectPoolStats, type OpContext, type PartitionDistribution, type PartitionMerkleInfo, PartitionReassigner, type PartitionReassignerConfig, PartitionService, type PartitionServiceConfig, type PartitionServiceEvents, type PooledEventPayload, type PooledMessage, type PooledRecord, type PooledTimestamp, PostgresAdapter, type PostgresAdapterOptions, ProcessorSandbox, type ProcessorSandboxConfig, type ProgressState, RateLimitInterceptor, type RateLimiterConfig, type RateLimiterStats, type ReadReplicaConfig, ReadReplicaHandler, type ReadRequest, type ReadResult, type ReassignmentEvent, ReduceTasklet, type RepairConfig, type RepairMetrics, type RepairResult, RepairScheduler, type RepairTask, ReplicationPipeline, type SearchConfig, SearchCoordinator, SecurityManager, ServerCoordinator, type ServerCoordinatorConfig, type ServerIndexConfig, type ServerOp, type ServerSearchResult, type SetWithResolverResult, type StorageValue, type Tasklet, TaskletScheduler, type TaskletSchedulerConfig, type TaskletSchedulerStats, TimestampInterceptor, coalescingPresets, createEventPayloadPool, createMessagePool, createRecordPool, createTimestampPool, getCoalescingPreset, getGlobalBufferPool, getGlobalEventPayloadPool, getGlobalMessagePool, getGlobalRecordPool, getGlobalTimestampPool, getNativeModuleStatus, getNativeStats, logNativeStatus, logger, mergeWithDefaults, setGlobalBufferPool, setGlobalEventPayloadPool, setGlobalMessagePool, setGlobalRecordPool, setGlobalTimestampPool, validateIndexConfig };
|