interaqt 0.8.1 → 0.8.2
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.
|
@@ -2240,7 +2240,7 @@ Interaction.create(config: InteractionConfig): InteractionInstance
|
|
|
2240
2240
|
- `config.conditions` (Condition|Conditions, optional): Execution conditions
|
|
2241
2241
|
- `config.sideEffects` (SideEffect[], optional): Side effect handlers
|
|
2242
2242
|
- `config.data` (Entity|Relation, optional): Associated data entity
|
|
2243
|
-
- `config.
|
|
2243
|
+
- `config.dataPolicy` (DataPolicy, optional): Data access policy for data fetching interactions
|
|
2244
2244
|
|
|
2245
2245
|
**Examples**
|
|
2246
2246
|
|
|
@@ -2280,37 +2280,33 @@ const GetAllUsers = Interaction.create({
|
|
|
2280
2280
|
data: User // Entity to query
|
|
2281
2281
|
})
|
|
2282
2282
|
|
|
2283
|
-
// Get filtered data with
|
|
2283
|
+
// Get filtered data with dataPolicy configuration
|
|
2284
2284
|
const GetActiveUsers = Interaction.create({
|
|
2285
2285
|
name: 'getActiveUsers',
|
|
2286
2286
|
action: GetAction,
|
|
2287
2287
|
data: User,
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
QueryItem.create({
|
|
2291
|
-
name: 'attributeQuery',
|
|
2292
|
-
value: ['id', 'name', 'email', 'status'] // Fields to retrieve
|
|
2293
|
-
})
|
|
2294
|
-
]
|
|
2288
|
+
dataPolicy: DataPolicy.create({
|
|
2289
|
+
attributeQuery: ['id', 'name', 'email', 'status'] // Fields to retrieve
|
|
2295
2290
|
})
|
|
2296
2291
|
})
|
|
2297
2292
|
|
|
2298
|
-
// Get data with
|
|
2293
|
+
// Get data with dynamic access control using dataPolicy
|
|
2299
2294
|
const GetUserPosts = Interaction.create({
|
|
2300
2295
|
name: 'getUserPosts',
|
|
2301
2296
|
action: GetAction,
|
|
2302
2297
|
data: Post,
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
}
|
|
2298
|
+
dataPolicy: DataPolicy.create({
|
|
2299
|
+
match: function(this: Controller, event: any) {
|
|
2300
|
+
// Users can only see their own posts unless they are admin
|
|
2301
|
+
if (event.user.role === 'admin') {
|
|
2302
|
+
return null // Admin can see all posts
|
|
2303
|
+
}
|
|
2304
|
+
return MatchExp.atom({
|
|
2305
|
+
key: 'author.id',
|
|
2306
|
+
value: ['=', event.user.id]
|
|
2307
|
+
})
|
|
2308
|
+
},
|
|
2309
|
+
attributeQuery: ['id', 'title', 'content', 'author', 'createdAt']
|
|
2314
2310
|
})
|
|
2315
2311
|
})
|
|
2316
2312
|
|
|
@@ -2319,17 +2315,9 @@ const GetPostsPaginated = Interaction.create({
|
|
|
2319
2315
|
name: 'getPostsPaginated',
|
|
2320
2316
|
action: GetAction,
|
|
2321
2317
|
data: Post,
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
name: 'attributeQuery',
|
|
2326
|
-
value: ['id', 'title', 'content', 'createdAt', 'author']
|
|
2327
|
-
}),
|
|
2328
|
-
QueryItem.create({
|
|
2329
|
-
name: 'modifier',
|
|
2330
|
-
value: { limit: 10, offset: 0 } // Pagination
|
|
2331
|
-
})
|
|
2332
|
-
]
|
|
2318
|
+
dataPolicy: DataPolicy.create({
|
|
2319
|
+
attributeQuery: ['id', 'title', 'content', 'createdAt', 'author'],
|
|
2320
|
+
modifier: { limit: 10, offset: 0 } // Pagination
|
|
2333
2321
|
})
|
|
2334
2322
|
})
|
|
2335
2323
|
```
|
|
@@ -2379,11 +2367,16 @@ const paginatedResult = await controller.callInteraction('getPostsPaginated', {
|
|
|
2379
2367
|
|
|
2380
2368
|
1. **Required**: Must use `GetAction` as the action
|
|
2381
2369
|
2. **Required**: Must specify `data` field with the Entity or Relation to query
|
|
2382
|
-
3. **Optional**: Use `
|
|
2383
|
-
4. **Runtime Query**: Pass `query` object in `callInteraction` to dynamically filter data
|
|
2370
|
+
3. **Optional**: Use `dataPolicy` in Interaction definition to set fixed data access policies (match constraints, field restrictions, default sorting/pagination)
|
|
2371
|
+
4. **Runtime Query**: Pass `query` object in `callInteraction` to dynamically filter data at runtime (user-provided filters)
|
|
2384
2372
|
5. **Conditions**: Can use conditions to control access based on query parameters
|
|
2385
2373
|
6. **Response**: Retrieved data is returned in `result.data` field
|
|
2386
2374
|
|
|
2375
|
+
**About dataPolicy vs runtime query:**
|
|
2376
|
+
- `dataPolicy` defines **fixed constraints** set by developers (e.g., "users can only see published posts", "limit to 10 items max")
|
|
2377
|
+
- Runtime `query` is **user-provided filters** passed at call time (e.g., "show me posts from last week")
|
|
2378
|
+
- The framework combines both: fixed dataPolicy constraints AND user runtime query filters
|
|
2379
|
+
|
|
2387
2380
|
### Action.create()
|
|
2388
2381
|
|
|
2389
2382
|
Create interaction action identifier.
|
|
@@ -2465,6 +2458,141 @@ const UserPostRelation = Relation.create({
|
|
|
2465
2458
|
});
|
|
2466
2459
|
```
|
|
2467
2460
|
|
|
2461
|
+
### DataPolicy.create()
|
|
2462
|
+
|
|
2463
|
+
Create data access policy for data fetching interactions. DataPolicy defines fixed constraints that developers set to control what data users can access and how it's retrieved.
|
|
2464
|
+
|
|
2465
|
+
**Syntax**
|
|
2466
|
+
```typescript
|
|
2467
|
+
DataPolicy.create(config: DataPolicyConfig): DataPolicyInstance
|
|
2468
|
+
```
|
|
2469
|
+
|
|
2470
|
+
**Parameters**
|
|
2471
|
+
- `config.match` (MatchExpression | Function, optional): Fixed match constraints that limit which records can be retrieved
|
|
2472
|
+
- Can be a static `MatchExp` for fixed filters
|
|
2473
|
+
- Can be a function `(event) => MatchExp | MatchAtom | null` for dynamic filters based on user context
|
|
2474
|
+
- Returning `null` means no fixed constraint (allow all records)
|
|
2475
|
+
- `config.modifier` (ModifierData, optional): Default sorting and pagination settings
|
|
2476
|
+
- `limit`: Maximum number of records to return
|
|
2477
|
+
- `offset`: Number of records to skip
|
|
2478
|
+
- `orderBy`: Sorting configuration `{ field: 'asc' | 'desc' }`
|
|
2479
|
+
- `config.attributeQuery` (AttributeQueryData, optional): Fields that are allowed to be retrieved
|
|
2480
|
+
- Restricts which fields users can query
|
|
2481
|
+
- Can be used for security (hide sensitive fields) or performance (limit data transfer)
|
|
2482
|
+
|
|
2483
|
+
**Examples**
|
|
2484
|
+
|
|
2485
|
+
```typescript
|
|
2486
|
+
// Basic field restriction
|
|
2487
|
+
const GetUsers = Interaction.create({
|
|
2488
|
+
name: 'getUsers',
|
|
2489
|
+
action: GetAction,
|
|
2490
|
+
data: User,
|
|
2491
|
+
dataPolicy: DataPolicy.create({
|
|
2492
|
+
attributeQuery: ['id', 'name', 'email'] // Don't allow password field
|
|
2493
|
+
})
|
|
2494
|
+
})
|
|
2495
|
+
|
|
2496
|
+
// Fixed filter - only published posts
|
|
2497
|
+
const GetPublishedPosts = Interaction.create({
|
|
2498
|
+
name: 'getPublishedPosts',
|
|
2499
|
+
action: GetAction,
|
|
2500
|
+
data: Post,
|
|
2501
|
+
dataPolicy: DataPolicy.create({
|
|
2502
|
+
match: MatchExp.atom({ key: 'status', value: ['=', 'published'] })
|
|
2503
|
+
})
|
|
2504
|
+
})
|
|
2505
|
+
|
|
2506
|
+
// Combined constraints
|
|
2507
|
+
const GetTopArticles = Interaction.create({
|
|
2508
|
+
name: 'getTopArticles',
|
|
2509
|
+
action: GetAction,
|
|
2510
|
+
data: Article,
|
|
2511
|
+
dataPolicy: DataPolicy.create({
|
|
2512
|
+
match: MatchExp.atom({ key: 'status', value: ['=', 'published'] }),
|
|
2513
|
+
modifier: { limit: 10, orderBy: { views: 'desc' } },
|
|
2514
|
+
attributeQuery: ['id', 'title', 'author', 'views', 'createdAt']
|
|
2515
|
+
})
|
|
2516
|
+
})
|
|
2517
|
+
|
|
2518
|
+
// Dynamic filter based on user context
|
|
2519
|
+
const GetMyPosts = Interaction.create({
|
|
2520
|
+
name: 'getMyPosts',
|
|
2521
|
+
action: GetAction,
|
|
2522
|
+
data: Post,
|
|
2523
|
+
dataPolicy: DataPolicy.create({
|
|
2524
|
+
match: function(this: Controller, event: any) {
|
|
2525
|
+
// Non-admin users can only see their own posts
|
|
2526
|
+
if (event.user.role === 'admin') {
|
|
2527
|
+
return null // Admin can see all
|
|
2528
|
+
}
|
|
2529
|
+
return MatchExp.atom({
|
|
2530
|
+
key: 'author.id',
|
|
2531
|
+
value: ['=', event.user.id]
|
|
2532
|
+
})
|
|
2533
|
+
},
|
|
2534
|
+
attributeQuery: ['id', 'title', 'content', 'status', 'createdAt']
|
|
2535
|
+
})
|
|
2536
|
+
})
|
|
2537
|
+
|
|
2538
|
+
// Async dynamic filter
|
|
2539
|
+
const GetDepartmentDocuments = Interaction.create({
|
|
2540
|
+
name: 'getDepartmentDocuments',
|
|
2541
|
+
action: GetAction,
|
|
2542
|
+
data: Document,
|
|
2543
|
+
dataPolicy: DataPolicy.create({
|
|
2544
|
+
match: async function(this: Controller, event: any) {
|
|
2545
|
+
// Fetch user's department from database
|
|
2546
|
+
const user = await this.system.storage.findOne('User',
|
|
2547
|
+
MatchExp.atom({ key: 'id', value: ['=', event.user.id] }),
|
|
2548
|
+
undefined,
|
|
2549
|
+
['id', 'department']
|
|
2550
|
+
)
|
|
2551
|
+
return MatchExp.atom({
|
|
2552
|
+
key: 'department',
|
|
2553
|
+
value: ['=', user?.department || 'general']
|
|
2554
|
+
})
|
|
2555
|
+
}
|
|
2556
|
+
})
|
|
2557
|
+
})
|
|
2558
|
+
```
|
|
2559
|
+
|
|
2560
|
+
**How dataPolicy Works with Runtime Query**
|
|
2561
|
+
|
|
2562
|
+
When users call the interaction, they can provide additional filters via the `query` parameter:
|
|
2563
|
+
|
|
2564
|
+
```typescript
|
|
2565
|
+
// Developer defines dataPolicy
|
|
2566
|
+
const GetProducts = Interaction.create({
|
|
2567
|
+
name: 'getProducts',
|
|
2568
|
+
action: GetAction,
|
|
2569
|
+
data: Product,
|
|
2570
|
+
dataPolicy: DataPolicy.create({
|
|
2571
|
+
match: MatchExp.atom({ key: 'status', value: ['=', 'active'] }),
|
|
2572
|
+
modifier: { limit: 50 }, // Max 50 items
|
|
2573
|
+
attributeQuery: ['id', 'name', 'price', 'category']
|
|
2574
|
+
})
|
|
2575
|
+
})
|
|
2576
|
+
|
|
2577
|
+
// User calls with additional filters
|
|
2578
|
+
const result = await controller.callInteraction('getProducts', {
|
|
2579
|
+
user: { id: 'user123' },
|
|
2580
|
+
query: {
|
|
2581
|
+
match: MatchExp.atom({ key: 'category', value: ['=', 'electronics'] }),
|
|
2582
|
+
modifier: { limit: 10 }, // Will use 10 (smaller than policy's 50)
|
|
2583
|
+
attributeQuery: ['id', 'name', 'price'] // Subset of allowed fields
|
|
2584
|
+
}
|
|
2585
|
+
})
|
|
2586
|
+
// Result: products that are BOTH active (from dataPolicy) AND electronics (from runtime query)
|
|
2587
|
+
```
|
|
2588
|
+
|
|
2589
|
+
**Key Points**
|
|
2590
|
+
|
|
2591
|
+
1. **dataPolicy.match** is combined with runtime `query.match` using AND logic
|
|
2592
|
+
2. **dataPolicy.modifier** provides defaults that can be overridden by runtime query (but can enforce max limits)
|
|
2593
|
+
3. **dataPolicy.attributeQuery** restricts which fields can be retrieved (security layer)
|
|
2594
|
+
4. **Function-based match** allows dynamic constraints based on user context (roles, permissions, etc.)
|
|
2595
|
+
|
|
2468
2596
|
### Payload.create()
|
|
2469
2597
|
|
|
2470
2598
|
Create interaction parameter definition.
|