@martinffx/atelier 2.0.0 → 3.0.1
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/CHANGELOG.md +21 -0
- package/README.md +94 -253
- package/package.json +1 -1
- package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +6 -6
- package/skills/spec-brainstorm/SKILL.md +5 -5
- package/skills/spec-implement/SKILL.md +2 -3
- package/skills/spec-plan/SKILL.md +21 -29
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
# Transactions and Atomic Operations
|
|
2
|
-
|
|
3
|
-
Patterns for multi-entity consistency and atomic counters in DynamoDB.
|
|
4
|
-
|
|
5
|
-
## Transaction Patterns
|
|
6
|
-
|
|
7
|
-
### Basic: Create with Parent Validation
|
|
8
|
-
|
|
9
|
-
Ensure parent entity exists before creating child entity.
|
|
10
|
-
|
|
11
|
-
**Use Case:** Create an issue only if the repository exists
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { PutTransaction } from 'dynamodb-toolbox/entity/actions/transactPut'
|
|
15
|
-
import { ConditionCheck } from 'dynamodb-toolbox'
|
|
16
|
-
import { execute } from 'dynamodb-toolbox/entity/actions/transactWrite'
|
|
17
|
-
|
|
18
|
-
async createIssue(issue: IssueEntity): Promise<IssueEntity> {
|
|
19
|
-
try {
|
|
20
|
-
// 1. Build transaction to put issue with duplicate check
|
|
21
|
-
const putIssueTransaction = this.issueRecord
|
|
22
|
-
.build(PutTransaction)
|
|
23
|
-
.item(issue.toRecord())
|
|
24
|
-
.options({
|
|
25
|
-
condition: { attr: "PK", exists: false } // Prevent duplicates
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// 2. Build condition check to verify repository exists
|
|
29
|
-
const repoCheckTransaction = this.repoRecord
|
|
30
|
-
.build(ConditionCheck)
|
|
31
|
-
.key({
|
|
32
|
-
owner: issue.owner,
|
|
33
|
-
repo_name: issue.repoName,
|
|
34
|
-
})
|
|
35
|
-
.condition({ attr: "PK", exists: true }) // Repo must exist
|
|
36
|
-
|
|
37
|
-
// 3. Execute both operations in transaction
|
|
38
|
-
await execute(putIssueTransaction, repoCheckTransaction)
|
|
39
|
-
|
|
40
|
-
// 4. Fetch the created item (transaction doesn't return it)
|
|
41
|
-
const created = await this.get(issue.owner, issue.repoName, issue.issueNumber)
|
|
42
|
-
if (!created) {
|
|
43
|
-
throw new Error("Failed to retrieve created issue")
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return created
|
|
47
|
-
} catch (error: unknown) {
|
|
48
|
-
// See error-handling.md for error mapping
|
|
49
|
-
handleTransactionError(error, {
|
|
50
|
-
entityType: "IssueEntity",
|
|
51
|
-
entityKey: issue.getEntityKey(),
|
|
52
|
-
parentEntityType: "RepositoryEntity",
|
|
53
|
-
parentEntityKey: issue.getParentEntityKey(),
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**Key Points:**
|
|
60
|
-
- `PutTransaction` creates the new entity
|
|
61
|
-
- `ConditionCheck` validates the parent exists
|
|
62
|
-
- Both succeed or both fail (atomic)
|
|
63
|
-
- Must fetch created item separately
|
|
64
|
-
|
|
65
|
-
### Advanced: Three-Way Validation
|
|
66
|
-
|
|
67
|
-
Validate multiple parent entities before creating relationship.
|
|
68
|
-
|
|
69
|
-
**Use Case:** Create a star only if both user and repository exist
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
async createStar(star: StarEntity): Promise<StarEntity> {
|
|
73
|
-
try {
|
|
74
|
-
// 1. Build transaction to put star
|
|
75
|
-
const putStarTransaction = this.starRecord
|
|
76
|
-
.build(PutTransaction)
|
|
77
|
-
.item(star.toRecord())
|
|
78
|
-
.options({
|
|
79
|
-
condition: { attr: "PK", exists: false }
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// 2. Build condition check to verify user exists
|
|
83
|
-
const userCheck = this.userRecord
|
|
84
|
-
.build(ConditionCheck)
|
|
85
|
-
.key({ username: star.username })
|
|
86
|
-
.condition({ attr: "PK", exists: true })
|
|
87
|
-
|
|
88
|
-
// 3. Build condition check to verify repo exists
|
|
89
|
-
const repoCheck = this.repoRecord
|
|
90
|
-
.build(ConditionCheck)
|
|
91
|
-
.key({
|
|
92
|
-
owner: star.repoOwner,
|
|
93
|
-
repo_name: star.repoName,
|
|
94
|
-
})
|
|
95
|
-
.condition({ attr: "PK", exists: true })
|
|
96
|
-
|
|
97
|
-
// 4. Execute all three operations
|
|
98
|
-
await execute(putStarTransaction, userCheck, repoCheck)
|
|
99
|
-
|
|
100
|
-
// Fetch and return created star
|
|
101
|
-
const created = await this.get(star.username, star.repoOwner, star.repoName)
|
|
102
|
-
if (!created) {
|
|
103
|
-
throw new Error("Failed to retrieve created star")
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return created
|
|
107
|
-
} catch (error: unknown) {
|
|
108
|
-
handleThreeWayTransactionError(error, star)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**DynamoDB Transaction Limit:** Maximum 100 operations per transaction.
|
|
114
|
-
|
|
115
|
-
## Atomic Counter Pattern
|
|
116
|
-
|
|
117
|
-
### Use Case: Sequential Issue/PR Numbering
|
|
118
|
-
|
|
119
|
-
Each repository needs its own issue counter that increments atomically.
|
|
120
|
-
|
|
121
|
-
### Counter Schema
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
import { Entity } from 'dynamodb-toolbox/entity'
|
|
125
|
-
import { item } from 'dynamodb-toolbox/schema/item'
|
|
126
|
-
import { string, number } from 'dynamodb-toolbox/schema'
|
|
127
|
-
|
|
128
|
-
const CounterRecord = new Entity({
|
|
129
|
-
name: "Counter",
|
|
130
|
-
table: AppTable,
|
|
131
|
-
schema: item({
|
|
132
|
-
org_id: string().required().key(),
|
|
133
|
-
repo_id: string().required().key(),
|
|
134
|
-
current_value: number().required().default(0),
|
|
135
|
-
}).and(_schema => ({
|
|
136
|
-
PK: string().key().link<typeof _schema>(
|
|
137
|
-
({ org_id, repo_id }) => `COUNTER#${org_id}#${repo_id}`
|
|
138
|
-
),
|
|
139
|
-
SK: string().key().default("METADATA"),
|
|
140
|
-
})),
|
|
141
|
-
})
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
**Key Design:**
|
|
145
|
-
- PK: `COUNTER#{org}#{repo}` - One counter per repository
|
|
146
|
-
- SK: `METADATA` - Static sort key
|
|
147
|
-
- `current_value`: Defaults to 0, increments atomically
|
|
148
|
-
|
|
149
|
-
### Atomic Increment Implementation
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import { UpdateItemCommand, $add } from 'dynamodb-toolbox'
|
|
153
|
-
|
|
154
|
-
class CounterRepository {
|
|
155
|
-
constructor(private entity: CounterRecord) {}
|
|
156
|
-
|
|
157
|
-
async incrementAndGet(orgId: string, repoId: string): Promise<number> {
|
|
158
|
-
const result = await this.entity
|
|
159
|
-
.build(UpdateItemCommand)
|
|
160
|
-
.item({
|
|
161
|
-
org_id: orgId,
|
|
162
|
-
repo_id: repoId,
|
|
163
|
-
current_value: $add(1), // Atomic increment
|
|
164
|
-
})
|
|
165
|
-
.options({
|
|
166
|
-
returnValues: "ALL_NEW" // Return updated value
|
|
167
|
-
})
|
|
168
|
-
.send()
|
|
169
|
-
|
|
170
|
-
if (!result.Attributes?.current_value) {
|
|
171
|
-
throw new Error(
|
|
172
|
-
"Failed to increment counter: invalid response from DynamoDB"
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return result.Attributes.current_value
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**How `$add(1)` Works:**
|
|
182
|
-
- If counter doesn't exist: Creates with `current_value = 1`
|
|
183
|
-
- If counter exists: Increments `current_value` by 1
|
|
184
|
-
- Operation is atomic - no race conditions
|
|
185
|
-
- Returns new value in single round-trip
|
|
186
|
-
|
|
187
|
-
### Usage in Issue Creation
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
async createIssue(issue: IssueEntity): Promise<IssueEntity> {
|
|
191
|
-
// 1. Get next issue number atomically
|
|
192
|
-
const issueNumber = await this.counterRepo.incrementAndGet(
|
|
193
|
-
issue.owner,
|
|
194
|
-
issue.repoName
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
// 2. Create issue with assigned number
|
|
198
|
-
const issueWithNumber = new IssueEntity({
|
|
199
|
-
...issue,
|
|
200
|
-
issueNumber, // Use atomically generated number
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
// 3. Use transaction to ensure repo exists
|
|
204
|
-
try {
|
|
205
|
-
const putIssueTransaction = this.issueRecord
|
|
206
|
-
.build(PutTransaction)
|
|
207
|
-
.item(issueWithNumber.toRecord())
|
|
208
|
-
.options({
|
|
209
|
-
condition: { attr: "PK", exists: false }
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
const repoCheckTransaction = this.repoRecord
|
|
213
|
-
.build(ConditionCheck)
|
|
214
|
-
.key({ owner: issue.owner, repo_name: issue.repoName })
|
|
215
|
-
.condition({ attr: "PK", exists: true })
|
|
216
|
-
|
|
217
|
-
await execute(putIssueTransaction, repoCheckTransaction)
|
|
218
|
-
|
|
219
|
-
return issueWithNumber
|
|
220
|
-
} catch (error: unknown) {
|
|
221
|
-
// Note: Counter was incremented but issue creation failed
|
|
222
|
-
// This creates a gap in issue numbers, which is acceptable
|
|
223
|
-
// (GitHub also has gaps in issue numbers)
|
|
224
|
-
handleTransactionError(error, /* ... */)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**Gap Handling:**
|
|
230
|
-
If transaction fails after counter increment, you get a gap in issue numbers. This is acceptable:
|
|
231
|
-
- GitHub has gaps in issue/PR numbers
|
|
232
|
-
- Alternative: Pre-allocate batches (more complex)
|
|
233
|
-
- Alternative: Use UUIDs instead (loses sequential ordering)
|
|
234
|
-
|
|
235
|
-
## Concurrent Issue Creation
|
|
236
|
-
|
|
237
|
-
The atomic counter pattern handles concurrent creation correctly:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// Five concurrent requests to create issues in same repo
|
|
241
|
-
const issues = Array.from({ length: 5 }, (_, i) =>
|
|
242
|
-
IssueEntity.fromRequest({
|
|
243
|
-
owner: "alice",
|
|
244
|
-
repo_name: "my-repo",
|
|
245
|
-
title: `Concurrent Issue ${i}`,
|
|
246
|
-
author: "testuser",
|
|
247
|
-
})
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
// All five execute in parallel
|
|
251
|
-
const created = await Promise.all(
|
|
252
|
-
issues.map(issue => issueRepo.create(issue))
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
// Result: Five unique sequential numbers
|
|
256
|
-
// e.g., [1, 2, 3, 4, 5] (order may vary)
|
|
257
|
-
const numbers = created.map(i => i.issueNumber)
|
|
258
|
-
console.log(new Set(numbers).size === 5) // true - all unique
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**DynamoDB Guarantees:**
|
|
262
|
-
- Each `$add(1)` operation is atomic
|
|
263
|
-
- Concurrent requests get unique sequential numbers
|
|
264
|
-
- No duplicates or race conditions
|
|
265
|
-
|
|
266
|
-
## Other Atomic Operations
|
|
267
|
-
|
|
268
|
-
### $subtract
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
current_value: $subtract(1) // Decrement by 1
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### $append
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
import { $append } from 'dynamodb-toolbox'
|
|
278
|
-
|
|
279
|
-
history: $append(['2024-01-01: Status changed to closed'])
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Appends to list attribute. Creates list with value if attribute doesn't exist.
|
|
283
|
-
|
|
284
|
-
### $remove
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
import { $remove } from 'dynamodb-toolbox'
|
|
288
|
-
|
|
289
|
-
deprecated_field: $remove() // Remove attribute entirely
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Transaction Error Handling
|
|
293
|
-
|
|
294
|
-
See [error-handling.md](error-handling.md) for detailed error handling patterns, including:
|
|
295
|
-
|
|
296
|
-
- Parsing `TransactionCanceledException` CancellationReasons
|
|
297
|
-
- Mapping to domain errors (DuplicateEntityError, EntityNotFoundError)
|
|
298
|
-
- Distinguishing which operation failed in multi-entity transactions
|
|
299
|
-
|
|
300
|
-
## Transaction Best Practices
|
|
301
|
-
|
|
302
|
-
### ✓ Do
|
|
303
|
-
|
|
304
|
-
- Use transactions for multi-entity consistency
|
|
305
|
-
- Use atomic counters for sequential numbering
|
|
306
|
-
- Keep transactions small (< 10 operations ideal)
|
|
307
|
-
- Accept gaps in sequential numbers
|
|
308
|
-
- Use `returnValues: "ALL_NEW"` to get updated values
|
|
309
|
-
|
|
310
|
-
### ✗ Don't
|
|
311
|
-
|
|
312
|
-
- Don't use transactions when single PutItem with condition suffices
|
|
313
|
-
- Don't create transactions with 50+ operations (performance cost)
|
|
314
|
-
- Don't assume transaction returns created item (must fetch separately)
|
|
315
|
-
- Don't retry blindly on transaction failure (may have partial state)
|
|
316
|
-
|
|
317
|
-
## Performance Considerations
|
|
318
|
-
|
|
319
|
-
**Transactions:**
|
|
320
|
-
- Cost: 2x write capacity units (prepares + commits)
|
|
321
|
-
- Latency: Slightly higher than single operations (~10-20ms overhead)
|
|
322
|
-
- Limit: 100 operations per transaction, 4MB total size
|
|
323
|
-
|
|
324
|
-
**Atomic Counters:**
|
|
325
|
-
- Cost: 1 WCU per increment (standard write)
|
|
326
|
-
- Latency: Sub-10ms typical
|
|
327
|
-
- Throughput: 1000 increments/second per counter partition
|
|
328
|
-
|
|
329
|
-
**Hot Counter Mitigation:**
|
|
330
|
-
If a single counter exceeds 1000 writes/second:
|
|
331
|
-
```typescript
|
|
332
|
-
// Shard the counter across multiple partitions
|
|
333
|
-
const shard = hashCode(orgId + repoId) % 10 // 10 shards
|
|
334
|
-
const counterId = `COUNTER#${orgId}#${repoId}#${shard}`
|
|
335
|
-
|
|
336
|
-
// Query all shards and sum for total count
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## Example: Complete Issue Creation Flow
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
class IssueRepository {
|
|
343
|
-
constructor(
|
|
344
|
-
private table: Table,
|
|
345
|
-
private issueRecord: IssueRecord,
|
|
346
|
-
private counterRecord: CounterRecord,
|
|
347
|
-
private repoRecord: RepoRecord
|
|
348
|
-
) {}
|
|
349
|
-
|
|
350
|
-
async create(issue: IssueEntity): Promise<IssueEntity> {
|
|
351
|
-
// Step 1: Get next issue number (atomic)
|
|
352
|
-
const issueNumber = await this.incrementCounter(
|
|
353
|
-
issue.owner,
|
|
354
|
-
issue.repoName
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
// Step 2: Create issue with number
|
|
358
|
-
const issueWithNumber = new IssueEntity({
|
|
359
|
-
...issue,
|
|
360
|
-
issueNumber,
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
// Step 3: Transaction - create issue + verify repo exists
|
|
365
|
-
const putIssue = this.issueRecord
|
|
366
|
-
.build(PutTransaction)
|
|
367
|
-
.item(issueWithNumber.toRecord())
|
|
368
|
-
.options({ condition: { attr: "PK", exists: false } })
|
|
369
|
-
|
|
370
|
-
const checkRepo = this.repoRecord
|
|
371
|
-
.build(ConditionCheck)
|
|
372
|
-
.key({ owner: issue.owner, repo_name: issue.repoName })
|
|
373
|
-
.condition({ attr: "PK", exists: true })
|
|
374
|
-
|
|
375
|
-
await execute(putIssue, checkRepo)
|
|
376
|
-
|
|
377
|
-
// Step 4: Fetch created issue
|
|
378
|
-
const created = await this.get(
|
|
379
|
-
issue.owner,
|
|
380
|
-
issue.repoName,
|
|
381
|
-
issueNumber
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
if (!created) {
|
|
385
|
-
throw new Error("Failed to retrieve created issue")
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return created
|
|
389
|
-
} catch (error: unknown) {
|
|
390
|
-
handleTransactionError(error, {
|
|
391
|
-
entityType: "IssueEntity",
|
|
392
|
-
entityKey: issueWithNumber.getEntityKey(),
|
|
393
|
-
parentEntityType: "RepositoryEntity",
|
|
394
|
-
parentEntityKey: issue.getParentEntityKey(),
|
|
395
|
-
operationName: "issue",
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
private async incrementCounter(
|
|
401
|
-
owner: string,
|
|
402
|
-
repoName: string
|
|
403
|
-
): Promise<number> {
|
|
404
|
-
const result = await this.counterRecord
|
|
405
|
-
.build(UpdateItemCommand)
|
|
406
|
-
.item({
|
|
407
|
-
org_id: owner,
|
|
408
|
-
repo_id: repoName,
|
|
409
|
-
current_value: $add(1),
|
|
410
|
-
})
|
|
411
|
-
.options({ returnValues: "ALL_NEW" })
|
|
412
|
-
.send()
|
|
413
|
-
|
|
414
|
-
return result.Attributes!.current_value
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
This pattern ensures:
|
|
420
|
-
- ✓ Unique sequential issue numbers (atomic counter)
|
|
421
|
-
- ✓ No orphaned issues (repo existence check)
|
|
422
|
-
- ✓ No duplicate issues (PK exists check)
|
|
423
|
-
- ✓ Consistent state (transaction)
|