@fairfox/polly 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/src/background/index.js +22 -12
- package/dist/src/background/index.js.map +8 -8
- package/dist/src/background/message-router.js +22 -12
- package/dist/src/background/message-router.js.map +8 -8
- package/dist/src/client/index.js +187 -154
- package/dist/src/client/index.js.map +4 -4
- package/dist/src/elysia/index.d.ts +2 -0
- package/dist/src/elysia/index.js +195 -25
- package/dist/src/elysia/index.js.map +8 -5
- package/dist/src/elysia/peer-repo-plugin.d.ts +79 -0
- package/dist/src/elysia/plugin.d.ts +3 -3
- package/dist/src/elysia/signaling-server-plugin.d.ts +121 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.js +1752 -13
- package/dist/src/index.js.map +31 -13
- package/dist/src/shared/adapters/index.js +22 -12
- package/dist/src/shared/adapters/index.js.map +7 -7
- package/dist/src/shared/lib/_client-only.d.ts +38 -0
- package/dist/src/shared/lib/access.d.ts +124 -0
- package/dist/src/shared/lib/blob-ref.d.ts +72 -0
- package/dist/src/shared/lib/context-helpers.js +22 -12
- package/dist/src/shared/lib/context-helpers.js.map +8 -8
- package/dist/src/shared/lib/crdt-specialised.d.ts +129 -0
- package/dist/src/shared/lib/crdt-state.d.ts +86 -0
- package/dist/src/shared/lib/encryption.d.ts +117 -0
- package/dist/src/shared/lib/errors.js +19 -9
- package/dist/src/shared/lib/errors.js.map +2 -2
- package/dist/src/shared/lib/mesh-network-adapter.d.ts +130 -0
- package/dist/src/shared/lib/mesh-signaling-client.d.ts +85 -0
- package/dist/src/shared/lib/mesh-state.d.ts +102 -0
- package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +132 -0
- package/dist/src/shared/lib/message-bus.js +22 -12
- package/dist/src/shared/lib/message-bus.js.map +8 -8
- package/dist/src/shared/lib/migrate-primitive.d.ts +100 -0
- package/dist/src/shared/lib/pairing.d.ts +170 -0
- package/dist/src/shared/lib/peer-relay-adapter.d.ts +80 -0
- package/dist/src/shared/lib/peer-repo-server.d.ts +83 -0
- package/dist/src/shared/lib/peer-state.d.ts +117 -0
- package/dist/src/shared/lib/primitive-registry.d.ts +88 -0
- package/dist/src/shared/lib/resource.js +22 -12
- package/dist/src/shared/lib/resource.js.map +5 -5
- package/dist/src/shared/lib/revocation.d.ts +126 -0
- package/dist/src/shared/lib/schema-version.d.ts +129 -0
- package/dist/src/shared/lib/signing.d.ts +118 -0
- package/dist/src/shared/lib/state.js +22 -12
- package/dist/src/shared/lib/state.js.map +5 -5
- package/dist/src/shared/lib/test-helpers.js +19 -9
- package/dist/src/shared/lib/test-helpers.js.map +2 -2
- package/dist/src/shared/state/app-state.js +22 -12
- package/dist/src/shared/state/app-state.js.map +6 -6
- package/dist/src/shared/types/messages.js +19 -9
- package/dist/src/shared/types/messages.js.map +2 -2
- package/dist/tools/init/src/cli.js +6 -2
- package/dist/tools/init/src/cli.js.map +3 -3
- package/dist/tools/quality/src/index.js +177 -0
- package/dist/tools/quality/src/index.js.map +10 -0
- package/dist/tools/test/src/adapters/index.d.ts +2 -2
- package/dist/tools/test/src/adapters/index.js +19 -9
- package/dist/tools/test/src/adapters/index.js.map +5 -5
- package/dist/tools/test/src/browser/harness.d.ts +80 -0
- package/dist/tools/test/src/browser/index.d.ts +32 -0
- package/dist/tools/test/src/browser/index.js +243 -0
- package/dist/tools/test/src/browser/index.js.map +10 -0
- package/dist/tools/test/src/browser/run.d.ts +26 -0
- package/dist/tools/test/src/index.js +19 -9
- package/dist/tools/test/src/index.js.map +5 -5
- package/dist/tools/test/src/test-utils.js +19 -9
- package/dist/tools/test/src/test-utils.js.map +2 -2
- package/dist/tools/verify/specs/tla/MeshState.cfg +21 -0
- package/dist/tools/verify/specs/tla/MeshState.tla +247 -0
- package/dist/tools/verify/specs/tla/PeerState.cfg +27 -0
- package/dist/tools/verify/specs/tla/PeerState.tla +238 -0
- package/dist/tools/verify/specs/tla/README.md +27 -3
- package/dist/tools/verify/src/cli.js +10 -6
- package/dist/tools/verify/src/cli.js.map +10 -10
- package/dist/tools/verify/src/config.js +19 -9
- package/dist/tools/verify/src/config.js.map +2 -2
- package/dist/tools/visualize/src/cli.js +6 -2
- package/dist/tools/visualize/src/cli.js.map +8 -8
- package/package.json +52 -12
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mesh-state — Phase 2 wrappers exposing $meshState, $meshText, $meshCounter,
|
|
3
|
+
* and $meshList. These are the application-facing constructors for the
|
|
4
|
+
* strongest resilience tier in RFC-041: every device is a full Automerge
|
|
5
|
+
* replica, the server is *not on the data path at all*, and the application
|
|
6
|
+
* functions with zero server uptime once direct peer connections are
|
|
7
|
+
* established.
|
|
8
|
+
*
|
|
9
|
+
* Each primitive wraps the corresponding Phase 0 base ($crdtState, $crdtText,
|
|
10
|
+
* $crdtCounter, $crdtList) with three additions:
|
|
11
|
+
*
|
|
12
|
+
* 1. The `primitive` label is hard-coded to "meshState" so the
|
|
13
|
+
* primitive-registry collision detection knows which family the key
|
|
14
|
+
* belongs to.
|
|
15
|
+
*
|
|
16
|
+
* 2. A handle factory that resolves the application's logical key to an
|
|
17
|
+
* Automerge DocumentId via a per-Repo key map, identical in shape to
|
|
18
|
+
* the $peerState factory but registered against a separate Repo
|
|
19
|
+
* configured for the mesh transport (signed and encrypted at the
|
|
20
|
+
* network layer).
|
|
21
|
+
*
|
|
22
|
+
* 3. Signing and encryption are mandatory, not optional. Where $peerState
|
|
23
|
+
* accepts encrypt/sign as opt-in flags (currently throwing in Phase 1),
|
|
24
|
+
* $meshState requires every operation to be signed by the originating
|
|
25
|
+
* peer's key and encrypted under the document's symmetric key. The
|
|
26
|
+
* mechanism lives in the wrapping MeshNetworkAdapter that the Repo
|
|
27
|
+
* uses for transport.
|
|
28
|
+
*
|
|
29
|
+
* The Repo itself is supplied by the application via {@link configureMeshState}
|
|
30
|
+
* or per-call via the `repo` option. In Phase 2 the production transport will
|
|
31
|
+
* be a WebRTC mesh adapter wrapping signing+encryption around an in-process
|
|
32
|
+
* RTCDataChannel; for tests and for the early Phase 2 cut, an in-memory
|
|
33
|
+
* loopback adapter pair satisfies the same contract.
|
|
34
|
+
*/
|
|
35
|
+
import type { Repo } from "@automerge/automerge-repo";
|
|
36
|
+
import type { Access } from "./access";
|
|
37
|
+
import { type SpecialisedPrimitive } from "./crdt-specialised";
|
|
38
|
+
import { type CrdtPrimitive } from "./crdt-state";
|
|
39
|
+
import type { Migrations, VersionedDoc } from "./schema-version";
|
|
40
|
+
/** Common option shape across all four $mesh* primitives. */
|
|
41
|
+
export interface MeshStateOptions {
|
|
42
|
+
/** Override the default Repo for this primitive. The Repo must be
|
|
43
|
+
* configured with the mesh transport (signing and encryption at the
|
|
44
|
+
* network layer). */
|
|
45
|
+
repo?: Repo;
|
|
46
|
+
/** Schema version target for the application. Migrations run on load. */
|
|
47
|
+
schemaVersion?: number;
|
|
48
|
+
/** Migration table keyed by target version. Required if schemaVersion is set. */
|
|
49
|
+
migrations?: Migrations;
|
|
50
|
+
/** Declarative read/write access. The mesh transport compiles this into
|
|
51
|
+
* a public-key set used by the signing layer to verify incoming ops. */
|
|
52
|
+
access?: Access;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set the default Repo that the $mesh* primitives use when no `repo` option
|
|
56
|
+
* is supplied. Calling this with a new Repo clears the per-Repo key map so
|
|
57
|
+
* that tests start each scenario with a fresh document space.
|
|
58
|
+
*
|
|
59
|
+
* Production code typically calls this once at application startup with a
|
|
60
|
+
* Repo configured for the mesh transport. Tests call it before each
|
|
61
|
+
* scenario with an in-memory or loopback Repo.
|
|
62
|
+
*/
|
|
63
|
+
export declare function configureMeshState(repo: Repo): void;
|
|
64
|
+
/**
|
|
65
|
+
* Reset the mesh-state subsystem to its initial unconfigured state.
|
|
66
|
+
* Intended for tests; production code should not call this.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resetMeshState(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Create a peer-replicated state primitive backed by Automerge with a mesh
|
|
71
|
+
* transport. Every device holds a full replica; no central server holds a
|
|
72
|
+
* replica. Operations flow peer-to-peer through signed and encrypted
|
|
73
|
+
* messages on the underlying transport.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const journal = $meshState<Journal>("journal", { entries: [] });
|
|
78
|
+
* await journal.loaded;
|
|
79
|
+
* journal.value = { entries: ["my private thoughts"] };
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function $meshState<T extends VersionedDoc>(key: string, initialValue: T, options?: MeshStateOptions): CrdtPrimitive<T>;
|
|
83
|
+
/**
|
|
84
|
+
* Create a peer-replicated text primitive backed by a mesh transport.
|
|
85
|
+
* Concurrent character-level edits from peers merge cleanly via Automerge's
|
|
86
|
+
* updateText splicing, and every operation is signed and encrypted before
|
|
87
|
+
* leaving the originating peer.
|
|
88
|
+
*/
|
|
89
|
+
export declare function $meshText(key: string, initialValue: string, options?: MeshStateOptions): SpecialisedPrimitive<string>;
|
|
90
|
+
/**
|
|
91
|
+
* Create a peer-replicated counter primitive backed by a mesh transport.
|
|
92
|
+
* Concurrent increments commute, and every operation is signed and
|
|
93
|
+
* encrypted before leaving the originating peer.
|
|
94
|
+
*/
|
|
95
|
+
export declare function $meshCounter(key: string, initialValue: number, options?: MeshStateOptions): SpecialisedPrimitive<number>;
|
|
96
|
+
/**
|
|
97
|
+
* Create a peer-replicated list primitive backed by a mesh transport.
|
|
98
|
+
* Phase 0 naive replacement applies to writes for now; Phase 1.1 will
|
|
99
|
+
* refine with structural diff-to-splice for concurrent insert/remove
|
|
100
|
+
* preservation.
|
|
101
|
+
*/
|
|
102
|
+
export declare function $meshList<T>(key: string, initialValue: T[], options?: MeshStateOptions): SpecialisedPrimitive<T[]>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mesh-webrtc-adapter — Phase 2 browser-side WebRTC transport for Polly's
|
|
3
|
+
* $meshState primitive. Extends Automerge's NetworkAdapter base class and
|
|
4
|
+
* uses real native RTCPeerConnection / RTCDataChannel instances to carry
|
|
5
|
+
* sync messages between peers.
|
|
6
|
+
*
|
|
7
|
+
* This is the "base" transport that MeshNetworkAdapter wraps with its
|
|
8
|
+
* sign-then-encrypt envelope. The stack is:
|
|
9
|
+
*
|
|
10
|
+
* $meshState
|
|
11
|
+
* └─ Repo
|
|
12
|
+
* └─ MeshNetworkAdapter (sign + encrypt)
|
|
13
|
+
* └─ MeshWebRTCAdapter (real data channels)
|
|
14
|
+
* └─ MeshSignalingClient (SDP/ICE relay)
|
|
15
|
+
* └─ signalingServer (Elysia plugin on the host app)
|
|
16
|
+
*
|
|
17
|
+
* Because WebRTC lives in browsers, this module is browser-only. It
|
|
18
|
+
* assumes global RTCPeerConnection, RTCDataChannel, and WebSocket types
|
|
19
|
+
* are available. Under bun:test the classes cannot be exercised
|
|
20
|
+
* end-to-end — the first validation path is either Playwright running a
|
|
21
|
+
* real browser, a Puppeteer harness, or a human testing a browser-side
|
|
22
|
+
* example app that consumes the adapter.
|
|
23
|
+
*
|
|
24
|
+
* What this module does at runtime:
|
|
25
|
+
*
|
|
26
|
+
* - Constructs a MeshWebRTCAdapter with a signalling client and a local
|
|
27
|
+
* peer id. No data channels exist at construction time.
|
|
28
|
+
*
|
|
29
|
+
* - When Automerge's NetworkSubsystem calls connect(peerId) on the
|
|
30
|
+
* adapter, it starts listening for signals from remote peers and is
|
|
31
|
+
* ready to build peer connections as they are discovered.
|
|
32
|
+
*
|
|
33
|
+
* - When a remote peer sends an SDP offer via the signalling channel,
|
|
34
|
+
* the adapter builds a fresh RTCPeerConnection, accepts the offer,
|
|
35
|
+
* produces an answer, sends it back through signalling, and wires the
|
|
36
|
+
* received data channel to emit Automerge Message events upward.
|
|
37
|
+
*
|
|
38
|
+
* - When the local repo calls send(message), the adapter looks up the
|
|
39
|
+
* peer connection for message.targetId and writes the serialised
|
|
40
|
+
* bytes to its data channel. If no connection exists yet, the adapter
|
|
41
|
+
* creates one by sending an SDP offer through signalling. Outgoing
|
|
42
|
+
* messages are queued until the channel is open.
|
|
43
|
+
*
|
|
44
|
+
* - Disconnect tears down every peer connection and closes the
|
|
45
|
+
* signalling client.
|
|
46
|
+
*/
|
|
47
|
+
import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo";
|
|
48
|
+
import type { MeshSignalingClient } from "./mesh-signaling-client";
|
|
49
|
+
/** Standard STUN servers for NAT traversal. In production, callers who
|
|
50
|
+
* need TURN fallback for peers behind symmetric NATs should replace this
|
|
51
|
+
* with their own ICE server list. */
|
|
52
|
+
export declare const DEFAULT_ICE_SERVERS: RTCIceServer[];
|
|
53
|
+
/** Options for constructing a {@link MeshWebRTCAdapter}. */
|
|
54
|
+
export interface MeshWebRTCAdapterOptions {
|
|
55
|
+
/** The signalling client the adapter uses to exchange SDP and ICE
|
|
56
|
+
* candidates with other peers. Typically constructed once per
|
|
57
|
+
* application and shared across any adapters that need it. */
|
|
58
|
+
signaling: MeshSignalingClient;
|
|
59
|
+
/** The local peer id. Must match the peer id the signalling client
|
|
60
|
+
* registered with. */
|
|
61
|
+
peerId: string;
|
|
62
|
+
/** Peer ids to connect to on startup. When `connect()` is called, the
|
|
63
|
+
* adapter iterates this list and initiates a WebRTC connection to each
|
|
64
|
+
* one by sending an SDP offer through the signalling channel. Peers
|
|
65
|
+
* not in this list can still connect by sending an offer *to* this
|
|
66
|
+
* adapter. The natural source for this list is the keyring's
|
|
67
|
+
* knownPeers map keys. */
|
|
68
|
+
knownPeerIds?: string[];
|
|
69
|
+
/** Optional ICE server list override. Defaults to {@link DEFAULT_ICE_SERVERS}. */
|
|
70
|
+
iceServers?: RTCIceServer[];
|
|
71
|
+
/** Optional data channel label. Defaults to "polly-mesh". Applications
|
|
72
|
+
* that share a signalling server between multiple meshes may want
|
|
73
|
+
* distinct labels per mesh. */
|
|
74
|
+
dataChannelLabel?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Automerge-Repo NetworkAdapter backed by real WebRTC data channels.
|
|
78
|
+
* Manages one RTCPeerConnection per remote peer and uses a supplied
|
|
79
|
+
* {@link MeshSignalingClient} for SDP/ICE exchange.
|
|
80
|
+
*/
|
|
81
|
+
export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
82
|
+
readonly signaling: MeshSignalingClient;
|
|
83
|
+
readonly iceServers: RTCIceServer[];
|
|
84
|
+
readonly dataChannelLabel: string;
|
|
85
|
+
readonly knownPeerIds: string[];
|
|
86
|
+
private readonly slots;
|
|
87
|
+
private ready;
|
|
88
|
+
private readyResolver;
|
|
89
|
+
constructor(options: MeshWebRTCAdapterOptions);
|
|
90
|
+
isReady(): boolean;
|
|
91
|
+
whenReady(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Start the adapter. Wires the signalling client's onSignal callback
|
|
94
|
+
* to the adapter's dispatch, opens the signalling connection if it
|
|
95
|
+
* is not already open, and marks the adapter ready.
|
|
96
|
+
*/
|
|
97
|
+
connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
|
|
98
|
+
disconnect(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Send a sync message to a specific remote peer. If no RTCPeerConnection
|
|
101
|
+
* exists yet, the adapter initiates one by producing an SDP offer and
|
|
102
|
+
* sending it through the signalling channel; the outgoing bytes are
|
|
103
|
+
* queued until the data channel is open.
|
|
104
|
+
*/
|
|
105
|
+
send(message: Message): void;
|
|
106
|
+
/**
|
|
107
|
+
* Entry point the signalling client calls when it receives a signal
|
|
108
|
+
* from a remote peer. Dispatches on the payload `kind` to either
|
|
109
|
+
* accept an offer (building an answer), apply an answer, or add an
|
|
110
|
+
* ICE candidate. Exposed publicly so the caller that constructs the
|
|
111
|
+
* signalling client can wire the onSignal callback directly to this
|
|
112
|
+
* method.
|
|
113
|
+
*/
|
|
114
|
+
handleSignal(fromPeerId: string, rawPayload: unknown): void;
|
|
115
|
+
private createInitiatingSlot;
|
|
116
|
+
private initiateOffer;
|
|
117
|
+
private handleOffer;
|
|
118
|
+
private handleAnswer;
|
|
119
|
+
private handleIceCandidate;
|
|
120
|
+
private wireConnection;
|
|
121
|
+
private wireDataChannel;
|
|
122
|
+
private dispatchMessage;
|
|
123
|
+
/** Pack an Automerge Message into binary for transmission over the
|
|
124
|
+
* data channel. The format mirrors MeshNetworkAdapter's internal
|
|
125
|
+
* serialisation: a length-prefixed JSON header followed by the raw
|
|
126
|
+
* data bytes. Returns a Uint8Array<ArrayBuffer> so the result is
|
|
127
|
+
* directly usable by RTCDataChannel.send under TypeScript's strict
|
|
128
|
+
* buffer-source typing. */
|
|
129
|
+
private serialiseMessage;
|
|
130
|
+
/** Inverse of {@link serialiseMessage}. */
|
|
131
|
+
private deserialiseMessage;
|
|
132
|
+
}
|
|
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
6
8
|
var __toCommonJS = (from) => {
|
|
7
|
-
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
8
10
|
if (entry)
|
|
9
11
|
return entry;
|
|
10
12
|
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
16
21
|
__moduleCache.set(from, entry);
|
|
17
22
|
return entry;
|
|
18
23
|
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
19
29
|
var __export = (target, all) => {
|
|
20
30
|
for (var name in all)
|
|
21
31
|
__defProp(target, name, {
|
|
22
32
|
get: all[name],
|
|
23
33
|
enumerable: true,
|
|
24
34
|
configurable: true,
|
|
25
|
-
set: (
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
26
36
|
});
|
|
27
37
|
};
|
|
28
38
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -259,7 +269,9 @@ class BroadcastChannelSyncAdapter {
|
|
|
259
269
|
channel = null;
|
|
260
270
|
listeners = [];
|
|
261
271
|
constructor(channelName = "polly-sync") {
|
|
262
|
-
if (typeof BroadcastChannel
|
|
272
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
273
|
+
console.warn("[SyncAdapter] BroadcastChannel not available");
|
|
274
|
+
} else {
|
|
263
275
|
this.channel = new BroadcastChannel(channelName);
|
|
264
276
|
this.channel.onmessage = (event) => {
|
|
265
277
|
if (event.data.type === "STATE_SYNC") {
|
|
@@ -268,8 +280,6 @@ class BroadcastChannelSyncAdapter {
|
|
|
268
280
|
});
|
|
269
281
|
}
|
|
270
282
|
};
|
|
271
|
-
} else {
|
|
272
|
-
console.warn("[SyncAdapter] BroadcastChannel not available");
|
|
273
283
|
}
|
|
274
284
|
}
|
|
275
285
|
broadcast(message) {
|
|
@@ -1529,4 +1539,4 @@ export {
|
|
|
1529
1539
|
MessageBus
|
|
1530
1540
|
};
|
|
1531
1541
|
|
|
1532
|
-
//# debugId=
|
|
1542
|
+
//# debugId=7BE490ECD586102364756E2164756E21
|