@tinycloud/replica 0.2.4-beta.2 → 0.3.0-beta.10
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 +29 -0
- package/dist/browser.d.ts +6 -0
- package/dist/browser.js +7 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +20 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/kv-storage-testing.d.ts +83 -0
- package/dist/kv-storage-testing.js +1145 -0
- package/dist/kv-storage-testing.js.map +1 -0
- package/dist/kv-storage.d.ts +155 -0
- package/dist/kv-storage.js +2588 -0
- package/dist/kv-storage.js.map +1 -0
- package/dist/kv-storage.worker.d.ts +2 -0
- package/dist/kv-storage.worker.f6f98ad5dc547a29274c0b539fb78e25f8a2051ccfaafc9490cb307051a0e0ef.js +19004 -0
- package/dist/kv-storage.worker.js +19004 -0
- package/dist/kv-storage.worker.js.map +1 -0
- package/dist/profile-gate.cjs +560 -0
- package/dist/profile-gate.cjs.map +1 -0
- package/dist/profile-gate.d.cts +89 -0
- package/dist/profile-gate.d.ts +89 -0
- package/dist/profile-gate.js +517 -0
- package/dist/profile-gate.js.map +1 -0
- package/dist/replica.worker.js +1276 -891
- package/dist/replica.worker.js.map +1 -1
- package/dist/sqlite.cjs +1107 -277
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +101 -8
- package/dist/sqlite.d.ts +101 -8
- package/dist/sqlite.js +1095 -267
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-f3-kYLQZ.d.cts → types-CQ61VnsP.d.cts} +1 -1
- package/dist/{types-f3-kYLQZ.d.ts → types-CQ61VnsP.d.ts} +1 -1
- package/package.json +23 -5
package/dist/sqlite.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReplicaConsentAnchor, ReplicaAdmitHook } from '@tinycloud/sdk-services/kv/replication';
|
|
2
|
+
import { R as ReplicaStore, j as ReplicaState, b as ReplicaStatus, h as ReplicaConfig, G as GrantRecord, i as ReplicaLastError, c as LeaseToken, l as VerifiedPage, g as LocalEntry, L as ListOpts } from './types-CQ61VnsP.cjs';
|
|
2
3
|
|
|
3
4
|
type SqlValue = string | number | null | Uint8Array;
|
|
4
5
|
/** The synchronous SQLite surface shared by `node:sqlite` and `bun:sqlite`. */
|
|
@@ -22,7 +23,52 @@ declare function nodeSqliteSupported(version: string): boolean;
|
|
|
22
23
|
*/
|
|
23
24
|
declare function loadSqlite(): Promise<SqliteOpener>;
|
|
24
25
|
|
|
26
|
+
interface SqliteLayoutEntry {
|
|
27
|
+
path: string;
|
|
28
|
+
kind: "file" | "directory" | "symlink";
|
|
29
|
+
dev: string;
|
|
30
|
+
ino: string;
|
|
31
|
+
}
|
|
32
|
+
type SqliteLayoutIdentity = {
|
|
33
|
+
dev: string;
|
|
34
|
+
ino: string;
|
|
35
|
+
};
|
|
36
|
+
/** JSON-safe exact inventory of non-mirror entries for durable approval. */
|
|
37
|
+
declare function inspectSqliteReplicaLayout(dir: string, options?: {
|
|
38
|
+
allowAdapterFence?: boolean;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
knownEntries: SqliteLayoutEntry[];
|
|
41
|
+
unknownPaths: string[];
|
|
42
|
+
unknownEntries: SqliteLayoutEntry[];
|
|
43
|
+
rootIdentity: SqliteLayoutIdentity;
|
|
44
|
+
}>;
|
|
45
|
+
/** Test-only interleaving seam for replacement races between enumerated unlinks. */
|
|
46
|
+
declare const LAYOUT_FAULTS: unique symbol;
|
|
47
|
+
/** Lease holder only: delete enumerated files, never recurse into an unapproved child. */
|
|
48
|
+
declare function removeSqliteReplicaLayout(dir: string, options?: {
|
|
49
|
+
approvedUnknown?: readonly SqliteLayoutEntry[];
|
|
50
|
+
expectedKnown?: readonly SqliteLayoutEntry[];
|
|
51
|
+
allowMissingDatabase?: boolean;
|
|
52
|
+
expectedRoot?: SqliteLayoutIdentity;
|
|
53
|
+
allowAdapterFence?: boolean;
|
|
54
|
+
[LAYOUT_FAULTS]?: {
|
|
55
|
+
beforeUnlink?(path: string): void | Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
|
|
25
59
|
declare const DURABILITY = "sqlite-wal-synchronous-full+fsynced-blobs";
|
|
60
|
+
type SqliteGrantInstall = {
|
|
61
|
+
installedAt: number;
|
|
62
|
+
effectiveExpiresAt: number;
|
|
63
|
+
consentDeadline: number;
|
|
64
|
+
consentAnchor: ReplicaConsentAnchor;
|
|
65
|
+
};
|
|
66
|
+
type SqliteGrantPolicy = {
|
|
67
|
+
at: number;
|
|
68
|
+
ttlMs: number;
|
|
69
|
+
consentDeadline: number;
|
|
70
|
+
consentAnchor: ReplicaConsentAnchor;
|
|
71
|
+
};
|
|
26
72
|
/**
|
|
27
73
|
* Runs a mutation section (every database write and every filesystem
|
|
28
74
|
* change). The CLI passes one that holds the profile lock briefly and
|
|
@@ -42,6 +88,10 @@ type SqliteReplicaStoreOptions = {
|
|
|
42
88
|
/** Clock for leases and status (ms since epoch). */
|
|
43
89
|
now?: () => number;
|
|
44
90
|
guard?: MutationGuard;
|
|
91
|
+
/** Quota admission, run inside applyPage's guarded mutation before any write (TC-858 S5). */
|
|
92
|
+
admit?: ReplicaAdmitHook;
|
|
93
|
+
/** Partition the store belongs to; reported to `admit`. */
|
|
94
|
+
partitionId?: string;
|
|
45
95
|
};
|
|
46
96
|
/**
|
|
47
97
|
* The CLI replica store: SQLite (WAL, synchronous=FULL) for entries, cursor
|
|
@@ -63,16 +113,53 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
63
113
|
}): Promise<{
|
|
64
114
|
state: ReplicaState;
|
|
65
115
|
status: ReplicaStatus;
|
|
116
|
+
grantInstall: SqliteGrantInstall | null;
|
|
117
|
+
activeGrantInstall: SqliteGrantInstall | null;
|
|
66
118
|
} | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Blob content bytes on disk in the replica at `dir` (0 when absent): the writer's blob files under `blobs/`,
|
|
121
|
+
* including writer temp files and blobs no entry references any more, so garbage a failed sync
|
|
122
|
+
* left behind still counts. Counts exactly what `collectOrphans` may delete: foreign entries
|
|
123
|
+
* (stray files, symlinks, unknown names) are neither counted nor touched. Only blob content is counted: not the database, its WAL, rows or the
|
|
124
|
+
* pending store. Filesystem-only: it opens no
|
|
125
|
+
* database, so it can sum a profile's replicas under the caller's own mutation hold.
|
|
126
|
+
*/
|
|
127
|
+
static contentBytes(dir: string): Promise<number>;
|
|
128
|
+
/**
|
|
129
|
+
* Remove blobs and temp files in the replica at `dir` that no committed row references: what a
|
|
130
|
+
* crash between writing blobs and committing (or between committing and collecting) leaves.
|
|
131
|
+
* Returns the number of files removed; 0 when the replica is absent or unreadable.
|
|
132
|
+
*
|
|
133
|
+
* The caller MUST hold the profile mutation guard, which every writer of this replica holds
|
|
134
|
+
* across its blob write and commit; that is what makes an unreferenced blob safe to delete
|
|
135
|
+
* (it cannot belong to an in-flight write). Never call it without that hold. The database is
|
|
136
|
+
* read only, so nothing here can take a lease or change committed state.
|
|
137
|
+
*/
|
|
138
|
+
static collectOrphans(dir: string): Promise<number>;
|
|
67
139
|
/**
|
|
68
140
|
* Open the replica store in `dir`. With `create: false` a missing replica is
|
|
69
141
|
* REPLICA_NOT_FOUND and nothing is written to disk.
|
|
70
142
|
*/
|
|
71
143
|
static open(dir: string, options: SqliteReplicaStoreOptions): Promise<SqliteReplicaStore>;
|
|
144
|
+
/**
|
|
145
|
+
* A gated read borrows this controller's already-verified writable handle.
|
|
146
|
+
* Borrow before the first await: the parent may close in the callback, but
|
|
147
|
+
* its pin and native handle remain live until the snapshot releases them.
|
|
148
|
+
* Serialize BEGIN through COMMIT/ROLLBACK with writes on the shared handle.
|
|
149
|
+
*/
|
|
150
|
+
readTransaction<T>(generation: number, checkGate: () => Promise<void>, section: (store: SqliteReplicaStore) => Promise<T>): Promise<T>;
|
|
72
151
|
open(): Promise<ReplicaState | null>;
|
|
73
152
|
init(c: ReplicaConfig): Promise<void>;
|
|
74
153
|
/** Update the retention opt-in; it takes effect at the next sync. */
|
|
75
154
|
setRetentionGrant(cid: string | null): Promise<void>;
|
|
155
|
+
getGrantInstall(cid: string): Promise<SqliteGrantInstall | null>;
|
|
156
|
+
/** Every installed CID, including superseded ones, for an origin-wide purge floor. */
|
|
157
|
+
grantInstallTimes(): Promise<Array<{
|
|
158
|
+
cid: string;
|
|
159
|
+
installedAt: number;
|
|
160
|
+
}>>;
|
|
161
|
+
installGatedGrant(g: GrantRecord, policy: SqliteGrantPolicy): Promise<SqliteGrantInstall>;
|
|
162
|
+
recomputeGrantInstall(g: GrantRecord, policy: SqliteGrantPolicy): Promise<SqliteGrantInstall | null>;
|
|
76
163
|
installGrant(g: GrantRecord): Promise<void>;
|
|
77
164
|
discardPendingGrant(cid: string, detail: string): Promise<void>;
|
|
78
165
|
recordPendingGrantError(cid: string, e: ReplicaLastError): Promise<void>;
|
|
@@ -87,13 +174,19 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
87
174
|
keepSource?: boolean;
|
|
88
175
|
}): Promise<void>;
|
|
89
176
|
/**
|
|
90
|
-
* Remove
|
|
91
|
-
*
|
|
92
|
-
* store opened, checked under the write lock, so a paused process can
|
|
93
|
-
* never remove a replica another process took over or recreated. Allowed
|
|
94
|
-
* on a revoked replica. The store is unusable afterwards; close it.
|
|
177
|
+
* Remove the source-created mirror layout under a live lease. Unknown files
|
|
178
|
+
* are removed only when their exact preflight identities were approved.
|
|
95
179
|
*/
|
|
96
|
-
destroy(t: LeaseToken
|
|
180
|
+
destroy(t: LeaseToken, options?: {
|
|
181
|
+
approvedUnknown?: readonly SqliteLayoutEntry[];
|
|
182
|
+
expectedKnown?: readonly SqliteLayoutEntry[];
|
|
183
|
+
expectedRoot?: {
|
|
184
|
+
dev: string;
|
|
185
|
+
ino: string;
|
|
186
|
+
};
|
|
187
|
+
allowMissingDatabase?: boolean;
|
|
188
|
+
allowAdapterFence?: boolean;
|
|
189
|
+
}): Promise<void>;
|
|
97
190
|
/**
|
|
98
191
|
* Record the revocation (durable before this returns: reads are blocked
|
|
99
192
|
* from here on, also after a restart), then purge. A purge that fails
|
|
@@ -111,4 +204,4 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
111
204
|
close(): Promise<void>;
|
|
112
205
|
}
|
|
113
206
|
|
|
114
|
-
export { DURABILITY, type MutationGuard, type SqlValue, type SqliteDatabase, type SqliteOpener, SqliteReplicaStore, type SqliteReplicaStoreOptions, loadSqlite, nodeSqliteSupported };
|
|
207
|
+
export { DURABILITY, type MutationGuard, type SqlValue, type SqliteDatabase, type SqliteLayoutEntry, type SqliteOpener, SqliteReplicaStore, type SqliteReplicaStoreOptions, inspectSqliteReplicaLayout, loadSqlite, nodeSqliteSupported, removeSqliteReplicaLayout };
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReplicaConsentAnchor, ReplicaAdmitHook } from '@tinycloud/sdk-services/kv/replication';
|
|
2
|
+
import { R as ReplicaStore, j as ReplicaState, b as ReplicaStatus, h as ReplicaConfig, G as GrantRecord, i as ReplicaLastError, c as LeaseToken, l as VerifiedPage, g as LocalEntry, L as ListOpts } from './types-CQ61VnsP.js';
|
|
2
3
|
|
|
3
4
|
type SqlValue = string | number | null | Uint8Array;
|
|
4
5
|
/** The synchronous SQLite surface shared by `node:sqlite` and `bun:sqlite`. */
|
|
@@ -22,7 +23,52 @@ declare function nodeSqliteSupported(version: string): boolean;
|
|
|
22
23
|
*/
|
|
23
24
|
declare function loadSqlite(): Promise<SqliteOpener>;
|
|
24
25
|
|
|
26
|
+
interface SqliteLayoutEntry {
|
|
27
|
+
path: string;
|
|
28
|
+
kind: "file" | "directory" | "symlink";
|
|
29
|
+
dev: string;
|
|
30
|
+
ino: string;
|
|
31
|
+
}
|
|
32
|
+
type SqliteLayoutIdentity = {
|
|
33
|
+
dev: string;
|
|
34
|
+
ino: string;
|
|
35
|
+
};
|
|
36
|
+
/** JSON-safe exact inventory of non-mirror entries for durable approval. */
|
|
37
|
+
declare function inspectSqliteReplicaLayout(dir: string, options?: {
|
|
38
|
+
allowAdapterFence?: boolean;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
knownEntries: SqliteLayoutEntry[];
|
|
41
|
+
unknownPaths: string[];
|
|
42
|
+
unknownEntries: SqliteLayoutEntry[];
|
|
43
|
+
rootIdentity: SqliteLayoutIdentity;
|
|
44
|
+
}>;
|
|
45
|
+
/** Test-only interleaving seam for replacement races between enumerated unlinks. */
|
|
46
|
+
declare const LAYOUT_FAULTS: unique symbol;
|
|
47
|
+
/** Lease holder only: delete enumerated files, never recurse into an unapproved child. */
|
|
48
|
+
declare function removeSqliteReplicaLayout(dir: string, options?: {
|
|
49
|
+
approvedUnknown?: readonly SqliteLayoutEntry[];
|
|
50
|
+
expectedKnown?: readonly SqliteLayoutEntry[];
|
|
51
|
+
allowMissingDatabase?: boolean;
|
|
52
|
+
expectedRoot?: SqliteLayoutIdentity;
|
|
53
|
+
allowAdapterFence?: boolean;
|
|
54
|
+
[LAYOUT_FAULTS]?: {
|
|
55
|
+
beforeUnlink?(path: string): void | Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
|
|
25
59
|
declare const DURABILITY = "sqlite-wal-synchronous-full+fsynced-blobs";
|
|
60
|
+
type SqliteGrantInstall = {
|
|
61
|
+
installedAt: number;
|
|
62
|
+
effectiveExpiresAt: number;
|
|
63
|
+
consentDeadline: number;
|
|
64
|
+
consentAnchor: ReplicaConsentAnchor;
|
|
65
|
+
};
|
|
66
|
+
type SqliteGrantPolicy = {
|
|
67
|
+
at: number;
|
|
68
|
+
ttlMs: number;
|
|
69
|
+
consentDeadline: number;
|
|
70
|
+
consentAnchor: ReplicaConsentAnchor;
|
|
71
|
+
};
|
|
26
72
|
/**
|
|
27
73
|
* Runs a mutation section (every database write and every filesystem
|
|
28
74
|
* change). The CLI passes one that holds the profile lock briefly and
|
|
@@ -42,6 +88,10 @@ type SqliteReplicaStoreOptions = {
|
|
|
42
88
|
/** Clock for leases and status (ms since epoch). */
|
|
43
89
|
now?: () => number;
|
|
44
90
|
guard?: MutationGuard;
|
|
91
|
+
/** Quota admission, run inside applyPage's guarded mutation before any write (TC-858 S5). */
|
|
92
|
+
admit?: ReplicaAdmitHook;
|
|
93
|
+
/** Partition the store belongs to; reported to `admit`. */
|
|
94
|
+
partitionId?: string;
|
|
45
95
|
};
|
|
46
96
|
/**
|
|
47
97
|
* The CLI replica store: SQLite (WAL, synchronous=FULL) for entries, cursor
|
|
@@ -63,16 +113,53 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
63
113
|
}): Promise<{
|
|
64
114
|
state: ReplicaState;
|
|
65
115
|
status: ReplicaStatus;
|
|
116
|
+
grantInstall: SqliteGrantInstall | null;
|
|
117
|
+
activeGrantInstall: SqliteGrantInstall | null;
|
|
66
118
|
} | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Blob content bytes on disk in the replica at `dir` (0 when absent): the writer's blob files under `blobs/`,
|
|
121
|
+
* including writer temp files and blobs no entry references any more, so garbage a failed sync
|
|
122
|
+
* left behind still counts. Counts exactly what `collectOrphans` may delete: foreign entries
|
|
123
|
+
* (stray files, symlinks, unknown names) are neither counted nor touched. Only blob content is counted: not the database, its WAL, rows or the
|
|
124
|
+
* pending store. Filesystem-only: it opens no
|
|
125
|
+
* database, so it can sum a profile's replicas under the caller's own mutation hold.
|
|
126
|
+
*/
|
|
127
|
+
static contentBytes(dir: string): Promise<number>;
|
|
128
|
+
/**
|
|
129
|
+
* Remove blobs and temp files in the replica at `dir` that no committed row references: what a
|
|
130
|
+
* crash between writing blobs and committing (or between committing and collecting) leaves.
|
|
131
|
+
* Returns the number of files removed; 0 when the replica is absent or unreadable.
|
|
132
|
+
*
|
|
133
|
+
* The caller MUST hold the profile mutation guard, which every writer of this replica holds
|
|
134
|
+
* across its blob write and commit; that is what makes an unreferenced blob safe to delete
|
|
135
|
+
* (it cannot belong to an in-flight write). Never call it without that hold. The database is
|
|
136
|
+
* read only, so nothing here can take a lease or change committed state.
|
|
137
|
+
*/
|
|
138
|
+
static collectOrphans(dir: string): Promise<number>;
|
|
67
139
|
/**
|
|
68
140
|
* Open the replica store in `dir`. With `create: false` a missing replica is
|
|
69
141
|
* REPLICA_NOT_FOUND and nothing is written to disk.
|
|
70
142
|
*/
|
|
71
143
|
static open(dir: string, options: SqliteReplicaStoreOptions): Promise<SqliteReplicaStore>;
|
|
144
|
+
/**
|
|
145
|
+
* A gated read borrows this controller's already-verified writable handle.
|
|
146
|
+
* Borrow before the first await: the parent may close in the callback, but
|
|
147
|
+
* its pin and native handle remain live until the snapshot releases them.
|
|
148
|
+
* Serialize BEGIN through COMMIT/ROLLBACK with writes on the shared handle.
|
|
149
|
+
*/
|
|
150
|
+
readTransaction<T>(generation: number, checkGate: () => Promise<void>, section: (store: SqliteReplicaStore) => Promise<T>): Promise<T>;
|
|
72
151
|
open(): Promise<ReplicaState | null>;
|
|
73
152
|
init(c: ReplicaConfig): Promise<void>;
|
|
74
153
|
/** Update the retention opt-in; it takes effect at the next sync. */
|
|
75
154
|
setRetentionGrant(cid: string | null): Promise<void>;
|
|
155
|
+
getGrantInstall(cid: string): Promise<SqliteGrantInstall | null>;
|
|
156
|
+
/** Every installed CID, including superseded ones, for an origin-wide purge floor. */
|
|
157
|
+
grantInstallTimes(): Promise<Array<{
|
|
158
|
+
cid: string;
|
|
159
|
+
installedAt: number;
|
|
160
|
+
}>>;
|
|
161
|
+
installGatedGrant(g: GrantRecord, policy: SqliteGrantPolicy): Promise<SqliteGrantInstall>;
|
|
162
|
+
recomputeGrantInstall(g: GrantRecord, policy: SqliteGrantPolicy): Promise<SqliteGrantInstall | null>;
|
|
76
163
|
installGrant(g: GrantRecord): Promise<void>;
|
|
77
164
|
discardPendingGrant(cid: string, detail: string): Promise<void>;
|
|
78
165
|
recordPendingGrantError(cid: string, e: ReplicaLastError): Promise<void>;
|
|
@@ -87,13 +174,19 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
87
174
|
keepSource?: boolean;
|
|
88
175
|
}): Promise<void>;
|
|
89
176
|
/**
|
|
90
|
-
* Remove
|
|
91
|
-
*
|
|
92
|
-
* store opened, checked under the write lock, so a paused process can
|
|
93
|
-
* never remove a replica another process took over or recreated. Allowed
|
|
94
|
-
* on a revoked replica. The store is unusable afterwards; close it.
|
|
177
|
+
* Remove the source-created mirror layout under a live lease. Unknown files
|
|
178
|
+
* are removed only when their exact preflight identities were approved.
|
|
95
179
|
*/
|
|
96
|
-
destroy(t: LeaseToken
|
|
180
|
+
destroy(t: LeaseToken, options?: {
|
|
181
|
+
approvedUnknown?: readonly SqliteLayoutEntry[];
|
|
182
|
+
expectedKnown?: readonly SqliteLayoutEntry[];
|
|
183
|
+
expectedRoot?: {
|
|
184
|
+
dev: string;
|
|
185
|
+
ino: string;
|
|
186
|
+
};
|
|
187
|
+
allowMissingDatabase?: boolean;
|
|
188
|
+
allowAdapterFence?: boolean;
|
|
189
|
+
}): Promise<void>;
|
|
97
190
|
/**
|
|
98
191
|
* Record the revocation (durable before this returns: reads are blocked
|
|
99
192
|
* from here on, also after a restart), then purge. A purge that fails
|
|
@@ -111,4 +204,4 @@ declare class SqliteReplicaStore implements ReplicaStore {
|
|
|
111
204
|
close(): Promise<void>;
|
|
112
205
|
}
|
|
113
206
|
|
|
114
|
-
export { DURABILITY, type MutationGuard, type SqlValue, type SqliteDatabase, type SqliteOpener, SqliteReplicaStore, type SqliteReplicaStoreOptions, loadSqlite, nodeSqliteSupported };
|
|
207
|
+
export { DURABILITY, type MutationGuard, type SqlValue, type SqliteDatabase, type SqliteLayoutEntry, type SqliteOpener, SqliteReplicaStore, type SqliteReplicaStoreOptions, inspectSqliteReplicaLayout, loadSqlite, nodeSqliteSupported, removeSqliteReplicaLayout };
|