agy-superpowers 5.2.2 → 5.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -150
- package/package.json +1 -1
- package/template/agent/rules/CLAUDE.md +80 -0
- package/template/agent/rules/code-styles.md +31 -32
- package/template/agent/rules/debug-confirmation-policy.md +2 -0
- package/template/agent/rules/file-length-policy.md +2 -0
- package/template/agent/rules/git-policy.md +7 -0
- package/template/agent/rules/language-matching.md +2 -0
- package/template/agent/rules/scratch-scripts.md +39 -0
- package/template/agent/rules/superpowers.md +8 -51
- package/template/agent/skills/executing-plans/SKILL.md +17 -0
- package/template/agent/skills/systematic-debugging/SKILL.md +16 -0
- package/template/agent/skills/test-driven-development/SKILL.md +16 -0
- package/template/agent/skills/verification-before-completion/SKILL.md +22 -0
- package/template/agent/skills/writing-plans/SKILL.md +16 -0
- package/template/agent/skills/ai-integrated-product/SKILL.md +0 -57
- package/template/agent/skills/analytics-setup/SKILL.md +0 -51
- package/template/agent/skills/api-design/SKILL.md +0 -193
- package/template/agent/skills/app-store-optimizer/SKILL.md +0 -127
- package/template/agent/skills/auth-and-identity/SKILL.md +0 -167
- package/template/agent/skills/backend-developer/SKILL.md +0 -148
- package/template/agent/skills/bootstrapper-finance/SKILL.md +0 -55
- package/template/agent/skills/chrome-extension-developer/SKILL.md +0 -53
- package/template/agent/skills/community-manager/SKILL.md +0 -115
- package/template/agent/skills/content-marketer/SKILL.md +0 -111
- package/template/agent/skills/conversion-optimizer/SKILL.md +0 -142
- package/template/agent/skills/cto-architect/SKILL.md +0 -133
- package/template/agent/skills/customer-success-manager/SKILL.md +0 -126
- package/template/agent/skills/data-analyst/SKILL.md +0 -147
- package/template/agent/skills/devops-engineer/SKILL.md +0 -117
- package/template/agent/skills/email-infrastructure/SKILL.md +0 -164
- package/template/agent/skills/game-design/SKILL.md +0 -194
- package/template/agent/skills/game-developer/SKILL.md +0 -175
- package/template/agent/skills/growth-hacker/SKILL.md +0 -122
- package/template/agent/skills/idea-validator/SKILL.md +0 -55
- package/template/agent/skills/indie-legal/SKILL.md +0 -53
- package/template/agent/skills/influencer-marketer/SKILL.md +0 -141
- package/template/agent/skills/landing-page-builder/SKILL.md +0 -59
- package/template/agent/skills/launch-strategist/SKILL.md +0 -62
- package/template/agent/skills/market-researcher/SKILL.md +0 -53
- package/template/agent/skills/micro-saas-builder/SKILL.md +0 -56
- package/template/agent/skills/monetization-strategist/SKILL.md +0 -119
- package/template/agent/skills/paid-acquisition-specialist/SKILL.md +0 -119
- package/template/agent/skills/pricing-psychologist/SKILL.md +0 -58
- package/template/agent/skills/real-time-features/SKILL.md +0 -194
- package/template/agent/skills/retention-specialist/SKILL.md +0 -123
- package/template/agent/skills/rust-developer/SKILL.md +0 -281
- package/template/agent/skills/rust-developer/references/rust-rules/_sections.md +0 -231
- package/template/agent/skills/rust-developer/references/rust-rules/anti-clone-excessive.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/anti-collect-intermediate.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/anti-empty-catch.md +0 -132
- package/template/agent/skills/rust-developer/references/rust-rules/anti-expect-lazy.md +0 -95
- package/template/agent/skills/rust-developer/references/rust-rules/anti-format-hot-path.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/anti-index-over-iter.md +0 -125
- package/template/agent/skills/rust-developer/references/rust-rules/anti-lock-across-await.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/anti-over-abstraction.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/anti-panic-expected.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/anti-premature-optimize.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/anti-string-for-str.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/anti-stringly-typed.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/anti-type-erasure.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/anti-unwrap-abuse.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/anti-vec-for-slice.md +0 -121
- package/template/agent/skills/rust-developer/references/rust-rules/api-builder-must-use.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/api-builder-pattern.md +0 -187
- package/template/agent/skills/rust-developer/references/rust-rules/api-common-traits.md +0 -165
- package/template/agent/skills/rust-developer/references/rust-rules/api-default-impl.md +0 -177
- package/template/agent/skills/rust-developer/references/rust-rules/api-extension-trait.md +0 -163
- package/template/agent/skills/rust-developer/references/rust-rules/api-from-not-into.md +0 -146
- package/template/agent/skills/rust-developer/references/rust-rules/api-impl-asref.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/api-impl-into.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/api-must-use.md +0 -125
- package/template/agent/skills/rust-developer/references/rust-rules/api-newtype-safety.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/api-non-exhaustive.md +0 -177
- package/template/agent/skills/rust-developer/references/rust-rules/api-parse-dont-validate.md +0 -184
- package/template/agent/skills/rust-developer/references/rust-rules/api-sealed-trait.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/api-serde-optional.md +0 -182
- package/template/agent/skills/rust-developer/references/rust-rules/api-typestate.md +0 -199
- package/template/agent/skills/rust-developer/references/rust-rules/async-bounded-channel.md +0 -175
- package/template/agent/skills/rust-developer/references/rust-rules/async-broadcast-pubsub.md +0 -185
- package/template/agent/skills/rust-developer/references/rust-rules/async-cancellation-token.md +0 -203
- package/template/agent/skills/rust-developer/references/rust-rules/async-clone-before-await.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/async-join-parallel.md +0 -158
- package/template/agent/skills/rust-developer/references/rust-rules/async-joinset-structured.md +0 -195
- package/template/agent/skills/rust-developer/references/rust-rules/async-mpsc-queue.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/async-no-lock-await.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/async-oneshot-response.md +0 -191
- package/template/agent/skills/rust-developer/references/rust-rules/async-select-racing.md +0 -198
- package/template/agent/skills/rust-developer/references/rust-rules/async-spawn-blocking.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/async-tokio-fs.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/async-tokio-runtime.md +0 -169
- package/template/agent/skills/rust-developer/references/rust-rules/async-try-join.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/async-watch-latest.md +0 -189
- package/template/agent/skills/rust-developer/references/rust-rules/doc-all-public.md +0 -113
- package/template/agent/skills/rust-developer/references/rust-rules/doc-cargo-metadata.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/doc-errors-section.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/doc-examples-section.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/doc-hidden-setup.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/doc-intra-links.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/doc-link-types.md +0 -169
- package/template/agent/skills/rust-developer/references/rust-rules/doc-module-inner.md +0 -116
- package/template/agent/skills/rust-developer/references/rust-rules/doc-panics-section.md +0 -128
- package/template/agent/skills/rust-developer/references/rust-rules/doc-question-mark.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/doc-safety-section.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/err-anyhow-app.md +0 -179
- package/template/agent/skills/rust-developer/references/rust-rules/err-context-chain.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/err-custom-type.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/err-doc-errors.md +0 -145
- package/template/agent/skills/rust-developer/references/rust-rules/err-expect-bugs-only.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/err-from-impl.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/err-lowercase-msg.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/err-no-unwrap-prod.md +0 -115
- package/template/agent/skills/rust-developer/references/rust-rules/err-question-mark.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/err-result-over-panic.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/err-source-chain.md +0 -155
- package/template/agent/skills/rust-developer/references/rust-rules/err-thiserror-lib.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/lint-cargo-metadata.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/lint-deny-correctness.md +0 -107
- package/template/agent/skills/rust-developer/references/rust-rules/lint-missing-docs.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/lint-pedantic-selective.md +0 -118
- package/template/agent/skills/rust-developer/references/rust-rules/lint-rustfmt-check.md +0 -157
- package/template/agent/skills/rust-developer/references/rust-rules/lint-unsafe-doc.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-complexity.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-perf.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-style.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-suspicious.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/lint-workspace-lints.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/mem-arena-allocator.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/mem-arrayvec.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/mem-assert-type-size.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/mem-avoid-format.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/mem-box-large-variant.md +0 -158
- package/template/agent/skills/rust-developer/references/rust-rules/mem-boxed-slice.md +0 -139
- package/template/agent/skills/rust-developer/references/rust-rules/mem-clone-from.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/mem-compact-string.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/mem-reuse-collections.md +0 -174
- package/template/agent/skills/rust-developer/references/rust-rules/mem-smaller-integers.md +0 -159
- package/template/agent/skills/rust-developer/references/rust-rules/mem-smallvec.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/mem-thinvec.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/mem-with-capacity.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/mem-write-over-format.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/mem-zero-copy.md +0 -164
- package/template/agent/skills/rust-developer/references/rust-rules/name-acronym-word.md +0 -99
- package/template/agent/skills/rust-developer/references/rust-rules/name-as-free.md +0 -104
- package/template/agent/skills/rust-developer/references/rust-rules/name-consts-screaming.md +0 -94
- package/template/agent/skills/rust-developer/references/rust-rules/name-crate-no-rs.md +0 -78
- package/template/agent/skills/rust-developer/references/rust-rules/name-funcs-snake.md +0 -76
- package/template/agent/skills/rust-developer/references/rust-rules/name-into-ownership.md +0 -123
- package/template/agent/skills/rust-developer/references/rust-rules/name-is-has-bool.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-convention.md +0 -129
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-method.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-type-match.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/name-lifetime-short.md +0 -86
- package/template/agent/skills/rust-developer/references/rust-rules/name-no-get-prefix.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/name-to-expensive.md +0 -118
- package/template/agent/skills/rust-developer/references/rust-rules/name-type-param-single.md +0 -92
- package/template/agent/skills/rust-developer/references/rust-rules/name-types-camel.md +0 -65
- package/template/agent/skills/rust-developer/references/rust-rules/name-variants-camel.md +0 -101
- package/template/agent/skills/rust-developer/references/rust-rules/opt-bounds-check.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/opt-cache-friendly.md +0 -187
- package/template/agent/skills/rust-developer/references/rust-rules/opt-codegen-units.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/opt-cold-unlikely.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-always-rare.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-never-cold.md +0 -181
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-small.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/opt-likely-hint.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/opt-lto-release.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/opt-pgo-profile.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/opt-simd-portable.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/opt-target-cpu.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/own-arc-shared.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/own-borrow-over-clone.md +0 -95
- package/template/agent/skills/rust-developer/references/rust-rules/own-clone-explicit.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/own-copy-small.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/own-cow-conditional.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/own-lifetime-elision.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/own-move-large.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/own-mutex-interior.md +0 -105
- package/template/agent/skills/rust-developer/references/rust-rules/own-rc-single-thread.md +0 -65
- package/template/agent/skills/rust-developer/references/rust-rules/own-refcell-interior.md +0 -97
- package/template/agent/skills/rust-developer/references/rust-rules/own-rwlock-readers.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/own-slice-over-vec.md +0 -119
- package/template/agent/skills/rust-developer/references/rust-rules/perf-black-box-bench.md +0 -153
- package/template/agent/skills/rust-developer/references/rust-rules/perf-chain-avoid.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/perf-collect-into.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/perf-collect-once.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/perf-drain-reuse.md +0 -137
- package/template/agent/skills/rust-developer/references/rust-rules/perf-entry-api.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/perf-extend-batch.md +0 -150
- package/template/agent/skills/rust-developer/references/rust-rules/perf-iter-lazy.md +0 -123
- package/template/agent/skills/rust-developer/references/rust-rules/perf-iter-over-index.md +0 -113
- package/template/agent/skills/rust-developer/references/rust-rules/perf-profile-first.md +0 -175
- package/template/agent/skills/rust-developer/references/rust-rules/perf-release-profile.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/proj-bin-dir.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/proj-flat-small.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/proj-lib-main-split.md +0 -148
- package/template/agent/skills/rust-developer/references/rust-rules/proj-mod-by-feature.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/proj-mod-rs-dir.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/proj-prelude-module.md +0 -155
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-crate-internal.md +0 -139
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-super-parent.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-use-reexport.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/proj-workspace-deps.md +0 -186
- package/template/agent/skills/rust-developer/references/rust-rules/proj-workspace-large.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/test-arrange-act-assert.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/test-cfg-test-module.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/test-criterion-bench.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/test-descriptive-names.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/test-doctest-examples.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/test-fixture-raii.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/test-integration-dir.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/test-mock-traits.md +0 -189
- package/template/agent/skills/rust-developer/references/rust-rules/test-mockall-mocking.md +0 -226
- package/template/agent/skills/rust-developer/references/rust-rules/test-proptest-properties.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/test-should-panic.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/test-tokio-async.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/test-use-super.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/type-enum-states.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/type-generic-bounds.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/type-never-diverge.md +0 -146
- package/template/agent/skills/rust-developer/references/rust-rules/type-newtype-ids.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/type-newtype-validated.md +0 -159
- package/template/agent/skills/rust-developer/references/rust-rules/type-no-stringly.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/type-option-nullable.md +0 -137
- package/template/agent/skills/rust-developer/references/rust-rules/type-phantom-marker.md +0 -188
- package/template/agent/skills/rust-developer/references/rust-rules/type-repr-transparent.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/type-result-fallible.md +0 -131
- package/template/agent/skills/saas-architect/SKILL.md +0 -139
- package/template/agent/skills/security-engineer/SKILL.md +0 -133
- package/template/agent/skills/seo-specialist/SKILL.md +0 -130
- package/template/agent/skills/solo-founder-ops/SKILL.md +0 -56
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# type-phantom-marker
|
|
2
|
-
|
|
3
|
-
> Use `PhantomData` to express type relationships without runtime cost
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
Sometimes your type needs to be parameterized by a type that doesn't appear in any field—for variance, drop order, or semantic purposes. `PhantomData<T>` tells the compiler your type is "associated with" `T` without storing any `T` data. It has zero runtime cost.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
// Type parameter unused - compiler error
|
|
13
|
-
struct Handle<T> {
|
|
14
|
-
id: u64,
|
|
15
|
-
// Error: parameter `T` is never used
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Workaround with unnecessary storage
|
|
19
|
-
struct Handle<T> {
|
|
20
|
-
id: u64,
|
|
21
|
-
_type: Option<T>, // Wastes memory, requires T: Default
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Good
|
|
26
|
-
|
|
27
|
-
```rust
|
|
28
|
-
use std::marker::PhantomData;
|
|
29
|
-
|
|
30
|
-
struct Handle<T> {
|
|
31
|
-
id: u64,
|
|
32
|
-
_marker: PhantomData<T>, // Zero-size, tells compiler about T
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
impl<T> Handle<T> {
|
|
36
|
-
fn new(id: u64) -> Self {
|
|
37
|
-
Handle {
|
|
38
|
-
id,
|
|
39
|
-
_marker: PhantomData,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Different Handle types are incompatible
|
|
45
|
-
struct User;
|
|
46
|
-
struct Order;
|
|
47
|
-
|
|
48
|
-
fn process_user(h: Handle<User>) { ... }
|
|
49
|
-
|
|
50
|
-
let user_handle = Handle::<User>::new(1);
|
|
51
|
-
let order_handle = Handle::<Order>::new(2);
|
|
52
|
-
|
|
53
|
-
process_user(user_handle); // OK
|
|
54
|
-
process_user(order_handle); // Error: expected Handle<User>, found Handle<Order>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Expressing Ownership
|
|
58
|
-
|
|
59
|
-
```rust
|
|
60
|
-
use std::marker::PhantomData;
|
|
61
|
-
|
|
62
|
-
// Owns T conceptually (like Box<T>)
|
|
63
|
-
struct Container<T> {
|
|
64
|
-
ptr: *mut T,
|
|
65
|
-
_marker: PhantomData<T>, // Acts like we own a T
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Drop will be called on T when Container drops
|
|
69
|
-
impl<T> Drop for Container<T> {
|
|
70
|
-
fn drop(&mut self) {
|
|
71
|
-
unsafe {
|
|
72
|
-
std::ptr::drop_in_place(self.ptr);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Expressing Borrowing
|
|
79
|
-
|
|
80
|
-
```rust
|
|
81
|
-
use std::marker::PhantomData;
|
|
82
|
-
|
|
83
|
-
// Borrows T for lifetime 'a
|
|
84
|
-
struct Ref<'a, T> {
|
|
85
|
-
ptr: *const T,
|
|
86
|
-
_marker: PhantomData<&'a T>, // Acts like &'a T
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Compiler tracks lifetime correctly
|
|
90
|
-
impl<'a, T> Ref<'a, T> {
|
|
91
|
-
fn get(&self) -> &'a T {
|
|
92
|
-
unsafe { &*self.ptr }
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Type-Level State Machine
|
|
98
|
-
|
|
99
|
-
```rust
|
|
100
|
-
use std::marker::PhantomData;
|
|
101
|
-
|
|
102
|
-
// States as zero-size types
|
|
103
|
-
struct Unlocked;
|
|
104
|
-
struct Locked;
|
|
105
|
-
|
|
106
|
-
struct Door<State> {
|
|
107
|
-
_state: PhantomData<State>,
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
impl Door<Unlocked> {
|
|
111
|
-
fn lock(self) -> Door<Locked> {
|
|
112
|
-
println!("Locking...");
|
|
113
|
-
Door { _state: PhantomData }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
fn open(&self) {
|
|
117
|
-
println!("Opening...");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
impl Door<Locked> {
|
|
122
|
-
fn unlock(self) -> Door<Unlocked> {
|
|
123
|
-
println!("Unlocking...");
|
|
124
|
-
Door { _state: PhantomData }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Can't call open() on Locked door - method doesn't exist
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
fn example() {
|
|
131
|
-
let door: Door<Unlocked> = Door { _state: PhantomData };
|
|
132
|
-
door.open(); // OK
|
|
133
|
-
let locked = door.lock();
|
|
134
|
-
// locked.open(); // Error: no method `open` for Door<Locked>
|
|
135
|
-
let unlocked = locked.unlock();
|
|
136
|
-
unlocked.open(); // OK
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Variance Control
|
|
141
|
-
|
|
142
|
-
```rust
|
|
143
|
-
use std::marker::PhantomData;
|
|
144
|
-
|
|
145
|
-
// Covariant in T (PhantomData<T>)
|
|
146
|
-
struct Producer<T> {
|
|
147
|
-
_marker: PhantomData<T>, // Covariant
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Contravariant in T (PhantomData<fn(T)>)
|
|
151
|
-
struct Consumer<T> {
|
|
152
|
-
_marker: PhantomData<fn(T)>, // Contravariant
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Invariant in T (PhantomData<fn(T) -> T>)
|
|
156
|
-
struct Both<T> {
|
|
157
|
-
_marker: PhantomData<fn(T) -> T>, // Invariant
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Common Uses
|
|
162
|
-
|
|
163
|
-
```rust
|
|
164
|
-
// 1. FFI handles with type safety
|
|
165
|
-
struct FileHandle<T: FileType> {
|
|
166
|
-
fd: i32,
|
|
167
|
-
_marker: PhantomData<T>,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 2. Generic iterators
|
|
171
|
-
struct Iter<'a, T> {
|
|
172
|
-
ptr: *const T,
|
|
173
|
-
end: *const T,
|
|
174
|
-
_marker: PhantomData<&'a T>,
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 3. Allocator-aware types
|
|
178
|
-
struct Vec<T, A: Allocator = Global> {
|
|
179
|
-
buf: RawVec<T, A>,
|
|
180
|
-
len: usize,
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## See Also
|
|
185
|
-
|
|
186
|
-
- [api-typestate](./api-typestate.md) - State machine pattern
|
|
187
|
-
- [api-newtype-safety](./api-newtype-safety.md) - Type-safe wrappers
|
|
188
|
-
- [type-newtype-ids](./type-newtype-ids.md) - ID types
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# type-repr-transparent
|
|
2
|
-
|
|
3
|
-
> Use `#[repr(transparent)]` for newtypes in FFI contexts
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
`#[repr(transparent)]` guarantees a newtype has the same memory layout as its inner type. This is essential for FFI where you need type safety in Rust but must match C ABI layouts. Without it, the compiler may add padding or change layout.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
// No layout guarantee - might not match inner type in FFI
|
|
13
|
-
struct Handle(u64);
|
|
14
|
-
|
|
15
|
-
// Passing to C code might fail
|
|
16
|
-
extern "C" {
|
|
17
|
-
fn process_handle(h: Handle); // May not work correctly
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Wrapping C type without layout guarantee
|
|
21
|
-
struct SafePointer(*mut c_void);
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Good
|
|
25
|
-
|
|
26
|
-
```rust
|
|
27
|
-
// Guaranteed same layout as inner type
|
|
28
|
-
#[repr(transparent)]
|
|
29
|
-
struct Handle(u64);
|
|
30
|
-
|
|
31
|
-
// Safe for FFI
|
|
32
|
-
extern "C" {
|
|
33
|
-
fn process_handle(h: Handle); // Works - same layout as u64
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// FFI pointer wrapper
|
|
37
|
-
#[repr(transparent)]
|
|
38
|
-
struct SafePointer(*mut c_void);
|
|
39
|
-
|
|
40
|
-
impl SafePointer {
|
|
41
|
-
// Safe Rust API around raw pointer
|
|
42
|
-
pub fn new(ptr: *mut c_void) -> Option<Self> {
|
|
43
|
-
if ptr.is_null() {
|
|
44
|
-
None
|
|
45
|
-
} else {
|
|
46
|
-
Some(SafePointer(ptr))
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## What repr(transparent) Guarantees
|
|
53
|
-
|
|
54
|
-
```rust
|
|
55
|
-
use std::mem::{size_of, align_of};
|
|
56
|
-
|
|
57
|
-
#[repr(transparent)]
|
|
58
|
-
struct Meters(f64);
|
|
59
|
-
|
|
60
|
-
// Same size
|
|
61
|
-
assert_eq!(size_of::<Meters>(), size_of::<f64>());
|
|
62
|
-
|
|
63
|
-
// Same alignment
|
|
64
|
-
assert_eq!(align_of::<Meters>(), align_of::<f64>());
|
|
65
|
-
|
|
66
|
-
// Same ABI - can pass where f64 expected
|
|
67
|
-
extern "C" fn measure(distance: Meters) { ... }
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## With PhantomData
|
|
71
|
-
|
|
72
|
-
```rust
|
|
73
|
-
use std::marker::PhantomData;
|
|
74
|
-
|
|
75
|
-
// PhantomData is zero-sized, doesn't affect layout
|
|
76
|
-
#[repr(transparent)]
|
|
77
|
-
struct TypedHandle<T> {
|
|
78
|
-
raw: u64,
|
|
79
|
-
_marker: PhantomData<T>, // Zero-sized, ignored for layout
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Still same layout as u64
|
|
83
|
-
assert_eq!(size_of::<TypedHandle<String>>(), size_of::<u64>());
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## NonZero Wrappers
|
|
87
|
-
|
|
88
|
-
```rust
|
|
89
|
-
use std::num::NonZeroU64;
|
|
90
|
-
|
|
91
|
-
#[repr(transparent)]
|
|
92
|
-
struct NonZeroHandle(NonZeroU64);
|
|
93
|
-
|
|
94
|
-
// Inherits null-pointer optimization
|
|
95
|
-
assert_eq!(size_of::<Option<NonZeroHandle>>(), size_of::<u64>());
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## FFI Pattern
|
|
99
|
-
|
|
100
|
-
```rust
|
|
101
|
-
mod ffi {
|
|
102
|
-
use std::os::raw::c_int;
|
|
103
|
-
|
|
104
|
-
#[repr(transparent)]
|
|
105
|
-
pub struct FileDescriptor(c_int);
|
|
106
|
-
|
|
107
|
-
extern "C" {
|
|
108
|
-
pub fn open(path: *const i8, flags: c_int) -> FileDescriptor;
|
|
109
|
-
pub fn close(fd: FileDescriptor) -> c_int;
|
|
110
|
-
pub fn read(fd: FileDescriptor, buf: *mut u8, len: usize) -> isize;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Safe wrapper
|
|
115
|
-
pub struct File {
|
|
116
|
-
fd: ffi::FileDescriptor,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
impl File {
|
|
120
|
-
pub fn open(path: &str) -> std::io::Result<Self> {
|
|
121
|
-
let c_path = std::ffi::CString::new(path)?;
|
|
122
|
-
let fd = unsafe { ffi::open(c_path.as_ptr(), 0) };
|
|
123
|
-
// ... error handling
|
|
124
|
-
Ok(File { fd })
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## When to Use
|
|
130
|
-
|
|
131
|
-
| Scenario | Use `#[repr(transparent)]`? |
|
|
132
|
-
|----------|----------------------------|
|
|
133
|
-
| FFI newtype wrappers | Yes |
|
|
134
|
-
| Type-safe handles | Yes |
|
|
135
|
-
| NonZero optimization | Yes |
|
|
136
|
-
| Pure Rust newtypes | Optional (doesn't hurt) |
|
|
137
|
-
| Multi-field structs | N/A (only for single-field) |
|
|
138
|
-
|
|
139
|
-
## See Also
|
|
140
|
-
|
|
141
|
-
- [type-newtype-ids](./type-newtype-ids.md) - Newtype pattern
|
|
142
|
-
- [type-phantom-marker](./type-phantom-marker.md) - PhantomData usage
|
|
143
|
-
- [api-newtype-safety](./api-newtype-safety.md) - Type-safe newtypes
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# type-result-fallible
|
|
2
|
-
|
|
3
|
-
> Use `Result<T, E>` for operations that can fail
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
`Result<T, E>` makes failure explicit in the type system. Callers must acknowledge and handle potential errors—they can't accidentally ignore failures. The `?` operator makes error propagation ergonomic while maintaining explicit error handling.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
// Returning Option loses error context
|
|
13
|
-
fn read_config(path: &str) -> Option<Config> {
|
|
14
|
-
let content = std::fs::read_to_string(path).ok()?; // Why did it fail?
|
|
15
|
-
toml::from_str(&content).ok() // Parse error lost
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Panicking on errors
|
|
19
|
-
fn read_config(path: &str) -> Config {
|
|
20
|
-
let content = std::fs::read_to_string(path).unwrap(); // Crashes
|
|
21
|
-
toml::from_str(&content).unwrap() // Crashes
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Sentinel values
|
|
25
|
-
fn divide(a: i32, b: i32) -> i32 {
|
|
26
|
-
if b == 0 { return -1; } // Magic value, easy to miss
|
|
27
|
-
a / b
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Good
|
|
32
|
-
|
|
33
|
-
```rust
|
|
34
|
-
// Result with clear error type
|
|
35
|
-
fn read_config(path: &str) -> Result<Config, ConfigError> {
|
|
36
|
-
let content = std::fs::read_to_string(path)
|
|
37
|
-
.map_err(ConfigError::IoError)?;
|
|
38
|
-
toml::from_str(&content)
|
|
39
|
-
.map_err(ConfigError::ParseError)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fn divide(a: i32, b: i32) -> Result<i32, DivisionError> {
|
|
43
|
-
if b == 0 {
|
|
44
|
-
return Err(DivisionError::DivideByZero);
|
|
45
|
-
}
|
|
46
|
-
Ok(a / b)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Caller must handle
|
|
50
|
-
match divide(10, 0) {
|
|
51
|
-
Ok(result) => println!("Result: {}", result),
|
|
52
|
-
Err(e) => println!("Error: {}", e),
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## The ? Operator
|
|
57
|
-
|
|
58
|
-
```rust
|
|
59
|
-
fn process_file(path: &str) -> Result<ProcessedData, Error> {
|
|
60
|
-
let content = std::fs::read_to_string(path)?; // Propagates Err
|
|
61
|
-
let parsed: RawData = serde_json::from_str(&content)?;
|
|
62
|
-
let validated = validate(parsed)?;
|
|
63
|
-
let processed = transform(validated)?;
|
|
64
|
-
Ok(processed)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Equivalent to:
|
|
68
|
-
fn process_file(path: &str) -> Result<ProcessedData, Error> {
|
|
69
|
-
let content = match std::fs::read_to_string(path) {
|
|
70
|
-
Ok(c) => c,
|
|
71
|
-
Err(e) => return Err(e.into()),
|
|
72
|
-
};
|
|
73
|
-
// ... etc
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Result Combinators
|
|
78
|
-
|
|
79
|
-
```rust
|
|
80
|
-
let result: Result<i32, Error> = Ok(42);
|
|
81
|
-
|
|
82
|
-
// map: transform success value
|
|
83
|
-
let doubled = result.map(|n| n * 2); // Ok(84)
|
|
84
|
-
|
|
85
|
-
// map_err: transform error
|
|
86
|
-
let with_context = result.map_err(|e| format!("Failed: {}", e));
|
|
87
|
-
|
|
88
|
-
// and_then: chain fallible operations
|
|
89
|
-
let processed = result.and_then(|n| {
|
|
90
|
-
if n > 0 { Ok(n * 2) } else { Err(Error::Negative) }
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// unwrap_or: provide default on error
|
|
94
|
-
let value = result.unwrap_or(0);
|
|
95
|
-
|
|
96
|
-
// ok(): convert to Option, discarding error
|
|
97
|
-
let maybe_value: Option<i32> = result.ok();
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Defining Error Types
|
|
101
|
-
|
|
102
|
-
```rust
|
|
103
|
-
use thiserror::Error;
|
|
104
|
-
|
|
105
|
-
#[derive(Error, Debug)]
|
|
106
|
-
pub enum ConfigError {
|
|
107
|
-
#[error("failed to read file: {0}")]
|
|
108
|
-
Io(#[from] std::io::Error),
|
|
109
|
-
|
|
110
|
-
#[error("failed to parse config: {0}")]
|
|
111
|
-
Parse(#[from] toml::de::Error),
|
|
112
|
-
|
|
113
|
-
#[error("missing required field: {0}")]
|
|
114
|
-
MissingField(String),
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
fn load_config(path: &str) -> Result<Config, ConfigError> {
|
|
118
|
-
let content = std::fs::read_to_string(path)?; // Io error
|
|
119
|
-
let config: Config = toml::from_str(&content)?; // Parse error
|
|
120
|
-
if config.name.is_empty() {
|
|
121
|
-
return Err(ConfigError::MissingField("name".into()));
|
|
122
|
-
}
|
|
123
|
-
Ok(config)
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## See Also
|
|
128
|
-
|
|
129
|
-
- [err-thiserror-lib](./err-thiserror-lib.md) - Defining error types
|
|
130
|
-
- [err-question-mark](./err-question-mark.md) - Using ? operator
|
|
131
|
-
- [type-option-nullable](./type-option-nullable.md) - Option vs Result
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: saas-architect
|
|
3
|
-
description: Use when designing multi-tenant SaaS architecture, tenant isolation, data models, or making core infrastructure decisions for a SaaS product
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# SaaS Architect Lens
|
|
7
|
-
|
|
8
|
-
> **Philosophy:** Multi-tenancy is not a feature — it's a fundamental architectural constraint.
|
|
9
|
-
> Every design decision must answer: "Is this tenant-safe?"
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Core Instincts
|
|
14
|
-
|
|
15
|
-
- **Tenant isolation first** — data leaking between tenants is an existential business risk
|
|
16
|
-
- **Design for the tenant, not the user** — every entity in the data model has a `tenant_id`
|
|
17
|
-
- **Shared infrastructure, isolated data** — the sweet spot for indie hackers
|
|
18
|
-
- **Plan the upgrade path** — schema-per-tenant → RLS → shared schema: picking wrong is expensive to change
|
|
19
|
-
- **Hard-delete rarely; soft-delete by default** — audit trails matter in B2B
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Tenancy Isolation Models
|
|
24
|
-
|
|
25
|
-
| Model | Isolation | Cost | Complexity | Best for |
|
|
26
|
-
|-------|-----------|------|------------|----------|
|
|
27
|
-
| **Separate database per tenant** | ✅ Strongest | 💰 Highest | High | Enterprise, regulated industries |
|
|
28
|
-
| **Schema per tenant** (PostgreSQL) | ✅ Strong | 💰 Medium | Medium | Mid-market SaaS |
|
|
29
|
-
| **Row-level security (RLS)** | ✅ Good | 💰 Low | Medium | Indie hacker / SMB SaaS |
|
|
30
|
-
| **Application-level filtering** | ⚠️ Weakest | 💰 Lowest | Low | Prototype only — never production |
|
|
31
|
-
|
|
32
|
-
**Recommended for indie hackers:** Row-Level Security (RLS) on PostgreSQL (Supabase, Neon). Strong isolation at low cost.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Tenant Data Model Pattern
|
|
37
|
-
|
|
38
|
-
```sql
|
|
39
|
-
-- Every table must have tenant_id
|
|
40
|
-
CREATE TABLE projects (
|
|
41
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
|
-
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
43
|
-
name TEXT NOT NULL,
|
|
44
|
-
created_at TIMESTAMPTZ DEFAULT now(),
|
|
45
|
-
deleted_at TIMESTAMPTZ -- soft delete
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
-- RLS: tenant can only see their own rows
|
|
49
|
-
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
|
|
50
|
-
CREATE POLICY tenant_isolation ON projects
|
|
51
|
-
USING (tenant_id = current_setting('app.tenant_id')::UUID);
|
|
52
|
-
|
|
53
|
-
-- Index tenant_id on EVERY tenant-scoped table
|
|
54
|
-
CREATE INDEX ON projects(tenant_id);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Tenant Routing Patterns
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
Option 1: Subdomain routing
|
|
63
|
-
acme.myapp.com → tenant lookup by subdomain → set tenant_id context
|
|
64
|
-
|
|
65
|
-
Option 2: Path routing
|
|
66
|
-
myapp.com/acme → extract slug from path → set tenant_id context
|
|
67
|
-
|
|
68
|
-
Option 3: Custom domain
|
|
69
|
-
app.acme.com → CNAME → myapp.com → DNS lookup → set tenant_id context
|
|
70
|
-
|
|
71
|
-
Recommended for indie hackers: Start with subdomain routing; add custom domains when users ask.
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## ❌ Anti-Patterns to Avoid
|
|
77
|
-
|
|
78
|
-
| ❌ NEVER DO | Why | ✅ DO INSTEAD |
|
|
79
|
-
|------------|-----|--------------|
|
|
80
|
-
| Application-level tenant filtering only | One missing WHERE clause = data breach | RLS at DB level = defense in depth |
|
|
81
|
-
| Tenant ID in JWT payload, enforced only in app | Bypassed by direct DB access | DB-level enforcement (RLS or schema) |
|
|
82
|
-
| Hard-delete tenant data immediately | Chargebacks, disputes, legal holds | Soft-delete + 30-day retention before purge |
|
|
83
|
-
| No tenant_id index | Full table scan at scale | `CREATE INDEX ON every_table(tenant_id)` |
|
|
84
|
-
| Single shared sequence for IDs | Enumerable IDs expose tenant data volume | UUIDs (v4 or v7) always |
|
|
85
|
-
| Storing cross-tenant references | Breaks isolation, schema nightmare | Denormalize data within tenant boundary |
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Tenant Lifecycle Management
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
Sign up → Create tenant record → Create owner user → Provision trial subscription
|
|
93
|
-
↓
|
|
94
|
-
Active → Upgrade → Downgrade → Cancel → Grace period (30 days) → Purge
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Required tenant states:** `trialing`, `active`, `past_due`, `canceled`, `suspended`
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Questions You Always Ask
|
|
102
|
-
|
|
103
|
-
**When adding a new model:**
|
|
104
|
-
- Does every record in this table belong to a tenant? → Add `tenant_id`
|
|
105
|
-
- Is there an index on `tenant_id`?
|
|
106
|
-
- Does the RLS policy cover this table?
|
|
107
|
-
- What happens when the tenant is deleted/canceled?
|
|
108
|
-
|
|
109
|
-
**When reviewing a query:**
|
|
110
|
-
- Is `tenant_id` in the WHERE clause? (Even with RLS, explicit filtering = clarity)
|
|
111
|
-
- Could this query return data from another tenant?
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Red Flags
|
|
116
|
-
|
|
117
|
-
**Must fix:**
|
|
118
|
-
- [ ] Tables with user data but no `tenant_id`
|
|
119
|
-
- [ ] Application-level tenant filtering without DB-level enforcement
|
|
120
|
-
- [ ] No index on `tenant_id` columns
|
|
121
|
-
- [ ] Hard-delete on tenant cancellation (no retention period)
|
|
122
|
-
|
|
123
|
-
**Should fix:**
|
|
124
|
-
- [ ] No soft-delete strategy for tenant-scoped records
|
|
125
|
-
- [ ] Cross-tenant foreign key references
|
|
126
|
-
- [ ] Tenant ID stored as integer (enumerable — use UUID)
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## Who to Pair With
|
|
131
|
-
- `backend-developer` — for query patterns and migration execution
|
|
132
|
-
- `auth-and-identity` — for tenant-scoped authentication
|
|
133
|
-
- `security-engineer` — for data isolation audit
|
|
134
|
-
- `devops-engineer` — for per-tenant resource provisioning
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## Tools
|
|
139
|
-
Supabase (RLS built-in) · Neon (branching per tenant possible) · PlanetScale (separate DBs) · Drizzle ORM / Prisma (schema management) · Zod (runtime tenant_id validation)
|