gemi 0.58.1 → 0.60.0
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/dist/ai/Agent.d.ts +33 -0
- package/dist/ai/Agent.d.ts.map +1 -0
- package/dist/ai/AgentController.d.ts +8 -0
- package/dist/ai/AgentController.d.ts.map +1 -0
- package/dist/ai/useChat.d.ts +12 -0
- package/dist/ai/useChat.d.ts.map +1 -0
- package/dist/app/index.js +1 -1
- package/dist/bin/gemi.js +500 -16
- package/dist/bin/gemi.js.map +10 -5
- package/dist/broadcasting/index.js +1 -1
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/{chunk-szss069z.js → chunk-06j6rsew.js} +2 -2
- package/dist/{chunk-szss069z.js.map → chunk-06j6rsew.js.map} +1 -1
- package/dist/{chunk-yjzs247s.js → chunk-0fm6jh9b.js} +2 -2
- package/dist/{chunk-yjzs247s.js.map → chunk-0fm6jh9b.js.map} +1 -1
- package/dist/chunk-23h0dmx2.js +5 -0
- package/dist/{chunk-87qab82w.js.map → chunk-23h0dmx2.js.map} +3 -3
- package/dist/{chunk-m3xy5xyf.js → chunk-2cwcfwg3.js} +2 -2
- package/dist/{chunk-m3xy5xyf.js.map → chunk-2cwcfwg3.js.map} +1 -1
- package/dist/{chunk-xzk827r3.js → chunk-2khdxyjb.js} +2 -2
- package/dist/{chunk-xzk827r3.js.map → chunk-2khdxyjb.js.map} +1 -1
- package/dist/{chunk-sy7jbdeb.js → chunk-3296fwss.js} +2 -2
- package/dist/{chunk-sy7jbdeb.js.map → chunk-3296fwss.js.map} +1 -1
- package/dist/{chunk-xdv1b8mr.js → chunk-3gvjn3q4.js} +2 -2
- package/dist/{chunk-xdv1b8mr.js.map → chunk-3gvjn3q4.js.map} +1 -1
- package/dist/{chunk-jhkjz9jr.js → chunk-4dan69s4.js} +2 -2
- package/dist/{chunk-3zxwscmf.js.map → chunk-4dan69s4.js.map} +1 -1
- package/dist/{chunk-hxf1re93.js → chunk-4mcyyh1v.js} +2 -2
- package/dist/{chunk-hxf1re93.js.map → chunk-4mcyyh1v.js.map} +1 -1
- package/dist/{chunk-w62m5f0n.js → chunk-5gdv4n4a.js} +3 -3
- package/dist/{chunk-w62m5f0n.js.map → chunk-5gdv4n4a.js.map} +1 -1
- package/dist/{chunk-j0c6ytkj.js → chunk-8kj3zrm9.js} +3 -3
- package/dist/{chunk-j0c6ytkj.js.map → chunk-8kj3zrm9.js.map} +1 -1
- package/dist/{chunk-0a2xgcj3.js → chunk-98a3k7bp.js} +2 -2
- package/dist/{chunk-0a2xgcj3.js.map → chunk-98a3k7bp.js.map} +1 -1
- package/dist/{chunk-5athahgr.js → chunk-9gsdcjt7.js} +2 -2
- package/dist/{chunk-5athahgr.js.map → chunk-9gsdcjt7.js.map} +1 -1
- package/dist/{chunk-pmhd6zfc.js → chunk-b0t4zp6b.js} +2 -2
- package/dist/{chunk-pmhd6zfc.js.map → chunk-b0t4zp6b.js.map} +1 -1
- package/dist/{chunk-3xadx444.js → chunk-bn1v4sfs.js} +2 -2
- package/dist/{chunk-3xadx444.js.map → chunk-bn1v4sfs.js.map} +1 -1
- package/dist/{chunk-9m2tbf3n.js → chunk-c40n5r4v.js} +2 -2
- package/dist/{chunk-9m2tbf3n.js.map → chunk-c40n5r4v.js.map} +1 -1
- package/dist/{chunk-a2sgjpvq.js → chunk-cejf873g.js} +2 -2
- package/dist/{chunk-a2sgjpvq.js.map → chunk-cejf873g.js.map} +1 -1
- package/dist/{chunk-tss5svjr.js → chunk-cw9y6k15.js} +2 -2
- package/dist/{chunk-tss5svjr.js.map → chunk-cw9y6k15.js.map} +1 -1
- package/dist/{chunk-5n2rvfh3.js → chunk-cyrxdj6a.js} +2 -2
- package/dist/{chunk-5n2rvfh3.js.map → chunk-cyrxdj6a.js.map} +1 -1
- package/dist/chunk-dgasxgsm.js +19 -0
- package/dist/{chunk-hwa5sqw5.js.map → chunk-dgasxgsm.js.map} +6 -6
- package/dist/{chunk-3g5bjvdf.js → chunk-f6dd4gd8.js} +2 -2
- package/dist/{chunk-3g5bjvdf.js.map → chunk-f6dd4gd8.js.map} +1 -1
- package/dist/{chunk-qgxr0g36.js → chunk-fb4cy6sq.js} +2 -2
- package/dist/{chunk-qgxr0g36.js.map → chunk-fb4cy6sq.js.map} +1 -1
- package/dist/{chunk-7ef5n8k2.js → chunk-fbvvqf9b.js} +2 -2
- package/dist/{chunk-7ef5n8k2.js.map → chunk-fbvvqf9b.js.map} +1 -1
- package/dist/{chunk-zbxgbr12.js → chunk-fxy42w6n.js} +2 -2
- package/dist/{chunk-zbxgbr12.js.map → chunk-fxy42w6n.js.map} +1 -1
- package/dist/{chunk-xey9cbap.js → chunk-get4mkx8.js} +2 -2
- package/dist/{chunk-xey9cbap.js.map → chunk-get4mkx8.js.map} +1 -1
- package/dist/{chunk-3zxwscmf.js → chunk-gwchvzdp.js} +2 -2
- package/dist/{chunk-jhkjz9jr.js.map → chunk-gwchvzdp.js.map} +1 -1
- package/dist/{chunk-k0fvsyeh.js → chunk-hsfjt13m.js} +1 -1
- package/dist/{chunk-yf7vz71n.js → chunk-hwhw98hc.js} +1 -1
- package/dist/{chunk-fjm4y8bn.js → chunk-hxb1eg2z.js} +2 -2
- package/dist/{chunk-fjm4y8bn.js.map → chunk-hxb1eg2z.js.map} +1 -1
- package/dist/{chunk-7b0x860b.js → chunk-j06g4sqc.js} +2 -2
- package/dist/{chunk-7b0x860b.js.map → chunk-j06g4sqc.js.map} +1 -1
- package/dist/{chunk-dgsgjg53.js → chunk-j23cengk.js} +2 -2
- package/dist/{chunk-dgsgjg53.js.map → chunk-j23cengk.js.map} +1 -1
- package/dist/{chunk-vj6f2bmb.js → chunk-khf9xda6.js} +3 -3
- package/dist/{chunk-vj6f2bmb.js.map → chunk-khf9xda6.js.map} +1 -1
- package/dist/{chunk-437085pe.js → chunk-p3qd0gqa.js} +2 -2
- package/dist/{chunk-437085pe.js.map → chunk-p3qd0gqa.js.map} +1 -1
- package/dist/{chunk-gw6agevz.js → chunk-pkjq9833.js} +3 -3
- package/dist/{chunk-gw6agevz.js.map → chunk-pkjq9833.js.map} +1 -1
- package/dist/{chunk-7j6wbv12.js → chunk-q0y0j3ne.js} +2 -2
- package/dist/{chunk-7j6wbv12.js.map → chunk-q0y0j3ne.js.map} +1 -1
- package/dist/chunk-q6qghsy7.js +5 -0
- package/dist/{chunk-b35e128b.js.map → chunk-q6qghsy7.js.map} +1 -1
- package/dist/{chunk-wbrj0gya.js → chunk-qva4841r.js} +2 -2
- package/dist/{chunk-wbrj0gya.js.map → chunk-qva4841r.js.map} +1 -1
- package/dist/{chunk-86jebsm4.js → chunk-rkbv3df7.js} +3 -3
- package/dist/{chunk-86jebsm4.js.map → chunk-rkbv3df7.js.map} +1 -1
- package/dist/{chunk-keehyx51.js → chunk-rpjsmr41.js} +2 -2
- package/dist/{chunk-keehyx51.js.map → chunk-rpjsmr41.js.map} +1 -1
- package/dist/{chunk-mwpdp09e.js → chunk-spbgpndn.js} +2 -2
- package/dist/{chunk-mwpdp09e.js.map → chunk-spbgpndn.js.map} +1 -1
- package/dist/{chunk-yy0eb9wn.js → chunk-stq96kya.js} +2 -2
- package/dist/{chunk-yy0eb9wn.js.map → chunk-stq96kya.js.map} +1 -1
- package/dist/{chunk-d125j8t0.js → chunk-tds3xq6b.js} +3 -3
- package/dist/{chunk-d125j8t0.js.map → chunk-tds3xq6b.js.map} +1 -1
- package/dist/{chunk-3337e5g0.js → chunk-tey1xayb.js} +2 -2
- package/dist/{chunk-3337e5g0.js.map → chunk-tey1xayb.js.map} +1 -1
- package/dist/{chunk-w7rf99w6.js → chunk-vj9538yn.js} +2 -2
- package/dist/{chunk-w7rf99w6.js.map → chunk-vj9538yn.js.map} +1 -1
- package/dist/{chunk-yed5whgs.js → chunk-wcv6qtpq.js} +3 -3
- package/dist/{chunk-yed5whgs.js.map → chunk-wcv6qtpq.js.map} +1 -1
- package/dist/{chunk-vr90r27j.js → chunk-wzvs3sym.js} +2 -2
- package/dist/{chunk-vr90r27j.js.map → chunk-wzvs3sym.js.map} +1 -1
- package/dist/{chunk-cyaz97p5.js → chunk-x6y1z4cq.js} +2 -2
- package/dist/{chunk-cyaz97p5.js.map → chunk-x6y1z4cq.js.map} +1 -1
- package/dist/chunk-x8beq9c4.js +55 -0
- package/dist/chunk-x8beq9c4.js.map +11 -0
- package/dist/{chunk-6235kb30.js → chunk-xecj8025.js} +3 -3
- package/dist/{chunk-6235kb30.js.map → chunk-xecj8025.js.map} +1 -1
- package/dist/chunk-y3zz410b.js +6 -0
- package/dist/{chunk-4sz6pwtn.js.map → chunk-y3zz410b.js.map} +2 -2
- package/dist/{chunk-eejmhtnc.js → chunk-y64j80v9.js} +2 -2
- package/dist/{chunk-eejmhtnc.js.map → chunk-y64j80v9.js.map} +1 -1
- package/dist/{chunk-zh2egcyb.js → chunk-ygfwtgvm.js} +2 -2
- package/dist/{chunk-zh2egcyb.js.map → chunk-ygfwtgvm.js.map} +1 -1
- package/dist/{chunk-4yt5x8s2.js → chunk-ys904esh.js} +2 -2
- package/dist/{chunk-4yt5x8s2.js.map → chunk-ys904esh.js.map} +1 -1
- package/dist/{chunk-y6a8r2bn.js → chunk-z1e55w67.js} +3 -3
- package/dist/{chunk-y6a8r2bn.js.map → chunk-z1e55w67.js.map} +1 -1
- package/dist/{chunk-qb5mv6pj.js → chunk-z2tcxwyr.js} +3 -3
- package/dist/{chunk-qb5mv6pj.js.map → chunk-z2tcxwyr.js.map} +1 -1
- package/dist/{chunk-hppagzz4.js → chunk-zhbrkpb3.js} +4 -4
- package/dist/{chunk-hppagzz4.js.map → chunk-zhbrkpb3.js.map} +1 -1
- package/dist/{chunk-grdahng8.js → chunk-zqsfanvk.js} +2 -2
- package/dist/{chunk-grdahng8.js.map → chunk-zqsfanvk.js.map} +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +3 -3
- package/dist/console/run.js +2 -2
- package/dist/console/run.js.map +1 -1
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +1 -1
- package/dist/facades/Features.d.ts +67 -1
- package/dist/facades/Features.d.ts.map +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +2 -2
- package/dist/i18n/dictionaryRuntime.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +2 -2
- package/dist/kernel/index.js +2 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +1 -1
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/httpDev.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +3 -3
- package/dist/services/events/Event.d.ts +6 -0
- package/dist/services/events/Event.d.ts.map +1 -1
- package/dist/services/features/FeatureFlagStore.d.ts +56 -0
- package/dist/services/features/FeatureFlagStore.d.ts.map +1 -1
- package/dist/services/features/FeatureManager.d.ts +35 -1
- package/dist/services/features/FeatureManager.d.ts.map +1 -1
- package/dist/services/features/types.d.ts +62 -0
- package/dist/services/features/types.d.ts.map +1 -1
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +8 -8
- package/dist/services/index.js.map +3 -3
- package/dist/services/logging/LogManager.d.ts +10 -1
- package/dist/services/logging/LogManager.d.ts.map +1 -1
- package/dist/services/logging/LogServiceProvider.d.ts +10 -0
- package/dist/services/logging/LogServiceProvider.d.ts.map +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +1 -1
- package/package.json +3 -2
- package/skills/gemi-react-best-practices/SKILL.md +231 -0
- package/skills/gemi-react-best-practices/rules/_sections.md +56 -0
- package/skills/gemi-react-best-practices/rules/_template.md +28 -0
- package/skills/gemi-react-best-practices/rules/bundle-deep-imports.md +48 -0
- package/skills/gemi-react-best-practices/rules/bundle-mount-gate-heavy-panels.md +63 -0
- package/skills/gemi-react-best-practices/rules/client-form-vs-mutation-hooks.md +57 -0
- package/skills/gemi-react-best-practices/rules/client-loading-error-exports.md +54 -0
- package/skills/gemi-react-best-practices/rules/client-no-effect-data-flow.md +68 -0
- package/skills/gemi-react-best-practices/rules/client-typed-links.md +51 -0
- package/skills/gemi-react-best-practices/rules/controller-authorize-every-tenant-read.md +65 -0
- package/skills/gemi-react-best-practices/rules/controller-parse-request-at-the-boundary.md +54 -0
- package/skills/gemi-react-best-practices/rules/controller-redirect-facade-throws.md +68 -0
- package/skills/gemi-react-best-practices/rules/controller-request-schema.md +61 -0
- package/skills/gemi-react-best-practices/rules/controller-throw-framework-errors.md +57 -0
- package/skills/gemi-react-best-practices/rules/i18n-define-dictionary-inline.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-analytics-connection.md +53 -0
- package/skills/gemi-react-best-practices/rules/orm-include-not-n-plus-one.md +56 -0
- package/skills/gemi-react-best-practices/rules/orm-paginate-helper.md +69 -0
- package/skills/gemi-react-best-practices/rules/orm-plain-rows-by-default.md +55 -0
- package/skills/gemi-react-best-practices/rules/orm-select-narrow.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-no-io.md +54 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-sequential.md +64 -0
- package/skills/gemi-react-best-practices/rules/payload-dont-overprefetch.md +54 -0
- package/skills/gemi-react-best-practices/rules/payload-instant-vs-prefetch.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-minimal-view-props.md +51 -0
- package/skills/gemi-react-best-practices/rules/payload-parallel-controller-work.md +56 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-late-queries.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-mirrors-usequery.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-debounce-search-variant.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-keep-previous-data.md +40 -0
- package/skills/gemi-react-best-practices/rules/query-lazy-vs-mount-gate.md +54 -0
- package/skills/gemi-react-best-practices/rules/query-mutate-over-refetch.md +55 -0
- package/skills/gemi-react-best-practices/rules/query-no-hand-rolled-fetch.md +60 -0
- package/skills/gemi-react-best-practices/rules/query-revalidate-on-focus.md +44 -0
- package/skills/gemi-react-best-practices/rules/query-share-cache-key.md +51 -0
- package/skills/gemi-react-best-practices/rules/query-suspense-default.md +52 -0
- package/skills/gemi-react-best-practices/rules/routing-cache-policy-constants.md +53 -0
- package/skills/gemi-react-best-practices/rules/routing-middleware-dsl.md +60 -0
- package/skills/gemi-react-best-practices/rules/routing-resource-routes.md +59 -0
- package/skills/gemi-react-best-practices/rules/routing-routers-are-classes.md +55 -0
- package/skills/gemi-react-best-practices/rules/service-lazy-not-module-scope.md +63 -0
- package/skills/gemi-react-best-practices/rules/service-queue-is-in-memory.md +52 -0
- package/skills/gemi-react-best-practices/rules/service-static-token-and-name.md +52 -0
- package/skills/gemi-react-best-practices/rules/structure-discovered-vs-registered.md +71 -0
- package/skills/gemi-react-best-practices/rules/structure-do-not-reinvent-the-framework.md +58 -0
- package/skills/gemi-react-best-practices/rules/testing-assert-behaviour-over-markup.md +54 -0
- package/skills/gemi-react-best-practices/rules/testing-match-the-suite.md +57 -0
- package/skills/gemi-react-best-practices/rules/testing-page-seeds-real-inputs.md +65 -0
- package/dist/chunk-01e3gk86.js +0 -55
- package/dist/chunk-01e3gk86.js.map +0 -11
- package/dist/chunk-4sz6pwtn.js +0 -6
- package/dist/chunk-87qab82w.js +0 -5
- package/dist/chunk-b35e128b.js +0 -5
- package/dist/chunk-hwa5sqw5.js +0 -19
- /package/dist/{chunk-k0fvsyeh.js.map → chunk-hsfjt13m.js.map} +0 -0
- /package/dist/{chunk-yf7vz71n.js.map → chunk-hwhw98hc.js.map} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Services and Jobs Need a Static String Identifier
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: works in dev, silently breaks in the production build
|
|
5
|
+
tags: service, jobs, minification, production
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Services and Jobs Need a Static String Identifier
|
|
9
|
+
|
|
10
|
+
A `Service` needs `static token`; a `Job` needs `static name`. Both are string
|
|
11
|
+
literals because **minification renames classes in the production build** — the
|
|
12
|
+
container key and the queue's dispatch name must survive that rename. This is a
|
|
13
|
+
class of bug that cannot reproduce locally: dev is unminified, so everything works
|
|
14
|
+
until it is deployed.
|
|
15
|
+
|
|
16
|
+
**Incorrect (relies on the class name surviving the build):**
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
export class ProcessVideoJob extends Job {
|
|
20
|
+
async run(params: Params) { /* … */ }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct:**
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
export class ProcessVideoJob extends Job {
|
|
28
|
+
static name = "ProcessVideoJob";
|
|
29
|
+
maxAttempts = 3;
|
|
30
|
+
|
|
31
|
+
async run(params: Params) { /* … */ }
|
|
32
|
+
onFail(error: Error, params: Params) { /* … */ }
|
|
33
|
+
onDeadletter(error: Error, params: Params) { /* … */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Billing extends Service {
|
|
37
|
+
static token = "Billing";
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Tokens must be unique.** Two jobs claiming one name: the framework refuses the
|
|
42
|
+
second with a line on stderr rather than silently dropping it — rename one.
|
|
43
|
+
- **`maxAttempts` defaults to 3.** `run` throwing calls `onFail` and requeues;
|
|
44
|
+
the final failure calls `onDeadletter` and drops the job.
|
|
45
|
+
- **`worker: true`** runs the job in a separate Worker thread, for CPU-bound work.
|
|
46
|
+
- **Dispatch is fire-and-forget** — `Job.dispatch(payload)` returns `void`. Pass only
|
|
47
|
+
serializable data, never a class instance or a function.
|
|
48
|
+
|
|
49
|
+
**Register services explicitly on `Kernel.services`, and note that boot order is
|
|
50
|
+
load-bearing** — a service listed first boots before those after it.
|
|
51
|
+
|
|
52
|
+
<https://nstfkc.github.io/gemi/jobs-and-queues.md>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Know Which Directories Are Discovered and Which Need Declaring
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: the difference between code that runs and code that silently never does
|
|
5
|
+
tags: structure, discovery, kernel, registration
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Know Which Directories Are Discovered and Which Need Declaring
|
|
9
|
+
|
|
10
|
+
`app/` has two kinds of directory, and confusing them produces the same symptom
|
|
11
|
+
either way: code that looks correct, imports cleanly, typechecks, and never runs.
|
|
12
|
+
|
|
13
|
+
**Discovered — writing the file *is* the registration:**
|
|
14
|
+
|
|
15
|
+
| Directory | Base class | Read at |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `app/cron/` | `CronJob` | boot |
|
|
18
|
+
| `app/jobs/` | `Job` | boot |
|
|
19
|
+
| `app/listeners/` | `Listener` | boot |
|
|
20
|
+
| `app/commands/` | `defineCommand` chains | `gemi run` only |
|
|
21
|
+
|
|
22
|
+
Nothing anywhere names these. Adding a `Job` subclass under `app/jobs/` registers
|
|
23
|
+
it; there is no list to append to, and appending to one you invented does nothing.
|
|
24
|
+
|
|
25
|
+
`app/listeners/` is the one the scaffold does not create — its absence is not an
|
|
26
|
+
error, it means the app has no listeners yet.
|
|
27
|
+
|
|
28
|
+
**Declared — the file is not enough:**
|
|
29
|
+
|
|
30
|
+
- **Models** are declared on the Kernel. A model class under `app/models/` that
|
|
31
|
+
the Kernel's `models` does not reach is not registered, and `gemi check models`
|
|
32
|
+
exists to report exactly that gap for policied models.
|
|
33
|
+
- **Routers** are registered explicitly — `app/http/routes/view.ts` and `api.ts`
|
|
34
|
+
are the roots, and a new router is reached by being mounted from one of them.
|
|
35
|
+
- **Config slices** under `app/config/` are named by the Kernel's `config`.
|
|
36
|
+
- **Service providers** under `app/providers/` are named by the Kernel's
|
|
37
|
+
`providers`.
|
|
38
|
+
|
|
39
|
+
**Incorrect (a job that never runs, because a hand-rolled list is not the mechanism):**
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// app/jobs/index.ts — invented; nothing reads this
|
|
43
|
+
export const jobs = [SendWelcomeEmail, ReindexProducts];
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Correct (the file is the registration):**
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// app/jobs/SendWelcomeEmail.ts
|
|
50
|
+
export class SendWelcomeEmail extends Job {
|
|
51
|
+
static token = "SendWelcomeEmail";
|
|
52
|
+
// …
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Incorrect (a model that typechecks and is never registered):**
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// app/models/Invoice.ts — written, but not reachable from the Kernel's `models`
|
|
60
|
+
export class Invoice extends Model {}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The four discovered directories have a cost worth knowing: **discovery imports
|
|
64
|
+
every file it walks**, because a class does not exist until its module has run. A
|
|
65
|
+
file in one of them that does work at import time does that work at boot. Keep
|
|
66
|
+
them to declarations — see `service-lazy-not-module-scope`.
|
|
67
|
+
|
|
68
|
+
The discovered directories can also be declared explicitly in their config slice
|
|
69
|
+
(`schedule`, `queue`, `events`, `command`), which turns the walk off and uses the
|
|
70
|
+
list verbatim. An empty array is a declaration too, and means "this app has none"
|
|
71
|
+
— not "fall back to walking".
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Build Structure the Framework Already Has
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: a parallel structure is one the framework's own tooling cannot see
|
|
5
|
+
tags: structure, container, services, conventions
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Build Structure the Framework Already Has
|
|
9
|
+
|
|
10
|
+
The reflex when a codebase needs somewhere to put shared code is to add `lib/`,
|
|
11
|
+
`utils/`, or a hand-rolled registry. In a gemi app most of those already exist
|
|
12
|
+
with a name, and the cost of the parallel one is not duplication — it is that the
|
|
13
|
+
framework's own machinery cannot see it.
|
|
14
|
+
|
|
15
|
+
| The reflex | What gemi already has |
|
|
16
|
+
|---|---|
|
|
17
|
+
| A `lib/` of shared classes with I/O | `app/services/`, classes extending `Service`, resolved from the container |
|
|
18
|
+
| A module-scope singleton client | A `Service` with a `static token`, injected where needed |
|
|
19
|
+
| A hand-rolled service locator or DI map | The container; `app/providers/` binds into it |
|
|
20
|
+
| A custom router, or routing by file path | Router classes under `app/http/routes/` |
|
|
21
|
+
| Ad-hoc `if (!req.body.name)` checks | An `HttpRequest` subclass under `app/http/requests/` |
|
|
22
|
+
| A `constants.ts` of env reads | A config slice under `app/config/` |
|
|
23
|
+
| A `scripts/` folder run with `bun x.ts` | `app/commands/`, run by `gemi run` |
|
|
24
|
+
| A barrel re-exporting a package's modules | Deep imports — see `bundle-deep-imports` |
|
|
25
|
+
|
|
26
|
+
**Incorrect (a registry the framework cannot see):**
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// app/lib/services.ts — invented
|
|
30
|
+
export const billing = new Billing(process.env.STRIPE_KEY!);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Constructed at module scope, so it runs whenever anything imports the file —
|
|
34
|
+
including a discovery walk that imports every file in a directory. Nothing can
|
|
35
|
+
rebind it in a test, and nothing resolves it by token.
|
|
36
|
+
|
|
37
|
+
**Correct:**
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// app/services/Billing.ts
|
|
41
|
+
export class Billing extends Service {
|
|
42
|
+
static token = "Billing";
|
|
43
|
+
// Constructed lazily by the container, rebindable in a test.
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Two things follow from this that are easy to get backwards:
|
|
48
|
+
|
|
49
|
+
- **A view's file path is not its URL.** `app/views/` is organised however you
|
|
50
|
+
like; routing is declared on router classes. Moving a view does not change a
|
|
51
|
+
route, and creating one under a path that "looks like" a URL registers nothing.
|
|
52
|
+
See `routing-routers-are-classes`.
|
|
53
|
+
- **`app/models/generated/` is output.** Editing it is editing a build artifact —
|
|
54
|
+
the change disappears on the next generate.
|
|
55
|
+
|
|
56
|
+
When a rule here and an existing pattern in your app disagree, check the app's
|
|
57
|
+
`CLAUDE.md` and git history before "fixing" it: the app may have a reason, and
|
|
58
|
+
ground rule 2 says the app wins.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mount and Query the DOM — Do Not Scrape Rendered Markup
|
|
3
|
+
impact: MEDIUM-HIGH
|
|
4
|
+
impactDescription: catches real regressions instead of string drift
|
|
5
|
+
tags: testing, testing-library, house-style
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Mount and Query the DOM — Do Not Scrape Rendered Markup
|
|
9
|
+
|
|
10
|
+
Asserting on `renderToStaticMarkup` / `renderToString` output — substring matches,
|
|
11
|
+
regex scrapes for `href=`, index arithmetic to prove ordering — tests the markup
|
|
12
|
+
rather than the component. It passes when the component is broken and fails when a
|
|
13
|
+
class name changes.
|
|
14
|
+
|
|
15
|
+
**Incorrect:**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const html = renderToStaticMarkup(<ProductList products={products} />);
|
|
19
|
+
expect(html).toContain("Chair");
|
|
20
|
+
expect(html.indexOf("Chair")).toBeLessThan(html.indexOf("Desk"));
|
|
21
|
+
expect(html).toMatch(/href="\/app\/abc\/products\/1"/);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct:**
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
render(<Page {...}><ProductList /></Page>);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByRole("link", { name: "Chair" })).toHaveAttribute(
|
|
30
|
+
"href", "/app/abc/products/1",
|
|
31
|
+
);
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByText("Chair").compareDocumentPosition(screen.getByText("Desk")),
|
|
34
|
+
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use `getByRole` / `getByLabelText`, `compareDocumentPosition` for order, and
|
|
38
|
+
`label[for]` for input association. **The one exception is a test whose SUBJECT is
|
|
39
|
+
the server payload** — e.g. guarding that a virtualised grid does not ship an empty
|
|
40
|
+
shell. Say so in the file when you take it.
|
|
41
|
+
|
|
42
|
+
**Assert what goes over the wire, not how a query was configured.** Checking that
|
|
43
|
+
`lazy` or `refreshInterval` was passed tests the arguments; it cannot tell a working
|
|
44
|
+
pager from one that never fetched. A non-lazy query is proved by a request arriving
|
|
45
|
+
on mount, a lazy one by no request arriving until something asks.
|
|
46
|
+
|
|
47
|
+
**Mock HTTP with MSW, not by replacing `globalThis.fetch`.** `setupServer` +
|
|
48
|
+
`server.listen({ onUnhandledRequest: "error" })` makes an undeclared request fail
|
|
49
|
+
instead of silently resolving, and lets a case assert against the real `Request` —
|
|
50
|
+
resolved URL, method, headers, parsed body.
|
|
51
|
+
|
|
52
|
+
**Make a stub load-bearing.** A test that removes `crypto.randomUUID` to reach a
|
|
53
|
+
fallback, then asserts a shape both branches satisfy, passes either way. Assert the
|
|
54
|
+
exact value.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Match the Suite You Are In
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: a test the runner never selects is a test that does not exist
|
|
5
|
+
tags: testing, runner, conventions
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Match the Suite You Are In
|
|
9
|
+
|
|
10
|
+
Before adding a test, read the neighbours and the runner config. Copy the runner, the
|
|
11
|
+
import source, and the file-naming convention already in use in that directory. A
|
|
12
|
+
test written for the wrong runner does not fail loudly — the runner simply never
|
|
13
|
+
selects the file, and it sits green forever without executing.
|
|
14
|
+
|
|
15
|
+
**This is the whole rule for the common case.** Everything below is the gemi-specific
|
|
16
|
+
part: the two things that trip up a component test in a gemi app regardless of which
|
|
17
|
+
runner you picked.
|
|
18
|
+
|
|
19
|
+
**A component test needs a DOM.** gemi renders on the server, so a suite configured
|
|
20
|
+
as a plain Node project has no `window`. Mounting a view there fails on the first
|
|
21
|
+
DOM call, not with a useful message. Either point that suite at a DOM environment
|
|
22
|
+
(`jsdom`, `happy-dom`) or put component tests in the half of the suite that has one.
|
|
23
|
+
|
|
24
|
+
**A test of the server dictionary readers must drop `window` first.**
|
|
25
|
+
`Dictionary.render` and `Dictionary.reference` throw in a browser-shaped environment,
|
|
26
|
+
so a DOM-providing preload breaks them for the whole run. Reach for the server
|
|
27
|
+
globals helper at module scope — a dictionary is often read while the test module
|
|
28
|
+
evaluates, before any `beforeEach` has run.
|
|
29
|
+
|
|
30
|
+
**Incorrect (a component test in the Node-only half of the suite):**
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// runs, finds no document, fails on render
|
|
34
|
+
import { render } from "@testing-library/react";
|
|
35
|
+
import { describe, it } from "vitest";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Correct (the same test where a DOM exists):**
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { render } from "@testing-library/react";
|
|
42
|
+
import { describe, it } from "vitest"; // or "bun:test" — whichever this suite uses
|
|
43
|
+
|
|
44
|
+
// vitest.config.ts: environment: "jsdom"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Two notes if your repo runs more than one runner:
|
|
48
|
+
|
|
49
|
+
- **The file name is usually the selection mechanism.** `bun test` has no
|
|
50
|
+
include-pattern config, so a repo running both typically selects the bun half by a
|
|
51
|
+
path fragment (`<name>.bun.test.ts`) and excludes that same fragment from the other
|
|
52
|
+
runner's config. Renaming a file is what moves it between halves.
|
|
53
|
+
- **`vi.hoisted` / `vi.mock` do not port.** They rely on Vitest's AST hoisting, which
|
|
54
|
+
`bun:test` has no equivalent for. A file using only `describe`/`it`/`expect` is an
|
|
55
|
+
import swap; a file using those needs rewriting.
|
|
56
|
+
|
|
57
|
+
See also `testing-page-seeds-real-inputs` for how to mount a gemi view under test.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mount With Page From gemi/testing, Never Mock gemi/client
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: tests the component instead of the mock
|
|
5
|
+
tags: testing, gemi-testing, house-style
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Mount With Page From gemi/testing, Never Mock gemi/client
|
|
9
|
+
|
|
10
|
+
`<Page>` supplies the inputs the framework normally provides — route params, search,
|
|
11
|
+
locale, user, and the query cache — so a view mounts for real. **Mocking
|
|
12
|
+
`gemi/client` is prohibited, and the rule is not scoped to component tests**: it
|
|
13
|
+
binds hook and model tests the same way, and spreading the real module through
|
|
14
|
+
(`...(await import("gemi/client"))`) is still mocking it.
|
|
15
|
+
|
|
16
|
+
**Incorrect (asserts the mock, not the component):**
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
mock.module("gemi/client", () => ({
|
|
20
|
+
useQuery: () => ({ data: [{ id: 1, name: "Chair" }] }),
|
|
21
|
+
useParams: () => ({ orgId: "abc" }),
|
|
22
|
+
}));
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (real hooks, seeded inputs):**
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Page } from "gemi/testing";
|
|
29
|
+
|
|
30
|
+
render(
|
|
31
|
+
<Page
|
|
32
|
+
pathname="/app/:orgId/products"
|
|
33
|
+
params={{ orgId: "abc" }}
|
|
34
|
+
searchParams="?tab=recent"
|
|
35
|
+
locale="en-US"
|
|
36
|
+
user={{ id: 1 }}
|
|
37
|
+
queryData={{ "/app/:orgId/products": [{ id: 1, name: "Chair" }] }}
|
|
38
|
+
fallback={<Skeleton />}
|
|
39
|
+
errorFallback={<Failed />}
|
|
40
|
+
onNavigate={onNavigate}
|
|
41
|
+
>
|
|
42
|
+
<ProductsRoute />
|
|
43
|
+
</Page>,
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- **`queryData` keys are the path as `useQuery` writes it**, minus `/api`, with
|
|
48
|
+
`:params` resolved against the page's `params`. A seeded query renders on the first
|
|
49
|
+
pass and issues no request.
|
|
50
|
+
- **Navigation is reported, not performed** — `onNavigate` receives
|
|
51
|
+
`(resolvedHref, "push" | "replace")`. To test the destination, mount a second
|
|
52
|
+
`<Page>`.
|
|
53
|
+
- **Seed `fallback` and `errorFallback` and assert those** for a non-lazy query:
|
|
54
|
+
it suspends while in flight and throws on failure, so `loading`/`error` are not
|
|
55
|
+
what that path returns.
|
|
56
|
+
|
|
57
|
+
**If an assertion can only be made by mocking `gemi/client`** — `prefetch` leaves no
|
|
58
|
+
DOM trace, a synchronous `useMutate` throw — **drop the assertion, not the rule**, and
|
|
59
|
+
say in the file why it went.
|
|
60
|
+
|
|
61
|
+
**Do not mock anything else either** unless it is genuinely impossible otherwise. A
|
|
62
|
+
Radix dropdown rendering nothing until it opens is an argument for opening it with
|
|
63
|
+
`userEvent`, not for stubbing it.
|
|
64
|
+
|
|
65
|
+
Reference: <https://nstfkc.github.io/gemi/testing.md>
|
package/dist/chunk-01e3gk86.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{$ as D,Z as x}from"./chunk-4sz6pwtn.js";import{ia as y,ja as I}from"./chunk-v7y8zm61.js";import"./chunk-5ad8era8.js";import"./chunk-ejrcyzx8.js";import"./chunk-tkdxv6cq.js";import"./chunk-xnytbrda.js";import"./chunk-jhkjz9jr.js";import{IncomingMessage as T,ServerResponse as g}from"http";import{Socket as w}from"net";import{join as E}from"path";import{createServer as k}from"vite";function N(H){return H.replace(/\x1b\[[0-9;]*m/g,"").replace(/\[[0-9;]*m/g,"").replace(/\n\s*\n/g,`
|
|
3
|
-
`).split(`
|
|
4
|
-
`).map((J)=>J.trim()).filter((J)=>J.length>0).join(`
|
|
5
|
-
`).trim()}function R(H){return`
|
|
6
|
-
<!DOCTYPE html>
|
|
7
|
-
<html lang="en">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="UTF-8" />
|
|
10
|
-
<meta
|
|
11
|
-
name="viewport"
|
|
12
|
-
content="width=device-width, initial-scale=1.0"
|
|
13
|
-
/>
|
|
14
|
-
|
|
15
|
-
<title>Error</title>
|
|
16
|
-
<style>
|
|
17
|
-
body {
|
|
18
|
-
font-family: Arial, sans-serif;
|
|
19
|
-
padding: 20px;
|
|
20
|
-
}
|
|
21
|
-
h1 {
|
|
22
|
-
color: red;
|
|
23
|
-
}
|
|
24
|
-
</style>
|
|
25
|
-
</head>
|
|
26
|
-
<body>
|
|
27
|
-
<div id="overlay"></div>
|
|
28
|
-
<script type="module" src="/refresh.js"></script>
|
|
29
|
-
<script type="module" src="/@vite/client"></script>
|
|
30
|
-
<script>
|
|
31
|
-
${N.toString()}
|
|
32
|
-
const err = ${JSON.stringify(H,Object.getOwnPropertyNames(H),2)}
|
|
33
|
-
|
|
34
|
-
window.addEventListener('load', () => {
|
|
35
|
-
const container = document.getElementById('overlay')
|
|
36
|
-
const ErrorOverlay = customElements.get('vite-error-overlay')
|
|
37
|
-
if (ErrorOverlay) {
|
|
38
|
-
const overlay = new ErrorOverlay({ ...err, message: cleanupTerminalText(err.message) });
|
|
39
|
-
container.appendChild(overlay)
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
</script>
|
|
43
|
-
</body>
|
|
44
|
-
</html>
|
|
45
|
-
`}function q(H,J){let j=new URL(J.url),K=new T(new w);K.url=j.pathname+j.search,K.method=J.method,K.headers=Object.fromEntries(J.headers);let O=new g(K),Q=[];return new Promise((Y)=>{O.write=(Z)=>{if(Z)Q.push(Buffer.from(Z));return!0},O.end=(Z)=>{if(Z&&typeof Z!=="function")Q.push(Buffer.from(Z));let z=new Headers;for(let[_,$]of Object.entries(O.getHeaders())){if($==null)continue;for(let U of Array.isArray($)?$:[$])z.append(_,String(U))}return Y(new Response(Q.length?Buffer.concat(Q):null,{status:O.statusCode,headers:z})),O},H.middlewares(K,O,()=>Y(null))})}var L=process.cwd(),b=E(L,"app");function d(H,J,j){let K=String(J),O=" ".repeat(K.length),Q=" ".repeat(Math.max(0,j-1))+"^";return`${K} | ${H}
|
|
46
|
-
${O} | ${Q}
|
|
47
|
-
`}function S(H){if(H&&typeof H==="object"){let J=H.position;if(J&&typeof J==="object"&&typeof J.file==="string")return{message:typeof H.message==="string"?H.message:String(H),stack:"",frame:typeof J.lineText==="string"?d(J.lineText,J.line,J.column):void 0,loc:{file:J.file,line:J.line,column:J.column}};return{message:typeof H.message==="string"?H.message:String(H),stack:typeof H.stack==="string"?H.stack:""}}return{message:String(H),stack:""}}function X(H){let J=globalThis.__gemiVite;if(!J)return;J.ws.send({type:"error",err:S(H)}),globalThis.__gemiErrorActive=!0}if(!globalThis.__gemiErrorHooked)globalThis.__gemiErrorHooked=!0,process.on("unhandledRejection",(H)=>{console.error(H),X(H)}),process.on("uncaughtException",(H)=>{console.error(H),X(H)});async function n(H,J){let j=Boolean(globalThis.__gemiVite),K=globalThis.__gemiVite??=await k({configFile:!1,plugins:[I()],server:{middlewareMode:!0,watch:{ignored:(Q)=>{let Y=Q.split(/[/\\]/).pop()??"";return Y===".env"||Y.startsWith(".env.")}}},appType:"custom",resolve:{dedupe:["react","react-dom"]},optimizeDeps:{include:["react","react-dom","react-dom/client"]},ssr:{external:[...y]}});process.env.ROOT_DIR=L,process.env.APP_DIR=b;let O=Bun.serve({port:process.env.PORT||5173,fetch:async(Q)=>{let{pathname:Y,host:Z,protocol:z}=new URL(Q.url);if(Y.startsWith("/render-error.js"))return new Response("window.render_error = true",{headers:{"Content-Type":"application/javascript"}});if(Y.startsWith("/refresh.js"))return new Response(`
|
|
48
|
-
import RefreshRuntime from "${z}//${Z}/@react-refresh";
|
|
49
|
-
RefreshRuntime.injectIntoGlobalHook(window);
|
|
50
|
-
window.$RefreshReg$ = () => {};
|
|
51
|
-
window.$RefreshSig$ = () => (type) => type;
|
|
52
|
-
window.__vite_plugin_react_preamble_installed__ = true;
|
|
53
|
-
`,{headers:{"Content-Type":"application/javascript"}});return await J(Q,async($)=>{try{let U=H.fetch.bind(H),A=await q(K,$)??await U($);if(A instanceof Response)return A;let F={},V={},C={},f=(W,B)=>`"${W}": () => import("${B}")`,P=[];for(let W of["404",...H.getFlatComponentTree.call(H)]){if(Bun.env.NODE_ENV==="test")break;let B=`${process.env.APP_DIR}`,G=await K.ssrLoadModule(`${B}/views/${W}.tsx`,{fixStacktrace:!0});F[W]=G.default,V[W]=G?.OpenGraph,C[W]=G,P.push(f(W,`/app/views/${W}.tsx`))}let M=`{${P.join(",")}}`;return await A({getStyles:async(W)=>await x(b,K,W),bootstrapModules:["/refresh.js","/app/client.tsx","/@vite/client"],viewImportMap:F,viewModules:C,ogMap:V,loaders:M,cssManifest:{}})}catch(U){if(console.error(U),K.ssrFixStacktrace?.(U),X(U),Y.startsWith("/api"))return new Response(JSON.stringify({error:U?.message??String(U)}),{status:500,headers:{"Content-Type":"application/json"}});return globalThis.__gemiErrorPageServed=!0,new Response(R(S(U)),{status:500,headers:{"Content-Type":"text/html"}})}})}});if(j)if(globalThis.__gemiErrorActive&&globalThis.__gemiErrorPageServed)globalThis.__gemiErrorActive=!1,globalThis.__gemiErrorPageServed=!1,K.ws.send({type:"full-reload"});else globalThis.__gemiErrorActive=!1,K.ws.send({type:"custom",event:"http-reload"});else D({port:O.port,rootDir:L});return O}export{S as viteErrorPayload,n as httpDev};
|
|
54
|
-
|
|
55
|
-
//# debugId=E1B84E5668A31A3E64756E2164756E21
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../server/httpDev.ts", "../server/renderErrorPage.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Socket } from \"node:net\";\nimport { join } from \"node:path\";\n\nimport { createServer, type ViteDevServer } from \"vite\";\n\nimport { App } from \"../app\";\nimport gemiVite from \"../vite\";\nimport { createDevStyles } from \"./styles\";\nimport { renderErrorPage } from \"./renderErrorPage\";\nimport { Instrumentation } from \"./types\";\nimport { printStartupBanner } from \"./banner\";\nimport { GEMI_EXTERNAL_SPECIFIERS } from \"../internal/gemiExternals\";\n\n// Run a Web `Request` through Vite's Connect middleware.\n// Resolves to a `Response` when Vite handles the request (module transforms,\n// assets, the HMR client, ...), or `null` when it calls `next()` — which, with\n// `appType: \"custom\"`, it does for everything it doesn't own.\nfunction viteMiddleware(vite: ViteDevServer, request: Request): Promise<Response | null> {\n const url = new URL(request.url);\n\n const req = new IncomingMessage(new Socket());\n req.url = url.pathname + url.search;\n req.method = request.method;\n req.headers = Object.fromEntries(request.headers);\n\n const res = new ServerResponse(req);\n const chunks: Buffer[] = [];\n\n return new Promise((resolve) => {\n res.write = ((chunk: any) => {\n if (chunk) chunks.push(Buffer.from(chunk));\n return true;\n }) as typeof res.write;\n\n res.end = ((chunk?: any) => {\n if (chunk && typeof chunk !== \"function\") chunks.push(Buffer.from(chunk));\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(res.getHeaders())) {\n if (value == null) continue;\n for (const v of Array.isArray(value) ? value : [value]) {\n headers.append(key, String(v));\n }\n }\n\n resolve(\n new Response(chunks.length ? Buffer.concat(chunks) : null, {\n status: res.statusCode,\n headers,\n }),\n );\n return res;\n }) as typeof res.end;\n\n vite.middlewares(req, res, () => resolve(null));\n });\n}\n\nconst rootDir = process.cwd();\nconst appDir = join(rootDir, \"app\");\n\ndeclare global {\n // Created once and reused across `bun --hot` reloads (see below).\n var __gemiVite: ViteDevServer | undefined;\n // Guards one-time registration of the process error hooks across reloads.\n var __gemiErrorHooked: boolean | undefined;\n // An error overlay is currently showing on the client (clear it on recovery).\n var __gemiErrorActive: boolean | undefined;\n // We served a standalone error *page* (not just a pushed overlay), so the\n // visible document has no live app — recovery needs a full page reload.\n var __gemiErrorPageServed: boolean | undefined;\n}\n\n// Build the `> 1 | ...\\n | ^` codeframe Vite's overlay renders — must\n// match its `codeframeRE` (a `<line> | <text>` line followed by a `| ^` line,\n// each ending in a newline).\nfunction codeFrame(lineText: string, line: number, column: number) {\n const gutter = String(line);\n const pad = \" \".repeat(gutter.length);\n const caret = \" \".repeat(Math.max(0, column - 1)) + \"^\";\n return `${gutter} | ${lineText}\\n${pad} | ${caret}\\n`;\n}\n\n// Normalize whatever Bun/JS throws into the { message, stack, frame, loc }\n// shape Vite's error overlay consumes. Two cases matter:\n// - a real `Error` (runtime top-level throw) carries a proper `.stack`;\n// - a Bun `BuildMessage` (syntax error) has NO `.stack`, only a `.position`\n// ({ file, line, column, lineText }) — so synthesize a codeframe + loc from\n// it, otherwise the overlay shows a bare message with no location.\n// (An `Error` instance also JSON-stringifies to `{}`, so fields are copied by\n// hand either way.)\nexport function viteErrorPayload(err: any) {\n if (err && typeof err === \"object\") {\n const pos = err.position;\n if (pos && typeof pos === \"object\" && typeof pos.file === \"string\") {\n return {\n message: typeof err.message === \"string\" ? err.message : String(err),\n stack: \"\",\n frame:\n typeof pos.lineText === \"string\"\n ? codeFrame(pos.lineText, pos.line, pos.column)\n : undefined,\n loc: { file: pos.file, line: pos.line, column: pos.column },\n };\n }\n return {\n message: typeof err.message === \"string\" ? err.message : String(err),\n stack: typeof err.stack === \"string\" ? err.stack : \"\",\n };\n }\n return { message: String(err), stack: \"\" };\n}\n\n// Push an error into the browser via Vite's HMR socket. `__gemiVite` persists\n// across `bun --hot` reloads, so this works even when the reload that produced\n// the error never got far enough to (re)run `httpDev`.\nfunction sendErrorToClient(err: any) {\n const vite = globalThis.__gemiVite;\n if (!vite) return;\n vite.ws.send({ type: \"error\", err: viteErrorPayload(err) as any });\n globalThis.__gemiErrorActive = true;\n}\n\n// `bun --hot` re-evaluates the module graph on every server-code change. A\n// syntax error or top-level throw in app code therefore fails during *module\n// load* — outside any request, and before `httpDev` re-runs — so Bun only\n// prints it to the console. Bun surfaces these as `unhandledRejection` (the\n// reload re-imports the entry and the failing dynamic import rejects), so hook\n// that here and forward it to the client. Registered once; process listeners\n// persist across reloads.\nif (!globalThis.__gemiErrorHooked) {\n globalThis.__gemiErrorHooked = true;\n process.on(\"unhandledRejection\", (reason) => {\n console.error(reason);\n sendErrorToClient(reason);\n });\n process.on(\"uncaughtException\", (err) => {\n console.error(err);\n sendErrorToClient(err);\n });\n}\n\nexport async function httpDev(app: App, instrumentation: Instrumentation) {\n // `bun --hot` re-runs this module on every server-code change, so keep a\n // single Vite server on `globalThis` instead of spawning a new one (and a new\n // HMR socket) on each reload.\n const isReload = Boolean(globalThis.__gemiVite);\n const vite = (globalThis.__gemiVite ??= await createServer({\n // gemi owns the Vite setup — the app has no `vite.config.mjs` to discover.\n // Register the gemi plugin explicitly instead; it loads `gemi.config.ts` and\n // appends the app's Vite plugins (e.g. `@vitejs/plugin-react`, which serves\n // `/@react-refresh` and the HMR preamble referenced below).\n configFile: false,\n plugins: [gemiVite()],\n server: {\n middlewareMode: true,\n // gemi reloads `.env` into process.env itself (see `watchEnv`). Stop Vite\n // from *also* watching env files: Vite's env-change handler restarts the\n // dev server, which closes the SSR module runner — but gemi keeps a single\n // Vite instance across `bun --hot` reloads, so an in-flight `ssrLoadModule`\n // hits the closed runner (\"Vite module runner has been closed\"). Ignoring\n // env files here only disables the restart; env *loading* at startup (and\n // `import.meta.env`) is unaffected.\n watch: {\n ignored: (file: string) => {\n const base = file.split(/[/\\\\]/).pop() ?? \"\";\n return base === \".env\" || base.startsWith(\".env.\");\n },\n },\n },\n appType: \"custom\",\n // Views are loaded through this SSR graph while the renderer\n // (react-dom/server in ViewRouteDispatcher) is imported by Bun.\n // Dedupe so Vite can never resolve a second React copy — a mismatch makes\n // the SSR dispatcher null (\"Invalid hook call\") after an HMR reload.\n resolve: { dedupe: [\"react\", \"react-dom\"] },\n // Pre-bundle the whole React surface the client uses in one pass. The client\n // imports `react-dom/client` (`init.tsx`) and `react-dom` (`createPortal` in\n // `HttpReload.tsx`); if only the first is discovered at startup, the first\n // request to a module using the second triggers a *second* optimize pass,\n // and the already-served page ends up importing a now-stale dep — surfacing\n // as \"does not provide an export named 't'\". Listing them up front bundles\n // them together so no re-optimization (and no version skew) happens.\n optimizeDeps: { include: [\"react\", \"react-dom\", \"react-dom/client\"] },\n // `gemi` is a linked package, so Vite would otherwise compile its own copy\n // into this graph — giving views a *different* `RouteStateContext` (and\n // other module-level singletons) than the Bun-loaded renderer's `Root`\n // provider, so `useRouteData()` reads the empty default (`i18n` undefined).\n // Externalizing makes view imports of `gemi/*` resolve to the one Bun\n // instance. Vite's `ssr.external` matches exact specifiers (subpaths are NOT\n // covered by the bare name, and regexes are ignored here), so every subpath\n // a view might import must be listed — see `internal/gemiExternals`, the\n // shared source of truth this and the SSR build (`vite/index.ts`) both use.\n ssr: {\n external: [...GEMI_EXTERNAL_SPECIFIERS],\n },\n }));\n\n process.env.ROOT_DIR = rootDir;\n process.env.APP_DIR = appDir;\n\n const server = Bun.serve({\n // Same override the prod server honors (`httpProd.ts`).\n port: process.env.PORT || 5173,\n fetch: async (req) => {\n const { pathname, host, protocol } = new URL(req.url);\n if (pathname.startsWith(\"/render-error.js\")) {\n return new Response(\"window.render_error = true\", {\n headers: {\n \"Content-Type\": \"application/javascript\",\n },\n });\n }\n if (pathname.startsWith(\"/refresh.js\")) {\n return new Response(\n `\n import RefreshRuntime from \"${protocol}//${host}/@react-refresh\";\n RefreshRuntime.injectIntoGlobalHook(window);\n window.$RefreshReg$ = () => {};\n window.$RefreshSig$ = () => (type) => type;\n window.__vite_plugin_react_preamble_installed__ = true;\n `,\n {\n headers: {\n \"Content-Type\": \"application/javascript\",\n },\n },\n );\n }\n const requestHandler = async (req: Request): Promise<Response> => {\n try {\n const handler = app.fetch.bind(app);\n const result = (await viteMiddleware(vite, req)) ?? (await handler(req));\n if (result instanceof Response) {\n return result;\n }\n\n const viewImportMap = {};\n const ogMap = {};\n const viewModules = {};\n const template = (viewName: string, path: string) =>\n `\"${viewName}\": () => import(\"${path}\")`;\n const templates = [];\n\n for (const fileName of [\"404\", ...app.getFlatComponentTree.call(app)]) {\n if (process.env.NODE_ENV === \"test\") {\n break;\n }\n const appDir = `${process.env.APP_DIR}`;\n const mod = await vite.ssrLoadModule(`${appDir}/views/${fileName}.tsx`, {\n fixStacktrace: true,\n });\n\n viewImportMap[fileName] = mod.default;\n ogMap[fileName] = mod?.OpenGraph;\n // The whole module, so a streaming render can put the view's\n // `Loading`/`Error` exports into the shell it sends.\n viewModules[fileName] = mod;\n // Emit a root-relative URL (`/app/views/Foo.tsx`), NOT the absolute\n // filesystem path used for `ssrLoadModule` above. The browser's\n // `window.loaders` preload and `client.tsx`'s `import.meta.glob` map\n // must import the exact same URL — otherwise Vite serves the module\n // under two URLs (`/app/views/Foo.tsx` vs `/Users/.../app/views/Foo.tsx`)\n // and loads/instantiates the view twice.\n templates.push(template(fileName, `/app/views/${fileName}.tsx`));\n }\n\n const loaders = `{${templates.join(\",\")}}`;\n\n return await result({\n getStyles: async (currentViews: string[]) =>\n await createDevStyles(appDir, vite, currentViews),\n bootstrapModules: [\"/refresh.js\", \"/app/client.tsx\", \"/@vite/client\"],\n viewImportMap,\n viewModules,\n ogMap,\n loaders,\n cssManifest: {},\n });\n } catch (err: any) {\n // Errors thrown *while handling a request* (a controller throwing, a\n // failing `ssrLoadModule`, ...) never hit the module-load hooks above,\n // so surface them here: forward to the overlay for any already-open\n // page, and render an error page for this response so a fresh load\n // (which has no live HMR socket yet) still shows the failure.\n console.error(err);\n vite.ssrFixStacktrace?.(err);\n sendErrorToClient(err);\n\n if (pathname.startsWith(\"/api\")) {\n return new Response(JSON.stringify({ error: err?.message ?? String(err) }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n // This document has no live app (so no HttpReload listener to clear the\n // overlay) — remember that, so recovery forces a full page reload.\n globalThis.__gemiErrorPageServed = true;\n return new Response(renderErrorPage(viteErrorPayload(err)), {\n status: 500,\n headers: { \"Content-Type\": \"text/html\" },\n });\n }\n };\n\n return await instrumentation(req, requestHandler);\n },\n });\n\n // On a reload `app` has already been rebuilt with the new code and Bun.serve\n // above has swapped in the new fetch handler, so the updated controllers are\n // live *now*. Tell the client to re-run its queries only at this point —\n // firing it from Vite's file watcher instead races Bun's reload and leaves\n // the UI one change behind.\n if (isReload) {\n // We got here, so the reload succeeded. Recover from any error that was\n // showing: a standalone error page must be fully reloaded to get the app\n // back, whereas an overlay pushed onto a live page is cleared by the\n // client's `http-reload` handler (no jarring full-page reload). A normal\n // reload just re-runs client queries against the now-live controllers.\n if (globalThis.__gemiErrorActive && globalThis.__gemiErrorPageServed) {\n globalThis.__gemiErrorActive = false;\n globalThis.__gemiErrorPageServed = false;\n vite.ws.send({ type: \"full-reload\" });\n } else {\n globalThis.__gemiErrorActive = false;\n vite.ws.send({ type: \"custom\", event: \"http-reload\" });\n }\n } else {\n // First start (not a `bun --hot` reload) — print the startup banner once.\n printStartupBanner({ port: server.port, rootDir });\n }\n\n return server;\n}\n",
|
|
6
|
-
"function cleanupTerminalText(text) {\n return (\n text\n // Remove ANSI escape sequences (colors, formatting)\n .replace(/\\x1b\\[[0-9;]*m/g, \"\")\n // Remove ANSI escape sequences with different format\n .replace(/\\[[0-9;]*m/g, \"\")\n // Remove excessive whitespace and normalize line breaks\n .replace(/\\n\\s*\\n/g, \"\\n\")\n // Trim each line\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .join(\"\\n\")\n .trim()\n );\n}\n\nexport function renderErrorPage(err: any) {\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta\n name=\"viewport\"\n content=\"width=device-width, initial-scale=1.0\"\n />\n\n <title>Error</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n padding: 20px;\n }\n h1 {\n color: red;\n }\n </style>\n </head>\n <body>\n <div id=\"overlay\"></div>\n <script type=\"module\" src=\"/refresh.js\"></script>\n <script type=\"module\" src=\"/@vite/client\"></script>\n <script>\n ${cleanupTerminalText.toString()}\n const err = ${JSON.stringify(err, Object.getOwnPropertyNames(err), 2)}\n\n window.addEventListener('load', () => {\n const container = document.getElementById('overlay')\n const ErrorOverlay = customElements.get('vite-error-overlay')\n if (ErrorOverlay) {\n const overlay = new ErrorOverlay({ ...err, message: cleanupTerminalText(err.message) });\n container.appendChild(overlay)\n }\n })\n </script>\n </body>\n</html>\n`;\n}\n"
|
|
7
|
-
],
|
|
8
|
-
"mappings": "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",
|
|
9
|
-
"debugId": "E1B84E5668A31A3E64756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|
package/dist/chunk-4sz6pwtn.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{pa as h}from"./chunk-tkdxv6cq.js";import{createElement as x}from"react";function j(i,n){let s=Object.keys(n).sort((u,b)=>b.length-u.length).map((u)=>u.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),r=new RegExp(s.join("|"),"g");return i.replace(r,(u)=>n[u])}async function J(i,n,s=[]){let r=[...s.map((t)=>`${i}/views/${t}.tsx`),`${i}/views/RootLayout.tsx`],u=new Set;for(let t of r){let e=n.moduleGraph.getModulesByFile(t);if(e)for(let d of e)u.add(d)}let b=[],p=[],c={};for(let t of u)if(t)for(let e of t.importedModules){if(e.file.includes("module.css"))c[e.file]=e.ssrTransformResult.map.sourcesContent.join("");if(e.file.includes(".css"))p.push(e.file)}for(let t of p){let e=await n.transformRequest(t+"?direct"),d=t.includes("module.css"),o="";if(d)o=j(c[t],e.default);b.push({isDev:!0,id:t,content:d?o:e.code})}return b.map((t,e)=>{return x("style",{key:e,type:"text/css","data-vite-dev-id":t.id,dangerouslySetInnerHTML:{__html:t.content}})})}async function L(i=[]){return i.map((n,s)=>{return x("style",{key:s,id:n?.id,type:"text/css",dangerouslySetInnerHTML:{__html:n.content}})})}import{existsSync as f}from"fs";import{basename as v}from"path";import{networkInterfaces as k}from"os";var y={name:"gemi",version:"0.58.1",private:!1,license:"MIT",author:"Enes Tufekci <enes@gemijs.dev>",repository:{type:"git",url:"git+https://github.com/nstfkc/gemi.git",directory:"packages/gemi"},bin:{gemi:"./dist/bin/gemi.js","gemi-orm-generator":"./dist/bin/orm-generator.js"},files:["dist/**/*","ide/typescript-plugin/package.json"],module:!0,exports:{"./http":"./http/index.ts","./client":"./client/index.ts","./testing":"./testing/index.ts","./app":"./app/index.ts","./facades":"./facades/index.ts","./email":"./email/index.ts","./vite":"./dist/vite/index.mjs","./kernel":"./kernel/index.ts","./services":"./services/index.ts","./broadcasting":"./broadcasting/index.ts","./i18n":"./i18n/index.ts","./dictionary":"./i18n/dictionaryRuntime.ts","./server":"./server/index.ts","./config":"./config/index.ts","./container":"./container/index.ts","./foundation":"./foundation/index.ts","./database":"./database/index.ts","./orm":"./orm/index.ts","./support":"./support/index.ts","./console/run":"./console/run.ts","./ide/typescript-plugin":"./ide/typescript-plugin/index.ts","./bun/preload":"./bun/preload.ts","./bun/plugin":"./bun/plugin.ts"},scripts:{lint:"oxlint",typecheck:"tsc --noEmit -p tsconfig.json && tsc --noEmit -p ide/typescript-plugin/tsconfig.json",build:"NODE_ENV=production bun run build:core && bun run build:bin && bun run build:types && bun run build:client && bun run build:plugin && bun run build:ts-plugin && bun run build:client-types && bun run build:augmentation","build:core":"bun ./scripts/build.ts","build:bin":"bun build --outdir=./dist/bin --target=bun --external=vite --external=react-dom --external=react/jsx-runtime --external=bun --external=sharp --external=@azure/storage-blob --sourcemap ./bin/gemi.ts ./bin/orm-generator.ts && bun ./scripts/prepare-bin.ts","build:client":"rm -rf dist/client dist/testing dist/chunks && vite build -c vite.client.config.mts","build:plugin":"vite build -c vite.plugin.config.mts","build:ts-plugin":"bun ./scripts/build-ts-plugin.ts","build:types":"tsc","build:client-types":"tsc -p tsconfig.browser.json",test:"bun --bun vitest","test:types":"vitest run --typecheck.only","test:packaging":"bun --bun vitest run --config vitest.packaging.config.ts","build:publish":"bun scripts/build-publish.ts",prepublishOnly:"echo 'Do not publish from this directory \u2014 its exports point at TS source. Run: bun run build:publish, then publish from .publish/' && exit 1","build:augmentation":"bun ./scripts/wire-augmentation.ts"},dependencies:{"@aws-sdk/client-s3":"^3.629.0","@aws-sdk/s3-request-presigner":"^3.629.0","ast-types":"^0.14.2",commander:"^12.1.0",history:"^5.3.0","jsx-email":"2.0.0-rc2.1","magic-string":"^0.30.17",open:"^10.1.0","react-error-boundary":"^6.0.0",recast:"^0.23.9",resend:"^6.12.3",satori:"^0.12.2","temporal-polyfill":"^0.2.5","twitter-api-v2":"^1.20.2","urlpattern-polyfill":"^10.0.0",uuid:"^9.0.1"},devDependencies:{"@babel/parser":"^7.25.4","@prisma/generator-helper":"^6.5.0","@repo/typescript-config":"*","@testing-library/react":"^16.3.2","@types/bun":"^1.3.14","@types/eslint":"^8.56.5","@types/node":"^20.11.24","@types/react":"^19.0.2","@types/react-dom":"^19.0.2",eslint:"^8.57.0",jsdom:"^30.0.1",oxlint:"^1.39.0",react:"19.2.3","react-dom":"19.2.3",typescript:"^5.7.3",vitest:"^4.0.0"},peerDependencies:{"@azure/storage-blob":"^12.28.0",react:">=19","react-dom":">=19",sharp:"^0.34.2",vite:"^8.0.0"},peerDependenciesMeta:{"@azure/storage-blob":{optional:!0}}};var z=[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D"],O=Boolean(process.stdout.isTTY)&&!process.env.NO_COLOR,g=(i,n)=>O?`\x1B[${i}m${n}\x1B[0m`:n,T=(i)=>g("1",i),l=(i)=>g("2",i),a=(i)=>g("36",i),q=(i)=>g("32",i),E=(i)=>g("35",i);function R(){return y.version??"?"}function _(i){for(let n of Object.values(k()))for(let s of n??[])if((s.family==="IPv4"||s.family===4)&&!s.internal)return`http://${s.address}:${i}`;return null}function G(i){return h(i).filter((n)=>f(n)).map((n)=>v(n))}function A(i){let{port:n,rootDir:s}=i,r=`http://localhost:${n}`,u=_(n),b=G(s),p=(t,e)=>` ${q("\u279C")} ${T(t.padEnd(9))}${e}`,c=["",...z.map((t)=>E(t)),"",` ${l("gemi")} ${a(`v${R()}`)}`,"",p("Local:",a(r)),p("Network:",u?a(u):l("unavailable")),p("Env:",b.length?b.join(", "):l("none")),""];console.log(c.join(`
|
|
3
|
-
`))}
|
|
4
|
-
export{J as Z,L as _,A as $};
|
|
5
|
-
|
|
6
|
-
//# debugId=3C46E83CE32D242D64756E2164756E21
|
package/dist/chunk-87qab82w.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{Ab as a,Bb as O,Jb as M,Lb as Q,Nb as l,Ob as t,Pb as w,Rb as s}from"./chunk-pmhd6zfc.js";import{Yb as X}from"./chunk-4yt5x8s2.js";import{Cc as E,Ec as q,Fc as L,Kc as g,_b as f,gc as S,mc as D,nc as J,oc as k,tc as U,uc as j}from"./chunk-hwa5sqw5.js";import{Vc as i}from"./chunk-86jebsm4.js";import{De as T,He as v,ye as r}from"./chunk-hxf1re93.js";import{Se as c,Te as h}from"./chunk-qb5mv6pj.js";import{qf as Z}from"./chunk-sy7jbdeb.js";import{vf as N}from"./chunk-w62m5f0n.js";import{Af as W,xf as b}from"./chunk-d125j8t0.js";async function Hx(x){try{if(!await x.shouldRun.call(x))return}catch(B){console.error(`Error evaluating shouldRun for cron job ${x.name}:`,B);return}try{await x.onTick.call(x)}catch(B){console.error(`Error executing cron job ${x.name}:`,B)}try{await x.callback.call(x)}catch(B){console.error(`Error in cron job ${x.name}:`,B)}try{await x.onComplete.call(x)}catch(B){console.error(`Error completing cron job ${x.name}:`,B)}}class I{static token="scheduler";handles=[];resolved;constructor(x){this.resolved=x.jobs}get jobs(){return[...this.resolved]}useJobs(x){this.resolved=x}start(x=(B)=>B()){if(this.resolved.length===0)return;let B=globalThis.__gemiCronJobs??=new Map,C=new Map;for(let G of this.resolved){let H=new G;if(!H.name){console.error(`Cron job must have a name \u2014 ${G.name||"an anonymous class"} has none, so it is not scheduled. If this is a base class that other jobs extend, move it out of the discovered directory: \`abstract\` does not survive to runtime, so a class under app/cron is indistinguishable from a job.`);continue}if(!H.cron){console.error(`Cron job must have an expression. Job: ${H.name}`);continue}let Y=C.get(H.name);if(Y!==void 0){console.error(`Two cron jobs claim the name "${H.name}" \u2014 ${Y} is scheduled and ${G.name||"an anonymous class"} is not. A name is the scheduler's key, so only one job can hold it. Rename one.`);continue}C.set(H.name,G.name||"an anonymous class"),B.get(H.name)?.stop();let _=Bun.cron(H.cron,()=>x(()=>Hx(H)));B.set(H.name,_),this.handles.push(_)}}stop(){for(let x of this.handles)x.stop();this.handles=[]}}class Xx extends X{register(){this.app.singleton(T,()=>new T(this.app.config.get("auth",{})))}}class Yx extends X{register(){this.app.singleton(I,()=>new I(Z(M(),this.app.config.get("schedule",{}))))}async boot(){let x=this.app.make(I),B=this.app.config.get("schedule",{});if(B.jobs===void 0){let{jobsDir:G}=Z(M(),B);x.useJobs(await w(G))}if(process.env.GEMI_NO_SCHEDULE==="1")return;let C=this.app;x.start((G)=>b.run(C,G))}}class R{}class F extends Error{modelName;kind="FeatureModelMissing";constructor(x){super(`No "${x}" model is registered, so every feature stays off. Add the model to prisma/schema.prisma and export it from app/models \u2014 see docs/feature-flags.md.`);this.modelName=x}}class A extends R{modelName;constructor(x="FeatureFlag"){super();this.modelName=x}get available(){return c(this.modelName)}async load(){if(!this.available)throw new F(this.modelName);let x=h(this.modelName);return await x.asSystem(()=>x.findMany())}}function cx(x){return x}function u(){return{features:void 0,enabled:!0,source:new A,model:"FeatureFlag",ttl:30,context:void 0,onEvaluate:void 0,maxClientFlags:200}}class Zx extends X{register(){this.app.singleton(E,()=>{let x=this.app.config.get("features",{}),B=Z(u(),x);if(x?.model!==void 0&&x?.source===void 0)B.source=new A(x.model);else if(x?.model!==void 0&&x.source instanceof A&&x.source.modelName!==x.model)U.warning(`app/config/features.ts sets \`model: "${x.model}"\` and a source reading "${x.source.modelName}". The source wins; drop \`model\` to silence this.`);return new E(B,(C)=>U.warning(C))})}async boot(){let x=this.app.make(E);if(!x.config.enabled)return;try{await x.refresh()}catch(B){U.error(`Could not warm the feature flag cache at boot. ${B instanceof Error?B.message:String(B)}`)}}}class _x extends X{register(){this.app.singleton(N,()=>new N(this.app.config.get("mail",{})))}}function $x(x){return`listener:${x}`}function d(x,B){Px(x);let{maxAttempts:C,worker:G}=B,H=x.event.name,Y=class extends l{maxAttempts=C;worker=G;async run(..._){let[$,P]=Ox(_),z=W(V).rehydrate($,P);await new x().handle(z)}onFail(_){console.error(`The listener ${x.name} threw while handling ${H} off the queue. The queue will retry it up to ${C} times in total; nothing the dispatcher did is waiting on it.`,_)}onDeadletter(_){console.error(`The listener ${x.name} failed every one of its ${C} attempts at ${H} and has been dead-lettered, so this side effect did not happen and will not be retried.`,_)}};return Object.defineProperty(Y,"name",{value:$x(x.name),writable:!0}),Y}function Px(x){if(Object.getOwnPropertyDescriptor(x,"name")?.writable)return;throw Error(`The listener ${x.name} sets \`queued = true\` but does not declare \`static name\`, so it would be registered with the queue `+"under its class name \u2014 which a production build renames while the "+`payload that names it does not. Add: static name = "${x.name}";`)}function Ox(x){return typeof x[0]==="string"?x:x[0]}class V{static token="events";listeners={};events={};warnedFor=new Set;warnedAboutWaiting=new Set;config;constructor(x={}){this.config=Z(Q(),x),this.useListeners(this.config.listeners)}useListeners(x){this.config.listeners=x,this.listeners={},this.events={};let B=new Set;for(let C of x){if(!C.event){console.error(`Listener ${C.name} does not declare \`static event\`, so there is no event name to register it under and its handle will never run. Add: static event = SomeEvent;`);continue}if(B.has(C.name)){console.error(`Two event listeners are named "${C.name}" \u2014 the first is `+"registered and this one is not. A listener's name identifies it across the framework, so only one class can hold it. Rename one.");continue}B.add(C.name);let G=C.event.name,H=new C;this.events[G]??=C.event,(this.listeners[G]??=[]).push({listener:C,job:H.queued?d(C,H):null})}}get queuedListenerJobs(){return Object.values(this.listeners).flat().flatMap(({job:x})=>x?[x]:[])}get registeredListeners(){return[...this.config.listeners]}dispatch(x,B){if(this.deferToCommit(x,B,!1))return;this.runListeners(x,B)}async dispatchAndWait(x,B){if(this.deferToCommit(x,B,!0))return;return this.runListeners(x,B)}deferToCommit(x,B,C){let{name:G,afterCommit:H}=x.constructor;if(!H)return!1;let Y=i(()=>this.runListeners(x,B));if(Y&&C)this.warnNothingIsWaitedFor(G);return Y}async runListeners(x,B){let C=x.constructor.name,G=this.listeners[C];if(!G?.length){this.warnNothingIsListening(C);return}for(let{listener:H,job:Y}of G){if(Y){this.enqueue(Y,H,C,B);continue}try{await new H().handle(x)}catch(_){console.error(`The listener ${H.name} threw while handling ${C}. The `+"listeners after it still ran \u2014 a listener's failure is its own.",_)}}}rehydrate(x,B=[]){let C=this.events[x];if(!C)throw Error(`Cannot rebuild the event "${x}" that came off the queue: no registered listener binds to an event of that name, so there is no class to construct. A queued listener runs in an application that `+"discovered its own listeners \u2014 check that this process sees the "+`same app/listeners as the one that dispatched, and that the event declares \`static name = "${x}"\`.`);return new C(...B)}enqueue(x,B,C,G){if(G===void 0){console.error(`The listener ${B.name} is queued, but ${C} reached the dispatcher without its constructor arguments, so there is nothing for the queue to rebuild it from and the listener was skipped. Dispatch through ${C}.dispatch(...) or ${C}.dispatchAndWait(...), which carry them.`);return}try{W(O).push(x,JSON.stringify([C,G]))}catch(H){console.error(`The listener ${B.name} could not be queued for ${C}, so it did not run and will not be retried. The listeners after it still ran.`,H)}}warnNothingIsWaitedFor(x){if(Bun.env.NODE_ENV==="production")return;if(this.warnedAboutWaiting.has(x))return;this.warnedAboutWaiting.add(x),console.warn(`[gemi] ${x}.dispatchAndWait() was called inside a transaction and ${x} declares \`static afterCommit = true\`, so it resolved `+"immediately with no listener having run \u2014 they run when the "+"transaction commits. Await the transaction instead, or drop afterCommit if the caller needs the side effects first. Development only.")}warnNothingIsListening(x){if(Bun.env.NODE_ENV==="production")return;if(this.warnedFor.has(x))return;this.warnedFor.add(x),console.warn(`[gemi] ${x} was dispatched and nothing is listening for it. A listener registers under the name its event declares, so check that app/listeners holds a Listener with \`static event = ${x}\` and that ${x} declares \`static name = "${x}"\`. Development only.`)}}class zx extends X{register(){this.app.singleton(V,()=>new V(this.app.config.get("events",{})))}async boot(){let x=this.app.make(V),B=this.app.config.get("events",{});if(B.listeners===void 0){let{listenersDir:H}=Z(Q(),B);x.useListeners(await s(H))}let C=x.queuedListenerJobs;if(C.length===0)return;let G=this.app.make(O);for(let H of C)G.registerJob(H)}}class Kx extends X{register(){this.app.singleton(S,()=>new S(this.app.config.get("filesystem",{})))}}class Vx extends X{register(){this.app.singleton(D,()=>new D(this.app.config.get("image",{})))}}class Ax extends X{register(){this.app.instance(j,new j)}}function FB(x){return x}function o(){return{maxFileSize:10485760,onLogFileClosed:()=>{},onLogCreated:()=>{}}}class Ix extends X{register(){this.app.singleton(J,()=>new J(Z(o(),this.app.config.get("log",{}))))}async boot(){await this.app.make(J).boot()}}class Ux extends X{register(){this.app.singleton(k,()=>new k(Z(r(),this.app.config.get("middleware",{}))))}}function bB(x){return x}function n(){return{channels:{}}}class Ex extends X{register(){this.app.singleton(f,()=>new f(Z(n(),this.app.config.get("broadcast",{}))))}}class Wx extends X{register(){this.app.singleton(O,()=>new O(this.app.config.get("queue",{})))}async boot(){let x=this.app.config.get("queue",{});if(x.jobs!==void 0)return;let{jobsDir:B}=Z(a(),x);this.app.make(O).useJobs(await t(B))}}class y{}function e(x,B){return Math.floor(x/B)*B}function Qx(x,B){if(B<=0)return 0;return Math.min(1,Math.max(0,(B-x)/B))}function p(x,B,C,G){return B*Qx(C,G)+x}function Jx(x){let{current:B,previous:C,elapsed:G,window:H,limit:Y,cost:_}=x,$=Y-_;if($<0)return H;let P=Math.max(0,H-G);if(C>0){let K=H*(1-($-B)/C);if(K<=H)return Math.max(0,Math.ceil(K-G))}if(B<=$)return P;if(B<=0)return P;let z=H*(1-$/B);return P+Math.max(0,Math.ceil(z))}function xx(x){let{allowed:B,current:C,previous:G,now:H,elapsed:Y,window:_,limit:$,cost:P}=x;if(B){let K=p(C,G,Y,_);return{allowed:!0,limit:$,remaining:Math.max(0,Math.floor($-K)),resetAt:H-Y+_,retryAfter:0}}let z=Jx({current:C,previous:G,elapsed:Y,window:_,limit:$,cost:P});return{allowed:!1,limit:$,remaining:0,resetAt:H+z,retryAfter:z}}class m extends y{buckets=new Map;maxKeys;constructor(x={}){super();this.maxKeys=Math.max(1,x.maxKeys??1e5)}consume(x){let{key:B,limit:C,window:G}=x,H=x.cost??1,Y=Date.now(),_=e(Y,G),$=Y-_,P=this.roll(B,G,_),K=p(P.current,P.previous,$,G)+H<=C;if(K)P.current+=H;if(this.buckets.delete(B),this.buckets.set(B,P),this.buckets.size>this.maxKeys)this.evict(Y);return xx({allowed:K,current:P.current,previous:P.previous,now:Y,elapsed:$,window:G,limit:C,cost:H})}clear(){this.buckets.clear()}get size(){return this.buckets.size}roll(x,B,C){let G=this.buckets.get(x);if(!G||G.window!==B)return{window:B,windowStart:C,current:0,previous:0};if(G.windowStart===C)return G;if(G.windowStart+B===C)return G.previous=G.current,G.current=0,G.windowStart=C,G;return G.previous=0,G.current=0,G.windowStart=C,G}evict(x){for(let[B,C]of this.buckets)if(x-C.windowStart>=C.window*2)this.buckets.delete(B);for(let B of this.buckets.keys()){if(this.buckets.size<=this.maxKeys)break;this.buckets.delete(B)}}}function HC(x){return x}function Bx(){return{driver:new m,limit:1000,window:60}}class qx extends X{register(){this.app.singleton(v,()=>new v(Z(Bx(),this.app.config.get("ratelimiter",{}))))}}function OC(x){return x}function Cx(){return{url:process.env.REDIS_URL,options:void 0}}class Lx extends X{register(){this.app.singleton(g,()=>new g(Z(Cx(),this.app.config.get("redis",{}))))}}function Gx(x,B){let C=x.get(B);if(!C)throw Error(`Missing "${B}" configuration. Route dispatching needs a root router \u2014 declare it in "app/config/route.ts".`);return C}class Tx extends X{register(){this.app.singleton(L,()=>new L(Gx(this.app.config,"route.api"))),this.app.singleton(q,()=>new q(Gx(this.app.config,"route.view")))}boot(){this.app.make(L),this.app.make(q)}}
|
|
3
|
-
export{I as Ta,Xx as Ua,Yx as Va,R as Wa,F as Xa,A as Ya,cx as Za,u as _a,Zx as $a,_x as ab,V as bb,zx as cb,Kx as db,Vx as eb,Ax as fb,FB as gb,o as hb,Ix as ib,Ux as jb,bB as kb,n as lb,Ex as mb,Wx as nb,e as ob,xx as pb,y as qb,m as rb,HC as sb,Bx as tb,qx as ub,OC as vb,Cx as wb,Lx as xb,Tx as yb};
|
|
4
|
-
|
|
5
|
-
//# debugId=5D03A6748D65150C64756E2164756E21
|
package/dist/chunk-b35e128b.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{Xb as o}from"./chunk-437085pe.js";import{Yb as i}from"./chunk-4yt5x8s2.js";import{qf as t}from"./chunk-sy7jbdeb.js";import{wf as r}from"./chunk-cyaz97p5.js";class n extends i{register(){this.app.singleton(r,()=>new r(t(o(),this.app.config.get("translation",{}))))}}
|
|
3
|
-
export{n as Qa};
|
|
4
|
-
|
|
5
|
-
//# debugId=367CF69A359EB0A064756E2164756E21
|