@zooid/server 0.0.9 → 0.0.10
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 +3 -3
- package/src/db/queries.ts +36 -16
- package/src/routes/server-meta.test.ts +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zooid/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ori Ben",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"ulidx": "^2.4.1",
|
|
22
22
|
"yaml": "^2.8.2",
|
|
23
23
|
"zod": "^4.3.6",
|
|
24
|
-
"@zooid/types": "0.0.
|
|
25
|
-
"@zooid/web": "0.0.
|
|
24
|
+
"@zooid/types": "0.0.10",
|
|
25
|
+
"@zooid/web": "0.0.10"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@cloudflare/vitest-pool-workers": "^0.12.13",
|
package/src/db/queries.ts
CHANGED
|
@@ -422,35 +422,55 @@ export async function upsertServerMeta(
|
|
|
422
422
|
email?: string | null;
|
|
423
423
|
},
|
|
424
424
|
): Promise<ServerIdentity> {
|
|
425
|
-
const tags = meta.tags ? JSON.stringify(meta.tags) :
|
|
425
|
+
const tags = meta.tags !== undefined ? JSON.stringify(meta.tags) : undefined;
|
|
426
|
+
|
|
427
|
+
// Build dynamic SET clause — only update fields that were provided.
|
|
428
|
+
// This distinguishes undefined (not provided → keep existing) from null (clear it).
|
|
429
|
+
const setClauses: string[] = ["updated_at = datetime('now')"];
|
|
430
|
+
const setBinds: (string | null)[] = [];
|
|
431
|
+
|
|
432
|
+
if (meta.name !== undefined) {
|
|
433
|
+
setClauses.push('name = ?');
|
|
434
|
+
setBinds.push(meta.name);
|
|
435
|
+
}
|
|
436
|
+
if (meta.description !== undefined) {
|
|
437
|
+
setClauses.push('description = ?');
|
|
438
|
+
setBinds.push(meta.description);
|
|
439
|
+
}
|
|
440
|
+
if (tags !== undefined) {
|
|
441
|
+
setClauses.push('tags = ?');
|
|
442
|
+
setBinds.push(tags);
|
|
443
|
+
}
|
|
444
|
+
if (meta.owner !== undefined) {
|
|
445
|
+
setClauses.push('owner = ?');
|
|
446
|
+
setBinds.push(meta.owner);
|
|
447
|
+
}
|
|
448
|
+
if (meta.company !== undefined) {
|
|
449
|
+
setClauses.push('company = ?');
|
|
450
|
+
setBinds.push(meta.company);
|
|
451
|
+
}
|
|
452
|
+
if (meta.email !== undefined) {
|
|
453
|
+
setClauses.push('email = ?');
|
|
454
|
+
setBinds.push(meta.email);
|
|
455
|
+
}
|
|
426
456
|
|
|
427
457
|
await db
|
|
428
458
|
.prepare(
|
|
429
459
|
`INSERT INTO server_meta (id, name, description, tags, owner, company, email, updated_at)
|
|
430
460
|
VALUES (1, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
431
461
|
ON CONFLICT(id) DO UPDATE SET
|
|
432
|
-
|
|
433
|
-
description = ?,
|
|
434
|
-
tags = ?,
|
|
435
|
-
owner = ?,
|
|
436
|
-
company = ?,
|
|
437
|
-
email = ?,
|
|
438
|
-
updated_at = datetime('now')`,
|
|
462
|
+
${setClauses.join(',\n ')}`,
|
|
439
463
|
)
|
|
440
464
|
.bind(
|
|
465
|
+
// INSERT values (use defaults for unspecified fields)
|
|
441
466
|
meta.name ?? 'Zooid',
|
|
442
467
|
meta.description ?? null,
|
|
443
|
-
tags,
|
|
444
|
-
meta.owner ?? null,
|
|
445
|
-
meta.company ?? null,
|
|
446
|
-
meta.email ?? null,
|
|
447
|
-
// ON CONFLICT values
|
|
448
|
-
meta.name ?? null,
|
|
449
|
-
meta.description ?? null,
|
|
450
|
-
tags,
|
|
468
|
+
tags ?? null,
|
|
451
469
|
meta.owner ?? null,
|
|
452
470
|
meta.company ?? null,
|
|
453
471
|
meta.email ?? null,
|
|
472
|
+
// ON CONFLICT SET values (only the provided fields)
|
|
473
|
+
...setBinds,
|
|
454
474
|
)
|
|
455
475
|
.run();
|
|
456
476
|
|
|
@@ -126,6 +126,57 @@ describe('Server meta routes', () => {
|
|
|
126
126
|
expect(body.owner).toBe('bob');
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
it('preserves fields not included in partial update', async () => {
|
|
130
|
+
await authRequest('/api/v1/server', {
|
|
131
|
+
method: 'PUT',
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
name: 'My Server',
|
|
134
|
+
description: 'Original desc',
|
|
135
|
+
tags: ['ai'],
|
|
136
|
+
owner: 'alice',
|
|
137
|
+
company: 'Acme',
|
|
138
|
+
email: 'alice@acme.com',
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Update only description — all other fields should be preserved
|
|
143
|
+
const res = await authRequest('/api/v1/server', {
|
|
144
|
+
method: 'PUT',
|
|
145
|
+
body: JSON.stringify({ description: 'Updated desc' }),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(res.status).toBe(200);
|
|
149
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
150
|
+
expect(body.description).toBe('Updated desc');
|
|
151
|
+
expect(body.name).toBe('My Server');
|
|
152
|
+
expect(body.tags).toEqual(['ai']);
|
|
153
|
+
expect(body.owner).toBe('alice');
|
|
154
|
+
expect(body.company).toBe('Acme');
|
|
155
|
+
expect(body.email).toBe('alice@acme.com');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('allows explicitly clearing a field with null', async () => {
|
|
159
|
+
await authRequest('/api/v1/server', {
|
|
160
|
+
method: 'PUT',
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
name: 'My Server',
|
|
163
|
+
description: 'Some desc',
|
|
164
|
+
owner: 'alice',
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const res = await authRequest('/api/v1/server', {
|
|
169
|
+
method: 'PUT',
|
|
170
|
+
body: JSON.stringify({ description: null }),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(res.status).toBe(200);
|
|
174
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
175
|
+
expect(body.description).toBeNull();
|
|
176
|
+
expect(body.name).toBe('My Server');
|
|
177
|
+
expect(body.owner).toBe('alice');
|
|
178
|
+
});
|
|
179
|
+
|
|
129
180
|
it('rejects without auth', async () => {
|
|
130
181
|
const res = await app.request(
|
|
131
182
|
'/api/v1/server',
|