@tulipes/core 0.1.2 → 0.1.4
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/README.md +17 -1
- package/dist/cli/init.d.ts +17 -8
- package/dist/cli/init.js +651 -70
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +6 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/http/error-handler.js +21 -0
- package/dist/http/error-handler.js.map +1 -1
- package/package.json +3 -2
- package/templates/CLAUDE.md +72 -0
- package/templates/claude/skills/tulipes-boot-errors/SKILL.md +64 -0
- package/templates/claude/skills/tulipes-endpoint/SKILL.md +82 -0
- package/templates/claude/skills/tulipes-env-variable/SKILL.md +78 -0
- package/templates/claude/skills/tulipes-model/SKILL.md +75 -0
- package/templates/claude/skills/tulipes-module/SKILL.md +60 -0
- package/templates/claude/skills/tulipes-permissions/SKILL.md +61 -0
- package/templates/claude/skills/tulipes-queue/SKILL.md +62 -0
- package/templates/claude/skills/tulipes-socket/SKILL.md +55 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tulipes-queue
|
|
3
|
+
description: Use when adding background work to a Tulipes app — emails, webhooks, exports, scheduled retries — or when a request handler is doing something slow. Covers the queues contract, producing jobs, and the web/worker process split.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding a background job
|
|
7
|
+
|
|
8
|
+
One file declares both sides of a queue, and the process mode decides what
|
|
9
|
+
happens with it: the **web** process registers the queue so routes can
|
|
10
|
+
produce into it; the **worker** process turns the processor into a live
|
|
11
|
+
consumer. Never duplicate the definition.
|
|
12
|
+
|
|
13
|
+
`modules/billing/queues/invoices.queues.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import type { Ctx } from "@tulipes/core/boot";
|
|
17
|
+
import type { QueueRegistry } from "@tulipes/core/queues";
|
|
18
|
+
|
|
19
|
+
export default function invoiceQueues({ models }: Ctx, queues: QueueRegistry): void {
|
|
20
|
+
queues.define("billing.send-invoice");
|
|
21
|
+
|
|
22
|
+
queues.process("billing.send-invoice", async (job) => {
|
|
23
|
+
const invoice = await models!.get("Invoice").findById(job.data.invoiceId);
|
|
24
|
+
// …do the slow thing here…
|
|
25
|
+
return { sent: true };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Produce from anywhere with the context:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
await queues!.add("billing.send-invoice", "send", { invoiceId: invoice.id });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Run a consumer with `yarn worker`. Without it, jobs pile up in Redis and
|
|
37
|
+
nothing processes them — that is the correct behaviour, not a bug.
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
- **Queue names use `.`, never `:`** (`billing.send-invoice`). BullMQ
|
|
42
|
+
reserves `:` as its Redis key separator; a `:` name crashes the boot.
|
|
43
|
+
- Names are globally unique, and a queue may have only one processor —
|
|
44
|
+
duplicates crash the boot naming both modules.
|
|
45
|
+
- A processor registered for a queue nobody defined is a boot error; call
|
|
46
|
+
`define()` in the same file.
|
|
47
|
+
- Requires `REDIS_URL` declared (sys `core` module) and set. Queue files
|
|
48
|
+
without it are a boot refusal.
|
|
49
|
+
- Worker mode with zero processors refuses to start: a consumer with
|
|
50
|
+
nothing to consume is a misconfigured deployment.
|
|
51
|
+
|
|
52
|
+
## Writing a processor
|
|
53
|
+
|
|
54
|
+
- **Jobs retry.** Make the handler idempotent — the same job may run twice
|
|
55
|
+
after a crash or timeout.
|
|
56
|
+
- Pass identifiers in `job.data`, not whole documents; re-read from the
|
|
57
|
+
database inside the processor so the job acts on current state.
|
|
58
|
+
- Keep payloads small and JSON-serializable.
|
|
59
|
+
- Throwing marks the job failed and schedules a retry per its options; let
|
|
60
|
+
it throw rather than swallowing errors.
|
|
61
|
+
- Both processes run the full boot pipeline, so `models`, `config` and
|
|
62
|
+
`Environment` are all available inside a processor.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tulipes-socket
|
|
3
|
+
description: Use when adding realtime features to a Tulipes app — websockets, live updates, presence, notifications — or when emitting events from a route or background job. Covers the sockets contract and namespace ownership.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding realtime
|
|
7
|
+
|
|
8
|
+
Socket.IO namespaces are claimed by modules, one owner each, in
|
|
9
|
+
`modules/<name>/sockets/*.sockets.ts`. Websockets are opt-in: an app with
|
|
10
|
+
no socket files starts no Socket.IO server at all.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import type { Ctx } from "@tulipes/core/boot";
|
|
14
|
+
import type { SocketRegistry } from "@tulipes/core/sockets";
|
|
15
|
+
|
|
16
|
+
export default function billingSockets({ config }: Ctx, sockets: SocketRegistry): void {
|
|
17
|
+
sockets.namespace("/billing", (nsp) => {
|
|
18
|
+
nsp.on("connection", (socket) => {
|
|
19
|
+
socket.emit("ready", { plan: config.billing?.plan });
|
|
20
|
+
|
|
21
|
+
socket.on("subscribe", (invoiceId: string) => {
|
|
22
|
+
socket.join(`invoice:${invoiceId}`);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Emit from anywhere with the context — a route, a bootstrap task, a queue
|
|
30
|
+
processor in the web process:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
ctx.sockets!.of("/billing").to(`invoice:${id}`).emit("invoice:paid", payload);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
|
|
38
|
+
- **Namespaces are claimed once.** A second module claiming `/billing`
|
|
39
|
+
crashes the boot naming both.
|
|
40
|
+
- `sockets.of()` throws on a namespace nobody claimed. Socket.IO's raw
|
|
41
|
+
`io.of()` would silently create it and emit into the void — use the
|
|
42
|
+
framework accessor so typos surface.
|
|
43
|
+
- Sockets exist in **web mode only**. `ctx.sockets` is undefined in the
|
|
44
|
+
worker process, so a queue processor cannot emit directly — publish to
|
|
45
|
+
Redis, or have the web process subscribe.
|
|
46
|
+
- Namespace names are lowercase kebab-case with a leading slash.
|
|
47
|
+
|
|
48
|
+
## Gotchas
|
|
49
|
+
|
|
50
|
+
- Authenticate in namespace middleware (`nsp.use(...)`), not per event —
|
|
51
|
+
the framework's HTTP pipeline does not run for socket connections.
|
|
52
|
+
- Scaling past one web process needs the Socket.IO Redis adapter;
|
|
53
|
+
otherwise an emit only reaches clients connected to that instance.
|
|
54
|
+
- Socket handlers run outside the request pipeline: no `HttpError`, no
|
|
55
|
+
request logging. Handle failures explicitly and emit an error event.
|