@sqaoss/flowy 1.13.0 → 1.13.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/package.json +1 -1
- package/server/src/resolvers.test.ts +21 -9
- package/server/src/resolvers.ts +25 -11
package/package.json
CHANGED
|
@@ -1395,21 +1395,33 @@ describe('createResolvers', () => {
|
|
|
1395
1395
|
})
|
|
1396
1396
|
|
|
1397
1397
|
describe('Mutation.createEdge — edge cases', () => {
|
|
1398
|
-
it('
|
|
1398
|
+
it('is idempotent: creating the same edge twice succeeds and yields one edge', () => {
|
|
1399
1399
|
const p = create(resolvers, { type: 'project', title: 'P' })
|
|
1400
1400
|
const f = create(resolvers, { type: 'feature', title: 'F' })
|
|
1401
|
-
resolvers.Mutation.createEdge(null, {
|
|
1401
|
+
const first = resolvers.Mutation.createEdge(null, {
|
|
1402
1402
|
sourceId: f.id,
|
|
1403
1403
|
targetId: p.id,
|
|
1404
1404
|
relation: 'part_of',
|
|
1405
1405
|
})
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
).
|
|
1406
|
+
const second = resolvers.Mutation.createEdge(null, {
|
|
1407
|
+
sourceId: f.id,
|
|
1408
|
+
targetId: p.id,
|
|
1409
|
+
relation: 'part_of',
|
|
1410
|
+
})
|
|
1411
|
+
// Both calls succeed and return the same canonical edge (same created_at).
|
|
1412
|
+
expect(second).toMatchObject({
|
|
1413
|
+
sourceId: f.id,
|
|
1414
|
+
targetId: p.id,
|
|
1415
|
+
relation: 'part_of',
|
|
1416
|
+
})
|
|
1417
|
+
expect(second.createdAt).toBe(first.createdAt)
|
|
1418
|
+
// Exactly one row exists for the triple — no duplicate.
|
|
1419
|
+
const { n } = db.raw
|
|
1420
|
+
.query(
|
|
1421
|
+
'SELECT COUNT(*) AS n FROM edges WHERE source_id = ? AND target_id = ? AND relation = ?',
|
|
1422
|
+
)
|
|
1423
|
+
.get(f.id, p.id, 'part_of') as { n: number }
|
|
1424
|
+
expect(n).toBe(1)
|
|
1413
1425
|
})
|
|
1414
1426
|
|
|
1415
1427
|
it('throws when source node does not exist', () => {
|
package/server/src/resolvers.ts
CHANGED
|
@@ -778,25 +778,39 @@ export function createResolvers(db: Db, opts: ResolverOptions = {}) {
|
|
|
778
778
|
throw validationError('A node cannot block itself')
|
|
779
779
|
}
|
|
780
780
|
const now = new Date().toISOString()
|
|
781
|
+
// Idempotent (F29): re-creating an existing edge is a no-op success, not
|
|
782
|
+
// a PK-violation error. ON CONFLICT DO NOTHING leaves the original row
|
|
783
|
+
// (and its created_at) untouched; we only audit a genuinely new edge.
|
|
781
784
|
db.raw.transaction(() => {
|
|
782
|
-
db.raw.run(
|
|
783
|
-
'INSERT INTO edges (source_id, target_id, relation, created_at) VALUES (?, ?, ?, ?)',
|
|
785
|
+
const result = db.raw.run(
|
|
786
|
+
'INSERT INTO edges (source_id, target_id, relation, created_at) VALUES (?, ?, ?, ?) ON CONFLICT (source_id, target_id, relation) DO NOTHING',
|
|
784
787
|
[args.sourceId, args.targetId, args.relation, now],
|
|
785
788
|
)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
789
|
+
if (result.changes > 0) {
|
|
790
|
+
// Record the edge against its source node so `auditLog(sourceId)`
|
|
791
|
+
// surfaces it. field = relation; newValue = the target it now links.
|
|
792
|
+
insertAudit(db, {
|
|
793
|
+
nodeId: args.sourceId,
|
|
794
|
+
action: 'create_edge',
|
|
795
|
+
field: args.relation,
|
|
796
|
+
newValue: args.targetId,
|
|
797
|
+
})
|
|
798
|
+
}
|
|
794
799
|
})()
|
|
800
|
+
// Read back the canonical row so the returned createdAt reflects the
|
|
801
|
+
// existing edge on a duplicate call, not the discarded `now`.
|
|
802
|
+
const edge = db.raw
|
|
803
|
+
.query(
|
|
804
|
+
'SELECT created_at FROM edges WHERE source_id = ? AND target_id = ? AND relation = ?',
|
|
805
|
+
)
|
|
806
|
+
.get(args.sourceId, args.targetId, args.relation) as {
|
|
807
|
+
created_at: string
|
|
808
|
+
}
|
|
795
809
|
return {
|
|
796
810
|
sourceId: args.sourceId,
|
|
797
811
|
targetId: args.targetId,
|
|
798
812
|
relation: args.relation,
|
|
799
|
-
createdAt:
|
|
813
|
+
createdAt: edge.created_at,
|
|
800
814
|
}
|
|
801
815
|
},
|
|
802
816
|
|