interaqt 1.1.0 → 1.1.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.
- package/README.md +329 -0
- package/agent/skill/interaqt-patterns.md +536 -0
- package/agent/skill/interaqt-recipes.md +501 -0
- package/agent/skill/interaqt-reference.md +409 -0
- package/dist/builtins/interaction/Interaction.d.ts +16 -10
- package/dist/builtins/interaction/Interaction.d.ts.map +1 -1
- package/dist/builtins/interaction/activity/ActivityManager.d.ts.map +1 -1
- package/dist/builtins/interaction/errors/ActivityErrors.d.ts +2 -2
- package/dist/builtins/interaction/errors/ActivityErrors.d.ts.map +1 -1
- package/dist/builtins/interaction/errors/InteractionErrors.d.ts +1 -1
- package/dist/builtins/interaction/errors/InteractionErrors.d.ts.map +1 -1
- package/dist/core/BoolExp.d.ts +2 -1
- package/dist/core/BoolExp.d.ts.map +1 -1
- package/dist/core/Computation.d.ts +3 -8
- package/dist/core/Computation.d.ts.map +1 -1
- package/dist/core/Custom.d.ts +2 -2
- package/dist/core/Custom.d.ts.map +1 -1
- package/dist/core/EventSource.d.ts +18 -16
- package/dist/core/EventSource.d.ts.map +1 -1
- package/dist/core/Property.d.ts +0 -3
- package/dist/core/Property.d.ts.map +1 -1
- package/dist/core/RealDictionary.d.ts +0 -3
- package/dist/core/RealDictionary.d.ts.map +1 -1
- package/dist/core/Relation.d.ts.map +1 -1
- package/dist/core/StateNode.d.ts +3 -3
- package/dist/core/StateNode.d.ts.map +1 -1
- package/dist/core/StateTransfer.d.ts +2 -6
- package/dist/core/StateTransfer.d.ts.map +1 -1
- package/dist/core/Transform.d.ts +2 -2
- package/dist/core/Transform.d.ts.map +1 -1
- package/dist/core/interfaces.d.ts +0 -9
- package/dist/core/interfaces.d.ts.map +1 -1
- package/dist/core/types.d.ts +0 -29
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/utils.d.ts +22 -6
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/drivers/Mysql.d.ts +5 -5
- package/dist/drivers/Mysql.d.ts.map +1 -1
- package/dist/drivers/PGLite.d.ts +5 -5
- package/dist/drivers/PGLite.d.ts.map +1 -1
- package/dist/drivers/PostgreSQL.d.ts +5 -5
- package/dist/drivers/PostgreSQL.d.ts.map +1 -1
- package/dist/drivers/SQLite.d.ts +5 -5
- package/dist/drivers/SQLite.d.ts.map +1 -1
- package/dist/index.js +194 -228
- package/dist/index.js.map +1 -1
- package/dist/runtime/Controller.d.ts +14 -14
- package/dist/runtime/Controller.d.ts.map +1 -1
- package/dist/runtime/MonoSystem.d.ts +4 -4
- package/dist/runtime/MonoSystem.d.ts.map +1 -1
- package/dist/runtime/Scheduler.d.ts +3 -3
- package/dist/runtime/Scheduler.d.ts.map +1 -1
- package/dist/runtime/System.d.ts +50 -51
- package/dist/runtime/System.d.ts.map +1 -1
- package/dist/runtime/computations/Any.d.ts +4 -4
- package/dist/runtime/computations/Any.d.ts.map +1 -1
- package/dist/runtime/computations/Average.d.ts +2 -2
- package/dist/runtime/computations/Average.d.ts.map +1 -1
- package/dist/runtime/computations/Computation.d.ts +41 -47
- package/dist/runtime/computations/Computation.d.ts.map +1 -1
- package/dist/runtime/computations/Count.d.ts +4 -4
- package/dist/runtime/computations/Count.d.ts.map +1 -1
- package/dist/runtime/computations/Every.d.ts +4 -4
- package/dist/runtime/computations/Every.d.ts.map +1 -1
- package/dist/runtime/computations/RealTime.d.ts +9 -17
- package/dist/runtime/computations/RealTime.d.ts.map +1 -1
- package/dist/runtime/computations/StateMachine.d.ts +2 -2
- package/dist/runtime/computations/Summation.d.ts +2 -2
- package/dist/runtime/computations/Summation.d.ts.map +1 -1
- package/dist/runtime/computations/TransitionFinder.d.ts +9 -4
- package/dist/runtime/computations/TransitionFinder.d.ts.map +1 -1
- package/dist/runtime/computations/WeightedSummation.d.ts +2 -2
- package/dist/runtime/computations/WeightedSummation.d.ts.map +1 -1
- package/dist/runtime/errors/ComputationErrors.d.ts +3 -3
- package/dist/runtime/errors/ComputationErrors.d.ts.map +1 -1
- package/dist/runtime/errors/ConditionErrors.d.ts +6 -6
- package/dist/runtime/errors/ConditionErrors.d.ts.map +1 -1
- package/dist/runtime/errors/FrameworkError.d.ts +4 -4
- package/dist/runtime/errors/FrameworkError.d.ts.map +1 -1
- package/dist/runtime/errors/SideEffectError.d.ts +1 -1
- package/dist/runtime/errors/SideEffectError.d.ts.map +1 -1
- package/dist/runtime/errors/SystemErrors.d.ts +1 -1
- package/dist/runtime/errors/SystemErrors.d.ts.map +1 -1
- package/dist/runtime/errors/index.d.ts +5 -5
- package/dist/runtime/errors/index.d.ts.map +1 -1
- package/dist/runtime/types/computation.d.ts +11 -0
- package/dist/runtime/types/computation.d.ts.map +1 -0
- package/dist/runtime/util.d.ts +6 -6
- package/dist/runtime/util.d.ts.map +1 -1
- package/dist/storage/erstorage/MatchExp.d.ts +4 -4
- package/dist/storage/erstorage/MatchExp.d.ts.map +1 -1
- package/dist/storage/erstorage/QueryExecutor.d.ts +1 -1
- package/dist/storage/erstorage/QueryExecutor.d.ts.map +1 -1
- package/dist/storage/erstorage/RecordQuery.d.ts +5 -5
- package/dist/storage/erstorage/RecordQuery.d.ts.map +1 -1
- package/dist/storage/erstorage/SQLBuilder.d.ts +11 -11
- package/dist/storage/erstorage/SQLBuilder.d.ts.map +1 -1
- package/dist/storage/erstorage/Setup.d.ts +1 -1
- package/dist/storage/erstorage/util/RecursiveContext.d.ts +4 -10
- package/dist/storage/erstorage/util/RecursiveContext.d.ts.map +1 -1
- package/dist/storage/erstorage/util.d.ts +3 -3
- package/dist/storage/erstorage/util.d.ts.map +1 -1
- package/dist/storage/utils.d.ts +2 -2
- package/dist/storage/utils.d.ts.map +1 -1
- package/package.json +4 -1
package/README.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">interaqt</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>A declarative reactive backend framework where you define <em>what data is</em>, not how to change it.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/interaqt"><img src="https://img.shields.io/npm/v/interaqt.svg" alt="npm version"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/interaqt"><img src="https://img.shields.io/npm/dm/interaqt.svg" alt="npm downloads"></a>
|
|
9
|
+
<a href="https://github.com/InteraqtDev/interaqt/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/interaqt.svg" alt="license"></a>
|
|
10
|
+
<a href="https://github.com/InteraqtDev/interaqt"><img src="https://img.shields.io/badge/TypeScript-strict-blue.svg" alt="TypeScript"></a>
|
|
11
|
+
<a href="#"><img src="https://img.shields.io/badge/coverage-92.41%25-brightgreen.svg" alt="coverage"></a>
|
|
12
|
+
</p>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The Problem
|
|
18
|
+
|
|
19
|
+
Traditional backend development forces you to think in terms of **operations** — "when X happens, update Y, then Z, then W." This imperative approach scatters related logic across handlers, creates consistency bugs, and makes systems increasingly brittle as complexity grows.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// The imperative way: fragile chains of manual updates
|
|
23
|
+
async function likePost(userId, postId) {
|
|
24
|
+
await createLike(userId, postId);
|
|
25
|
+
const count = await countLikes(postId);
|
|
26
|
+
await updatePost(postId, { likeCount: count }); // easy to forget
|
|
27
|
+
await notifyAuthor(postId); // easy to break
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The Solution
|
|
32
|
+
|
|
33
|
+
interaqt flips the model. Instead of describing *procedures*, you **declare what your data is** — and the framework keeps everything consistent, automatically.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// The interaqt way: declare what data IS
|
|
37
|
+
const Post = Entity.create({
|
|
38
|
+
name: 'Post',
|
|
39
|
+
properties: [
|
|
40
|
+
Property.create({ name: 'title', type: 'string' }),
|
|
41
|
+
Property.create({
|
|
42
|
+
name: 'likeCount',
|
|
43
|
+
// "like count IS the number of like relationships"
|
|
44
|
+
computation: Count.create({ record: LikeRelation })
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
No update handlers. No sync bugs. When a like relationship is created, `likeCount` updates itself — because it's *defined* as the count of likes.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Core Ideas
|
|
55
|
+
|
|
56
|
+
**1. Only Interactions create data** — User interactions are the single source of truth. Everything else is derived.
|
|
57
|
+
|
|
58
|
+
**2. Data is a function of events** — Properties, counts, states, and aggregates are declared as computations over events and relations, not manually maintained.
|
|
59
|
+
|
|
60
|
+
**3. Unidirectional flow** — `Interaction → Event → Computation → Data`. No reverse wiring. No tangled update cycles.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quick Example: Social Post System
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import {
|
|
68
|
+
Entity, Property, Relation, Interaction, Action,
|
|
69
|
+
Payload, PayloadItem, Controller, MonoSystem,
|
|
70
|
+
Count, Transform, InteractionEventEntity
|
|
71
|
+
} from 'interaqt'
|
|
72
|
+
|
|
73
|
+
// --- Define your data model ---
|
|
74
|
+
|
|
75
|
+
const User = Entity.create({
|
|
76
|
+
name: 'User',
|
|
77
|
+
properties: [
|
|
78
|
+
Property.create({ name: 'name', type: 'string' }),
|
|
79
|
+
Property.create({
|
|
80
|
+
name: 'postCount',
|
|
81
|
+
computation: Count.create({ record: AuthorRelation })
|
|
82
|
+
})
|
|
83
|
+
]
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const Post = Entity.create({
|
|
87
|
+
name: 'Post',
|
|
88
|
+
properties: [
|
|
89
|
+
Property.create({ name: 'title', type: 'string' }),
|
|
90
|
+
Property.create({ name: 'content', type: 'string' }),
|
|
91
|
+
Property.create({
|
|
92
|
+
name: 'likeCount',
|
|
93
|
+
computation: Count.create({ record: LikeRelation })
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// --- Define relationships ---
|
|
99
|
+
|
|
100
|
+
const AuthorRelation = Relation.create({
|
|
101
|
+
source: User,
|
|
102
|
+
sourceProperty: 'posts',
|
|
103
|
+
target: Post,
|
|
104
|
+
targetProperty: 'author',
|
|
105
|
+
type: 'n:1'
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const LikeRelation = Relation.create({
|
|
109
|
+
source: User,
|
|
110
|
+
sourceProperty: 'likedPosts',
|
|
111
|
+
target: Post,
|
|
112
|
+
targetProperty: 'likedBy',
|
|
113
|
+
type: 'n:n'
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// --- Define interactions ---
|
|
117
|
+
|
|
118
|
+
const CreatePost = Interaction.create({
|
|
119
|
+
name: 'CreatePost',
|
|
120
|
+
action: Action.create({ name: 'createPost' }),
|
|
121
|
+
payload: Payload.create({
|
|
122
|
+
items: [
|
|
123
|
+
PayloadItem.create({ name: 'title', type: 'string', required: true }),
|
|
124
|
+
PayloadItem.create({ name: 'content', type: 'string', required: true })
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const LikePost = Interaction.create({
|
|
130
|
+
name: 'LikePost',
|
|
131
|
+
action: Action.create({ name: 'likePost' }),
|
|
132
|
+
payload: Payload.create({
|
|
133
|
+
items: [
|
|
134
|
+
PayloadItem.create({ name: 'postId', base: Post, isRef: true })
|
|
135
|
+
]
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// --- Boot the system ---
|
|
140
|
+
|
|
141
|
+
const system = new MonoSystem(new PGLiteDB())
|
|
142
|
+
const controller = new Controller({
|
|
143
|
+
system,
|
|
144
|
+
entities: [User, Post],
|
|
145
|
+
relations: [AuthorRelation, LikeRelation],
|
|
146
|
+
eventSources: [CreatePost, LikePost]
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
await controller.setup(true)
|
|
150
|
+
|
|
151
|
+
// --- Use it ---
|
|
152
|
+
|
|
153
|
+
await controller.dispatch(CreatePost, {
|
|
154
|
+
user: { id: 'user-1' },
|
|
155
|
+
payload: { title: 'Hello World', content: 'My first post' }
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await controller.dispatch(LikePost, {
|
|
159
|
+
user: { id: 'user-2' },
|
|
160
|
+
payload: { postId: 'post-1' }
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// post.likeCount is now 1 — automatically.
|
|
164
|
+
// user.postCount is now 1 — automatically.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Reactive Computations
|
|
170
|
+
|
|
171
|
+
The real power of interaqt lives in its computation primitives. Attach them to any Property, Entity, or Relation — they react to data changes automatically.
|
|
172
|
+
|
|
173
|
+
| Computation | What it declares |
|
|
174
|
+
|---|---|
|
|
175
|
+
| **Count** | "This value IS the number of related records" |
|
|
176
|
+
| **Summation** | "This value IS the sum over a field in related records" |
|
|
177
|
+
| **WeightedSummation** | "This value IS a weighted sum (e.g., inventory = stock − sold)" |
|
|
178
|
+
| **Average** | "This value IS the average of a field across relations" |
|
|
179
|
+
| **Every** | "This boolean IS true when ALL related records satisfy a condition" |
|
|
180
|
+
| **Any** | "This boolean IS true when ANY related record satisfies a condition" |
|
|
181
|
+
| **Transform** | "This entity/relation IS created when a matching event occurs" |
|
|
182
|
+
| **StateMachine** | "This value IS the current state, transitioning on specific events" |
|
|
183
|
+
|
|
184
|
+
### Example: E-commerce Inventory
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const Product = Entity.create({
|
|
188
|
+
name: 'Product',
|
|
189
|
+
properties: [
|
|
190
|
+
Property.create({ name: 'initialStock', type: 'number' }),
|
|
191
|
+
Property.create({
|
|
192
|
+
name: 'currentStock',
|
|
193
|
+
// "current stock IS initial stock minus total quantities ordered"
|
|
194
|
+
computation: WeightedSummation.create({
|
|
195
|
+
record: OrderItemRelation,
|
|
196
|
+
callback: (item) => ({ weight: -1, value: item.quantity })
|
|
197
|
+
})
|
|
198
|
+
}),
|
|
199
|
+
Property.create({
|
|
200
|
+
name: 'totalSales',
|
|
201
|
+
computation: WeightedSummation.create({
|
|
202
|
+
record: OrderItemRelation,
|
|
203
|
+
callback: (item) => ({ weight: 1, value: item.quantity })
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Architecture
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
src/
|
|
216
|
+
├── core/ Data model: Entity, Relation, Property, Computation definitions
|
|
217
|
+
├── runtime/ Execution: Controller, System, Scheduler, computation handles
|
|
218
|
+
├── storage/ Persistence: ERStorage, SQL builder, query executors
|
|
219
|
+
├── builtins/ Built-in EventSource types: Interaction, Activity, User
|
|
220
|
+
└── drivers/ Database adapters
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Dependency direction:** `builtins → runtime → storage → core`. Clean layers, no circular imports.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Database Support
|
|
228
|
+
|
|
229
|
+
interaqt works with the database you already use:
|
|
230
|
+
|
|
231
|
+
| Driver | Package | Use Case |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| **PostgreSQL** | `pg` | Production |
|
|
234
|
+
| **SQLite** | `better-sqlite3` | Embedded / edge |
|
|
235
|
+
| **MySQL** | `mysql2` | Production |
|
|
236
|
+
| **PGLite** | `@electric-sql/pglite` | Testing (in-memory) |
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { MonoSystem } from 'interaqt'
|
|
240
|
+
|
|
241
|
+
// Pick your driver
|
|
242
|
+
import { PostgreSQLDB } from 'interaqt/drivers'
|
|
243
|
+
const system = new MonoSystem(new PostgreSQLDB({ /* connection config */ }))
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Installation
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npm install interaqt
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Then install the database driver you need:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# PostgreSQL
|
|
258
|
+
npm install pg
|
|
259
|
+
|
|
260
|
+
# SQLite
|
|
261
|
+
npm install better-sqlite3
|
|
262
|
+
|
|
263
|
+
# MySQL
|
|
264
|
+
npm install mysql2
|
|
265
|
+
|
|
266
|
+
# In-memory (for testing)
|
|
267
|
+
npm install @electric-sql/pglite
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Key Concepts at a Glance
|
|
273
|
+
|
|
274
|
+
| Concept | Role |
|
|
275
|
+
|---|---|
|
|
276
|
+
| **Entity** | A data type (User, Post, Order, ...) |
|
|
277
|
+
| **Property** | A field on an entity — can be a static value or a reactive computation |
|
|
278
|
+
| **Relation** | A typed connection between entities (1:1, 1:n, n:n) |
|
|
279
|
+
| **Interaction** | An event triggered by a user — the *only* way new data enters the system |
|
|
280
|
+
| **Action** | An identifier for an interaction type (not a handler — no logic!) |
|
|
281
|
+
| **Computation** | A reactive declaration: Count, Transform, StateMachine, etc. |
|
|
282
|
+
| **Activity** | An ordered sequence of related Interactions for complex workflows |
|
|
283
|
+
| **Controller** | The single dispatch entry point: `controller.dispatch(interaction, args)` |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Advanced Features
|
|
288
|
+
|
|
289
|
+
- **StateMachine** — Model entity lifecycles with explicit state transitions triggered by events
|
|
290
|
+
- **Filtered Entities** — Virtual views over entities, like reactive database views
|
|
291
|
+
- **Activities** — Compose multi-step business workflows from ordered Interactions
|
|
292
|
+
- **Attributive Permissions** — Declarative, entity-aware access control
|
|
293
|
+
- **Dictionary** — Global reactive key-value state
|
|
294
|
+
- **Hard Deletion** — Built-in support for both soft and hard delete patterns
|
|
295
|
+
- **Side Effects** — Hook into record mutations for external integrations (email, payments, file uploads)
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Development
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
git clone https://github.com/InteraqtDev/interaqt.git
|
|
303
|
+
cd interaqt
|
|
304
|
+
|
|
305
|
+
npm install
|
|
306
|
+
npm test # Run all tests
|
|
307
|
+
npm run test:runtime # Runtime tests only
|
|
308
|
+
npm run test:storage # Storage tests only
|
|
309
|
+
npm run test:core # Core tests only
|
|
310
|
+
npm run build # Build to dist/
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Philosophy
|
|
316
|
+
|
|
317
|
+
> **Stop thinking "how to do." Start thinking "what it is."**
|
|
318
|
+
|
|
319
|
+
In interaqt, you never write update logic. You declare:
|
|
320
|
+
- *what* each piece of data is (a count, a sum, a state, a transformation)
|
|
321
|
+
- *when* entities and relations come into existence (through Interactions)
|
|
322
|
+
|
|
323
|
+
The framework handles propagation, consistency, and persistence. Your business logic becomes a clear, auditable set of declarations rather than a tangled web of imperative handlers.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## License
|
|
328
|
+
|
|
329
|
+
[MIT](./LICENSE)
|