@vibes.diy/prompts 12.1.1 → 12.1.2
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/llms/backend.md +77 -22
- package/package.json +4 -4
package/llms/backend.md
CHANGED
|
@@ -127,7 +127,8 @@ ctx.userInfo; // { userHandle } or null — who the handler is acting as
|
|
|
127
127
|
ctx.secrets; // Record<string,string> — the owner's per-vibe secrets; frozen, {} when none
|
|
128
128
|
await ctx.db.put(doc, { db: "notes", id: "optional-id" }); // resolves to the doc id AFTER commit
|
|
129
129
|
await ctx.db.delete(docId, { db: "notes" });
|
|
130
|
-
const docs = await ctx.db.query({ db: "notes" }); //
|
|
130
|
+
const docs = await ctx.db.query({ db: "notes", field: "type", key: "note", limit: 100 }); // a filtered page
|
|
131
|
+
const doc = await ctx.db.get("state:cursor", { db: "notes" }); // one doc by _id, or null
|
|
131
132
|
const text = await ctx.callAI("prompt", { model: "openrouter/auto", max_tokens: 500 }); // server-side AI call
|
|
132
133
|
const res = await ctx.fetch("https://api.example.com/x", { headers }); // outbound HTTP — use this, not bare fetch()
|
|
133
134
|
```
|
|
@@ -137,13 +138,52 @@ const res = await ctx.fetch("https://api.example.com/x", { headers }); // outbou
|
|
|
137
138
|
database that triggered the event is the default.
|
|
138
139
|
- Always `await` db calls; they resolve only after the write commits (or throw
|
|
139
140
|
when the access function denies it).
|
|
140
|
-
- `ctx.db.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
- `ctx.db.get(id, { db })` resolves the single document with that `_id`, or
|
|
142
|
+
`null` when it is missing or deleted. It is the right read whenever you can
|
|
143
|
+
name the document — a tick's state doc, a token lookup, a singleton. Never
|
|
144
|
+
scan a database looking for a doc whose id you already know.
|
|
145
|
+
- `ctx.db.query({ db, field, key, keys, range, prefix, limit, after })` reads a
|
|
146
|
+
**page** of the database's latest docs. Every filter is optional and they
|
|
147
|
+
combine: `field` names the document field to match on, then `key` (one exact
|
|
148
|
+
value), `keys` (any of several), `range` (`[from, to]`) or `prefix` (a string,
|
|
149
|
+
or an array prefix — charwise-encoded, same semantics the client's queries
|
|
150
|
+
use) narrows it. Three edges to get right: **no `field` means no filtering**
|
|
151
|
+
(you get a raw page of everything), a bare `field` with no selector still
|
|
152
|
+
drops docs that lack that field, and `prefix` takes the **raw** value — a
|
|
153
|
+
plain string or array, never anything pre-encoded (the encoding is internal).
|
|
154
|
+
`limit` only clamps the page **down**; the 2000-doc cap stays. Ask for what
|
|
155
|
+
the handler actually needs — reading the whole database and filtering in the
|
|
156
|
+
handler wastes the cap and goes blind as the app grows. Sorting still happens
|
|
157
|
+
in the handler.
|
|
158
|
+
- The result is a real `Array` of docs that **also** destructures — both
|
|
159
|
+
`const docs = await ctx.db.query(...)` and
|
|
160
|
+
`const { docs, next } = await ctx.db.query(...)` work.
|
|
161
|
+
- **Paginate on `next`, never on emptiness.** `next` is computed from the raw
|
|
162
|
+
page *before* filtering, and only when that page came back full — so a
|
|
163
|
+
narrowly filtered page can be **empty and still carry `next`**. Stopping at
|
|
164
|
+
the first empty page silently truncates the read:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
let after;
|
|
168
|
+
do {
|
|
169
|
+
const page = await ctx.db.query({ db: "faves", field: "type", key: "favorite", limit: 500, after });
|
|
170
|
+
for (const doc of page) {
|
|
171
|
+
// …
|
|
172
|
+
}
|
|
173
|
+
after = page.next;
|
|
174
|
+
} while (after);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`next` rides on the array itself, so read it **before** transforming: any
|
|
178
|
+
array operation (`.filter()`, `.map()`, spread) returns a plain array with no
|
|
179
|
+
`next`. On the last page the property is absent, not undefined — `"next" in
|
|
180
|
+
page` is `false`.
|
|
181
|
+
- Both reads are read-ACL-gated as the acting identity, through the same gate:
|
|
182
|
+
anonymous `fetch` callers are denied, and in `fetch`/`onChange` a database
|
|
183
|
+
bound to an access function cannot be read at all (keep those lanes' read
|
|
184
|
+
databases on plain ACLs). In `scheduled` (admin mode) reads are
|
|
185
|
+
**unfiltered** — access-fn-bound databases return every doc, across all users
|
|
186
|
+
and channels. Made for `scheduled` sweeps: read, decide, then `put`/`delete`.
|
|
147
187
|
- The one exception to that last sentence is the declared opt-in above,
|
|
148
188
|
`config.fetch.unfilteredReads` — a `fetch` handler that names a database there
|
|
149
189
|
reads it unfiltered, anonymous callers included, and owes the authorization
|
|
@@ -220,8 +260,8 @@ const SNAPSHOT = [
|
|
|
220
260
|
];
|
|
221
261
|
|
|
222
262
|
async function ensureSnapshot(ctx) {
|
|
223
|
-
|
|
224
|
-
if (
|
|
263
|
+
// Point-read by id — the snapshot has a known `_id`, so there's nothing to scan for.
|
|
264
|
+
if (await ctx.db.get(SNAPSHOT[0]._id, { db: "nodeReleases" })) return;
|
|
225
265
|
for (const doc of SNAPSHOT) await ctx.db.put(doc, { db: "nodeReleases" });
|
|
226
266
|
}
|
|
227
267
|
|
|
@@ -233,18 +273,19 @@ function unchanged(existing, next) {
|
|
|
233
273
|
return Object.keys(next).every((k) => existing[k] === next[k]);
|
|
234
274
|
}
|
|
235
275
|
|
|
236
|
-
|
|
237
|
-
|
|
276
|
+
// The diff source is a POINT-READ per incoming id, not a query over the stored
|
|
277
|
+
// set: the work is bounded by what the feed sent (≤20 docs), never by how big
|
|
278
|
+
// the database has grown — so a db that outgrows a query page can never
|
|
279
|
+
// silently eject rows from the diff and turn this tick into a rewrite loop.
|
|
280
|
+
async function putIfChanged(ctx, next) {
|
|
281
|
+
const existing = await ctx.db.get(next._id, { db: "nodeReleases" });
|
|
282
|
+
if (unchanged(existing, next)) return;
|
|
238
283
|
await ctx.db.put(next, { db: "nodeReleases" });
|
|
239
284
|
}
|
|
240
285
|
|
|
241
286
|
export async function scheduled(event, ctx) {
|
|
242
287
|
await ensureSnapshot(ctx);
|
|
243
288
|
|
|
244
|
-
// Read current docs once so each write can diff against what's already stored.
|
|
245
|
-
const current = await ctx.db.query({ db: "nodeReleases" });
|
|
246
|
-
const byId = new Map(current.map((doc) => [doc._id, doc]));
|
|
247
|
-
|
|
248
289
|
// One request, no retry storm; ctx.fetch enforces 15s/10MB and egress caps.
|
|
249
290
|
const response = await ctx.fetch("https://api.github.com/repos/nodejs/node/releases?per_page=20", {
|
|
250
291
|
headers: { accept: "application/vnd.github+json" },
|
|
@@ -258,7 +299,7 @@ export async function scheduled(event, ctx) {
|
|
|
258
299
|
if (denied?.vibesEgressDenied === true) {
|
|
259
300
|
// A real state change — putIfChanged writes once on entering denial, then
|
|
260
301
|
// nothing while it persists. No per-tick timestamp, so no churn.
|
|
261
|
-
await putIfChanged(ctx,
|
|
302
|
+
await putIfChanged(ctx, {
|
|
262
303
|
_id: "refresh:status",
|
|
263
304
|
type: "refreshStatus",
|
|
264
305
|
state: "egress-denied",
|
|
@@ -276,7 +317,7 @@ export async function scheduled(event, ctx) {
|
|
|
276
317
|
for (const row of rows.slice(0, 20)) {
|
|
277
318
|
if (!row?.id || !row?.html_url || !row?.published_at) continue;
|
|
278
319
|
// Stable per-item `_id` + diff: a row whose content is identical is skipped.
|
|
279
|
-
await putIfChanged(ctx,
|
|
320
|
+
await putIfChanged(ctx, {
|
|
280
321
|
_id: `release:github-${row.id}`,
|
|
281
322
|
type: "release",
|
|
282
323
|
title: String(row.name || row.tag_name || "Node.js release"),
|
|
@@ -286,7 +327,7 @@ export async function scheduled(event, ctx) {
|
|
|
286
327
|
});
|
|
287
328
|
}
|
|
288
329
|
|
|
289
|
-
await putIfChanged(ctx,
|
|
330
|
+
await putIfChanged(ctx, {
|
|
290
331
|
_id: "refresh:status",
|
|
291
332
|
type: "refreshStatus",
|
|
292
333
|
state: "fresh",
|
|
@@ -337,15 +378,29 @@ down. Catch that exact 403 shape and show the saved snapshot with honest copy
|
|
|
337
378
|
render a broken screen, or silently label saved data as fresh.
|
|
338
379
|
|
|
339
380
|
**Zero-churn discipline for scheduled ticks.** A cron runs forever, so it must
|
|
340
|
-
not write forever.
|
|
341
|
-
|
|
342
|
-
|
|
381
|
+
not write forever. Give every item a stable `_id` and `put` **only** the rows
|
|
382
|
+
whose content actually changed — the `putIfChanged` diff above, which
|
|
383
|
+
point-reads each id it is about to write. Diff against **point-reads of the
|
|
384
|
+
incoming ids**, so the work is bounded by what arrived this tick, not by how
|
|
385
|
+
big the database has grown; reach for a keyed `ctx.db.query` only when the
|
|
386
|
+
stored set itself is the target (and then walk `next` to exhaustion before
|
|
387
|
+
diffing — a one-page diff source re-puts everything the page missed, forever). Never stamp a per-tick `checkedAt`/`updatedAt`/run
|
|
343
388
|
counter onto a doc: it makes every put look new, and each spurious put mints a
|
|
344
389
|
fresh revision and a sync-feed row that re-tickles every replica on every tick.
|
|
345
390
|
A failed or empty upstream fetch aborts with no writes, preserving the last-good
|
|
346
391
|
rows. Only a genuine content change — a new or updated row, a real state
|
|
347
392
|
transition — earns a write.
|
|
348
393
|
|
|
394
|
+
**A tick's own memory is a document it reads by id.** Whatever a scheduled
|
|
395
|
+
handler needs to know about what it already did — a cursor, a last-synced
|
|
396
|
+
marker, a per-run state — lives in one doc with a fixed `_id`, read with
|
|
397
|
+
`ctx.db.get(id, { db })`. Never re-derive that answer by scanning the database:
|
|
398
|
+
a sweep that diffs against its own whole-db read goes blind the moment the
|
|
399
|
+
database outgrows one page, and then rewrites everything on every tick, forever.
|
|
400
|
+
Keep a copy of the state in module scope as well, and read a **missing or failed**
|
|
401
|
+
state read as *"don't know — skip this tick"*, never as *"nothing has been done
|
|
402
|
+
yet"*: that second reading is exactly what restarts the endless rewrite.
|
|
403
|
+
|
|
349
404
|
## fetch — the app's HTTP endpoint
|
|
350
405
|
|
|
351
406
|
Runs for requests to the app's `/_api` route. The request path is rooted after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "12.1.
|
|
3
|
+
"version": "12.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@adviser/cement": "~0.5.34",
|
|
27
|
-
"@vibes.diy/call-ai-v2": "^12.1.
|
|
28
|
-
"@vibes.diy/identity": "^12.1.
|
|
29
|
-
"@vibes.diy/use-vibes-types": "^12.1.
|
|
27
|
+
"@vibes.diy/call-ai-v2": "^12.1.2",
|
|
28
|
+
"@vibes.diy/identity": "^12.1.2",
|
|
29
|
+
"@vibes.diy/use-vibes-types": "^12.1.2",
|
|
30
30
|
"arktype": "~2.2.3",
|
|
31
31
|
"json-schema-faker": "~0.6.2"
|
|
32
32
|
},
|