@t4h.framework/orm 0.11.3 → 0.11.5
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/.ai/skills/framework-orm/SKILL.md +87 -316
- package/CHANGELOG.md +27 -0
- package/README.md +21 -266
- package/dist/activities/ORMFindOneActivity.d.ts +2 -0
- package/dist/activities/ORMFindOneActivity.d.ts.map +1 -1
- package/dist/activities/ORMLoadActivity.d.ts.map +1 -1
- package/dist/activities/ORMLoadActivity.js +4 -3
- package/dist/activities/ORMLoadActivity.js.map +1 -1
- package/dist/claims/ORMClaim.js.map +1 -1
- package/dist/constraints/assert-constraints.d.ts +1 -1
- package/dist/constraints/assert-constraints.d.ts.map +1 -1
- package/dist/constraints/assert-constraints.js +1 -1
- package/dist/constraints/assert-constraints.js.map +1 -1
- package/dist/constraints/assert-property-type-constraint.js.map +1 -1
- package/dist/decorators/Entity.d.ts.map +1 -1
- package/dist/decorators/Entity.js +1 -1
- package/dist/decorators/Entity.js.map +1 -1
- package/dist/decorators/Index.d.ts.map +1 -1
- package/dist/decorators/Index.js.map +1 -1
- package/dist/decorators/ManyToOne.js.map +1 -1
- package/dist/decorators/OneToMany.d.ts +1 -1
- package/dist/decorators/OneToMany.d.ts.map +1 -1
- package/dist/decorators/OneToMany.js.map +1 -1
- package/dist/decorators/Property.d.ts.map +1 -1
- package/dist/decorators/Property.js.map +1 -1
- package/dist/decorators/metadata.d.ts +11 -9
- package/dist/decorators/metadata.d.ts.map +1 -1
- package/dist/decorators/metadata.js.map +1 -1
- package/dist/helpers/Insert.d.ts +4 -3
- package/dist/helpers/Insert.d.ts.map +1 -1
- package/dist/helpers/Insert.js +22 -15
- package/dist/helpers/Insert.js.map +1 -1
- package/dist/helpers/Populate.d.ts.map +1 -1
- package/dist/helpers/Populate.js +10 -5
- package/dist/helpers/Populate.js.map +1 -1
- package/dist/helpers/Serialize.d.ts +4 -3
- package/dist/helpers/Serialize.d.ts.map +1 -1
- package/dist/helpers/Serialize.js +20 -18
- package/dist/helpers/Serialize.js.map +1 -1
- package/dist/helpers/isPlainObject.js.map +1 -1
- package/dist/helpers/refs.d.ts +1 -1
- package/dist/helpers/refs.d.ts.map +1 -1
- package/dist/helpers/refs.js.map +1 -1
- package/dist/models/MapCollection.d.ts +1 -0
- package/dist/models/MapCollection.d.ts.map +1 -1
- package/dist/models/MapCollection.js +12 -5
- package/dist/models/MapCollection.js.map +1 -1
- package/dist/models/ORM.d.ts.map +1 -1
- package/dist/models/ORM.js +32 -16
- package/dist/models/ORM.js.map +1 -1
- package/dist/models/Ref.d.ts.map +1 -1
- package/dist/models/Ref.js +3 -2
- package/dist/models/Ref.js.map +1 -1
- package/dist/models/RefCollection.d.ts.map +1 -1
- package/dist/models/RefCollection.js +6 -4
- package/dist/models/RefCollection.js.map +1 -1
- package/dist/typings/FindWhere.d.ts +1 -1
- package/dist/typings/FindWhere.d.ts.map +1 -1
- package/dist/typings/ORM.d.ts +5 -5
- package/dist/typings/ORM.d.ts.map +1 -1
- package/dist/typings/Relations.d.ts +2 -2
- package/dist/typings/Relations.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -1,362 +1,133 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framework-orm
|
|
3
3
|
description: >-
|
|
4
|
-
Guides
|
|
5
|
-
decorators (@Entity, @Property, @Index
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Guides the use of @t4h.framework/orm in T4H Framework workflows: entity
|
|
5
|
+
decorators (@Entity, @Property, @Index, @ManyToOne, @OneToMany), the ORM
|
|
6
|
+
class (create, upsert, find, findOne, exists, delete), Ref, RefCollection and
|
|
7
|
+
MapCollection, getSnapshot with populate, where operators ($in, $like, $or,
|
|
8
|
+
...), ConstraintError, and ORMClaim. Use when you define database entities,
|
|
9
|
+
read or write rows in a workflow, model relations between tables, give or
|
|
10
|
+
mock ORMClaim in a test with TestWorkflowEnvironment, or write an ORMClaim
|
|
11
|
+
implementation.
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
# @t4h.framework/orm
|
|
13
15
|
|
|
14
|
-
ORM
|
|
16
|
+
Each `ORM`, `Ref` and `RefCollection` call is an activity recorded in the
|
|
17
|
+
workflow history. The runtime gives the database through `ORMClaim`.
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
## Define an entity
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ORMDeleteActivity,
|
|
25
|
-
getEntityMetadata, getEntityTableName, getEntityProperties,
|
|
26
|
-
type EntityInsertData, type EntityPartialData, type FindWhere,
|
|
27
|
-
type SerializableCreateInput, type SerializableFindInput,
|
|
28
|
-
type SerializableGetSnapshotInput, type SerializableUpdateInput,
|
|
29
|
-
type SerializableRemoveInput, type SerializableDeleteInput,
|
|
30
|
-
} from '@t4h.framework/orm'
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Node `>=22`. See [README.md](../../../README.md).
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Architecture
|
|
38
|
-
|
|
39
|
-
`Workflow` → `ORM` / `Ref` methods (`History.bind` on ORM activities) → `ORMClaim` (runtime database adapter).
|
|
40
|
-
|
|
41
|
-
| Layer | Role |
|
|
42
|
-
|-------|------|
|
|
43
|
-
| **ORM** | High-level API; applies defaults + constraints before activities |
|
|
44
|
-
| **Ref** / **RefCollection** | Row handles returned by create/find |
|
|
45
|
-
| **Activities** | Shipped, replay-safe activities the `ORM`/`Ref` APIs drive; delegate to `ORMClaim` |
|
|
46
|
-
| **ORMClaim** | Abstract claim; runtime provides concrete DB implementation |
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Defining entities
|
|
51
|
-
|
|
52
|
-
Enable decorator metadata in the project `tsconfig.json`:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"compilerOptions": {
|
|
57
|
-
"experimentalDecorators": true,
|
|
58
|
-
"emitDecoratorMetadata": true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Import `reflect-metadata` where decorators are evaluated (Property decorator imports it).
|
|
64
|
-
|
|
65
|
-
### @Entity
|
|
21
|
+
1. Enable `experimentalDecorators` and `emitDecoratorMetadata` in
|
|
22
|
+
`tsconfig.json`.
|
|
23
|
+
2. Decorate the class with `@Entity({ tableName })` (required option) and each
|
|
24
|
+
column with `@Property`.
|
|
25
|
+
3. Declare `id` and each property with a `default` as optional. Also set
|
|
26
|
+
`nullable: true` on `id`: the store sets it.
|
|
66
27
|
|
|
67
28
|
```typescript
|
|
68
29
|
@Entity({ tableName: 'users' })
|
|
69
30
|
class User {
|
|
70
|
-
@Property({ type: '
|
|
71
|
-
public
|
|
31
|
+
@Property({ type: 'uuid', nullable: true })
|
|
32
|
+
public id?: string
|
|
72
33
|
|
|
73
|
-
@Property({ type: 'string' })
|
|
34
|
+
@Property({ type: 'string', index: true })
|
|
74
35
|
public email!: string
|
|
75
36
|
|
|
76
37
|
@Property({ type: 'boolean', default: true })
|
|
77
|
-
public active
|
|
38
|
+
public active?: boolean
|
|
78
39
|
}
|
|
79
40
|
```
|
|
80
41
|
|
|
81
|
-
|
|
82
|
-
- Use `getEntityTableName(User)` or rely on ORM to resolve the table name.
|
|
83
|
-
|
|
84
|
-
### @Property
|
|
85
|
-
|
|
86
|
-
Options: `type?`, `nullable?` (default required), `default?`, `index?` (adds index metadata).
|
|
42
|
+
## Create the ORM once
|
|
87
43
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Class-level composite indexes:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
@Index({ properties: ['tenantId', 'email'], name: 'accounts_tenant_email_idx' })
|
|
96
|
-
class Account { /* ... */ }
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`properties` must be non-empty or the decorator throws. Per-property `@Property({ index: true })` also registers `{ properties: [propertyKey] }`.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## ORM configuration
|
|
104
|
-
|
|
105
|
-
`ORM` extends `SerializableClass` — instances can be serialized for workflow persistence. `connectionString` is the `Secret` an `EnvDesign` entry declared `type: Secret` hands the workflow (a string is a `tsc` error); the host reads the value where each query runs.
|
|
44
|
+
Declare `DATABASE_URL: { type: Secret }` with `EnvDesign` in
|
|
45
|
+
`src/constants/environments.ts`, and create the `ORM` at module level, not in
|
|
46
|
+
the workflow function:
|
|
106
47
|
|
|
107
48
|
```typescript
|
|
108
49
|
import { environments } from '../constants/environments.js'
|
|
109
50
|
|
|
110
|
-
const orm = new ORM({
|
|
111
|
-
connectionString: environments.DATABASE_URL,
|
|
112
|
-
entities: [User,
|
|
113
|
-
})
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
- `entities`: array of entity **constructors** (decorated classes).
|
|
117
|
-
- `config.entities` stores resolved `EntityMetadata` (table name, properties map, indexes).
|
|
118
|
-
|
|
119
|
-
Restore from serialized state:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
const orm = await ORM.fromSerializable(serialized, v => v)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## CRUD in workflows
|
|
128
|
-
|
|
129
|
-
ORM methods bind activities via `History.bind` — calls inside a workflow are **reconciler-driven** (replay-safe).
|
|
130
|
-
|
|
131
|
-
### Create
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
// Single row → Ref<T>
|
|
135
|
-
const userRef = await orm.create(User, {
|
|
136
|
-
name: 'John',
|
|
137
|
-
email: 'john@example.com',
|
|
138
|
-
active: true,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
// Batch → RefCollection<T>
|
|
142
|
-
const usersRef = await orm.create(User, [
|
|
143
|
-
{ name: 'Alice', email: 'alice@example.com', active: true },
|
|
144
|
-
{ name: 'Bob', email: 'bob@example.com', active: true },
|
|
145
|
-
])
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Defaults are applied before persistence (`applyDefaultProperties`). Omitted fields with `@Property({ default })` are filled in.
|
|
149
|
-
|
|
150
|
-
### Upsert
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
// Single row → Ref<T>; array → RefCollection<T>
|
|
154
|
-
const userRef = await orm.upsert(
|
|
155
|
-
User,
|
|
156
|
-
{ email: 'john@example.com', name: 'John', active: true },
|
|
157
|
-
{ conflictOn: ['email'] },
|
|
158
|
-
)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Rows are validated like `create`. If a row matching `conflictOn` (equality on every listed property) exists, only the passed fields are written; defaults apply on insert only. Every row must have a value for each `conflictOn` property (`ConstraintError` otherwise), and `conflictOn` cannot be empty. The call is a single `ORMUpsertActivity` backed by `ORMClaim.upsert`, which has a default (`find`, then `create` or `update`) — mock `upsert` in tests, or `find`/`create`/`update` if the mock extends `ORMClaim`. A retry after a crash updates instead of duplicating, but it is not atomic across workflows: concurrent writes of the same key may both insert.
|
|
162
|
-
|
|
163
|
-
### Find
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const userRef = await orm.findOne(User, {
|
|
167
|
-
where: { email: 'john@example.com' },
|
|
168
|
-
orderBy: { name: 'asc' },
|
|
169
|
-
}) // Ref<User> | null
|
|
170
|
-
|
|
171
|
-
const usersRef = await orm.find(User, {
|
|
172
|
-
where: { active: true },
|
|
173
|
-
orderBy: { name: 'asc' },
|
|
174
|
-
limit: 10,
|
|
175
|
-
offset: 0,
|
|
176
|
-
}) // RefCollection<User>
|
|
177
|
-
|
|
178
|
-
const exists = await orm.exists(User, {
|
|
179
|
-
where: { email: 'john@example.com' },
|
|
180
|
-
}) // boolean (delegates to findOne)
|
|
181
|
-
|
|
182
|
-
const deleted = await orm.delete(User, {
|
|
183
|
-
where: { active: false },
|
|
184
|
-
}) // number — deletes every match; `{}` empties the table
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
`findOne` activity internally calls `claim.find` with `limit: 1`.
|
|
188
|
-
|
|
189
|
-
### Ref operations
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
const snapshot = await userRef.getSnapshot() // T | null — entity instance
|
|
193
|
-
await userRef.update({ name: 'Jane' }) // partial patch only
|
|
194
|
-
await userRef.remove()
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
`getSnapshot` returns `null` when the row no longer exists. `update` validates only keys present in the patch.
|
|
198
|
-
|
|
199
|
-
**RefCollection:** `toArray()`, `length`, `at(i)`, async `forEach`/`map`/`filter`, `Symbol.iterator`.
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## Query operators
|
|
204
|
-
|
|
205
|
-
`where` uses `FindWhere<T>` — implicit equality or operator objects:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
await orm.find(User, {
|
|
209
|
-
where: {
|
|
210
|
-
name: 'John', // $eq (implicit)
|
|
211
|
-
active: { $ne: false },
|
|
212
|
-
email: { $like: '%@example%' },
|
|
213
|
-
age: { $gte: 18, $lt: 65 },
|
|
214
|
-
status: { $in: ['active', 'pending'] },
|
|
215
|
-
$or: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
216
|
-
$and: [{ active: true }, { age: { $gte: 18 } }],
|
|
217
|
-
},
|
|
51
|
+
export const orm = new ORM({
|
|
52
|
+
connectionString: environments.DATABASE_URL,
|
|
53
|
+
entities: [User, Post],
|
|
218
54
|
})
|
|
219
55
|
```
|
|
220
56
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## Constraint validation
|
|
226
|
-
|
|
227
|
-
Validation runs in the ORM library (`ORM.create`, `ORM.upsert` and `Ref.update`), **not** in `ORMClaim` implementations. Failures throw `ConstraintError`.
|
|
228
|
-
|
|
229
|
-
### Create pipeline
|
|
230
|
-
|
|
231
|
-
1. `applyDefaultProperties` — fills `default` for `undefined` fields
|
|
232
|
-
2. `assertPropertyTypeConstraint` — validates declared types
|
|
233
|
-
3. `assertNonNullablePropertyConstraint` — required fields must be present
|
|
234
|
-
|
|
235
|
-
### Update pipeline
|
|
236
|
-
|
|
237
|
-
1. Defaults only for keys present in the patch
|
|
238
|
-
2. Same asserts, scoped to patch keys via `{ keys: Object.keys(data) }`
|
|
239
|
-
|
|
240
|
-
### Handling errors
|
|
57
|
+
## Read and write
|
|
241
58
|
|
|
242
59
|
```typescript
|
|
243
|
-
|
|
60
|
+
const userRef = await orm.create(User, { email: input.email }) // Ref<User>
|
|
61
|
+
const found = await orm.findOne(User, { where: { email: input.email } }) // Ref<User> | null
|
|
62
|
+
const active = await orm.find(User, { where: { active: true }, limit: 10 }) // RefCollection<User>
|
|
244
63
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (error instanceof ConstraintError) {
|
|
249
|
-
// e.g. 'Property "name" is required'
|
|
250
|
-
// e.g. 'Property "name" must be of type string'
|
|
251
|
-
}
|
|
252
|
-
}
|
|
64
|
+
const user = await userRef.getSnapshot() // User | null
|
|
65
|
+
await userRef.update({ active: false }) // partial patch
|
|
66
|
+
const snapshots = await active.getSnapshots() // one activity for all refs
|
|
253
67
|
```
|
|
254
68
|
|
|
255
|
-
|
|
69
|
+
## Relations
|
|
256
70
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
Runtime must register a concrete implementation:
|
|
71
|
+
The `@ManyToOne` side owns the column and is a `Ref<T>`. The `@OneToMany` side
|
|
72
|
+
is a lazy `MapCollection<T>`. Read related rows in the same activity with
|
|
73
|
+
`populate`:
|
|
262
74
|
|
|
263
75
|
```typescript
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
Prefer **`ORM` / `Ref` APIs** in workflow code — they apply constraints and return typed handles. Use activities directly only for advanced/testing scenarios.
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
## Workflow example
|
|
306
|
-
|
|
307
|
-
Define entities, instantiate `ORM` with `connectionString` + `entities`, and use it inside a `Workflow`:
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
export const orm = new ORM({ connectionString: environments.DATABASE_URL, entities: [User] })
|
|
311
|
-
|
|
312
|
-
export const ormWorkflow = new Workflow({ id: 'orm' }, async () => {
|
|
313
|
-
await orm.create(User, { name: 'John', email: 'john@example.com', age: 20 })
|
|
314
|
-
const user = await orm.findOne(User, { where: { email: 'john@example.com' } })
|
|
315
|
-
await user?.update({ name: 'John Updated' })
|
|
316
|
-
await user?.remove()
|
|
317
|
-
return { user }
|
|
318
|
-
})
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Ensure the runtime provides `ORMClaim`.
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
## Testing
|
|
326
|
-
|
|
327
|
-
Test the **workflow** with `TestWorkflowEnvironment` from
|
|
328
|
-
`@t4h.framework/core/testing`, mocking `ORMClaim` — the single claim the ORM
|
|
329
|
-
activities resolve. Provide the methods your workflow triggers (`create`, `upsert`, `find`,
|
|
330
|
-
`findOne`, `getSnapshot`, `update`, `remove`). See the **framework-workflow-testing**
|
|
331
|
-
skill for the harness API.
|
|
76
|
+
@OneToMany(() => Post, post => post.author)
|
|
77
|
+
public posts!: MapCollection<Post>
|
|
78
|
+
|
|
79
|
+
@ManyToOne(() => User)
|
|
80
|
+
public author!: Ref<User>
|
|
81
|
+
|
|
82
|
+
const user = await userRef.getSnapshot({ populate: ['posts.comments'] })
|
|
83
|
+
const titles = await user?.posts.map(post => post.title)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Traps
|
|
87
|
+
|
|
88
|
+
- `getSnapshot()` returns `null` when the row no longer exists. A populated
|
|
89
|
+
`@ManyToOne` can also be `null`, also when the relation is not nullable.
|
|
90
|
+
- A snapshot is a copy. Write through the `Ref` (`ref.update`), and use
|
|
91
|
+
`ref.load('author')` to get the `Ref` of a related row.
|
|
92
|
+
- `MapCollection` is not a promise. Call a method (`toArray()`, `map()`,
|
|
93
|
+
`count()`); do not `await user.posts`.
|
|
94
|
+
- `refs.map(ref => ref.getSnapshot())` runs one activity per ref. Use
|
|
95
|
+
`refs.getSnapshots()` or `populate`.
|
|
96
|
+
- A populate is the output of one activity and goes into the history. Keep it
|
|
97
|
+
narrow on large relations; filter with `where` and `limit`.
|
|
98
|
+
- `orm.delete(User, { where: {} })` deletes every row of the table.
|
|
99
|
+
- `upsert` needs a non-empty `conflictOn` and a value for each listed property.
|
|
100
|
+
It is not atomic across workflows: two workflows can both insert.
|
|
101
|
+
- A bad row throws `ConstraintError` before an activity runs.
|
|
102
|
+
- Do not construct or extend the `ORM*Activity` classes. The lint rule
|
|
103
|
+
`@t4h.framework/core/no-framework-activity-in-app` rejects it. Use `ORM` and
|
|
104
|
+
`Ref`.
|
|
105
|
+
- To write a relation, give a `Ref` (or the data of a new row in `create`).
|
|
106
|
+
You cannot write the `@OneToMany` side of an existing row.
|
|
107
|
+
|
|
108
|
+
## Test
|
|
109
|
+
|
|
110
|
+
Give `ORMClaim` with `mockClaim` and mock only what the workflow calls.
|
|
111
|
+
`ORMClaim` has no `findOne` (it calls `find` with `limit: 1`), and
|
|
112
|
+
`getSnapshot` on a `Ref` calls `getSnapshots`:
|
|
332
113
|
|
|
333
114
|
```typescript
|
|
334
115
|
import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
|
|
335
116
|
import { ORMClaim } from '@t4h.framework/orm'
|
|
336
117
|
|
|
337
|
-
const
|
|
338
|
-
const
|
|
118
|
+
const create = vi.fn(async () => [{ id: 1 }])
|
|
119
|
+
const getSnapshots = vi.fn(async () => [{ id: 1, email: 'john@example.com' }])
|
|
339
120
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
await env.execute(workflow, input, {
|
|
343
|
-
claims: [mockClaim(ORMClaim, { create: mockCreate, findOne: mockFindOne })],
|
|
121
|
+
await TestWorkflowEnvironment.create().execute(onboard, input, {
|
|
122
|
+
claims: [mockClaim(ORMClaim, { create, getSnapshots })],
|
|
344
123
|
})
|
|
345
124
|
```
|
|
346
125
|
|
|
347
|
-
|
|
348
|
-
activity runs, so you can assert an invalid input rejects without the claim being
|
|
349
|
-
called: `expect(mockCreate).not.toHaveBeenCalled()`.
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
## Checklist for agents
|
|
354
|
-
|
|
355
|
-
1. **`@Entity` + `@Property`** with `experimentalDecorators` + `emitDecoratorMetadata`.
|
|
356
|
-
2. Register entities in **`ORM({ entities: [...] })`**; runtime provides **`ORMClaim`**.
|
|
357
|
-
3. Use **`orm` / `Ref` APIs** in workflows — not raw SQL or direct claim calls.
|
|
358
|
-
4. Handle **`ConstraintError`**; validation runs before activities.
|
|
359
|
-
5. **`Ref.update`** is partial; **`getSnapshot`** returns entity instances.
|
|
360
|
-
6. Test workflows with **`TestWorkflowEnvironment`** + **`mockClaim(ORMClaim, ...)`**; only use exported APIs.
|
|
126
|
+
## Reference
|
|
361
127
|
|
|
362
|
-
|
|
128
|
+
- Overview, runtime and tests: https://tech4humans-brasil.github.io/framework/orm/
|
|
129
|
+
- Entities and validation: https://tech4humans-brasil.github.io/framework/orm/entities
|
|
130
|
+
- Queries, upsert, delete, operators: https://tech4humans-brasil.github.io/framework/orm/queries
|
|
131
|
+
- `Ref` and `RefCollection`: https://tech4humans-brasil.github.io/framework/orm/records
|
|
132
|
+
- Relations, populate, `MapCollection`: https://tech4humans-brasil.github.io/framework/orm/relations
|
|
133
|
+
- Types: `node_modules/@t4h.framework/orm/dist/orm.d.ts`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @t4h.framework/orm
|
|
2
2
|
|
|
3
|
+
## 0.11.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#257](https://github.com/tech4humans-brasil/framework/pull/257) [`586bfb4`](https://github.com/tech4humans-brasil/framework/commit/586bfb4663f4a478527ee0c8db360603f034d615) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The orm README is short and points to the docs site. The docs and the skill now match the source: the entity examples declare `id` as optional, and a test mocks `find` and `getSnapshots` of `ORMClaim`, not `findOne`.
|
|
8
|
+
|
|
9
|
+
## 0.11.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#241](https://github.com/tech4humans-brasil/framework/pull/241) [`f6b8454`](https://github.com/tech4humans-brasil/framework/commit/f6b845484d22e7c8e7240fe2e113f1cdea7c8ce3) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds seven rules to `@t4h.framework/core/eslint` and turns them on in `configs.recommended`:
|
|
14
|
+
- `no-io-in-workflow` reports `fetch` and calls to network, file, database and mail modules in a workflow and in the `constructor`, `toInput` and `toOutput` of an activity. It also reports `zlib` and `stream` calls there, because they work on the content in memory on each replay.
|
|
15
|
+
- `no-binary-subclass` reports a class that extends `Binary`.
|
|
16
|
+
- `no-framework-activity-in-app` reports `new` and `extends` on an activity of a framework package other than the core.
|
|
17
|
+
- `no-testing-import-in-source` reports an import of a `@t4h.framework/<package>/testing` entry out of a test file.
|
|
18
|
+
- `require-workflow-id` reports a `Workflow` with no `id`.
|
|
19
|
+
- `no-secret-to-string` reports a `Secret` that becomes a string.
|
|
20
|
+
- `no-literal-secret-in-authorization` reports a string literal in the `secret` or `clientSecret` field of an `Authorization`.
|
|
21
|
+
|
|
22
|
+
The package skills now name these rules in place of the text that the rules enforce. The docs and skills give each `Workflow` an `id`, read the HMAC secret from `EnvDesign`, and do not document a custom HTTP activity.
|
|
23
|
+
|
|
24
|
+
- [#243](https://github.com/tech4humans-brasil/framework/pull/243) [`0ffd663`](https://github.com/tech4humans-brasil/framework/commit/0ffd663ce8369edd7b54206cb3216568ad1de471) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The README examples read each value from `environments`, an `EnvDesign` in `src/constants/environments.ts`, and pass each credential as a `Secret`. They no longer use `process.env`, the raw `env` from the core or a literal in a `Secret` field. The jwks README describes the real context fields (`signature`, `rawPayload`) and says that a token that `jwt.verify` rejects throws.
|
|
25
|
+
|
|
26
|
+
- [#240](https://github.com/tech4humans-brasil/framework/pull/240) [`0606115`](https://github.com/tech4humans-brasil/framework/commit/0606115e2e0baa994e9f0ed3559ee54ecac88e44) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The skills do not repeat a rule that a lint rule, `tsc` or a runtime error already enforces. Each such rule is now one line that names the gate. The examples that broke a gate (`process.env`, `as any`, an inline secret, an `EnvDesign` in a workflow file, `ref.output` before `wait()`) are correct now.
|
|
27
|
+
|
|
28
|
+
- [#226](https://github.com/tech4humans-brasil/framework/pull/226) [`e11c2ee`](https://github.com/tech4humans-brasil/framework/commit/e11c2ee0d959dff4ea1c8861ec036f9f299b743c) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - `RefCollection.filter` waits for an async predicate. Before, it kept every ref, because a pending promise is truthy. A missing relation, plan row or loaded row now throws a clear error. The `mappedBy` callback of `OneToMany` returns `Ref<unknown>` in place of `Ref<any>`.
|
|
29
|
+
|
|
3
30
|
## 0.11.3
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|