gorig-cli 1.0.24 → 1.0.25
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 +112 -22
- package/commands/create.js +267 -112
- package/commands/doc.js +21 -8
- package/commands/init.js +316 -255
- package/commands/skill.js +55 -17
- package/package.json +1 -1
- package/templates/config.go.ejs +6 -5
- package/templates/config.yaml.ejs +3 -38
- package/templates/controller.go.ejs +11 -42
- package/templates/crud.controller.go.ejs +71 -0
- package/templates/crud.doc.md.ejs +60 -0
- package/templates/crud.dto.go.ejs +103 -0
- package/templates/crud.integration.init_test.go.ejs +17 -0
- package/templates/crud.integration_test.go.ejs +66 -0
- package/templates/crud.model.go.ejs +25 -0
- package/templates/crud.module.README.md.ejs +70 -0
- package/templates/crud.mongo.config.yaml.ejs +19 -0
- package/templates/crud.mysql.config.yaml.ejs +14 -0
- package/templates/crud.router.go.ejs +18 -0
- package/templates/crud.service.go.ejs +179 -0
- package/templates/crud.test.go.ejs +52 -0
- package/templates/dto.go.ejs +16 -0
- package/templates/gitignore.ejs +18 -49
- package/templates/hello.controller.go.ejs +19 -0
- package/templates/hello.router.go.ejs +12 -0
- package/templates/hello.service.go.ejs +31 -0
- package/templates/hello.test.go.ejs +34 -0
- package/templates/init.go.ejs +1 -27
- package/templates/main.go.ejs +2 -5
- package/templates/model.go.ejs +3 -4
- package/templates/project.README.md.ejs +35 -0
- package/templates/router.go.ejs +4 -8
- package/templates/service.go.ejs +25 -77
- package/templates/skills/gorig-backend/SKILL.md +89 -0
- package/templates/skills/gorig-backend/agents/openai.yaml +4 -0
- package/templates/skills/gorig-backend/assets/api-doc-template.md +59 -0
- package/templates/skills/gorig-backend/assets/gorig.gitignore +65 -0
- package/templates/skills/gorig-backend/assets/module-readme-template.md +58 -0
- package/templates/skills/gorig-backend/references/advanced-data-access.md +275 -0
- package/templates/skills/gorig-backend/references/auth-security.md +194 -0
- package/templates/skills/gorig-backend/references/business-scenarios.md +155 -0
- package/templates/skills/gorig-backend/references/cache.md +301 -0
- package/templates/skills/gorig-backend/references/capability-matrix.md +37 -0
- package/templates/skills/gorig-backend/references/configuration.md +48 -0
- package/templates/skills/gorig-backend/references/framework-api.md +190 -0
- package/templates/skills/gorig-backend/references/messaging.md +143 -0
- package/templates/skills/gorig-backend/references/onboarding-files.md +46 -0
- package/templates/skills/gorig-backend/references/outbound-http.md +162 -0
- package/templates/skills/gorig-backend/references/persistent-crud.md +332 -0
- package/templates/skills/gorig-backend/references/project-bootstrap.md +128 -0
- package/templates/skills/gorig-backend/references/scheduled-tasks.md +231 -0
- package/templates/skills/gorig-backend/references/service-lifecycle.md +51 -0
- package/templates/skills/gorig-backend/references/source-map.md +43 -0
- package/templates/skills/gorig-backend/references/source-policy.md +58 -0
- package/templates/skills/gorig-backend/references/sse.md +121 -0
- package/templates/skills/gorig-backend/references/testing.md +171 -0
- package/templates/skills/gorig-backend/scripts/check-source-links.sh +48 -0
- package/templates/skills/gorig-backend/scripts/detect-gorig-context.sh +67 -0
- package/templates/skills/gorig-backend/scripts/verify-basic-project.sh +108 -0
- package/npm_publish copy.sh +0 -65
- package/templates/internal.go.ejs +0 -49
- package/templates/req.go.ejs +0 -8
- package/templates/resp.go.ejs +0 -8
- package/templates/service.pub.go.ejs +0 -22
- package/templates/skills/claude/gorig-backend/SKILL.md +0 -766
- package/templates/skills/claude/gorig-backend/assets/api-doc-template.md +0 -50
- package/templates/skills/claude/gorig-backend/assets/module-readme-template.md +0 -46
- package/templates/skills/claude/gorig-backend/references/onboarding-files.md +0 -44
- package/templates/skills/codex/gorig-backend/SKILL.md +0 -766
- package/templates/skills/codex/gorig-backend/agents/openai.yaml +0 -4
- package/templates/skills/codex/gorig-backend/assets/api-doc-template.md +0 -50
- package/templates/skills/codex/gorig-backend/assets/module-readme-template.md +0 -46
- package/templates/skills/codex/gorig-backend/references/onboarding-files.md +0 -44
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Authentication and Security
|
|
2
|
+
|
|
3
|
+
Use this reference when the request involves login, logout, protected routes, token refresh, user identity, roles, access filtering, CORS, debounce/rate protection, request signing, secrets, or sensitive logs.
|
|
4
|
+
|
|
5
|
+
Always inspect the target project's resolved Gorig source first. Redis token manager is implemented in the current source line and is expected to be available in releases that include `mid/tokenx/redis.manager.go` such as the planned `v0.0.53+`. Local `master` commit `4dcf601` has a real Redis integration test in `test/tokenx_redis_test.go`; that test passes when Redis is configured and skips with `redis is not configured` when the environment is missing Redis.
|
|
6
|
+
|
|
7
|
+
## Business Workflow
|
|
8
|
+
|
|
9
|
+
Translate business language before coding:
|
|
10
|
+
|
|
11
|
+
- "Login" means a route verifies credentials, creates a token, records it through the selected token manager, and returns only non-sensitive user data.
|
|
12
|
+
- "Only logged-in users can access" means protected routes must use token middleware and service logic must read the injected user context, not trust a request body user id.
|
|
13
|
+
- "Admin only" or "role-based access" means `SignUserDef` with `userInfo` filters when the required attributes are inside the token claims; use service-layer authorization when the rule depends on live database state.
|
|
14
|
+
- "Logout" means the current token is destroyed and the same token no longer accesses protected routes.
|
|
15
|
+
- "Refresh token" means the old token is eligible for refresh, a new token is generated, the manager swaps old to new, and the old token stops working.
|
|
16
|
+
|
|
17
|
+
For a material auth change, present this confirmation plan before editing:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Business goal:
|
|
21
|
+
- <who can do what, and what logout/expiry should mean>
|
|
22
|
+
|
|
23
|
+
Identity model:
|
|
24
|
+
- User id source:
|
|
25
|
+
- Token claims/userInfo:
|
|
26
|
+
- Role or attribute rules:
|
|
27
|
+
- Whether live database checks are required:
|
|
28
|
+
|
|
29
|
+
Recommended design:
|
|
30
|
+
- Token manager:
|
|
31
|
+
- Protected routes:
|
|
32
|
+
- Forbidden behavior:
|
|
33
|
+
- Logout/refresh behavior:
|
|
34
|
+
|
|
35
|
+
Security boundary:
|
|
36
|
+
- Secret source:
|
|
37
|
+
- Sensitive fields excluded from logs/responses:
|
|
38
|
+
- Redis-token status for this Gorig version:
|
|
39
|
+
|
|
40
|
+
Verification:
|
|
41
|
+
- Login -> protected route -> logout:
|
|
42
|
+
- Missing/malformed/expired/refreshed/revoked/forbidden token:
|
|
43
|
+
- User context and trace context:
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Token Manager
|
|
47
|
+
|
|
48
|
+
Memory token is the verified default for local and simple single-process flows:
|
|
49
|
+
|
|
50
|
+
```go
|
|
51
|
+
svc := tokenx.Get(tokenx.Jwt, tokenx.Memory)
|
|
52
|
+
token, err := svc.Manager.GenerateAndRecord(ctx, userID, userInfo, 3600)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Important behavior:
|
|
56
|
+
|
|
57
|
+
- `Generator.Generate(userID, userInfo, expireAt)` uses `expireAt` as seconds from now in the inspected source.
|
|
58
|
+
- `Manager.GenerateAndRecord` may reuse an existing token for the same user id and same derived user type.
|
|
59
|
+
- `Manager.Clean(userID)` revokes all tokens for the user.
|
|
60
|
+
- `Manager.Destroy(token)` revokes one token.
|
|
61
|
+
- Memory token state is process-local plus `./tokens.json` persistence in inspected versions; do not present it as cross-instance session storage.
|
|
62
|
+
- In inspected versions, `Destroy(token)` deletes from memory but does not itself force an immediate `tokens.json` rewrite. Verify logout/revocation in the target app, especially for the last active token in a process.
|
|
63
|
+
- Do not log raw tokens or full userInfo when it may include phone numbers, emails, roles not intended for clients, or other sensitive data.
|
|
64
|
+
|
|
65
|
+
Redis token rules:
|
|
66
|
+
|
|
67
|
+
- Confirm the target version includes `mid/tokenx/redis.manager.go` or an equivalent Redis manager implementation before using `httpx.SignRedis()` or `SignUserRedis()`.
|
|
68
|
+
- For `v0.0.53+` or any project resolved to source containing the Redis manager, treat Redis token as an implemented and usable capability.
|
|
69
|
+
- Check whether the target source includes `test/tokenx_redis_test.go` or equivalent integration coverage.
|
|
70
|
+
- When Redis is configured, run `go test ./test -run TestRedisTokenManager -v` or the target project's equivalent as the runtime integration check.
|
|
71
|
+
- When Redis is not configured and the test skips, report the missing Redis configuration and tell the user how to provide it; do not describe Redis token as unavailable or switch to memory unless the user accepts the single-process tradeoff.
|
|
72
|
+
- The master integration test covers `GenerateAndRecord`, same-user/same-`userInfo` token reuse, old-token cleanup when userInfo changes, `Clean(userID)`, `Refresh`, `Destroy`, and `IsEffective`.
|
|
73
|
+
- Never silently switch a user-requested Redis session design to memory token without explaining the restart/cross-process behavior difference.
|
|
74
|
+
|
|
75
|
+
## Middleware
|
|
76
|
+
|
|
77
|
+
Use memory middleware for simple local single-process flows. Use Redis middleware when the business needs Redis-backed token state and the target version includes the Redis token manager:
|
|
78
|
+
|
|
79
|
+
```go
|
|
80
|
+
group := root.Group("/account")
|
|
81
|
+
group.GET("/me", httpx.SignDef(), me)
|
|
82
|
+
group.GET("/admin", httpx.SignUserDef(map[string]interface{}{"role": "admin"}), admin)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Middleware expectations:
|
|
86
|
+
|
|
87
|
+
- Missing `Authorization` header returns a token parameter error.
|
|
88
|
+
- Header must be `Bearer <token>` and token length must pass the framework check.
|
|
89
|
+
- Valid token sets `consts.TokenKey`, user id via `apix.SetUserID`, and userInfo via `apix.SetUserInfo`.
|
|
90
|
+
- User id is also inserted into request context through `consts.UserIDKey`, so logger fields can include it when the same context is propagated.
|
|
91
|
+
- `SignUserDef` filters against JWT `UserInfo`; it does not query the database.
|
|
92
|
+
- A mismatched filter returns forbidden; use service-level checks for data-owner rules such as "only the order owner can update this order".
|
|
93
|
+
|
|
94
|
+
## Minimal Login Flow
|
|
95
|
+
|
|
96
|
+
Keep the first slice small:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
POST /auth/login
|
|
100
|
+
- verify demo or real credentials in service
|
|
101
|
+
- generate and record token
|
|
102
|
+
- return token plus public user profile
|
|
103
|
+
|
|
104
|
+
GET /auth/me
|
|
105
|
+
- protected with SignDef
|
|
106
|
+
- read user id and userInfo from apix
|
|
107
|
+
|
|
108
|
+
POST /auth/logout
|
|
109
|
+
- protected with SignDef
|
|
110
|
+
- destroy current token from httpx.GetToken
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
When no user store exists yet, use an explicit demo account only in local fixtures and label it as non-production.
|
|
114
|
+
|
|
115
|
+
## Refresh Flow
|
|
116
|
+
|
|
117
|
+
Use the manager directly and test both old and new tokens:
|
|
118
|
+
|
|
119
|
+
```go
|
|
120
|
+
oldToken := httpx.GetToken(ctx)
|
|
121
|
+
if !svc.Manager.IsMeetRefresh(oldToken) {
|
|
122
|
+
response.ErrorTokenRefreshFail(ctx)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
newToken, err := svc.Generator.Generate(userID, userInfo, refreshSeconds)
|
|
126
|
+
if err != nil || !svc.Manager.Refresh(oldToken, newToken) {
|
|
127
|
+
response.ErrorTokenRefreshFail(ctx)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
After refresh:
|
|
133
|
+
|
|
134
|
+
- old token must fail protected routes;
|
|
135
|
+
- new token must pass protected routes;
|
|
136
|
+
- user context must still be present.
|
|
137
|
+
|
|
138
|
+
## CORS
|
|
139
|
+
|
|
140
|
+
The built-in HTTP service installs `httpx.CORS()` globally in inspected source. For direct router tests or custom servers, attach it explicitly.
|
|
141
|
+
|
|
142
|
+
Verify:
|
|
143
|
+
|
|
144
|
+
- `OPTIONS` preflight returns HTTP 204;
|
|
145
|
+
- `Access-Control-Allow-Origin` echoes the request origin when present;
|
|
146
|
+
- `Access-Control-Allow-Headers` includes `Authorization` and any custom allowed headers;
|
|
147
|
+
- credentials are allowed only when this matches the business security model.
|
|
148
|
+
|
|
149
|
+
Use `httpx.SetOtherAllowHeaders("X-Tenant-ID")` only for explicit header requirements.
|
|
150
|
+
|
|
151
|
+
## Debounce and Rate Protection
|
|
152
|
+
|
|
153
|
+
The built-in HTTP service installs `httpx.Debounce(200 * time.Millisecond)` in inspected source. For route-level behavior tests, attach `httpx.Debounce(duration)` directly.
|
|
154
|
+
|
|
155
|
+
Behavior:
|
|
156
|
+
|
|
157
|
+
- The key is path plus query for GET requests.
|
|
158
|
+
- If a valid token is present, the key uses the token's user id.
|
|
159
|
+
- Otherwise, the key uses client IP.
|
|
160
|
+
- A repeated request within the duration returns HTTP 429.
|
|
161
|
+
- `httpx.DebouceAw(path...)` whitelists exact paths. Preserve the misspelling because it is the inspected API name.
|
|
162
|
+
- `httpx.DebounceDisable()` disables the global debounce switch for the process; use it cautiously in tests because it affects later tests.
|
|
163
|
+
|
|
164
|
+
## Request Signing Boundary
|
|
165
|
+
|
|
166
|
+
Do not invent request-signature support. If the business asks for "API signature" or "signed callbacks":
|
|
167
|
+
|
|
168
|
+
1. Search the target source for a verified signing middleware/helper.
|
|
169
|
+
2. If absent, propose a custom middleware with nonce, timestamp, body hash, shared secret, replay window, and tests.
|
|
170
|
+
3. Require explicit confirmation because this is security-sensitive custom behavior.
|
|
171
|
+
|
|
172
|
+
## Secret and Log Rules
|
|
173
|
+
|
|
174
|
+
- Read JWT secrets from configuration or environment, never from hard-coded examples.
|
|
175
|
+
- Use placeholder values such as `change-me-local-jwt-key`; never include real tokens, passwords, webhook URLs, or production hosts in fixtures.
|
|
176
|
+
- Do not log `Authorization`, raw token strings, passwords, refresh secrets, or full outbound request bodies containing credentials.
|
|
177
|
+
- Existing middleware may log headers; if protecting sensitive routes, inspect and adjust logging behavior before production use.
|
|
178
|
+
- The inspected memory `Destroy` logs the raw token. Treat this as a production hardening item if framework behavior is unchanged in the target version.
|
|
179
|
+
|
|
180
|
+
## Verification Checklist
|
|
181
|
+
|
|
182
|
+
Tests must cover:
|
|
183
|
+
|
|
184
|
+
- valid token reaches protected route;
|
|
185
|
+
- missing header fails;
|
|
186
|
+
- malformed `Authorization` fails;
|
|
187
|
+
- expired JWT is tested through `IsNotExpired`/`IsEffective`, and protected-route behavior is tested for manager-expired or unrecorded tokens according to the resolved version;
|
|
188
|
+
- refreshed token swaps old to new;
|
|
189
|
+
- revoked/logout token fails;
|
|
190
|
+
- forbidden role or attribute fails;
|
|
191
|
+
- middleware writes `apix.GetUserID`, `apix.GetUserInfo`, request-context user id, and preserves trace context;
|
|
192
|
+
- CORS preflight and allowed headers;
|
|
193
|
+
- debounce repeated request and whitelisted path;
|
|
194
|
+
- examples and fixtures contain no real secrets.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Business Scenario Workflow
|
|
2
|
+
|
|
3
|
+
Use this reference when the user describes a business outcome instead of naming a Gorig component. This file is a workflow, not a rule catalog. Do not keep adding one-off mappings for every module or domain.
|
|
4
|
+
|
|
5
|
+
The goal is to help the model reason from the user's business language to a small, confirmed backend plan using the target project's actual code and the verified Gorig capabilities.
|
|
6
|
+
|
|
7
|
+
## Interaction Workflow
|
|
8
|
+
|
|
9
|
+
When the user asks for a feature in ordinary language, do this before coding:
|
|
10
|
+
|
|
11
|
+
1. Restate the business outcome.
|
|
12
|
+
2. Inspect the existing project model, service, routes, docs, config, and tests that are likely related.
|
|
13
|
+
3. Identify the workflow shape:
|
|
14
|
+
- trigger: user action, persisted data change, schedule, external callback, or stream request;
|
|
15
|
+
- wait/ordering: immediate, delayed, periodic, ordered, retryable, or one-shot;
|
|
16
|
+
- durability: may be lost on restart, must survive restart/deploy, or must be auditable;
|
|
17
|
+
- visibility: backend-only, API-visible, browser-real-time, or external-system-visible;
|
|
18
|
+
- consistency: best-effort, at-least-once, exactly-once expected by business, or idempotent enough;
|
|
19
|
+
- state owner: existing table/model, new field, new log table, cache, message payload, token/session manager, external service, or no persisted state;
|
|
20
|
+
- identity/security: anonymous, logged-in user, role/attribute restricted, owner-only, signed external callback, or system-to-system secret;
|
|
21
|
+
- outbound dependency: none, internal service, third-party API, user-provided URL, or webhook/callback.
|
|
22
|
+
4. Generate a recommended design using Gorig capabilities already verified for the resolved version.
|
|
23
|
+
5. Compare alternatives only when the business requirement has a real tradeoff, such as precision vs. infrastructure, synchronous vs. async, polling vs. streaming, or local vs. cross-process behavior.
|
|
24
|
+
6. Recommend the smallest design that satisfies the stated business outcome.
|
|
25
|
+
7. Separate current scope from future extensions.
|
|
26
|
+
8. Ask only the blocking confirmation questions.
|
|
27
|
+
9. Wait for explicit confirmation before editing code.
|
|
28
|
+
|
|
29
|
+
## Gorig Capability Families
|
|
30
|
+
|
|
31
|
+
Use these as ingredients during analysis. Do not expose them as choices the user must understand unless a tradeoff depends on them.
|
|
32
|
+
|
|
33
|
+
- Service-layer logic: synchronous business rules and durable database updates.
|
|
34
|
+
- Persistent CRUD: durable business state, audit logs, queryable records, and API-visible resources.
|
|
35
|
+
- `cronx` scheduled tasks: periodic checks, background maintenance, delayed work, one-shot work, timeout, panic recovery, and shutdown.
|
|
36
|
+
- Redis-backed persistent cron tasks: delayed work that should survive restart/deploy when Redis is available; handlers must be idempotent.
|
|
37
|
+
- `messagex`: async fan-out, sequential handling, retry, DLQ, and cross-process events when Redis-backed.
|
|
38
|
+
- SSE: browser-visible server push when the user needs live updates.
|
|
39
|
+
- Cache: read acceleration, counters, local persistence, and multi-level caching when stale/loss behavior is acceptable.
|
|
40
|
+
- Authentication and security middleware: login/logout, protected routes, token refresh/revocation, user context, role/attribute filtering, CORS, and debounce protection.
|
|
41
|
+
- Outbound HTTP: external API calls, header/context propagation, timeouts, bad upstream response handling, XML/JSON/form payloads, and controlled image fetching.
|
|
42
|
+
- Direct database access: only when `domainx/dx` cannot express the required operation and the risk is justified.
|
|
43
|
+
|
|
44
|
+
## Candidate Design Guidance
|
|
45
|
+
|
|
46
|
+
For the recommended design, describe:
|
|
47
|
+
|
|
48
|
+
- business behavior: what the user will observe;
|
|
49
|
+
- data impact: fields, tables, indexes, logs, or no schema change;
|
|
50
|
+
- runtime behavior: sync, delayed, scheduled, event-driven, stream, cache;
|
|
51
|
+
- security behavior: who can call it, how identity is proven, what data is forbidden, and which secrets/config values are required;
|
|
52
|
+
- outbound behavior: external endpoint, timeout, retry/idempotency, and fallback/error behavior;
|
|
53
|
+
- failure behavior: restart, duplicate execution, missing dependency, timeout, panic, retry;
|
|
54
|
+
- verification: ordinary tests, integration tests, smoke checks, and skipped checks if infrastructure is missing.
|
|
55
|
+
|
|
56
|
+
When alternatives matter, describe only the meaningful difference. Do not list options just to satisfy a template.
|
|
57
|
+
|
|
58
|
+
## Assumption Discipline
|
|
59
|
+
|
|
60
|
+
Do not silently relax a business requirement to make the implementation simpler.
|
|
61
|
+
|
|
62
|
+
If the recommended design changes any of these properties, state the assumption and ask for confirmation:
|
|
63
|
+
|
|
64
|
+
- timing precision: exact delay vs. scan/polling drift;
|
|
65
|
+
- durability: survives restart/deploy vs. best-effort in-process work;
|
|
66
|
+
- consistency: immediate update vs. eventual consistency;
|
|
67
|
+
- visibility: real-time push vs. refresh/polling;
|
|
68
|
+
- delivery: at-most-once, at-least-once, retry, or possible duplicate execution;
|
|
69
|
+
- data ownership: existing business table vs. new field/log table/cache/message payload.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
|
|
73
|
+
- If using periodic scanning for a requirement phrased as "after N minutes", state the maximum expected delay, such as "N minutes plus up to one scan interval", and ask whether that is acceptable.
|
|
74
|
+
- If using Redis-backed delayed work, state that Redis is an infrastructure prerequisite and that handlers must be idempotent.
|
|
75
|
+
- If skipping SSE for a "future real-time" statement, state that the current feature will be visible through stored data/API only.
|
|
76
|
+
- If using cache, state what can be stale and for how long.
|
|
77
|
+
- If using memory token, state that sessions are local to the process and not suitable as a cross-instance login store.
|
|
78
|
+
- If using Redis token, state the target Gorig version and Redis test requirement before claiming it works.
|
|
79
|
+
- If calling a third-party service, state timeout, bad-response behavior, and which headers/secrets are sent.
|
|
80
|
+
|
|
81
|
+
When a requirement uses strong language such as "must not be lost", "exactly", "immediately", "real-time", or "cannot duplicate", treat it as a blocking confirmation point unless the existing project already proves the behavior.
|
|
82
|
+
|
|
83
|
+
Avoid these mistakes:
|
|
84
|
+
|
|
85
|
+
- Do not choose a component only because the user used a word that resembles it.
|
|
86
|
+
- Do not replace a stated timing or durability requirement with an unrelated simpler mechanism without explaining the behavior difference.
|
|
87
|
+
- Do not expand into full CRUD, public APIs, or SSE just because they might be useful later.
|
|
88
|
+
- Do not claim runtime behavior is verified when Redis, MySQL, MongoDB, or other required infrastructure was unavailable.
|
|
89
|
+
- Do not forward user tokens to external services unless that is the explicit contract.
|
|
90
|
+
- Do not put real secrets, production URLs, raw tokens, or passwords in generated examples or fixtures.
|
|
91
|
+
- Do not hard-code one domain's nouns into the plan for another domain.
|
|
92
|
+
|
|
93
|
+
## Confirmation Plan Shape
|
|
94
|
+
|
|
95
|
+
Use this structure. Fill it with facts from the target project after inspection.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
Business goal:
|
|
99
|
+
- <business outcome in user language>
|
|
100
|
+
|
|
101
|
+
What I found in the project:
|
|
102
|
+
- Related modules/models/fields:
|
|
103
|
+
- Existing configuration and external dependencies:
|
|
104
|
+
- Current gaps:
|
|
105
|
+
|
|
106
|
+
Scenario breakdown:
|
|
107
|
+
- Trigger:
|
|
108
|
+
- Delay/order/periodicity:
|
|
109
|
+
- Durability requirement:
|
|
110
|
+
- User visibility:
|
|
111
|
+
- Consistency and idempotency requirement:
|
|
112
|
+
- Identity and permission requirement:
|
|
113
|
+
- External service dependency:
|
|
114
|
+
|
|
115
|
+
Recommended design:
|
|
116
|
+
- <recommended option and why it best matches the stated business outcome>
|
|
117
|
+
|
|
118
|
+
Optional tradeoffs:
|
|
119
|
+
- <only include when there is a real tradeoff; omit this section when the recommendation is obvious>
|
|
120
|
+
|
|
121
|
+
Current scope:
|
|
122
|
+
- Must implement:
|
|
123
|
+
- Out of scope but keep extension points:
|
|
124
|
+
|
|
125
|
+
Need confirmation:
|
|
126
|
+
- <only blocking questions>
|
|
127
|
+
- <include any assumption that relaxes timing, durability, consistency, or real-time behavior>
|
|
128
|
+
|
|
129
|
+
Verification plan:
|
|
130
|
+
- Business-effect tests:
|
|
131
|
+
- Component/infrastructure tests:
|
|
132
|
+
- If a dependency is unavailable, which checks will be skipped and cannot be claimed verified:
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Verification Principle
|
|
136
|
+
|
|
137
|
+
Verification must prove the business effect first and the framework primitive second.
|
|
138
|
+
|
|
139
|
+
Examples of business-effect assertions:
|
|
140
|
+
|
|
141
|
+
- An entity that still matches the delayed condition receives the intended action.
|
|
142
|
+
- An entity that no longer matches the condition is not changed.
|
|
143
|
+
- Duplicate or retried executions are idempotent.
|
|
144
|
+
- Audit/log records are created exactly as required.
|
|
145
|
+
- Real-time delivery is proven only if it is in current scope.
|
|
146
|
+
|
|
147
|
+
Component-level checks support the above:
|
|
148
|
+
|
|
149
|
+
- scheduled/delayed task executes, times out, recovers, or is skipped with an infrastructure reason;
|
|
150
|
+
- persistent delayed task handles bad payloads and runs with Redis when available;
|
|
151
|
+
- message subscriber receives payload, preserves order when required, retries, and cleans up;
|
|
152
|
+
- SSE response has the correct event shape and disconnect cleanup;
|
|
153
|
+
- cache hit/miss/expiry/invalidation behavior is proven.
|
|
154
|
+
|
|
155
|
+
Never mark a feature verified only because the framework primitive compiled.
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Cache
|
|
2
|
+
|
|
3
|
+
Use this reference when a task needs cached reads, local persistence, Redis-backed cache, counters, multi-level cache, cache invalidation, or SQLite paged cache.
|
|
4
|
+
|
|
5
|
+
Always inspect the target project's resolved Gorig source first. The examples below reflect the locally inspected Gorig `master` commit `35bbefb`.
|
|
6
|
+
|
|
7
|
+
## Backends
|
|
8
|
+
|
|
9
|
+
The inspected cache package exposes one generic interface:
|
|
10
|
+
|
|
11
|
+
```go
|
|
12
|
+
type Cache[T any] interface {
|
|
13
|
+
Get(key string) (T, error)
|
|
14
|
+
Set(key string, value T, expiration time.Duration) error
|
|
15
|
+
Del(key string) error
|
|
16
|
+
Exists(key string) (bool, error)
|
|
17
|
+
RPush(key string, value T) error
|
|
18
|
+
BRPop(timeout time.Duration, key string) (T, error)
|
|
19
|
+
Incr(key string) (int64, error)
|
|
20
|
+
Expire(key string, expiration time.Duration) error
|
|
21
|
+
Flush() error
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Create typed caches with `cache.New[T]`:
|
|
26
|
+
|
|
27
|
+
```go
|
|
28
|
+
memory := cache.New[Order](cache.Memory, time.Minute, time.Minute)
|
|
29
|
+
jsonFile := cache.New[Order](cache.JSON, "orders")
|
|
30
|
+
sqlite := cache.New[Order](cache.Sqlite, "orders")
|
|
31
|
+
redis := cache.New[Order](cache.Redis)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Backend notes:
|
|
35
|
+
|
|
36
|
+
- Memory is process-local and disappears on restart.
|
|
37
|
+
- JSON persists under `.cache/<name>.json` and is useful for small local data.
|
|
38
|
+
- SQLite persists under `.cache/<name>.db` and is useful for local durable cache values.
|
|
39
|
+
- Redis requires configured Redis infrastructure. Do not claim Redis behavior is verified from compile-only checks.
|
|
40
|
+
|
|
41
|
+
Check initialization and handle errors before depending on a cache:
|
|
42
|
+
|
|
43
|
+
```go
|
|
44
|
+
if memory == nil || !memory.IsInitialized() {
|
|
45
|
+
return errors.Sys("memory cache is not initialized")
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Backend Choice
|
|
50
|
+
|
|
51
|
+
Choose the cache backend from the target project's runtime needs and existing conventions.
|
|
52
|
+
|
|
53
|
+
Supported Gorig cache backends in the inspected baseline:
|
|
54
|
+
|
|
55
|
+
| Backend | Use when | Configuration |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Memory | Fast process-local acceleration; loss on restart is acceptable. | No external config. |
|
|
58
|
+
| JSON | Small local durable cache values are useful during development or simple deployments. | Cache name creates `.cache/<name>.json`. |
|
|
59
|
+
| SQLite | Local durable cache values need better structure than JSON. | Cache name creates `.cache/<name>.db`; paged cache creates `.cache/<name>.pg.db`. |
|
|
60
|
+
| Redis | Cross-process cache, shared deployment cache, or explicit Redis requirement. | Requires `redis.addr`, `redis.password`, `redis.db`, or `GORIG_REDIS_*` overrides. |
|
|
61
|
+
|
|
62
|
+
Selection rules:
|
|
63
|
+
|
|
64
|
+
- When the backend is not specified, inspect existing project conventions and present reasonable backend options with tradeoffs.
|
|
65
|
+
- If the project already has a cache helper, key convention, TTL convention, or versioning scheme, prefer extending that pattern.
|
|
66
|
+
- If Redis is selected or already established by the project, inspect Redis configuration before implementing.
|
|
67
|
+
- If local cache is selected, prefer Memory for disposable acceleration, JSON or SQLite only when restart persistence is useful.
|
|
68
|
+
- Do not introduce counters, multi-level cache, Redis, or other infrastructure unless the request requires them.
|
|
69
|
+
- Treat cache as an acceleration layer unless the feature explicitly accepts loss, expiry, and cross-process state behavior.
|
|
70
|
+
|
|
71
|
+
## Redis Configuration
|
|
72
|
+
|
|
73
|
+
Before implementing Redis-backed cache or multi-level cache, inspect `_bin/local.yaml`, `_bin/dev.yaml`, `_bin/prod.yaml`, test config under `test/_bin/`, and deployment environment variables.
|
|
74
|
+
|
|
75
|
+
The inspected cache package reads these keys:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
redis:
|
|
79
|
+
addr: 127.0.0.1:6379
|
|
80
|
+
password: ""
|
|
81
|
+
db: 0
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Environment overrides use Gorig's `GORIG` prefix and `.` -> `_` replacement:
|
|
85
|
+
|
|
86
|
+
- `redis.addr` -> `GORIG_REDIS_ADDR`
|
|
87
|
+
- `redis.password` -> `GORIG_REDIS_PASSWORD`
|
|
88
|
+
- `redis.db` -> `GORIG_REDIS_DB`
|
|
89
|
+
|
|
90
|
+
Guidance:
|
|
91
|
+
|
|
92
|
+
- Do not silently add Redis as a project dependency for Memory, JSON, or SQLite cache work.
|
|
93
|
+
- When Redis is required and config is missing, use either non-secret skeleton keys in environment YAML files or rely on environment variables.
|
|
94
|
+
- Never commit real Redis passwords or private connection strings.
|
|
95
|
+
- Prefer `redis.addr` values such as `127.0.0.1:6379` for disposable local development and environment variables for shared environments.
|
|
96
|
+
- In the inspected baseline, an empty `redis.addr` logs an initialization warning and Redis cache creation returns nil after the failed ping. Treat that as unavailable infrastructure, not as verified Redis behavior.
|
|
97
|
+
- For tests, add Redis config only to disposable test configuration or inject `GORIG_REDIS_ADDR`, `GORIG_REDIS_PASSWORD`, and `GORIG_REDIS_DB` for the test process.
|
|
98
|
+
|
|
99
|
+
When adding a skeleton, keep secrets empty:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
redis:
|
|
103
|
+
addr: 127.0.0.1:6379
|
|
104
|
+
password: ""
|
|
105
|
+
db: 0
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Cache-Aside Pattern
|
|
109
|
+
|
|
110
|
+
Use cache-aside when the database or external service remains the source of truth.
|
|
111
|
+
|
|
112
|
+
```go
|
|
113
|
+
key := fmt.Sprintf("order:%d", id)
|
|
114
|
+
|
|
115
|
+
if item, err := orderCache.Get(key); err == nil {
|
|
116
|
+
return &item, nil
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
item, err := LoadOrderFromDB(ctx, id)
|
|
120
|
+
if err != nil {
|
|
121
|
+
return nil, err
|
|
122
|
+
}
|
|
123
|
+
if err := orderCache.Set(key, *item, 5*time.Minute); err != nil {
|
|
124
|
+
logger.Error(ctx, "set order cache failed", zap.Error(err))
|
|
125
|
+
}
|
|
126
|
+
return item, nil
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
After writes, delete or refresh every affected key:
|
|
130
|
+
|
|
131
|
+
```go
|
|
132
|
+
if err := SaveOrder(ctx, req); err != nil {
|
|
133
|
+
return err
|
|
134
|
+
}
|
|
135
|
+
_ = orderCache.Del(fmt.Sprintf("order:%d", req.ID))
|
|
136
|
+
_ = orderCache.Del(fmt.Sprintf("order:list:%s", req.TenantID))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Prefer deletion over stale overwrites when list keys, permission scopes, or computed projections are hard to enumerate.
|
|
140
|
+
|
|
141
|
+
## Direct Service Example
|
|
142
|
+
|
|
143
|
+
Use this pattern for a detail query such as `customer.Info(ctx, id)`. Keep the exact DTO/model names from the target project.
|
|
144
|
+
|
|
145
|
+
```go
|
|
146
|
+
var customerInfoCache = cache.New[Resp](cache.Redis)
|
|
147
|
+
|
|
148
|
+
const customerInfoTTL = 5 * time.Minute
|
|
149
|
+
|
|
150
|
+
func customerInfoKey(id uint64) string {
|
|
151
|
+
return fmt.Sprintf("customer:info:%d", id)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func Info(ctx context.Context, id uint64) (*Resp, *errors.Error) {
|
|
155
|
+
key := customerInfoKey(id)
|
|
156
|
+
if customerInfoCache != nil && customerInfoCache.IsInitialized() {
|
|
157
|
+
if cached, err := customerInfoCache.Get(key); err == nil {
|
|
158
|
+
return &cached, nil
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
d, err := dx.On[model.D](ctx).WithID(id).Get()
|
|
163
|
+
if err != nil {
|
|
164
|
+
return nil, err
|
|
165
|
+
}
|
|
166
|
+
resp := ToResp(d)
|
|
167
|
+
|
|
168
|
+
if customerInfoCache != nil && customerInfoCache.IsInitialized() {
|
|
169
|
+
_ = customerInfoCache.Set(key, *resp, customerInfoTTL)
|
|
170
|
+
}
|
|
171
|
+
return resp, nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func invalidateCustomerInfo(id uint64) {
|
|
175
|
+
if customerInfoCache != nil && customerInfoCache.IsInitialized() {
|
|
176
|
+
_ = customerInfoCache.Del(customerInfoKey(id))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Call `invalidateCustomerInfo(id)` after successful update or delete. If list or page queries are cached, also delete or version the affected list/page namespace after create, update, and delete.
|
|
182
|
+
|
|
183
|
+
For list/page cache keys, derive keys from normalized request DTO fields, not raw JSON:
|
|
184
|
+
|
|
185
|
+
```go
|
|
186
|
+
func customerListKey(req *ListReq) string {
|
|
187
|
+
normalized := fmt.Sprintf("%s|%s|%s|%s", req.CustomerNo, req.Name, req.Phone, req.Status)
|
|
188
|
+
sum := sha1.Sum([]byte(normalized))
|
|
189
|
+
return fmt.Sprintf("customer:list:%x", sum)
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
When caching paginated responses whose result field is `any`, prefer a typed local cache DTO and convert back to the framework response after `Get`.
|
|
194
|
+
|
|
195
|
+
## Source of Truth Boundary
|
|
196
|
+
|
|
197
|
+
Cache is acceptable as an acceleration layer when:
|
|
198
|
+
|
|
199
|
+
- misses can be loaded from a durable source,
|
|
200
|
+
- stale values are tolerable within the TTL,
|
|
201
|
+
- invalidation is deterministic enough for the business workflow, and
|
|
202
|
+
- the service can continue safely if the cache is empty.
|
|
203
|
+
|
|
204
|
+
Cache is not an acceptable source of truth for:
|
|
205
|
+
|
|
206
|
+
- money, inventory, permissions, audit events, or legal records,
|
|
207
|
+
- uniqueness checks that require durable consistency,
|
|
208
|
+
- workflows where lost local files after restart would corrupt state,
|
|
209
|
+
- cross-process state unless Redis or another shared backend is explicitly configured and tested.
|
|
210
|
+
|
|
211
|
+
## Expiration and Invalidation
|
|
212
|
+
|
|
213
|
+
```go
|
|
214
|
+
err := c.Set("session:123", value, 30*time.Minute)
|
|
215
|
+
ok, err := c.Exists("session:123")
|
|
216
|
+
err = c.Expire("session:123", 5*time.Minute)
|
|
217
|
+
err = c.Del("session:123")
|
|
218
|
+
err = c.Flush()
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Use `0` expiration only when the backend and business semantics allow no expiry. Avoid `Flush` in request paths unless the cache namespace is disposable.
|
|
222
|
+
|
|
223
|
+
## Counters
|
|
224
|
+
|
|
225
|
+
```go
|
|
226
|
+
counter := cache.New[int64](cache.Memory, time.Minute, time.Minute)
|
|
227
|
+
next, err := counter.Incr("job:attempts")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`Incr` is useful for local counters and Redis counters. It is not a substitute for durable database counters when exact cross-process consistency is required.
|
|
231
|
+
|
|
232
|
+
## Multi-Level Cache and Singleflight
|
|
233
|
+
|
|
234
|
+
`NewCacheTool` searches cache layers in order. On lower-layer hit it backfills earlier layers. On full miss it calls the loader through `singleflight`, then stores the loaded value in every layer.
|
|
235
|
+
|
|
236
|
+
```go
|
|
237
|
+
l1 := cache.New[Order](cache.Memory, time.Minute, time.Minute)
|
|
238
|
+
l2 := cache.New[Order](cache.Redis)
|
|
239
|
+
|
|
240
|
+
tool := cache.NewCacheTool[Order](ctx, []cache.Cache[Order]{l1, l2}, func(key string) (Order, error) {
|
|
241
|
+
return LoadOrderByCacheKey(ctx, key)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
value, err := tool.Get("order:123", 5*time.Minute)
|
|
245
|
+
err = tool.Set("order:123", value, 5*time.Minute)
|
|
246
|
+
err = tool.Delete("order:123")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Use this pattern for hot reads with expensive loaders. Verify concurrent misses call the loader once for the same key in the target value type and backend combination.
|
|
250
|
+
|
|
251
|
+
## SQLite Paged Cache
|
|
252
|
+
|
|
253
|
+
Use SQLite paged cache for local time-series or queryable snapshots, not as a replacement for the application database.
|
|
254
|
+
|
|
255
|
+
```go
|
|
256
|
+
type Stat struct {
|
|
257
|
+
At int64 `json:"at" idx:"at"`
|
|
258
|
+
Method string `json:"method" idx_group:"method_uri"`
|
|
259
|
+
URI string `json:"uri" idx_group:"method_uri"`
|
|
260
|
+
Count int64 `json:"count"`
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
pg, err := cache.NewSQLiteCachePage[Stat]("stat_cache")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The inspected implementation creates `.cache/<name>.pg.db` and auto-indexes fields from `idx` and `idx_group` tags. Verify query methods against the resolved source before using paged cache in feature code.
|
|
267
|
+
|
|
268
|
+
## Key Design
|
|
269
|
+
|
|
270
|
+
Use stable, namespaced keys:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
<app>:<env>:<module>:<entity>:<id>
|
|
274
|
+
<app>:<env>:<module>:list:<tenant>:<filter-hash>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Rules:
|
|
278
|
+
|
|
279
|
+
- Include tenant or permission scope when data visibility differs by user.
|
|
280
|
+
- Hash long filter payloads instead of embedding raw JSON.
|
|
281
|
+
- Do not include secrets in keys.
|
|
282
|
+
- Keep list keys and item keys separate so invalidation is explicit.
|
|
283
|
+
- Document all keys created by a service in the module README when cache behavior is part of the feature.
|
|
284
|
+
|
|
285
|
+
## Verification Checklist
|
|
286
|
+
|
|
287
|
+
Local backends:
|
|
288
|
+
|
|
289
|
+
- Memory: set, get, miss, expiry, delete, counter, flush.
|
|
290
|
+
- JSON: persistence file creation, get after recreate, delete, expiry.
|
|
291
|
+
- SQLite: persistence file creation, get after recreate, delete, expiry.
|
|
292
|
+
- Multi-level cache: L1 miss, L2 hit backfill, full miss loader, delete across layers.
|
|
293
|
+
- Singleflight: concurrent same-key misses call the loader once.
|
|
294
|
+
|
|
295
|
+
Redis:
|
|
296
|
+
|
|
297
|
+
- Run only with disposable or explicitly supplied Redis configuration.
|
|
298
|
+
- Verify set/get, expiry, delete, counter, and cross-process visibility.
|
|
299
|
+
- Report Redis as skipped when configuration is absent.
|
|
300
|
+
|
|
301
|
+
Delivery evidence must include exact commands, backend configuration, skipped checks, and whether cache is used as acceleration or as state.
|