@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.
@@ -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.