@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zooid/server",
3
- "version": "0.0.9",
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.9",
25
- "@zooid/web": "0.0.9"
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) : null;
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
- name = COALESCE(?, server_meta.name),
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',