@t4h.framework/orm 0.3.0 → 0.5.0
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 +339 -0
- package/README.md +54 -0
- package/dist/activities/ORMGetSnapshotActivity.d.ts +3 -3
- package/dist/activities/ORMGetSnapshotActivity.d.ts.map +1 -1
- package/dist/activities/ORMGetSnapshotActivity.js.map +1 -1
- package/dist/claims/ORMClaim.d.ts +1 -1
- package/dist/claims/ORMClaim.d.ts.map +1 -1
- package/dist/constraints/__tests__/create-entity-metadata.d.ts +3 -0
- package/dist/constraints/__tests__/create-entity-metadata.d.ts.map +1 -0
- package/dist/constraints/__tests__/create-entity-metadata.js +10 -0
- package/dist/constraints/__tests__/create-entity-metadata.js.map +1 -0
- package/dist/constraints/apply-default-properties.d.ts +7 -0
- package/dist/constraints/apply-default-properties.d.ts.map +1 -0
- package/dist/constraints/apply-default-properties.js +16 -0
- package/dist/constraints/apply-default-properties.js.map +1 -0
- package/dist/constraints/assert-constraints.d.ts +7 -0
- package/dist/constraints/assert-constraints.d.ts.map +1 -0
- package/dist/constraints/assert-constraints.js +13 -0
- package/dist/constraints/assert-constraints.js.map +1 -0
- package/dist/constraints/assert-non-nullable-property-constraint.d.ts +7 -0
- package/dist/constraints/assert-non-nullable-property-constraint.d.ts.map +1 -0
- package/dist/constraints/assert-non-nullable-property-constraint.js +14 -0
- package/dist/constraints/assert-non-nullable-property-constraint.js.map +1 -0
- package/dist/constraints/assert-property-type-constraint.d.ts +7 -0
- package/dist/constraints/assert-property-type-constraint.d.ts.map +1 -0
- package/dist/constraints/assert-property-type-constraint.js +30 -0
- package/dist/constraints/assert-property-type-constraint.js.map +1 -0
- package/dist/constraints/assert-unique-constraint.d.ts +11 -0
- package/dist/constraints/assert-unique-constraint.d.ts.map +1 -0
- package/dist/constraints/assert-unique-constraint.js +41 -0
- package/dist/constraints/assert-unique-constraint.js.map +1 -0
- package/dist/decorators/Index.d.ts +6 -0
- package/dist/decorators/Index.d.ts.map +1 -0
- package/dist/decorators/Index.js +10 -0
- package/dist/decorators/Index.js.map +1 -0
- package/dist/decorators/Property.d.ts +1 -0
- package/dist/decorators/Property.d.ts.map +1 -1
- package/dist/decorators/Property.js +17 -1
- package/dist/decorators/Property.js.map +1 -1
- package/dist/decorators/metadata.d.ts +5 -2
- package/dist/decorators/metadata.d.ts.map +1 -1
- package/dist/decorators/metadata.js +1 -1
- package/dist/decorators/metadata.js.map +1 -1
- package/dist/errors/ConstraintError.d.ts +4 -0
- package/dist/errors/ConstraintError.d.ts.map +1 -0
- package/dist/errors/ConstraintError.js +7 -0
- package/dist/errors/ConstraintError.js.map +1 -0
- package/dist/models/ORM.d.ts +25 -5
- package/dist/models/ORM.d.ts.map +1 -1
- package/dist/models/ORM.js +42 -7
- package/dist/models/ORM.js.map +1 -1
- package/dist/models/Ref.d.ts +1 -1
- package/dist/models/Ref.d.ts.map +1 -1
- package/dist/models/Ref.js +11 -2
- package/dist/models/Ref.js.map +1 -1
- package/dist/orm.d.ts +3 -0
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +3 -0
- package/dist/orm.js.map +1 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +52 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: framework-orm
|
|
3
|
+
description: >-
|
|
4
|
+
Guides correct use of @t4h.framework/orm in T4H Framework workflows: entity
|
|
5
|
+
decorators (@Entity, @Property, @Index), the ORM model (create, find, findOne,
|
|
6
|
+
exists), Ref and RefCollection, query operators, constraint validation, ORM
|
|
7
|
+
activities, and ORMClaim wiring. Use when defining database entities, persisting
|
|
8
|
+
or querying rows in workflows, implementing ORMClaim providers, testing ORM
|
|
9
|
+
workflows with TestWorkflowEnvironment, or working in packages/orm.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @t4h.framework/orm
|
|
13
|
+
|
|
14
|
+
ORM activities, claim, and models for database operations within workflows. Package path: `framework/packages/orm`. Peer dependency: `@t4h.framework/core`.
|
|
15
|
+
|
|
16
|
+
Import from `src/orm.ts` only — do not reference unexported modules:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import {
|
|
20
|
+
Entity, Property, Index, ORM, Ref, RefCollection,
|
|
21
|
+
ORMClaim, ConstraintError,
|
|
22
|
+
ORMCreateActivity, ORMFindActivity, ORMFindOneActivity,
|
|
23
|
+
ORMGetSnapshotActivity, ORMUpdateActivity, ORMRemoveActivity,
|
|
24
|
+
getEntityMetadata, getEntityTableName, getEntityProperties,
|
|
25
|
+
type EntityInsertData, type EntityPartialData, type FindWhere,
|
|
26
|
+
type SerializableCreateInput, type SerializableFindInput,
|
|
27
|
+
type SerializableGetSnapshotInput, type SerializableUpdateInput,
|
|
28
|
+
type SerializableRemoveInput,
|
|
29
|
+
} from '@t4h.framework/orm'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Node `>=22`. See [README.md](../../../README.md).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
`Workflow` → `ORM` / `Ref` methods (`History.bind` on ORM activities) → `ORMClaim` (runtime database adapter).
|
|
39
|
+
|
|
40
|
+
| Layer | Role |
|
|
41
|
+
|-------|------|
|
|
42
|
+
| **ORM** | High-level API; applies defaults + constraints before activities |
|
|
43
|
+
| **Ref** / **RefCollection** | Row handles returned by create/find |
|
|
44
|
+
| **Activities** | Shipped, replay-safe activities the `ORM`/`Ref` APIs drive; delegate to `ORMClaim` |
|
|
45
|
+
| **ORMClaim** | Abstract claim; runtime provides concrete DB implementation |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Defining entities
|
|
50
|
+
|
|
51
|
+
Enable decorator metadata in the project `tsconfig.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"compilerOptions": {
|
|
56
|
+
"experimentalDecorators": true,
|
|
57
|
+
"emitDecoratorMetadata": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Import `reflect-metadata` where decorators are evaluated (Property decorator imports it).
|
|
63
|
+
|
|
64
|
+
### @Entity
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
@Entity({ tableName: 'users' })
|
|
68
|
+
class User {
|
|
69
|
+
@Property({ type: 'string' })
|
|
70
|
+
public name!: string
|
|
71
|
+
|
|
72
|
+
@Property({ type: 'string' })
|
|
73
|
+
public email!: string
|
|
74
|
+
|
|
75
|
+
@Property({ type: 'boolean', default: true })
|
|
76
|
+
public active!: boolean
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `tableName` is optional; defaults to the class name (`User` → `"User"`).
|
|
81
|
+
- Use `getEntityTableName(User)` or rely on ORM to resolve the table name.
|
|
82
|
+
|
|
83
|
+
### @Property
|
|
84
|
+
|
|
85
|
+
Options: `type?`, `nullable?` (default required), `default?`, `index?` (adds index metadata).
|
|
86
|
+
|
|
87
|
+
Omit `type` to infer from design metadata (`String`→`string`, `Number`→`number`, etc.). Declare domain types (`uuid`) explicitly. Validated types: `string`, `uuid`, `number`, `boolean`, `array`, `date`, `object`. No `type` → skip type validation.
|
|
88
|
+
|
|
89
|
+
### @Index
|
|
90
|
+
|
|
91
|
+
Class-level composite indexes:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
@Index({ properties: ['tenantId', 'email'], name: 'accounts_tenant_email_idx' })
|
|
95
|
+
class Account { /* ... */ }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`properties` must be non-empty or the decorator throws. Per-property `@Property({ index: true })` also registers `{ properties: [propertyKey] }`.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ORM configuration
|
|
103
|
+
|
|
104
|
+
`ORM` extends `SerializableClass` — instances can be serialized for workflow persistence.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const orm = new ORM({
|
|
108
|
+
connectionString: 'postgresql://localhost:5432/mydb',
|
|
109
|
+
entities: [User, Product],
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- `entities`: array of entity **constructors** (decorated classes).
|
|
114
|
+
- `config.entities` stores resolved `EntityMetadata` (table name, properties map, indexes).
|
|
115
|
+
|
|
116
|
+
Restore from serialized state:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const orm = await ORM.fromSerializable(serialized, v => v)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## CRUD in workflows
|
|
125
|
+
|
|
126
|
+
ORM methods bind activities via `History.bind` — calls inside a workflow are **reconciler-driven** (replay-safe).
|
|
127
|
+
|
|
128
|
+
### Create
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Single row → Ref<T>
|
|
132
|
+
const userRef = await orm.create(User, {
|
|
133
|
+
name: 'John',
|
|
134
|
+
email: 'john@example.com',
|
|
135
|
+
active: true,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Batch → RefCollection<T>
|
|
139
|
+
const usersRef = await orm.create(User, [
|
|
140
|
+
{ name: 'Alice', email: 'alice@example.com', active: true },
|
|
141
|
+
{ name: 'Bob', email: 'bob@example.com', active: true },
|
|
142
|
+
])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Defaults are applied before persistence (`applyDefaultProperties`). Omitted fields with `@Property({ default })` are filled in.
|
|
146
|
+
|
|
147
|
+
### Find
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const userRef = await orm.findOne(User, {
|
|
151
|
+
where: { email: 'john@example.com' },
|
|
152
|
+
orderBy: { name: 'asc' },
|
|
153
|
+
}) // Ref<User> | null
|
|
154
|
+
|
|
155
|
+
const usersRef = await orm.find(User, {
|
|
156
|
+
where: { active: true },
|
|
157
|
+
orderBy: { name: 'asc' },
|
|
158
|
+
limit: 10,
|
|
159
|
+
offset: 0,
|
|
160
|
+
}) // RefCollection<User>
|
|
161
|
+
|
|
162
|
+
const exists = await orm.exists(User, {
|
|
163
|
+
where: { email: 'john@example.com' },
|
|
164
|
+
}) // boolean (delegates to findOne)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`findOne` activity internally calls `claim.find` with `limit: 1`.
|
|
168
|
+
|
|
169
|
+
### Ref operations
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const snapshot = await userRef.getSnapshot() // T | null — entity instance
|
|
173
|
+
await userRef.update({ name: 'Jane' }) // partial patch only
|
|
174
|
+
await userRef.remove()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`getSnapshot` returns `null` when the row no longer exists. `update` validates only keys present in the patch.
|
|
178
|
+
|
|
179
|
+
**RefCollection:** `toArray()`, `length`, `at(i)`, async `forEach`/`map`/`filter`, `Symbol.iterator`.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Query operators
|
|
184
|
+
|
|
185
|
+
`where` uses `FindWhere<T>` — implicit equality or operator objects:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
await orm.find(User, {
|
|
189
|
+
where: {
|
|
190
|
+
name: 'John', // $eq (implicit)
|
|
191
|
+
active: { $ne: false },
|
|
192
|
+
email: { $like: '%@example%' },
|
|
193
|
+
age: { $gte: 18, $lt: 65 },
|
|
194
|
+
status: { $in: ['active', 'pending'] },
|
|
195
|
+
$or: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
196
|
+
$and: [{ active: true }, { age: { $gte: 18 } }],
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Available operators: `$eq`, `$ne`, `$in`, `$nin`, `$not`, `$gt`, `$gte`, `$lt`, `$lte`, `$like`, `$ilike`, `$and`, `$or`.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Constraint validation
|
|
206
|
+
|
|
207
|
+
Validation runs in the ORM library (`ORM.create` and `Ref.update`), **not** in `ORMClaim` implementations. Failures throw `ConstraintError`.
|
|
208
|
+
|
|
209
|
+
### Create pipeline
|
|
210
|
+
|
|
211
|
+
1. `applyDefaultProperties` — fills `default` for `undefined` fields
|
|
212
|
+
2. `assertPropertyTypeConstraint` — validates declared types
|
|
213
|
+
3. `assertNonNullablePropertyConstraint` — required fields must be present
|
|
214
|
+
|
|
215
|
+
### Update pipeline
|
|
216
|
+
|
|
217
|
+
1. Defaults only for keys present in the patch
|
|
218
|
+
2. Same asserts, scoped to patch keys via `{ keys: Object.keys(data) }`
|
|
219
|
+
|
|
220
|
+
### Handling errors
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { ConstraintError, ORM } from '@t4h.framework/orm'
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await orm.create(User, { email: 'bad' } as EntityInsertData<User>)
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof ConstraintError) {
|
|
229
|
+
// e.g. 'Property "name" is required'
|
|
230
|
+
// e.g. 'Property "name" must be of type string'
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Constraint checks run **before** activities are invoked — failed creates/updates never reach `ORMClaim`.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## ORMClaim (runtime dependency)
|
|
240
|
+
|
|
241
|
+
Runtime must register a concrete implementation:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
{ provide: ORMClaim, value: new MyORMClaimImpl() }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Contract:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
abstract class ORMClaim<R extends SerializableRowRef> {
|
|
251
|
+
abstract find(input: SerializableFindInput): R[] | Promise<R[]>
|
|
252
|
+
abstract create(input: SerializableCreateInput): R[] | Promise<R[]>
|
|
253
|
+
abstract getSnapshot(input: SerializableGetSnapshotInput<R>): SerializedRow | null | Promise<SerializedRow | null>
|
|
254
|
+
abstract update(input: SerializableUpdateInput<R>): void | Promise<void>
|
|
255
|
+
abstract remove(input: SerializableRemoveInput<R>): void | Promise<void>
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Activity inputs always include `orm` + `table`; create adds `data[]`, find adds `where` (+ optional `orderBy`/`limit`/`offset`), snapshot/update/remove add `ref` (opaque `Serializable` row handle). Update also includes partial `data`.
|
|
260
|
+
|
|
261
|
+
The `ORM`/`Ref` calls run inside a workflow (the runtime, or `TestWorkflowEnvironment` in tests, provides the reconciler context).
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Activities
|
|
266
|
+
|
|
267
|
+
These are the **shipped, replay-safe activities** the `ORM`/`Ref` APIs drive for you (you can also pass them to `History.reconciler` directly). Never define your own:
|
|
268
|
+
|
|
269
|
+
| Activity | Input | Output |
|
|
270
|
+
|----------|-------|--------|
|
|
271
|
+
| `ORMCreateActivity` | `SerializableCreateInput` | `SerializableRowRef[]` |
|
|
272
|
+
| `ORMFindActivity` | `SerializableFindInput` | `SerializableRowRef[]` |
|
|
273
|
+
| `ORMFindOneActivity` | `SerializableFindInput` | `SerializableRowRef \| null` |
|
|
274
|
+
| `ORMGetSnapshotActivity` | `SerializableGetSnapshotInput` | `SerializedRow \| null` |
|
|
275
|
+
| `ORMUpdateActivity` | `SerializableUpdateInput` | `void` |
|
|
276
|
+
| `ORMRemoveActivity` | `SerializableRemoveInput` | `void` |
|
|
277
|
+
|
|
278
|
+
Prefer **`ORM` / `Ref` APIs** in workflow code — they apply constraints and return typed handles. Use activities directly only for advanced/testing scenarios.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Workflow example
|
|
283
|
+
|
|
284
|
+
Define entities, instantiate `ORM` with `connectionString` + `entities`, and use it inside a `Workflow`:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
export const orm = new ORM({ connectionString: '...', entities: [User] })
|
|
288
|
+
|
|
289
|
+
export const ormWorkflow = new Workflow({ id: 'orm' }, async () => {
|
|
290
|
+
await orm.create(User, { name: 'John', email: 'john@example.com', age: 20 })
|
|
291
|
+
const user = await orm.findOne(User, { where: { email: 'john@example.com' } })
|
|
292
|
+
await user?.update({ name: 'John Updated' })
|
|
293
|
+
await user?.remove()
|
|
294
|
+
return { user }
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Ensure the runtime provides `ORMClaim`.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Testing
|
|
303
|
+
|
|
304
|
+
Test the **workflow** with `TestWorkflowEnvironment` from
|
|
305
|
+
`@t4h.framework/core/testing`, mocking `ORMClaim` — the single claim the ORM
|
|
306
|
+
activities resolve. Provide the methods your workflow triggers (`create`, `find`,
|
|
307
|
+
`findOne`, `getSnapshot`, `update`, `remove`). See the **framework-workflow-testing**
|
|
308
|
+
skill for the harness API.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
|
|
312
|
+
import { ORMClaim } from '@t4h.framework/orm'
|
|
313
|
+
|
|
314
|
+
const mockCreate = vi.fn().mockImplementation(async () => [{ id: 1 }])
|
|
315
|
+
const mockFindOne = vi.fn().mockImplementation(async () => ({ id: 1, name: 'John' }))
|
|
316
|
+
|
|
317
|
+
const env = TestWorkflowEnvironment.create({ now: 0 })
|
|
318
|
+
|
|
319
|
+
await env.execute(workflow, input, {
|
|
320
|
+
claims: [mockClaim(ORMClaim, { create: mockCreate, findOne: mockFindOne })],
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Constraint/validation errors are applied by the `ORM`/`Ref` layer before the
|
|
325
|
+
activity runs, so you can assert an invalid input rejects without the claim being
|
|
326
|
+
called: `expect(mockCreate).not.toHaveBeenCalled()`.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Checklist for agents
|
|
331
|
+
|
|
332
|
+
1. **`@Entity` + `@Property`** with `experimentalDecorators` + `emitDecoratorMetadata`.
|
|
333
|
+
2. Register entities in **`ORM({ entities: [...] })`**; runtime provides **`ORMClaim`**.
|
|
334
|
+
3. Use **`orm` / `Ref` APIs** in workflows — not raw SQL or direct claim calls.
|
|
335
|
+
4. Handle **`ConstraintError`**; validation runs before activities.
|
|
336
|
+
5. **`Ref.update`** is partial; **`getSnapshot`** returns entity instances.
|
|
337
|
+
6. Test workflows with **`TestWorkflowEnvironment`** + **`mockClaim(ORMClaim, ...)`**; only use exported APIs.
|
|
338
|
+
|
|
339
|
+
See also: `packages/orm/README.md`; **framework-workflow-testing** for the test harness.
|
package/README.md
CHANGED
|
@@ -64,6 +64,60 @@ class User {
|
|
|
64
64
|
})
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
When `type` is omitted, it is inferred from the TypeScript property type if the project enables `emitDecoratorMetadata` (and imports `reflect-metadata`). Domain types such as `uuid` must be declared explicitly.
|
|
68
|
+
|
|
69
|
+
If `nullable` is omitted, the property is required at runtime validation.
|
|
70
|
+
|
|
71
|
+
### TypeScript configuration for entity classes
|
|
72
|
+
|
|
73
|
+
Projects that define entities with `@Property()` should enable:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"compilerOptions": {
|
|
78
|
+
"experimentalDecorators": true,
|
|
79
|
+
"emitDecoratorMetadata": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Constraints
|
|
85
|
+
|
|
86
|
+
Row validation runs in the ORM library (`ORM.create` and `Ref.update`), not in `ORMClaim` implementations. Failed checks throw `ConstraintError`.
|
|
87
|
+
|
|
88
|
+
### Pipeline
|
|
89
|
+
|
|
90
|
+
**Create** (full row, after defaults):
|
|
91
|
+
|
|
92
|
+
1. `applyDefaultProperties` — fills `default` for `undefined` fields
|
|
93
|
+
2. `assertPropertyTypeConstraint` — validates declared types
|
|
94
|
+
3. `assertNonNullablePropertyConstraint` — required fields must be present
|
|
95
|
+
4. `assertUniqueConstraint` — no duplicates in the batch, then no conflicting row in storage via `orm.exists`
|
|
96
|
+
|
|
97
|
+
**Update** (partial patch only):
|
|
98
|
+
|
|
99
|
+
1. Defaults only for keys present in the patch
|
|
100
|
+
2. Same asserts, scoped to patch keys
|
|
101
|
+
3. Unique checks skip values unchanged vs `getSnapshot()` (same row keeps its current unique value)
|
|
102
|
+
|
|
103
|
+
### Supported property types
|
|
104
|
+
|
|
105
|
+
`string`, `uuid`, `number`, `boolean`, `array`, `date`, `object`. Properties without `type` skip type validation.
|
|
106
|
+
|
|
107
|
+
### Example
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { ConstraintError, ORM } from '@t4h.framework/orm'
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await orm.create(User, { email: 'taken@example.com' })
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof ConstraintError) {
|
|
116
|
+
// handle validation failure
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
67
121
|
## ORM Model
|
|
68
122
|
|
|
69
123
|
The `ORM` class provides a high-level API for database operations. It wraps the underlying activities (`ORMCreateActivity`, `ORMFindActivity`, `ORMFindOneActivity`).
|
|
@@ -3,11 +3,11 @@ import type { SerializableGetSnapshotInput, SerializableRowRef, SerializedRow }
|
|
|
3
3
|
declare const CLAIMS_KEY: unique symbol;
|
|
4
4
|
export declare class ORMGetSnapshotActivity extends SyncActivity<[
|
|
5
5
|
input: SerializableGetSnapshotInput<SerializableRowRef>
|
|
6
|
-
], SerializableGetSnapshotInput<SerializableRowRef>, SerializedRow, SerializedRow> {
|
|
6
|
+
], SerializableGetSnapshotInput<SerializableRowRef>, SerializedRow | null, SerializedRow | null> {
|
|
7
7
|
private readonly [CLAIMS_KEY];
|
|
8
8
|
toInput(input: SerializableGetSnapshotInput<SerializableRowRef>): SerializableGetSnapshotInput<SerializableRowRef>;
|
|
9
|
-
run(input: SerializableGetSnapshotInput<SerializableRowRef>): SerializedRow | Promise<SerializedRow
|
|
10
|
-
toOutput(output: SerializedRow): SerializedRow;
|
|
9
|
+
run(input: SerializableGetSnapshotInput<SerializableRowRef>): SerializedRow | Promise<SerializedRow | null> | null;
|
|
10
|
+
toOutput(output: SerializedRow | null): SerializedRow | null;
|
|
11
11
|
}
|
|
12
12
|
export {};
|
|
13
13
|
//# sourceMappingURL=ORMGetSnapshotActivity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ORMGetSnapshotActivity.d.ts","sourceRoot":"","sources":["../../src/activities/ORMGetSnapshotActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAS,MAAM,qBAAqB,CAAA;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,QAAA,MAAM,UAAU,eAA6C,CAAA;AAE7D,qBAAa,sBAAuB,SAAQ,YAAY,CACtD;IAAC,KAAK,EAAE,4BAA4B,CAAC,kBAAkB,CAAC;CAAC,EACzD,4BAA4B,CAAC,kBAAkB,CAAC,EAChD,aAAa,
|
|
1
|
+
{"version":3,"file":"ORMGetSnapshotActivity.d.ts","sourceRoot":"","sources":["../../src/activities/ORMGetSnapshotActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAS,MAAM,qBAAqB,CAAA;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,QAAA,MAAM,UAAU,eAA6C,CAAA;AAE7D,qBAAa,sBAAuB,SAAQ,YAAY,CACtD;IAAC,KAAK,EAAE,4BAA4B,CAAC,kBAAkB,CAAC;CAAC,EACzD,4BAA4B,CAAC,kBAAkB,CAAC,EAChD,aAAa,GAAG,IAAI,EACpB,aAAa,GAAG,IAAI,CACrB;IACC,OAAO,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAE3B;IAEK,OAAO,CACZ,KAAK,EAAE,4BAA4B,CAAC,kBAAkB,CAAC,GACtD,4BAA4B,CAAC,kBAAkB,CAAC;IAI5C,GAAG,CAAC,KAAK,EAAE,4BAA4B,CAAC,kBAAkB,CAAC;IAI3D,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,aAAa,GAAG,IAAI;CAGpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ORMGetSnapshotActivity.js","sourceRoot":"","sources":["../../src/activities/ORMGetSnapshotActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEzD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAOhD,MAAM,UAAU,GAAG,MAAM,CAAC,kCAAkC,CAAC,CAAA;AAE7D,MAAM,OAAO,sBAAuB,SAAQ,YAK3C;IACkB,CAAC,UAAU,CAAC,GAAG,IAAI,KAAK,CAAC;QACxC,GAAG,EAAE,QAAQ;KACd,CAAC,CAAA;IAEK,OAAO,CACZ,KAAuD;QAEvD,OAAO,KAAK,CAAA;IACd,CAAC;IAEM,GAAG,CAAC,KAAuD;QAChE,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAChD,CAAC;IAEM,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"ORMGetSnapshotActivity.js","sourceRoot":"","sources":["../../src/activities/ORMGetSnapshotActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEzD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAOhD,MAAM,UAAU,GAAG,MAAM,CAAC,kCAAkC,CAAC,CAAA;AAE7D,MAAM,OAAO,sBAAuB,SAAQ,YAK3C;IACkB,CAAC,UAAU,CAAC,GAAG,IAAI,KAAK,CAAC;QACxC,GAAG,EAAE,QAAQ;KACd,CAAC,CAAA;IAEK,OAAO,CACZ,KAAuD;QAEvD,OAAO,KAAK,CAAA;IACd,CAAC;IAEM,GAAG,CAAC,KAAuD;QAChE,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAChD,CAAC;IAEM,QAAQ,CAAC,MAA4B;QAC1C,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
|
|
@@ -2,7 +2,7 @@ import type { SerializableCreateInput, SerializableFindInput, SerializableGetSna
|
|
|
2
2
|
export declare abstract class ORMClaim<R extends SerializableRowRef> {
|
|
3
3
|
abstract find(input: SerializableFindInput): R[] | Promise<R[]>;
|
|
4
4
|
abstract create(input: SerializableCreateInput): R[] | Promise<R[]>;
|
|
5
|
-
abstract getSnapshot(input: SerializableGetSnapshotInput<R>): SerializedRow | Promise<SerializedRow>;
|
|
5
|
+
abstract getSnapshot(input: SerializableGetSnapshotInput<R>): SerializedRow | null | Promise<SerializedRow | null>;
|
|
6
6
|
abstract update(input: SerializableUpdateInput<R>): void | Promise<void>;
|
|
7
7
|
abstract remove(input: SerializableRemoveInput<R>): void | Promise<void>;
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ORMClaim.d.ts","sourceRoot":"","sources":["../../src/claims/ORMClaim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,8BAAsB,QAAQ,CAAC,CAAC,SAAS,kBAAkB;aACzC,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;aAEtD,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;aAE1D,WAAW,CACzB,KAAK,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACrC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ORMClaim.d.ts","sourceRoot":"","sources":["../../src/claims/ORMClaim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,8BAAsB,QAAQ,CAAC,CAAC,SAAS,kBAAkB;aACzC,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;aAEtD,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;aAE1D,WAAW,CACzB,KAAK,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACrC,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;aAEvC,MAAM,CACpB,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAChC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;aAEP,MAAM,CACpB,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAChC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-entity-metadata.d.ts","sourceRoot":"","sources":["../../../src/constraints/__tests__/create-entity-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EACjB,MAAM,8BAA8B,CAAA;AAErC,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,GACzD,cAAc,CAUhB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createEntityMetadata(properties) {
|
|
2
|
+
return {
|
|
3
|
+
indexes: [],
|
|
4
|
+
properties: new Map(Object.entries(properties).map(([name, meta]) => [
|
|
5
|
+
name,
|
|
6
|
+
{ name, indexes: [], ...meta },
|
|
7
|
+
])),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=create-entity-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-entity-metadata.js","sourceRoot":"","sources":["../../../src/constraints/__tests__/create-entity-metadata.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,oBAAoB,CAClC,UAA0D;IAE1D,OAAO;QACL,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,IAAI,GAAG,CACjB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;YAC/C,IAAI;YACJ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE;SAC/B,CAAC,CACH;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EntityMetadata, SerializedRow } from '../orm.js';
|
|
2
|
+
export declare function applyDefaultProperties(rows: SerializedRow[] | SerializedRow, entity: EntityMetadata, keys?: string[]): {
|
|
3
|
+
[x: string]: import("@t4h.framework/core").Serializable;
|
|
4
|
+
} | {
|
|
5
|
+
[x: string]: import("@t4h.framework/core").Serializable;
|
|
6
|
+
}[];
|
|
7
|
+
//# sourceMappingURL=apply-default-properties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-default-properties.d.ts","sourceRoot":"","sources":["../../src/constraints/apply-default-properties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAmB9D,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,aAAa,EAAE,GAAG,aAAa,EACrC,MAAM,EAAE,cAAc,EACtB,IAAI,CAAC,EAAE,MAAM,EAAE;;;;IAMhB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function applyDefaultPropertiesToRow(raw, entity, keys) {
|
|
2
|
+
const row = { ...raw };
|
|
3
|
+
for (const [name, meta] of entity.properties.entries()) {
|
|
4
|
+
if (keys && !keys.includes(name))
|
|
5
|
+
continue;
|
|
6
|
+
if (row[name] === undefined && meta.default !== undefined)
|
|
7
|
+
row[name] = meta.default;
|
|
8
|
+
}
|
|
9
|
+
return row;
|
|
10
|
+
}
|
|
11
|
+
export function applyDefaultProperties(rows, entity, keys) {
|
|
12
|
+
if (Array.isArray(rows))
|
|
13
|
+
return rows.map(raw => applyDefaultPropertiesToRow(raw, entity, keys));
|
|
14
|
+
return applyDefaultPropertiesToRow(rows, entity, keys);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=apply-default-properties.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-default-properties.js","sourceRoot":"","sources":["../../src/constraints/apply-default-properties.ts"],"names":[],"mappings":"AAEA,SAAS,2BAA2B,CAClC,GAAkB,EAClB,MAAsB,EACtB,IAAe;IAEf,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,CAAA;IAEtB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE1C,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YACvD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAA;IAC5B,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,IAAqC,EACrC,MAAsB,EACtB,IAAe;IAEf,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;IAExE,OAAO,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;AACxD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type EntityConstructor, type SerializedRow } from '../orm.js';
|
|
2
|
+
interface AssertConstraintsOptions {
|
|
3
|
+
keys?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function assertConstraints<T>(entity: EntityConstructor<T>, rows: SerializedRow[], options?: AssertConstraintsOptions): Promise<void>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=assert-constraints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-constraints.d.ts","sourceRoot":"","sources":["../../src/constraints/assert-constraints.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAA;AAIlB,UAAU,wBAAwB;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC5B,IAAI,EAAE,aAAa,EAAE,EACrB,OAAO,CAAC,EAAE,wBAAwB,iBAWnC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getEntityMetadata, } from '../orm.js';
|
|
2
|
+
import { assertNonNullablePropertyConstraint } from './assert-non-nullable-property-constraint.js';
|
|
3
|
+
import { assertPropertyTypeConstraint } from './assert-property-type-constraint.js';
|
|
4
|
+
export async function assertConstraints(entity, rows, options) {
|
|
5
|
+
const entityMetadata = getEntityMetadata(entity);
|
|
6
|
+
assertPropertyTypeConstraint(rows, entityMetadata, {
|
|
7
|
+
keys: options?.keys,
|
|
8
|
+
});
|
|
9
|
+
assertNonNullablePropertyConstraint(rows, entityMetadata, {
|
|
10
|
+
keys: options?.keys,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=assert-constraints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-constraints.js","sourceRoot":"","sources":["../../src/constraints/assert-constraints.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAGlB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,mCAAmC,EAAE,MAAM,8CAA8C,CAAA;AAClG,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAA;AAMnF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA4B,EAC5B,IAAqB,EACrB,OAAkC;IAElC,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAEhD,4BAA4B,CAAC,IAAI,EAAE,cAAc,EAAE;QACjD,IAAI,EAAE,OAAO,EAAE,IAAI;KACpB,CAAC,CAAA;IAEF,mCAAmC,CAAC,IAAI,EAAE,cAAc,EAAE;QACxD,IAAI,EAAE,OAAO,EAAE,IAAI;KACpB,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EntityMetadata, SerializedRow } from '../orm.js';
|
|
2
|
+
interface AssertNonNullablePropertyConstraintOptions {
|
|
3
|
+
keys?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function assertNonNullablePropertyConstraint(rows: SerializedRow[], entity: EntityMetadata, options?: AssertNonNullablePropertyConstraintOptions): void;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=assert-non-nullable-property-constraint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-non-nullable-property-constraint.d.ts","sourceRoot":"","sources":["../../src/constraints/assert-non-nullable-property-constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9D,UAAU,0CAA0C;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,aAAa,EAAE,EACrB,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,0CAA0C,QAYrD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ConstraintError } from '../errors/ConstraintError.js';
|
|
2
|
+
export function assertNonNullablePropertyConstraint(rows, entity, options) {
|
|
3
|
+
rows.forEach(row => {
|
|
4
|
+
for (const [name, meta] of entity.properties.entries()) {
|
|
5
|
+
if (options?.keys && !options.keys.includes(name))
|
|
6
|
+
continue;
|
|
7
|
+
if (meta.nullable)
|
|
8
|
+
continue;
|
|
9
|
+
if (row[name] === null || row[name] === undefined)
|
|
10
|
+
throw new ConstraintError(`Property "${name}" is required`);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=assert-non-nullable-property-constraint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-non-nullable-property-constraint.js","sourceRoot":"","sources":["../../src/constraints/assert-non-nullable-property-constraint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAO9D,MAAM,UAAU,mCAAmC,CACjD,IAAqB,EACrB,MAAsB,EACtB,OAAoD;IAEpD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACjB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;YACvD,IAAI,OAAO,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAQ;YAE3D,IAAI,IAAI,CAAC,QAAQ;gBAAE,SAAQ;YAE3B,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS;gBAC/C,MAAM,IAAI,eAAe,CAAC,aAAa,IAAI,eAAe,CAAC,CAAA;QAC/D,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EntityMetadata, SerializedRow } from '../orm.js';
|
|
2
|
+
interface AssertPropertyTypeConstraintOptions {
|
|
3
|
+
keys?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function assertPropertyTypeConstraint(rows: SerializedRow[], entity: EntityMetadata, options?: AssertPropertyTypeConstraintOptions): void;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=assert-property-type-constraint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-property-type-constraint.d.ts","sourceRoot":"","sources":["../../src/constraints/assert-property-type-constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAgB9D,UAAU,mCAAmC;IAC3C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,aAAa,EAAE,EACrB,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,mCAAmC,QAyB9C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ConstraintError } from '../errors/ConstraintError.js';
|
|
2
|
+
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
3
|
+
const PROPERTY_TYPE = {
|
|
4
|
+
string: (value) => typeof value === 'string',
|
|
5
|
+
uuid: (value) => typeof value === 'string' && UUID_REGEX.test(value),
|
|
6
|
+
number: (value) => typeof value === 'number' && !Number.isNaN(value),
|
|
7
|
+
boolean: (value) => typeof value === 'boolean',
|
|
8
|
+
array: (value) => Array.isArray(value),
|
|
9
|
+
date: (value) => value instanceof Date || typeof value === 'string',
|
|
10
|
+
object: (value) => typeof value === 'object' && value !== null && !Array.isArray(value),
|
|
11
|
+
};
|
|
12
|
+
export function assertPropertyTypeConstraint(rows, entity, options) {
|
|
13
|
+
rows.forEach(row => {
|
|
14
|
+
for (const [name, meta] of entity.properties.entries()) {
|
|
15
|
+
if (options?.keys && !options.keys.includes(name))
|
|
16
|
+
continue;
|
|
17
|
+
const value = row[name];
|
|
18
|
+
if (!meta.type)
|
|
19
|
+
continue;
|
|
20
|
+
if (value === undefined || value === null)
|
|
21
|
+
continue;
|
|
22
|
+
const validator = PROPERTY_TYPE[meta.type];
|
|
23
|
+
if (!validator)
|
|
24
|
+
throw new ConstraintError(`Property "${name}" has unsupported type "${meta.type}"`);
|
|
25
|
+
if (!validator(value))
|
|
26
|
+
throw new ConstraintError(`Property "${name}" must be of type ${meta.type}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=assert-property-type-constraint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-property-type-constraint.js","sourceRoot":"","sources":["../../src/constraints/assert-property-type-constraint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAG9D,MAAM,UAAU,GACd,+EAA+E,CAAA;AAEjF,MAAM,aAAa,GAAG;IACpB,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ;IACrD,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;IAC7E,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;IAC7E,OAAO,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,SAAS;IACvD,KAAK,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;IAC/C,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,YAAY,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;IAC5E,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CACzB,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;CACvE,CAAA;AAMD,MAAM,UAAU,4BAA4B,CAC1C,IAAqB,EACrB,MAAsB,EACtB,OAA6C;IAE7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACjB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;YACvD,IAAI,OAAO,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAQ;YAE3D,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;YAEvB,IAAI,CAAC,IAAI,CAAC,IAAI;gBAAE,SAAQ;YAExB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;gBAAE,SAAQ;YAEnD,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,IAAkC,CAAC,CAAA;YAExE,IAAI,CAAC,SAAS;gBACZ,MAAM,IAAI,eAAe,CACvB,aAAa,IAAI,2BAA2B,IAAI,CAAC,IAAI,GAAG,CACzD,CAAA;YAEH,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBACnB,MAAM,IAAI,eAAe,CACvB,aAAa,IAAI,qBAAqB,IAAI,CAAC,IAAI,EAAE,CAClD,CAAA;QACL,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Serializable } from '@t4h.framework/core';
|
|
2
|
+
import type { EntityMetadata, SerializedRow } from '../orm.js';
|
|
3
|
+
import type { FindWhere } from '../typings/FindWhere.js';
|
|
4
|
+
interface AssertUniqueConstraintOptions<T> {
|
|
5
|
+
keys?: string[];
|
|
6
|
+
validator: (where: FindWhere<T>) => Promise<unknown>;
|
|
7
|
+
isUnchanged?: (name: string, value: Serializable) => boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function assertUniqueConstraint<T>(rows: SerializedRow[], entity: EntityMetadata, options: AssertUniqueConstraintOptions<T>): Promise<void>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=assert-unique-constraint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-unique-constraint.d.ts","sourceRoot":"","sources":["../../src/constraints/assert-unique-constraint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,UAAU,6BAA6B,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACpD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAA;CAC7D;AA6BD,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,IAAI,EAAE,aAAa,EAAE,EACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,6BAA6B,CAAC,CAAC,CAAC,iBA6B1C"}
|