@xnetjs/data 0.0.2 → 0.0.3
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 +112 -0
- package/dist/account-BZHETMC4.js +12 -0
- package/dist/auth/index.d.ts +413 -0
- package/dist/auth/index.js +102 -0
- package/dist/budget-WR2Q4YH3.js +12 -0
- package/dist/canvas-HFMMVQLD.js +10 -0
- package/dist/channel-DXDYJ3ZH.js +12 -0
- package/dist/chat-message-QUUQGXEW.js +10 -0
- package/dist/chunk-2DSDH2ZH.js +52 -0
- package/dist/chunk-337E3ZOB.js +54 -0
- package/dist/chunk-37267RSF.js +41 -0
- package/dist/chunk-3JKW6TDE.js +66 -0
- package/dist/chunk-3RB2FMMO.js +148 -0
- package/dist/{chunk-BQBPA5HS.js → chunk-4U4KBCDN.js} +16 -2
- package/dist/chunk-4UQTURCD.js +722 -0
- package/dist/chunk-54Y7QRZN.js +109 -0
- package/dist/chunk-5FHJE7RC.js +46 -0
- package/dist/chunk-64AM6JPM.js +40 -0
- package/dist/chunk-6CN53PON.js +143 -0
- package/dist/chunk-6GB7PBCR.js +40 -0
- package/dist/chunk-6OPZJ7MG.js +78 -0
- package/dist/{chunk-IDMBCRUC.js → chunk-77UDLVQY.js} +1 -1
- package/dist/chunk-7GMJOAPF.js +52 -0
- package/dist/chunk-7PDRDSFH.js +69 -0
- package/dist/chunk-C5IRSEEZ.js +69 -0
- package/dist/chunk-CHZUSBSB.js +45 -0
- package/dist/chunk-DOZJXPQ6.js +232 -0
- package/dist/chunk-DTPSTMUD.js +105 -0
- package/dist/chunk-E6S557NC.js +402 -0
- package/dist/chunk-EIQX7INF.js +45 -0
- package/dist/chunk-ENTHVNOC.js +57 -0
- package/dist/chunk-EZA5UT4Y.js +5543 -0
- package/dist/chunk-GR3NDMBY.js +76 -0
- package/dist/chunk-GWYVY2K4.js +63 -0
- package/dist/chunk-IJ675GJR.js +42 -0
- package/dist/chunk-IP4BEVVD.js +57 -0
- package/dist/chunk-J4IVJA5M.js +102 -0
- package/dist/chunk-J6EDPPH4.js +84 -0
- package/dist/{chunk-SZC345Z2.js → chunk-JESSJ5CZ.js} +1015 -1110
- package/dist/chunk-JO6Y4E5V.js +73 -0
- package/dist/chunk-KPVBKKZS.js +47 -0
- package/dist/chunk-KTJLFJEQ.js +43 -0
- package/dist/chunk-L66WXPTW.js +32 -0
- package/dist/chunk-LGD72FVD.js +48 -0
- package/dist/chunk-O64EK5FK.js +887 -0
- package/dist/chunk-PMUQACPY.js +33 -0
- package/dist/chunk-PSUJEKVV.js +1722 -0
- package/dist/chunk-QVZXXJSP.js +6122 -0
- package/dist/chunk-QWFTRZQT.js +142 -0
- package/dist/chunk-RSSZXCLL.js +51 -0
- package/dist/chunk-S4B5OPZU.js +415 -0
- package/dist/{chunk-VYR5GPJP.js → chunk-SPFKSTPT.js} +9 -3
- package/dist/chunk-U6CBYDVZ.js +50 -0
- package/dist/chunk-UDUPQKRB.js +49 -0
- package/dist/chunk-V6HZ3LF3.js +380 -0
- package/dist/chunk-WMSCEBFH.js +64 -0
- package/dist/chunk-WUR3WWQM.js +65 -0
- package/dist/chunk-XHEA5UER.js +75 -0
- package/dist/chunk-XTUGZSYQ.js +75 -0
- package/dist/chunk-XWA3CJQ6.js +45 -0
- package/dist/chunk-YEQ6LLUY.js +49 -0
- package/dist/chunk-YWN27MLD.js +47 -0
- package/dist/chunk-YXZGYLKV.js +65 -0
- package/dist/chunk-ZICIXDEE.js +54 -0
- package/dist/chunk-ZMCO3O2Z.js +70 -0
- package/dist/clone-DpbVKLUu.d.ts +2205 -0
- package/dist/comment-Y2Y3QYYI.js +10 -0
- package/dist/crm-AAJXYIJY.js +68 -0
- package/dist/dashboard-3DC6B4BJ.js +10 -0
- package/dist/database/index.d.ts +882 -0
- package/dist/database/index.js +523 -0
- package/dist/database-DRGL5UYS.js +10 -0
- package/dist/database-field-F47FY3XX.js +10 -0
- package/dist/database-row-77OXW2NF.js +10 -0
- package/dist/database-select-option-6S3TTA7A.js +10 -0
- package/dist/database-view-YW7DQG5X.js +10 -0
- package/dist/experiment-TJOAL3OH.js +10 -0
- package/dist/external-item-D654PKLI.js +14 -0
- package/dist/external-reference-K5SUJIQN.js +8 -0
- package/dist/feed-L23XTNDI.js +12 -0
- package/dist/feed-item-GSO242J6.js +12 -0
- package/dist/folder-ONKSWHUT.js +21 -0
- package/dist/game-BF7DP5JU.js +50 -0
- package/dist/grant-NNKMWXPX.js +8 -0
- package/dist/grant-expiration-cleaner-DBiNTWbD.d.ts +249 -0
- package/dist/import-batch-JMESZ4VI.js +12 -0
- package/dist/inbox-state-AN6FPCIY.js +12 -0
- package/dist/index.d.ts +224 -4548
- package/dist/index.js +2206 -4754
- package/dist/map-AUV65YS3.js +10 -0
- package/dist/media-asset-4I5YPR4R.js +8 -0
- package/dist/memory-NV6EVJBE.js +12 -0
- package/dist/metric-X7W55DLC.js +10 -0
- package/dist/milestone-JJGOGK4R.js +12 -0
- package/dist/moderation-QSUVBCNS.js +30 -0
- package/dist/observation-UZSXXGBC.js +10 -0
- package/dist/page-NI7XODS6.js +10 -0
- package/dist/posting-FZOYVGH2.js +12 -0
- package/dist/profile-42A43GES.js +8 -0
- package/dist/project-G2CTLDXY.js +10 -0
- package/dist/query-ast-CWo6_30h.d.ts +226 -0
- package/dist/reaction-VAQBVDUD.js +10 -0
- package/dist/registry-CQoN_p8U.d.ts +99 -0
- package/dist/saved-view-AOQHGHNM.js +10 -0
- package/dist/schema/index.d.ts +6182 -0
- package/dist/schema/index.js +631 -0
- package/dist/schema-extension-5BUXRDPB.js +18 -0
- package/dist/space-PZ737CIS.js +42 -0
- package/dist/space-membership-N35NRX6F.js +14 -0
- package/dist/store/index.d.ts +391 -0
- package/dist/store/index.js +135 -0
- package/dist/store-CvgiydME.d.ts +338 -0
- package/dist/sync/awareness.d.ts +69 -0
- package/dist/sync/awareness.js +20 -0
- package/dist/system-Q5I6STDC.js +38 -0
- package/dist/tag-C677GKQW.js +16 -0
- package/dist/task-WQLMPN5U.js +16 -0
- package/dist/task-view-T5S32V4L.js +10 -0
- package/dist/transaction-U3ITRGJP.js +12 -0
- package/dist/transcription-F3T23LLO.js +12 -0
- package/dist/types-Def-5Nka.d.ts +1525 -0
- package/dist/updates.d.ts +44 -0
- package/dist/updates.js +16 -0
- package/dist/user-widget-4SJJFPG5.js +8 -0
- package/dist/view-types-DSde-uT_.d.ts +316 -0
- package/package.json +32 -9
- package/dist/canvas-HSIKIQFK.js +0 -7
- package/dist/chunk-2L5ZUGG5.js +0 -53
- package/dist/chunk-4MTS5KAQ.js +0 -22
- package/dist/chunk-GZHARFKC.js +0 -25
- package/dist/chunk-WKIKJTI2.js +0 -45
- package/dist/comment-277JD7DV.js +0 -7
- package/dist/database-W3KEHLI3.js +0 -7
- package/dist/database-row-3O2QSNZN.js +0 -7
- package/dist/grant-QVZ454YC.js +0 -7
- package/dist/page-O4WTOIEO.js +0 -7
- package/dist/task-SEKAYJH7.js +0 -7
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Schema system, NodeStore, and Yjs CRDT engine for xNet. This is the central data package -- it defines how structured data and rich text documents are created, stored, and synced.
|
|
4
4
|
|
|
5
|
+
> **Status:** Mixed
|
|
6
|
+
> Stable entrypoints: `@xnetjs/data/schema`, `@xnetjs/data/store`, `@xnetjs/data/updates`, `@xnetjs/data/awareness`
|
|
7
|
+
> Experimental entrypoints: `@xnetjs/data/database`, `@xnetjs/data/auth`
|
|
8
|
+
|
|
9
|
+
The root `@xnetjs/data` barrel remains supported for compatibility, but new code should prefer the narrower entrypoints above. See [`docs/reference/api-lifecycle-matrix.md`](../../docs/reference/api-lifecycle-matrix.md) for the current matrix.
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -18,6 +24,7 @@ pnpm add @xnetjs/data
|
|
|
18
24
|
- **Blob service** -- File upload/download with content addressing
|
|
19
25
|
- **Storage adapters** -- SQLite and in-memory NodeStore adapters
|
|
20
26
|
- **Temp ID mapping** -- Optimistic creates with server-assigned IDs
|
|
27
|
+
- **Query AST** -- Validated relation, aggregate, query-set, and saved-view descriptors
|
|
21
28
|
|
|
22
29
|
## Usage
|
|
23
30
|
|
|
@@ -95,6 +102,54 @@ const tasks = await store.list(TaskSchema)
|
|
|
95
102
|
await store.remove(task.id)
|
|
96
103
|
```
|
|
97
104
|
|
|
105
|
+
### Batch Writes
|
|
106
|
+
|
|
107
|
+
Use `NodeStore.batchWrite()` for high-volume deterministic imports, restores, migrations, and other
|
|
108
|
+
bulk workflows that already know stable node IDs. The store still signs one change per draft and
|
|
109
|
+
preserves LWW semantics, but storage adapters can apply materialized nodes, changes, Lamport state,
|
|
110
|
+
and touched secondary indexes in one owned batch.
|
|
111
|
+
|
|
112
|
+
```mermaid
|
|
113
|
+
sequenceDiagram
|
|
114
|
+
participant Caller as "Importer / migration"
|
|
115
|
+
participant Store as "NodeStore.batchWrite()"
|
|
116
|
+
participant Storage as "NodeStorageAdapter.applyNodeBatch()"
|
|
117
|
+
participant SQLite as "SQLite transaction"
|
|
118
|
+
|
|
119
|
+
Caller->>Store: deterministic drafts + policy
|
|
120
|
+
Store->>Storage: batch preflight
|
|
121
|
+
Store->>Store: sign changes + materialize nodes
|
|
122
|
+
Store->>Storage: apply materialized nodes and changes
|
|
123
|
+
Storage->>SQLite: write nodes, properties, changes, touched indexes
|
|
124
|
+
Store-->>Caller: counters, timings, affected schemas
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const result = await store.batchWrite({
|
|
129
|
+
kind: 'deterministic-import',
|
|
130
|
+
drafts: [
|
|
131
|
+
{
|
|
132
|
+
id: 'social:content:example',
|
|
133
|
+
schemaId: 'xnet://xnet.fyi/social/Content',
|
|
134
|
+
properties: { title: 'Example post', platform: 'youtube' }
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
policy: {
|
|
138
|
+
indexMode: 'touched',
|
|
139
|
+
notificationMode: 'batch',
|
|
140
|
+
syncMode: 'defer'
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
console.log(result.created, result.updated, result.timings.applyMs, result.storage)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`indexMode: 'touched'` is the import default. It avoids whole-schema rebuilds by reindexing only the
|
|
148
|
+
nodes changed by the batch. `notificationMode: 'batch'` coalesces live reload work. Deferred sync is
|
|
149
|
+
an advisory hint for runtimes that can coalesce outbound replication.
|
|
150
|
+
`importDeterministicNodes()` remains available for existing callers and returns the same storage
|
|
151
|
+
counters and phase timings while preserving its detailed `changes` array.
|
|
152
|
+
|
|
98
153
|
### Yjs Documents
|
|
99
154
|
|
|
100
155
|
```typescript
|
|
@@ -120,6 +175,62 @@ applySignedUpdate(docB, signed)
|
|
|
120
175
|
mergeDocuments(docA, docB)
|
|
121
176
|
```
|
|
122
177
|
|
|
178
|
+
### Canonical Query AST
|
|
179
|
+
|
|
180
|
+
`useQuery()` remains the stable React read API. Advanced relation includes, aggregate reads, dashboards, and saved views are represented in `@xnetjs/data` as a canonical AST before they become public hook shortcuts.
|
|
181
|
+
|
|
182
|
+
```mermaid
|
|
183
|
+
flowchart LR
|
|
184
|
+
Helpers["queryOperators(), from(), count()"]
|
|
185
|
+
AST["QueryAST"]
|
|
186
|
+
Validate["validateQueryAST()"]
|
|
187
|
+
Plan["planQueryASTAggregates()"]
|
|
188
|
+
Indexes["getQueryASTRelationIndexRequirements()"]
|
|
189
|
+
Saved["SavedViewDescriptor / SavedViewSchema"]
|
|
190
|
+
|
|
191
|
+
Helpers --> AST
|
|
192
|
+
AST --> Validate
|
|
193
|
+
Validate --> Plan
|
|
194
|
+
Validate --> Indexes
|
|
195
|
+
AST --> Saved
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import {
|
|
200
|
+
PageSchema,
|
|
201
|
+
TaskSchema,
|
|
202
|
+
count,
|
|
203
|
+
countDistinct,
|
|
204
|
+
defineNodeQueryAST,
|
|
205
|
+
defineSavedViewDescriptor,
|
|
206
|
+
from,
|
|
207
|
+
queryOperators,
|
|
208
|
+
validateSavedViewDescriptor
|
|
209
|
+
} from '@xnetjs/data'
|
|
210
|
+
|
|
211
|
+
const task = queryOperators<(typeof TaskSchema)['_properties']>()
|
|
212
|
+
|
|
213
|
+
const query = defineNodeQueryAST(PageSchema, {
|
|
214
|
+
include: {
|
|
215
|
+
tasks: from(TaskSchema, 'page', {
|
|
216
|
+
where: task.neq('status', 'done'),
|
|
217
|
+
page: { first: 25, count: 'exact' },
|
|
218
|
+
aggregates: [count(), countDistinct('assignee', 'assigneeCount')]
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const saved = defineSavedViewDescriptor({
|
|
224
|
+
title: 'Open task dashboard',
|
|
225
|
+
scope: 'workspace',
|
|
226
|
+
query
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const validation = validateSavedViewDescriptor(saved)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The AST helpers only construct and validate descriptors. Execution of relation expansion, aggregate pushdown, and future `useFind()` pattern queries should go through planner gates before becoming stable public APIs.
|
|
233
|
+
|
|
123
234
|
## Architecture
|
|
124
235
|
|
|
125
236
|
```mermaid
|
|
@@ -206,6 +317,7 @@ All telemetry respects user consent settings and privacy buckets (no exact value
|
|
|
206
317
|
| `schema/node.ts` | FlatNode type (flattened properties) |
|
|
207
318
|
| `schema/registry.ts` | Schema registry |
|
|
208
319
|
| `store/store.ts` | NodeStore engine |
|
|
320
|
+
| `store/query-ast.ts` | Canonical query AST and planner gates |
|
|
209
321
|
| `store/sqlite-adapter.ts` | SQLite NodeStore adapter (recommended) |
|
|
210
322
|
| `store/memory-adapter.ts` | In-memory NodeStore adapter |
|
|
211
323
|
| `store/tempids.ts` | Temp ID mapping for optimistic creates |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNT_SCHEMA_IRI,
|
|
3
|
+
AccountSchema
|
|
4
|
+
} from "./chunk-3JKW6TDE.js";
|
|
5
|
+
import "./chunk-6OPZJ7MG.js";
|
|
6
|
+
import "./chunk-ZICIXDEE.js";
|
|
7
|
+
import "./chunk-O64EK5FK.js";
|
|
8
|
+
import "./chunk-JESSJ5CZ.js";
|
|
9
|
+
export {
|
|
10
|
+
ACCOUNT_SCHEMA_IRI,
|
|
11
|
+
AccountSchema
|
|
12
|
+
};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { AllowExpr, DenyExpr, AuthExpression, AndExpr, OrExpr, NotExpr, PublicExpr, AuthenticatedExpr, CreatorRoleResolver, PropertyRoleResolver, RelationRoleResolver, MembershipRoleResolver, AuthorizationDefinition, SerializedAuthorization, SerializedAuthExpression, DID } from '@xnetjs/core';
|
|
2
|
+
export { AUTH_ACTIONS, AllowExpr, AndExpr, AuthAction, AuthCheckInput, AuthDecision, AuthDenyReason, AuthExpression, AuthTrace, AuthTraceStep, AuthenticatedExpr, AuthorizationDefinition, CreatorRoleResolver, DenyExpr, MembershipRoleResolver, NotExpr, OrExpr, PolicyEvaluator, PropertyRoleResolver, PublicExpr, RelationRoleResolver, RoleRefExpr, RoleResolver, SerializedAuthExpression, SerializedAuthorization, SerializedRoleResolver } from '@xnetjs/core';
|
|
3
|
+
import { E as PropertyDefinition, a as Schema, h as NodeState, S as SchemaIRI } from '../types-Def-5Nka.js';
|
|
4
|
+
export { aX as DEFAULT_OFFLINE_POLICY, aV as GRANT_SCHEMA_IRI, b1 as Grant, w as GrantIndex, b6 as GrantIndexOptions, b5 as GrantIndexStore, b0 as GrantInput, b4 as GrantNode, aU as GrantRateLimiter, b9 as GrantRateLimiterOptions, O as OfflineAuthPolicy, b8 as RevocationConfig, b7 as RevocationConsistency, aT as StoreAuth, e as StoreAuthAPI, b2 as StoreAuthError, b3 as StoreAuthErrorCode, a$ as StoreAuthKeyManager, aZ as StoreAuthOptions, a_ as StoreAuthStore, aW as isGrantActive, aY as mergeOfflinePolicy } from '../types-Def-5Nka.js';
|
|
5
|
+
export { A as ActionPermission, m as AuthDecisionEvent, b as DecisionCache, g as DecisionCacheOptions, D as DefaultPolicyEvaluator, j as DefaultPolicyEvaluatorOptions, a as DefaultRoleResolver, i as DefaultRoleResolverOptions, k as DefinedSchemaLike, G as GrantExpirationCleaner, o as GrantExpirationCleanerOptions, n as GrantExpirationStore, H as HubPolicy, N as NodeStoreReader, P as PermissionMatrix, l as RevalidationEvent, R as RoleSummary, S as SchemaRegistryReader, c as buildPermissionMatrix, f as createPolicyEvaluator, d as describeRoleResolver, e as evaluateExpression, h as hubActionsForSpaceRole, s as schemaToHubPolicy } from '../grant-expiration-cleaner-DBiNTWbD.js';
|
|
6
|
+
import { PublicKeyResolver } from '@xnetjs/crypto';
|
|
7
|
+
import '@xnetjs/sqlite';
|
|
8
|
+
import '@xnetjs/sync';
|
|
9
|
+
import '../registry-CQoN_p8U.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Authorization expression builders for schema definitions.
|
|
13
|
+
*
|
|
14
|
+
* These functions create typed AST nodes for authorization rules.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { allow, deny, and, or, not, role, PUBLIC, AUTHENTICATED } from '@xnetjs/data/auth'
|
|
19
|
+
*
|
|
20
|
+
* const TaskSchema = defineSchema({
|
|
21
|
+
* // ...
|
|
22
|
+
* authorization: {
|
|
23
|
+
* roles: {
|
|
24
|
+
* owner: role.creator(),
|
|
25
|
+
* editor: role.property('editors'),
|
|
26
|
+
* admin: role.relation('project', 'admin')
|
|
27
|
+
* },
|
|
28
|
+
* actions: {
|
|
29
|
+
* read: allow('editor', 'admin', 'owner'),
|
|
30
|
+
* write: allow('editor', 'admin', 'owner'),
|
|
31
|
+
* delete: allow('admin', 'owner'),
|
|
32
|
+
* share: allow('admin', 'owner')
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Allow access if the subject has any of the specified roles.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* read: allow('viewer', 'editor', 'admin', 'owner')
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function allow(...roles: string[]): AllowExpr;
|
|
48
|
+
/**
|
|
49
|
+
* Deny access if the subject has any of the specified roles.
|
|
50
|
+
* Deny ALWAYS takes precedence over allow.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* read: and(allow('member'), deny('banned'))
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function deny(...roles: string[]): DenyExpr;
|
|
58
|
+
/**
|
|
59
|
+
* Logical AND - all sub-expressions must be true.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* write: and(allow('editor'), deny('readonly'))
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function and(...exprs: AuthExpression[]): AndExpr;
|
|
67
|
+
/**
|
|
68
|
+
* Logical OR - any sub-expression must be true.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* read: or(allow('owner'), PUBLIC)
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function or(...exprs: AuthExpression[]): OrExpr;
|
|
76
|
+
/**
|
|
77
|
+
* Logical NOT - negate the sub-expression.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* write: and(AUTHENTICATED, not(allow('banned')))
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
declare function not(expr: AuthExpression): NotExpr;
|
|
85
|
+
/**
|
|
86
|
+
* Public access - always allows access.
|
|
87
|
+
* Use for read-only public content.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* read: PUBLIC
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare const PUBLIC: PublicExpr;
|
|
95
|
+
/**
|
|
96
|
+
* Authenticated access - allows any authenticated user.
|
|
97
|
+
* Use for open-but-not-anonymous content.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* read: AUTHENTICATED
|
|
102
|
+
* write: AUTHENTICATED
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare const AUTHENTICATED: AuthenticatedExpr;
|
|
106
|
+
/**
|
|
107
|
+
* Role resolver builders.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* roles: {
|
|
112
|
+
* owner: role.creator(),
|
|
113
|
+
* assignee: role.property('assignee'),
|
|
114
|
+
* admin: role.relation('project', 'admin')
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const role: {
|
|
119
|
+
/**
|
|
120
|
+
* Role held by the node's creator.
|
|
121
|
+
* The DID in `createdBy` holds this role.
|
|
122
|
+
*/
|
|
123
|
+
creator(): CreatorRoleResolver;
|
|
124
|
+
/**
|
|
125
|
+
* Role determined by a person property on the node.
|
|
126
|
+
* The DID(s) in that property hold this role.
|
|
127
|
+
*
|
|
128
|
+
* @param propertyName - Name of the person/person[] property
|
|
129
|
+
*/
|
|
130
|
+
property(propertyName: string): PropertyRoleResolver;
|
|
131
|
+
/**
|
|
132
|
+
* Role inherited from a related node.
|
|
133
|
+
* Users who hold `targetRole` on the related node hold this role.
|
|
134
|
+
*
|
|
135
|
+
* @param relationName - Name of the relation property
|
|
136
|
+
* @param targetRole - Role to check on the related node
|
|
137
|
+
*/
|
|
138
|
+
relation(relationName: string, targetRole: string): RelationRoleResolver;
|
|
139
|
+
/**
|
|
140
|
+
* Role determined by membership edges that reference THIS node.
|
|
141
|
+
*
|
|
142
|
+
* Unlike `relation` (which follows a forward relation) this is a reverse-edge
|
|
143
|
+
* lookup: the subject holds the role when a `config.edgeSchema` edge points at
|
|
144
|
+
* this node (or, when `parentProp` is set, any ancestor) with the subject as
|
|
145
|
+
* `memberProp` and a `roleProp` rank `>= minRole`. This is how Space
|
|
146
|
+
* membership cascades down a nested container tree.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* spaceMember: role.members({
|
|
151
|
+
* edgeSchema: SPACE_MEMBERSHIP_SCHEMA_IRI, containerProp: 'space',
|
|
152
|
+
* memberProp: 'member', roleProp: 'role', minRole: 'member',
|
|
153
|
+
* roleOrder: SPACE_ROLES, parentProp: 'parent'
|
|
154
|
+
* })
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
members(config: {
|
|
158
|
+
edgeSchema: string;
|
|
159
|
+
containerProp: string;
|
|
160
|
+
memberProp: string;
|
|
161
|
+
roleProp: string;
|
|
162
|
+
minRole: string;
|
|
163
|
+
roleOrder: readonly string[];
|
|
164
|
+
parentProp?: string;
|
|
165
|
+
}): MembershipRoleResolver;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Alias for role.relation() for use in authorization expressions.
|
|
169
|
+
* @deprecated Use role.relation() instead
|
|
170
|
+
*/
|
|
171
|
+
declare const relation: (relationName: string, targetRole: string) => RelationRoleResolver;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Authorization schema validation.
|
|
175
|
+
*
|
|
176
|
+
* Validates that authorization blocks are well-formed:
|
|
177
|
+
* - Role references in actions exist in the roles definition
|
|
178
|
+
* - Property-based roles reference existing person properties
|
|
179
|
+
* - Expression depth limits are enforced
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Result of authorization schema validation.
|
|
184
|
+
*/
|
|
185
|
+
interface AuthValidationResult {
|
|
186
|
+
valid: boolean;
|
|
187
|
+
errors: AuthValidationError[];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A single validation error.
|
|
191
|
+
*/
|
|
192
|
+
interface AuthValidationError {
|
|
193
|
+
/** Error code for programmatic handling */
|
|
194
|
+
code: AuthSchemaErrorCode;
|
|
195
|
+
/** Human-readable error message */
|
|
196
|
+
message: string;
|
|
197
|
+
/** JSON path to the error location */
|
|
198
|
+
path: string;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Error codes for authorization schema validation.
|
|
202
|
+
*/
|
|
203
|
+
type AuthSchemaErrorCode = 'AUTH_SCHEMA_INVALID_ROLE_REF' | 'AUTH_SCHEMA_INVALID_ACTION_REF' | 'AUTH_SCHEMA_INVALID_RELATION_PATH' | 'AUTH_SCHEMA_ROLE_CYCLE' | 'AUTH_SCHEMA_EXPR_LIMIT_EXCEEDED' | 'AUTH_SCHEMA_UNSAFE_PUBLIC_MUTATION' | 'AUTH_SCHEMA_INVALID_FIELD_REF' | 'AUTH_SCHEMA_INVALID_PUBLIC_PROP';
|
|
204
|
+
/**
|
|
205
|
+
* Roles that are always available without definition.
|
|
206
|
+
*/
|
|
207
|
+
declare const BUILTIN_ROLES: readonly ["owner"];
|
|
208
|
+
/**
|
|
209
|
+
* Extract all role references from an authorization expression.
|
|
210
|
+
*/
|
|
211
|
+
declare function extractRoleRefs(expr: AuthExpression): string[];
|
|
212
|
+
/**
|
|
213
|
+
* Count the number of nodes in an expression tree.
|
|
214
|
+
*/
|
|
215
|
+
declare function countExpressionNodes(expr: AuthExpression): number;
|
|
216
|
+
/**
|
|
217
|
+
* Check if an expression allows public access.
|
|
218
|
+
*/
|
|
219
|
+
declare function hasPublicAccess(expr: AuthExpression): boolean;
|
|
220
|
+
/**
|
|
221
|
+
* Maximum allowed nodes in an expression tree.
|
|
222
|
+
*/
|
|
223
|
+
declare const MAX_EXPRESSION_NODES = 50;
|
|
224
|
+
/**
|
|
225
|
+
* Validate an authorization definition against property definitions.
|
|
226
|
+
*
|
|
227
|
+
* Checks:
|
|
228
|
+
* 1. All role references in action expressions exist in roles
|
|
229
|
+
* 2. Property-based roles reference existing person properties
|
|
230
|
+
* 3. Relation-based roles reference existing relation properties
|
|
231
|
+
* 4. publicProps reference existing properties
|
|
232
|
+
* 5. Expression depth limits are enforced
|
|
233
|
+
* 6. Write/delete/share actions don't use PUBLIC (unsafe)
|
|
234
|
+
*/
|
|
235
|
+
declare function validateAuthorization(auth: AuthorizationDefinition, properties: Record<string, PropertyDefinition>): AuthValidationResult;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Serialization and deserialization of authorization definitions.
|
|
239
|
+
*
|
|
240
|
+
* Authorization blocks in schemas need to be serialized to JSON for storage
|
|
241
|
+
* and deserialized back to typed objects for evaluation.
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Serialize an AuthExpression to its JSON-serializable form.
|
|
246
|
+
*/
|
|
247
|
+
declare function serializeAuthExpression(expr: AuthExpression): SerializedAuthExpression;
|
|
248
|
+
/**
|
|
249
|
+
* Deserialize a SerializedAuthExpression back to its typed form.
|
|
250
|
+
*/
|
|
251
|
+
declare function deserializeAuthExpression(serialized: SerializedAuthExpression): AuthExpression;
|
|
252
|
+
/**
|
|
253
|
+
* Serialize an AuthorizationDefinition to its JSON-serializable form.
|
|
254
|
+
*/
|
|
255
|
+
declare function serializeAuthorization(auth: AuthorizationDefinition): SerializedAuthorization;
|
|
256
|
+
/**
|
|
257
|
+
* Deserialize a SerializedAuthorization back to its typed form.
|
|
258
|
+
*/
|
|
259
|
+
declare function deserializeAuthorization(serialized: SerializedAuthorization): AuthorizationDefinition;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Authorization presets - common authorization patterns as reusable factories.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* import { presets } from '@xnetjs/data/auth'
|
|
267
|
+
*
|
|
268
|
+
* const NoteSchema = defineSchema({
|
|
269
|
+
* name: 'Note',
|
|
270
|
+
* namespace: 'xnet://myapp/',
|
|
271
|
+
* properties: { ... },
|
|
272
|
+
* authorization: presets.private()
|
|
273
|
+
* })
|
|
274
|
+
*
|
|
275
|
+
* const PublicArticleSchema = defineSchema({
|
|
276
|
+
* name: 'Article',
|
|
277
|
+
* namespace: 'xnet://myapp/',
|
|
278
|
+
* properties: { ... },
|
|
279
|
+
* authorization: presets.publicRead()
|
|
280
|
+
* })
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Authorization presets for common patterns.
|
|
286
|
+
*/
|
|
287
|
+
declare const presets: {
|
|
288
|
+
/**
|
|
289
|
+
* Private - only the creator can access.
|
|
290
|
+
*
|
|
291
|
+
* Use for personal notes, private documents, etc.
|
|
292
|
+
*/
|
|
293
|
+
private: () => AuthorizationDefinition;
|
|
294
|
+
/**
|
|
295
|
+
* Public read - anyone can read, only creator can write.
|
|
296
|
+
*
|
|
297
|
+
* Use for published content, blog posts, etc.
|
|
298
|
+
*/
|
|
299
|
+
publicRead: () => AuthorizationDefinition;
|
|
300
|
+
/**
|
|
301
|
+
* Collaborative - roles inherited from a parent relation.
|
|
302
|
+
*
|
|
303
|
+
* Use for items within a workspace/project that inherits permissions.
|
|
304
|
+
*
|
|
305
|
+
* @param parentRelation - Name of the relation property to the parent node
|
|
306
|
+
*/
|
|
307
|
+
collaborative: (parentRelation: string) => AuthorizationDefinition;
|
|
308
|
+
/**
|
|
309
|
+
* Open - any authenticated user can read/write, only creator can delete.
|
|
310
|
+
*
|
|
311
|
+
* Use for wiki-style collaborative content.
|
|
312
|
+
*/
|
|
313
|
+
open: () => AuthorizationDefinition;
|
|
314
|
+
/**
|
|
315
|
+
* Team-based - permissions based on a team/editors property.
|
|
316
|
+
*
|
|
317
|
+
* @param editorsProperty - Name of the person[] property containing editors
|
|
318
|
+
*/
|
|
319
|
+
team: (editorsProperty: string) => AuthorizationDefinition;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Authorization mode helpers for schema compatibility.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
type AuthMode = 'legacy' | 'compat' | 'enforce';
|
|
327
|
+
/**
|
|
328
|
+
* Determine authorization mode for a schema.
|
|
329
|
+
*/
|
|
330
|
+
declare function getAuthMode(schema: Schema): AuthMode;
|
|
331
|
+
/**
|
|
332
|
+
* Warn developers when a schema has no authorization block.
|
|
333
|
+
*/
|
|
334
|
+
declare function warnLegacySchema(schema: Schema): void;
|
|
335
|
+
|
|
336
|
+
declare const PUBLIC_CONTENT_KEY: Uint8Array<ArrayBuffer>;
|
|
337
|
+
declare const PUBLIC_RECIPIENT = "PUBLIC";
|
|
338
|
+
type Recipient = DID | typeof PUBLIC_RECIPIENT;
|
|
339
|
+
interface GrantRecordLike {
|
|
340
|
+
id: string;
|
|
341
|
+
properties: Record<string, unknown>;
|
|
342
|
+
}
|
|
343
|
+
interface GrantIndexReader {
|
|
344
|
+
findGrantsForResource(resourceId: string): GrantRecordLike[];
|
|
345
|
+
}
|
|
346
|
+
interface RecipientDependencies {
|
|
347
|
+
getNode: (nodeId: string) => Promise<NodeState | null>;
|
|
348
|
+
getSchema?: (schemaId: SchemaIRI) => Promise<Schema | undefined>;
|
|
349
|
+
/** Lists all nodes of a schema — required to expand membership-edge roles. */
|
|
350
|
+
listNodes?: (schemaId: string) => Promise<NodeState[]>;
|
|
351
|
+
grantIndex?: GrantIndexReader;
|
|
352
|
+
maxDepth?: number;
|
|
353
|
+
}
|
|
354
|
+
declare function computeRecipients(schema: Schema, node: NodeState, dependencies: RecipientDependencies): Promise<Recipient[]>;
|
|
355
|
+
declare function hasRecipientsChanged(previous: Recipient[], next: Recipient[]): boolean;
|
|
356
|
+
declare function canTransitionToPublic(readExpr: AuthExpression | undefined): boolean;
|
|
357
|
+
|
|
358
|
+
interface AuthMigrationDependencies extends RecipientDependencies {
|
|
359
|
+
encryptExistingNode: (node: NodeState, schema: Schema) => Promise<void>;
|
|
360
|
+
rotateContentKeyForNode: (nodeId: string, recipients: Recipient[]) => Promise<void>;
|
|
361
|
+
}
|
|
362
|
+
declare function handleAuthMigration(oldSchema: Schema, newSchema: Schema, node: NodeState, dependencies: AuthMigrationDependencies): Promise<void>;
|
|
363
|
+
|
|
364
|
+
interface EncryptionLayer {
|
|
365
|
+
encryptAndStoreNode(node: NodeState, recipientKeys: Map<DID, Uint8Array>): Promise<void>;
|
|
366
|
+
}
|
|
367
|
+
interface MigrationOptions {
|
|
368
|
+
batchSize?: number;
|
|
369
|
+
onProgress?: (done: number, total: number) => void;
|
|
370
|
+
}
|
|
371
|
+
interface MigrationError {
|
|
372
|
+
nodeId: string;
|
|
373
|
+
error: string;
|
|
374
|
+
}
|
|
375
|
+
interface MigrationResult {
|
|
376
|
+
total: number;
|
|
377
|
+
migrated: number;
|
|
378
|
+
failed: number;
|
|
379
|
+
errors: MigrationError[];
|
|
380
|
+
}
|
|
381
|
+
interface AuthMigratorStore {
|
|
382
|
+
get(nodeId: string): Promise<NodeState | null>;
|
|
383
|
+
list(options?: {
|
|
384
|
+
schemaId?: string;
|
|
385
|
+
includeDeleted?: boolean;
|
|
386
|
+
}): Promise<NodeState[]>;
|
|
387
|
+
}
|
|
388
|
+
interface AuthMigratorSchemaRegistry {
|
|
389
|
+
get(schemaId: SchemaIRI): Promise<{
|
|
390
|
+
schema: Schema;
|
|
391
|
+
} | undefined>;
|
|
392
|
+
}
|
|
393
|
+
declare class AuthMigrator {
|
|
394
|
+
private readonly store;
|
|
395
|
+
private readonly schemaRegistry;
|
|
396
|
+
private readonly publicKeyResolver;
|
|
397
|
+
private readonly grantIndex;
|
|
398
|
+
private readonly encryptionLayer;
|
|
399
|
+
constructor(store: AuthMigratorStore, schemaRegistry: AuthMigratorSchemaRegistry, publicKeyResolver: PublicKeyResolver, grantIndex: GrantIndexReader, encryptionLayer: EncryptionLayer);
|
|
400
|
+
migrateSchema(schemaIri: SchemaIRI, options?: MigrationOptions): Promise<MigrationResult>;
|
|
401
|
+
private resolveSchema;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
interface AuthFeatureFlags {
|
|
405
|
+
enforceLocal: boolean;
|
|
406
|
+
enforceRemote: boolean;
|
|
407
|
+
enforceHub: boolean;
|
|
408
|
+
enforceEncryption: boolean;
|
|
409
|
+
logDecisions: boolean;
|
|
410
|
+
}
|
|
411
|
+
declare const AUTH_FEATURE_FLAGS: AuthFeatureFlags;
|
|
412
|
+
|
|
413
|
+
export { AUTHENTICATED, AUTH_FEATURE_FLAGS, type AuthFeatureFlags, type AuthMigrationDependencies, AuthMigrator, type AuthMigratorSchemaRegistry, type AuthMigratorStore, type AuthMode, type AuthSchemaErrorCode, type AuthValidationError, type AuthValidationResult, BUILTIN_ROLES, type EncryptionLayer, type GrantIndexReader, type GrantRecordLike, MAX_EXPRESSION_NODES, type MigrationError, type MigrationOptions, type MigrationResult, PUBLIC, PUBLIC_CONTENT_KEY, PUBLIC_RECIPIENT, type Recipient, type RecipientDependencies, allow, and, canTransitionToPublic, computeRecipients, countExpressionNodes, deny, deserializeAuthExpression, deserializeAuthorization, extractRoleRefs, getAuthMode, handleAuthMigration, hasPublicAccess, hasRecipientsChanged, not, or, presets, relation, role, serializeAuthExpression, serializeAuthorization, validateAuthorization, warnLegacySchema };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AUTHENTICATED,
|
|
3
|
+
AUTH_ACTIONS,
|
|
4
|
+
AUTH_FEATURE_FLAGS,
|
|
5
|
+
AuthMigrator,
|
|
6
|
+
BUILTIN_ROLES,
|
|
7
|
+
DEFAULT_OFFLINE_POLICY,
|
|
8
|
+
DecisionCache,
|
|
9
|
+
DefaultPolicyEvaluator,
|
|
10
|
+
DefaultRoleResolver,
|
|
11
|
+
GRANT_SCHEMA_IRI,
|
|
12
|
+
GrantExpirationCleaner,
|
|
13
|
+
GrantIndex,
|
|
14
|
+
GrantRateLimiter,
|
|
15
|
+
MAX_EXPRESSION_NODES,
|
|
16
|
+
PUBLIC,
|
|
17
|
+
PUBLIC_CONTENT_KEY,
|
|
18
|
+
PUBLIC_RECIPIENT,
|
|
19
|
+
StoreAuth,
|
|
20
|
+
StoreAuthError,
|
|
21
|
+
allow,
|
|
22
|
+
and,
|
|
23
|
+
buildPermissionMatrix,
|
|
24
|
+
canTransitionToPublic,
|
|
25
|
+
computeRecipients,
|
|
26
|
+
countExpressionNodes,
|
|
27
|
+
createPolicyEvaluator,
|
|
28
|
+
deny,
|
|
29
|
+
describeRoleResolver,
|
|
30
|
+
deserializeAuthExpression,
|
|
31
|
+
deserializeAuthorization,
|
|
32
|
+
evaluateExpression,
|
|
33
|
+
extractRoleRefs,
|
|
34
|
+
getAuthMode,
|
|
35
|
+
handleAuthMigration,
|
|
36
|
+
hasPublicAccess,
|
|
37
|
+
hasRecipientsChanged,
|
|
38
|
+
hubActionsForSpaceRole,
|
|
39
|
+
isGrantActive,
|
|
40
|
+
mergeOfflinePolicy,
|
|
41
|
+
not,
|
|
42
|
+
or,
|
|
43
|
+
presets,
|
|
44
|
+
relation,
|
|
45
|
+
role,
|
|
46
|
+
schemaToHubPolicy,
|
|
47
|
+
serializeAuthExpression,
|
|
48
|
+
serializeAuthorization,
|
|
49
|
+
validateAuthorization,
|
|
50
|
+
warnLegacySchema
|
|
51
|
+
} from "../chunk-JESSJ5CZ.js";
|
|
52
|
+
export {
|
|
53
|
+
AUTHENTICATED,
|
|
54
|
+
AUTH_ACTIONS,
|
|
55
|
+
AUTH_FEATURE_FLAGS,
|
|
56
|
+
AuthMigrator,
|
|
57
|
+
BUILTIN_ROLES,
|
|
58
|
+
DEFAULT_OFFLINE_POLICY,
|
|
59
|
+
DecisionCache,
|
|
60
|
+
DefaultPolicyEvaluator,
|
|
61
|
+
DefaultRoleResolver,
|
|
62
|
+
GRANT_SCHEMA_IRI,
|
|
63
|
+
GrantExpirationCleaner,
|
|
64
|
+
GrantIndex,
|
|
65
|
+
GrantRateLimiter,
|
|
66
|
+
MAX_EXPRESSION_NODES,
|
|
67
|
+
PUBLIC,
|
|
68
|
+
PUBLIC_CONTENT_KEY,
|
|
69
|
+
PUBLIC_RECIPIENT,
|
|
70
|
+
StoreAuth,
|
|
71
|
+
StoreAuthError,
|
|
72
|
+
allow,
|
|
73
|
+
and,
|
|
74
|
+
buildPermissionMatrix,
|
|
75
|
+
canTransitionToPublic,
|
|
76
|
+
computeRecipients,
|
|
77
|
+
countExpressionNodes,
|
|
78
|
+
createPolicyEvaluator,
|
|
79
|
+
deny,
|
|
80
|
+
describeRoleResolver,
|
|
81
|
+
deserializeAuthExpression,
|
|
82
|
+
deserializeAuthorization,
|
|
83
|
+
evaluateExpression,
|
|
84
|
+
extractRoleRefs,
|
|
85
|
+
getAuthMode,
|
|
86
|
+
handleAuthMigration,
|
|
87
|
+
hasPublicAccess,
|
|
88
|
+
hasRecipientsChanged,
|
|
89
|
+
hubActionsForSpaceRole,
|
|
90
|
+
isGrantActive,
|
|
91
|
+
mergeOfflinePolicy,
|
|
92
|
+
not,
|
|
93
|
+
or,
|
|
94
|
+
presets,
|
|
95
|
+
relation,
|
|
96
|
+
role,
|
|
97
|
+
schemaToHubPolicy,
|
|
98
|
+
serializeAuthExpression,
|
|
99
|
+
serializeAuthorization,
|
|
100
|
+
validateAuthorization,
|
|
101
|
+
warnLegacySchema
|
|
102
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUDGET_SCHEMA_IRI,
|
|
3
|
+
BudgetSchema
|
|
4
|
+
} from "./chunk-337E3ZOB.js";
|
|
5
|
+
import "./chunk-6OPZJ7MG.js";
|
|
6
|
+
import "./chunk-ZICIXDEE.js";
|
|
7
|
+
import "./chunk-O64EK5FK.js";
|
|
8
|
+
import "./chunk-JESSJ5CZ.js";
|
|
9
|
+
export {
|
|
10
|
+
BUDGET_SCHEMA_IRI,
|
|
11
|
+
BudgetSchema
|
|
12
|
+
};
|