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.query` (Query, optional): Query definition for data fetching
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 query configuration
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
- query: Query.create({
2289
- items: [
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 complex conditions
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
- conditions: Condition.create({
2304
- name: 'canViewPosts',
2305
- content: async function(this: Controller, event) {
2306
- // Check if user can view posts based on query
2307
- const targetUserId = event.query?.match?.key === 'author.id'
2308
- ? event.query.match.value[1]
2309
- : null;
2310
-
2311
- // Allow users to see their own posts or public posts
2312
- return targetUserId === event.user.id || event.user.role === 'admin';
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
- query: Query.create({
2323
- items: [
2324
- QueryItem.create({
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 `query` in Interaction definition for fixed query configuration
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.
package/package.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "sync:agent": "tsx scripts/sync-agent-files.ts",
40
40
  "release": "release-it"
41
41
  },
42
- "version": "0.8.1",
42
+ "version": "0.8.2",
43
43
  "main": "dist/index.js",
44
44
  "files": [
45
45
  "dist",