aws-samples / aws-samples/sample-collaborative-ai-dlc

refactor: optimise Gremlin CRUD queries

Open
#35 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
75
Forks
23
Avg merge
3d 17h
Merged PRs (30d)
24

Description

The lambda CRUD handlers that talk to Neptune share a few patterns that could be tightened up. This issue tracks a general cleanup across all of them.

### 1. Let Neptune generate vertex IDs

Handlers currently generate a UUID in JS and store it as a custom `id` property on the vertex. Neptune already assigns a `T.id` to every vertex, so the custom `id` property is redundant — it costs an extra property write and index entry per node, and reads have to do `.has('TimelineEvent', 'id', id)` instead of Neptune's native `g.V(id)` lookup.

Example — the current `timeline-events` POST handler:

```js
const id = randomUUID();

await g.V().has('Sprint', 'id', sprintId).as('s')
.addV('TimelineEvent')
.property('id', id)
.property('type', data.type)
// ...more properties
.as('e')
.addE('HAS_TIMELINE_EVENT').from_('s').to('e')
.next();

return res(201, { id, /* ... */ });
```

Could become:

```js
const { value: { id } } = await g.V(sprintId).as('s')
.addV('TimelineEvent')
.property('type', data.type)
// ...more properties
.as('e')
.addE('HAS_TIMELINE_EVENT').from_('s').to('e')
.select('e')
.project('id').by(t.id)
.next();

return res(201, { id, /* ... */ });
```

Reads similarly switch from `.has('TimelineEvent', 'id', id)` to `g.V(id)`.

### 2. Use `mergeV` + `.project(...).by(...)` for writes and reads

Reads currently use `valueMap()` and then a JS mapper rebuilds the response shape. Writes chain a dozen `.property(...)` calls and then return a hand-built object that duplicates every field. Both can collapse: writes can hand Neptune a property object via `mergeV`, and the returned shape can come from a single Gremlin `.project(...).by(...)` that selects exactly the fields the API contract exposes.

Note that `mergeV` is an upsert — if nothing matches the search criteria it creates, otherwise it matches. For a POST this is effectively always a create, since the criteria include the freshly-generated `T.id`.

Stored property names stay snake_case to match the existing schema (see `lambda/agents-ecs/mcp-server-graph/index.js`); the camelCase API shape is produced by the `.project(...).by(...)` aliases.

Example — the current `timeline-events` POST handler:

```js
const id = randomUUID();
const timestamp = data.timestamp || new Date().toISOString();

await g.V().has('Sprint', 'id', sprintId).as('s')
.addV('TimelineEvent')
.property('id', id)
.property('type', data.type)
.property('title', data.title)
.property('detail', data.detail || '')
.property('user_id', data.userId || '')
.property('user_name', data.userName || '')
.property('timestamp', timestamp)
.property('sprint_id', sprintId)
.as('e')
.addE('HAS_TIMELINE_EVENT').from_('s').to('e')
.next();

return res(201, {
id,
type: data.type,
title: data.title,
detail: data.detail || '',
userId: data.userId || '',
userName: data.userName || '',
timestamp,
sprintId,
});
```

Could become:

```js
const { value: event } = await g.V(sprintId).as('s')
.mergeV({
[t.label]: 'TimelineEvent',
type: data.type,
title: data.title,
detail: data.detail ?? '',
user_id: data.userId ?? '',
user_name: data.userName ?? '',
timestamp: data.timestamp ?? new Date().toISOString(),
sprint_id: sprintId,
}).as('e')
.addE('HAS_TIMELINE_EVENT').from_('s').to('e')
.select('e')
.project('id', 'type', 'title', 'detail', 'userId', 'userName', 'timestamp', 'sprintId')
.by(t.id)
.by('type')
.by('title')
.by('detail')
.by('user_id')
.by('user_name')
.by('timestamp')
.by('sprint_id')
.next();

return res(201, event);
```

Benefits:
- properties defined once in a plain object literal instead of a `.property()` chain,
- no manual UUID generation or duplicate `id` property,
- no per-handler mapper functions,
- no duplicated response object,
- camelCase API shape produced by `.project(...)` aliases instead of a separate JS mapper,
- same pattern works for reads and for the "return the thing I just wrote" case.

### Scope

The CRUD handlers that follow this pattern: `timeline-events`, `artifacts`, `general-info`, `tasks`, `sprints`, `projects`, `code-files`, `requirements`, `user-stories`, `questions`, `reviews`. Parts of `agents` also fit.

API response shapes should stay the same — this is an internal refactor, not a contract change.

Contributor guide

Open the contributing guide

Research direction

Start with the timeline-events CRUD handler and compare its current Gremlin operations with the schema reference in lambda/agents-ecs/mcp-server-graph/index.js. Then inspect the listed handlers—artifacts, general-info, tasks, sprints, projects, code-files, requirements, user-stories, questions, reviews, and applicable agents—for the same patterns. Done means native vertex IDs, mergeV/project-based operations, and unchanged API response shapes across the in-scope handlers.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
backend, databases
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.