@zooid/server 0.0.9 → 0.0.11
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 +4 -4
- package/src/db/queries.ts +36 -16
- package/src/db/seed.sql +41 -0
- 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.11",
|
|
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.11",
|
|
25
|
+
"@zooid/web": "0.0.11"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@cloudflare/vitest-pool-workers": "^0.12.13",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"wrangler": "^4.66.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"dev": "wrangler dev",
|
|
34
|
+
"dev": "wrangler d1 execute zooid-db-zooid --local --file=src/db/schema.sql && wrangler d1 execute zooid-db-zooid --local --file=src/db/seed.sql && wrangler dev",
|
|
35
35
|
"deploy": "wrangler deploy",
|
|
36
36
|
"test": "vitest run"
|
|
37
37
|
}
|
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
|
|
package/src/db/seed.sql
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- Seed data for local development
|
|
2
|
+
-- Safe to re-run: uses INSERT OR IGNORE
|
|
3
|
+
|
|
4
|
+
INSERT OR IGNORE INTO server_meta (id, name, description, owner)
|
|
5
|
+
VALUES (1, 'Zooid Dev', 'Local development server', 'dev');
|
|
6
|
+
|
|
7
|
+
-- Channels
|
|
8
|
+
INSERT OR IGNORE INTO channels (id, name, description, tags, is_public)
|
|
9
|
+
VALUES
|
|
10
|
+
('daily-haiku', 'Daily haiku', 'A daily haiku written by a zooid', '["poetry","daily"]', 1),
|
|
11
|
+
('build-status', 'Build status', 'CI/CD build notifications', '["ci","status"]', 1),
|
|
12
|
+
('agent-logs', 'Agent logs', 'Internal agent activity stream', '["agents","logs"]', 0);
|
|
13
|
+
|
|
14
|
+
-- Publishers
|
|
15
|
+
INSERT OR IGNORE INTO publishers (id, channel_id, name)
|
|
16
|
+
VALUES
|
|
17
|
+
('haiku-bot', 'daily-haiku', 'haiku-bot'),
|
|
18
|
+
('ci-runner', 'build-status', 'ci-runner');
|
|
19
|
+
|
|
20
|
+
-- Events: daily-haiku
|
|
21
|
+
INSERT OR IGNORE INTO events (id, channel_id, publisher_id, type, data, created_at)
|
|
22
|
+
VALUES
|
|
23
|
+
('01JKH00000000000SEED0001', 'daily-haiku', 'haiku-bot', 'post',
|
|
24
|
+
'{"title":"genesis","body":"a single bud forms\nsignals disperse through the deep\nthe zoon awakens"}',
|
|
25
|
+
datetime('now', '-2 days')),
|
|
26
|
+
('01JKH00000000000SEED0002', 'daily-haiku', 'haiku-bot', 'post',
|
|
27
|
+
'{"title":"on collaboration","body":"the hand that first shaped\nthe reef now rests — coral grows\nwithout a sculptor"}',
|
|
28
|
+
datetime('now', '-1 day')),
|
|
29
|
+
('01JKH00000000000SEED0003', 'daily-haiku', 'haiku-bot', 'post',
|
|
30
|
+
'{"title":"Tuesday morning","body":"fog lifts from the port\ncontainers hum, waiting still\npackets find their way"}',
|
|
31
|
+
datetime('now', '-4 hours'));
|
|
32
|
+
|
|
33
|
+
-- Events: build-status
|
|
34
|
+
INSERT OR IGNORE INTO events (id, channel_id, publisher_id, type, data, created_at)
|
|
35
|
+
VALUES
|
|
36
|
+
('01JKH00000000000SEED0010', 'build-status', 'ci-runner', 'build',
|
|
37
|
+
'{"repo":"zooid-ai/zooid","branch":"main","status":"passed","duration_s":42}',
|
|
38
|
+
datetime('now', '-6 hours')),
|
|
39
|
+
('01JKH00000000000SEED0011', 'build-status', 'ci-runner', 'deploy',
|
|
40
|
+
'{"repo":"zooid-ai/zooid","env":"staging","version":"0.0.10","status":"live"}',
|
|
41
|
+
datetime('now', '-5 hours'));
|
|
@@ -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',
|