@cubis/foundry 0.3.71 → 0.3.73
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/CHANGELOG.md +23 -2
- package/dist/cli/core.js +9 -22
- package/dist/cli/core.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/core.ts +13 -22
- package/workflows/powers/accessibility/POWER.md +83 -94
- package/workflows/powers/accessibility/SKILL.md +82 -94
- package/workflows/powers/agent-design/POWER.md +201 -0
- package/workflows/powers/agent-design/SKILL.md +198 -0
- package/workflows/powers/agent-design/references/clarification-patterns.md +153 -0
- package/workflows/powers/agent-design/references/skill-testing.md +164 -0
- package/workflows/powers/agent-design/references/workflow-patterns.md +226 -0
- package/workflows/powers/agentic-eval/POWER.md +62 -0
- package/workflows/powers/agentic-eval/SKILL.md +59 -0
- package/workflows/powers/agentic-eval/references/rubric-and-regression-checklist.md +11 -0
- package/workflows/powers/api-designer/POWER.md +43 -71
- package/workflows/powers/api-designer/SKILL.md +43 -71
- package/workflows/powers/api-patterns/POWER.md +42 -56
- package/workflows/powers/api-patterns/SKILL.md +42 -57
- package/workflows/powers/architecture-designer/POWER.md +43 -60
- package/workflows/powers/architecture-designer/SKILL.md +43 -60
- package/workflows/powers/ask-questions-if-underspecified/POWER.md +51 -3
- package/workflows/powers/auth-architect/POWER.md +69 -0
- package/workflows/powers/auth-architect/SKILL.md +66 -0
- package/workflows/powers/auth-architect/references/session-token-policy-checklist.md +45 -0
- package/workflows/powers/behavioral-modes/POWER.md +100 -9
- package/workflows/powers/c-pro/POWER.md +105 -0
- package/workflows/powers/c-pro/SKILL.md +102 -0
- package/workflows/powers/c-pro/references/build-systems-and-toolchains.md +148 -0
- package/workflows/powers/c-pro/references/common-ub-and-portability.md +166 -0
- package/workflows/powers/c-pro/references/debugging-with-sanitizers.md +205 -0
- package/workflows/powers/c-pro/references/memory-safety-and-build-checklist.md +60 -0
- package/workflows/powers/c-pro/references/posix-and-platform-apis.md +244 -0
- package/workflows/powers/changelog-generator/POWER.md +127 -63
- package/workflows/powers/changelog-generator/SKILL.md +126 -63
- package/workflows/powers/ci-cd-pipelines/POWER.md +156 -0
- package/workflows/powers/ci-cd-pipelines/SKILL.md +153 -0
- package/workflows/powers/ci-cd-pipelines/references/github-actions-patterns.md +160 -0
- package/workflows/powers/ci-cd-pipelines/references/pipeline-security-checklist.md +57 -0
- package/workflows/powers/cli-developer/POWER.md +152 -95
- package/workflows/powers/cli-developer/SKILL.md +152 -95
- package/workflows/powers/cpp-pro/POWER.md +111 -0
- package/workflows/powers/cpp-pro/SKILL.md +108 -0
- package/workflows/powers/cpp-pro/references/concurrency-primitives.md +266 -0
- package/workflows/powers/cpp-pro/references/move-semantics-and-value-types.md +149 -0
- package/workflows/powers/cpp-pro/references/performance-and-profiling.md +191 -0
- package/workflows/powers/cpp-pro/references/raii-and-modern-cpp-checklist.md +87 -0
- package/workflows/powers/cpp-pro/references/template-and-concepts-patterns.md +205 -0
- package/workflows/powers/csharp-pro/POWER.md +47 -22
- package/workflows/powers/csharp-pro/SKILL.md +47 -22
- package/workflows/powers/dart-pro/POWER.md +68 -0
- package/workflows/powers/dart-pro/SKILL.md +65 -0
- package/workflows/powers/dart-pro/references/isolate-and-concurrency.md +180 -0
- package/workflows/powers/dart-pro/references/null-safety-and-async-patterns.md +133 -0
- package/workflows/powers/dart-pro/references/package-structure-and-linting.md +193 -0
- package/workflows/powers/dart-pro/references/sealed-records-patterns.md +173 -0
- package/workflows/powers/dart-pro/references/testing-and-mocking.md +235 -0
- package/workflows/powers/database-design/POWER.md +47 -33
- package/workflows/powers/database-design/SKILL.md +47 -33
- package/workflows/powers/database-optimizer/POWER.md +43 -64
- package/workflows/powers/database-optimizer/SKILL.md +43 -64
- package/workflows/powers/database-skills/POWER.md +59 -93
- package/workflows/powers/database-skills/SKILL.md +59 -93
- package/workflows/powers/debugging-strategies/POWER.md +69 -0
- package/workflows/powers/debugging-strategies/SKILL.md +66 -0
- package/workflows/powers/debugging-strategies/references/reproduce-isolate-verify-checklist.md +42 -0
- package/workflows/powers/deep-research/POWER.md +67 -0
- package/workflows/powers/deep-research/SKILL.md +64 -0
- package/workflows/powers/deep-research/references/multi-round-research-loop.md +80 -0
- package/workflows/powers/design-system-builder/POWER.md +130 -116
- package/workflows/powers/design-system-builder/SKILL.md +130 -116
- package/workflows/powers/devops-engineer/POWER.md +120 -57
- package/workflows/powers/devops-engineer/SKILL.md +120 -57
- package/workflows/powers/docker-kubernetes/POWER.md +94 -0
- package/workflows/powers/docker-kubernetes/SKILL.md +91 -0
- package/workflows/powers/docker-kubernetes/references/dockerfile-optimization-checklist.md +35 -0
- package/workflows/powers/docker-kubernetes/references/kubernetes-deployment-patterns.md +59 -0
- package/workflows/powers/documentation-templates/POWER.md +158 -127
- package/workflows/powers/documentation-templates/SKILL.md +158 -127
- package/workflows/powers/drizzle-expert/POWER.md +66 -0
- package/workflows/powers/drizzle-expert/SKILL.md +63 -0
- package/workflows/powers/drizzle-expert/references/runtime-pairing-matrix.md +16 -0
- package/workflows/powers/drizzle-expert/references/schema-and-migration-playbook.md +18 -0
- package/workflows/powers/error-ux-observability/POWER.md +144 -131
- package/workflows/powers/error-ux-observability/SKILL.md +143 -131
- package/workflows/powers/fastapi-expert/POWER.md +46 -60
- package/workflows/powers/fastapi-expert/SKILL.md +46 -60
- package/workflows/powers/firebase/POWER.md +65 -0
- package/workflows/powers/firebase/SKILL.md +62 -0
- package/workflows/powers/firebase/references/platform-routing.md +16 -0
- package/workflows/powers/firebase/references/rules-and-indexes-checklist.md +11 -0
- package/workflows/powers/flutter-design-system/POWER.md +63 -0
- package/workflows/powers/flutter-design-system/SKILL.md +60 -0
- package/workflows/powers/flutter-design-system/references/shared-widgets.md +29 -0
- package/workflows/powers/flutter-design-system/references/tokens-and-theme.md +34 -0
- package/workflows/powers/flutter-drift/POWER.md +65 -0
- package/workflows/powers/flutter-drift/SKILL.md +62 -0
- package/workflows/powers/flutter-drift/references/migrations.md +22 -0
- package/workflows/powers/flutter-drift/references/query-patterns.md +26 -0
- package/workflows/powers/flutter-feature/POWER.md +65 -0
- package/workflows/powers/flutter-feature/SKILL.md +62 -0
- package/workflows/powers/flutter-feature/references/architecture-rules.md +85 -0
- package/workflows/powers/flutter-feature/references/composite-provider.md +58 -0
- package/workflows/powers/flutter-feature/references/outbox-pattern.md +87 -0
- package/workflows/powers/flutter-feature/references/testing-patterns.md +218 -0
- package/workflows/powers/flutter-go-router/POWER.md +64 -0
- package/workflows/powers/flutter-go-router/SKILL.md +61 -0
- package/workflows/powers/flutter-go-router/references/guards-and-deeplinks.md +20 -0
- package/workflows/powers/flutter-go-router/references/typed-routes.md +27 -0
- package/workflows/powers/flutter-offline-sync/POWER.md +62 -0
- package/workflows/powers/flutter-offline-sync/SKILL.md +59 -0
- package/workflows/powers/flutter-offline-sync/references/outbox-full.md +44 -0
- package/workflows/powers/flutter-repository/POWER.md +64 -0
- package/workflows/powers/flutter-repository/SKILL.md +61 -0
- package/workflows/powers/flutter-repository/references/drift-patterns.md +21 -0
- package/workflows/powers/flutter-repository/references/retrofit-patterns.md +20 -0
- package/workflows/powers/flutter-riverpod/POWER.md +70 -0
- package/workflows/powers/flutter-riverpod/SKILL.md +67 -0
- package/workflows/powers/flutter-riverpod/references/async-and-mutations.md +19 -0
- package/workflows/powers/flutter-riverpod/references/async-lifecycle.md +19 -0
- package/workflows/powers/flutter-riverpod/references/provider-selection.md +20 -0
- package/workflows/powers/flutter-riverpod/references/testing.md +21 -0
- package/workflows/powers/flutter-riverpod/references/version-matrix.md +24 -0
- package/workflows/powers/flutter-state-machine/POWER.md +62 -0
- package/workflows/powers/flutter-state-machine/SKILL.md +59 -0
- package/workflows/powers/flutter-state-machine/references/app-state-contract.md +23 -0
- package/workflows/powers/flutter-state-machine/references/ui-rendering.md +14 -0
- package/workflows/powers/flutter-testing/POWER.md +64 -0
- package/workflows/powers/flutter-testing/SKILL.md +61 -0
- package/workflows/powers/flutter-testing/references/offline-sync-tests.md +16 -0
- package/workflows/powers/flutter-testing/references/test-layers.md +33 -0
- package/workflows/powers/frontend-code-review/POWER.md +137 -0
- package/workflows/powers/frontend-code-review/SKILL.md +134 -0
- package/workflows/powers/frontend-code-review/references/common-antipatterns.md +86 -0
- package/workflows/powers/frontend-code-review/references/performance-budgets.md +56 -0
- package/workflows/powers/frontend-code-review/references/review-checklists.md +47 -0
- package/workflows/powers/frontend-design/POWER.md +163 -362
- package/workflows/powers/frontend-design/SKILL.md +163 -362
- package/workflows/powers/game-development/POWER.md +57 -140
- package/workflows/powers/game-development/SKILL.md +57 -140
- package/workflows/powers/geo-fundamentals/POWER.md +64 -126
- package/workflows/powers/geo-fundamentals/SKILL.md +64 -127
- package/workflows/powers/git-workflow/POWER.md +135 -0
- package/workflows/powers/git-workflow/SKILL.md +132 -0
- package/workflows/powers/git-workflow/references/pr-review-checklist.md +63 -0
- package/workflows/powers/golang-pro/POWER.md +46 -35
- package/workflows/powers/golang-pro/SKILL.md +46 -35
- package/workflows/powers/graphql-architect/POWER.md +44 -62
- package/workflows/powers/graphql-architect/SKILL.md +44 -62
- package/workflows/powers/i18n-localization/POWER.md +118 -103
- package/workflows/powers/i18n-localization/SKILL.md +118 -103
- package/workflows/powers/java-pro/POWER.md +47 -22
- package/workflows/powers/java-pro/SKILL.md +47 -22
- package/workflows/powers/javascript-pro/POWER.md +47 -34
- package/workflows/powers/javascript-pro/SKILL.md +47 -34
- package/workflows/powers/kotlin-pro/POWER.md +46 -23
- package/workflows/powers/kotlin-pro/SKILL.md +46 -23
- package/workflows/powers/legacy-modernizer/POWER.md +43 -60
- package/workflows/powers/legacy-modernizer/SKILL.md +43 -60
- package/workflows/powers/mcp-builder/POWER.md +65 -0
- package/workflows/powers/mcp-builder/SKILL.md +62 -0
- package/workflows/powers/mcp-builder/references/testing-and-evals.md +17 -0
- package/workflows/powers/mcp-builder/references/transport-and-tool-design.md +17 -0
- package/workflows/powers/microservices-architect/POWER.md +43 -70
- package/workflows/powers/microservices-architect/SKILL.md +43 -70
- package/workflows/powers/mobile-design/POWER.md +110 -345
- package/workflows/powers/mobile-design/SKILL.md +110 -345
- package/workflows/powers/mongodb/POWER.md +67 -0
- package/workflows/powers/mongodb/SKILL.md +64 -0
- package/workflows/powers/mongodb/references/mongodb-checklist.md +20 -0
- package/workflows/powers/mysql/POWER.md +67 -0
- package/workflows/powers/mysql/SKILL.md +64 -0
- package/workflows/powers/mysql/references/mysql-checklist.md +20 -0
- package/workflows/powers/neki/POWER.md +67 -0
- package/workflows/powers/neki/SKILL.md +64 -0
- package/workflows/powers/neki/references/neki-checklist.md +18 -0
- package/workflows/powers/nestjs-expert/POWER.md +45 -91
- package/workflows/powers/nestjs-expert/SKILL.md +45 -91
- package/workflows/powers/nextjs-developer/POWER.md +51 -44
- package/workflows/powers/nextjs-developer/SKILL.md +51 -44
- package/workflows/powers/nodejs-best-practices/POWER.md +48 -29
- package/workflows/powers/nodejs-best-practices/SKILL.md +48 -29
- package/workflows/powers/observability/POWER.md +109 -0
- package/workflows/powers/observability/SKILL.md +106 -0
- package/workflows/powers/observability/references/alerting-and-slo-checklist.md +87 -0
- package/workflows/powers/observability/references/opentelemetry-setup-guide.md +121 -0
- package/workflows/powers/openai-docs/POWER.md +61 -0
- package/workflows/powers/openai-docs/SKILL.md +58 -0
- package/workflows/powers/openai-docs/references/official-source-playbook.md +10 -0
- package/workflows/powers/performance-profiling/POWER.md +61 -114
- package/workflows/powers/performance-profiling/SKILL.md +61 -114
- package/workflows/powers/php-pro/POWER.md +116 -0
- package/workflows/powers/php-pro/SKILL.md +113 -0
- package/workflows/powers/php-pro/references/architecture-and-di.md +239 -0
- package/workflows/powers/php-pro/references/modern-php-features.md +189 -0
- package/workflows/powers/php-pro/references/performance-and-deployment.md +197 -0
- package/workflows/powers/php-pro/references/php84-strict-typing-checklist.md +161 -0
- package/workflows/powers/php-pro/references/testing-and-static-analysis.md +235 -0
- package/workflows/powers/playwright-e2e/POWER.md +85 -0
- package/workflows/powers/playwright-e2e/SKILL.md +82 -0
- package/workflows/powers/playwright-e2e/references/locator-trace-flake-checklist.md +80 -0
- package/workflows/powers/postgres/POWER.md +67 -0
- package/workflows/powers/postgres/SKILL.md +64 -0
- package/workflows/powers/postgres/references/postgres-checklist.md +20 -0
- package/workflows/powers/prompt-engineer/POWER.md +47 -30
- package/workflows/powers/prompt-engineer/SKILL.md +47 -30
- package/workflows/powers/python-pro/POWER.md +47 -36
- package/workflows/powers/python-pro/SKILL.md +47 -36
- package/workflows/powers/react-best-practices/POWER.md +56 -33
- package/workflows/powers/react-best-practices/SKILL.md +56 -33
- package/workflows/powers/react-expert/POWER.md +47 -37
- package/workflows/powers/react-expert/SKILL.md +47 -37
- package/workflows/powers/redis/POWER.md +67 -0
- package/workflows/powers/redis/SKILL.md +64 -0
- package/workflows/powers/redis/references/redis-checklist.md +19 -0
- package/workflows/powers/ruby-pro/POWER.md +118 -0
- package/workflows/powers/ruby-pro/SKILL.md +115 -0
- package/workflows/powers/ruby-pro/references/modern-ruby-features.md +189 -0
- package/workflows/powers/ruby-pro/references/object-design-patterns.md +220 -0
- package/workflows/powers/ruby-pro/references/performance-and-profiling.md +224 -0
- package/workflows/powers/ruby-pro/references/ruby-concurrency-and-testing.md +190 -0
- package/workflows/powers/ruby-pro/references/testing-and-rspec.md +236 -0
- package/workflows/powers/rust-pro/POWER.md +45 -31
- package/workflows/powers/rust-pro/SKILL.md +45 -31
- package/workflows/powers/security-engineer/POWER.md +129 -0
- package/workflows/powers/security-engineer/SKILL.md +126 -0
- package/workflows/powers/seo-fundamentals/POWER.md +59 -102
- package/workflows/powers/seo-fundamentals/SKILL.md +59 -102
- package/workflows/powers/serverless-patterns/POWER.md +171 -0
- package/workflows/powers/serverless-patterns/SKILL.md +168 -0
- package/workflows/powers/skill-creator/POWER.md +90 -0
- package/workflows/powers/skill-creator/SKILL.md +87 -0
- package/workflows/powers/skill-creator/references/platform-formats.md +181 -0
- package/workflows/powers/skill-creator/references/schemas.md +430 -0
- package/workflows/powers/spec-miner/POWER.md +49 -57
- package/workflows/powers/spec-miner/SKILL.md +49 -57
- package/workflows/powers/sqlite/POWER.md +67 -0
- package/workflows/powers/sqlite/SKILL.md +64 -0
- package/workflows/powers/sqlite/references/sqlite-checklist.md +19 -0
- package/workflows/powers/sre-engineer/POWER.md +123 -64
- package/workflows/powers/sre-engineer/SKILL.md +123 -64
- package/workflows/powers/static-analysis/POWER.md +121 -77
- package/workflows/powers/static-analysis/SKILL.md +121 -77
- package/workflows/powers/stripe-best-practices/POWER.md +140 -17
- package/workflows/powers/stripe-best-practices/SKILL.md +139 -17
- package/workflows/powers/supabase/POWER.md +67 -0
- package/workflows/powers/supabase/SKILL.md +64 -0
- package/workflows/powers/supabase/references/supabase-checklist.md +19 -0
- package/workflows/powers/swift-pro/POWER.md +118 -0
- package/workflows/powers/swift-pro/SKILL.md +115 -0
- package/workflows/powers/swift-pro/references/concurrency-patterns.md +165 -0
- package/workflows/powers/swift-pro/references/protocol-and-generics.md +172 -0
- package/workflows/powers/swift-pro/references/sendable-and-isolation.md +116 -0
- package/workflows/powers/swift-pro/references/swift-concurrency-and-protocols.md +260 -0
- package/workflows/powers/swift-pro/references/testing-and-packages.md +192 -0
- package/workflows/powers/tailwind-patterns/POWER.md +71 -240
- package/workflows/powers/tailwind-patterns/SKILL.md +71 -240
- package/workflows/powers/testing-patterns/POWER.md +155 -10
- package/workflows/powers/testing-patterns/SKILL.md +155 -10
- package/workflows/powers/typescript-pro/POWER.md +47 -38
- package/workflows/powers/typescript-pro/SKILL.md +47 -38
- package/workflows/powers/vitess/POWER.md +67 -0
- package/workflows/powers/vitess/SKILL.md +64 -0
- package/workflows/powers/vitess/references/vitess-checklist.md +19 -0
- package/workflows/powers/vulnerability-scanner/POWER.md +146 -10
- package/workflows/powers/vulnerability-scanner/SKILL.md +146 -10
- package/workflows/powers/web-perf/POWER.md +43 -170
- package/workflows/powers/web-perf/SKILL.md +43 -170
- package/workflows/powers/webapp-testing/POWER.md +43 -164
- package/workflows/powers/webapp-testing/SKILL.md +43 -164
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +65 -42
- package/workflows/workflows/agent-environment-setup/platforms/claude/rules/CLAUDE.md +8 -6
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +65 -41
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +8 -6
- package/workflows/workflows/agent-environment-setup/shared/rules/STEERING.md +9 -8
- package/workflows/workflows/agent-environment-setup/shared/rules/overrides/codex.md +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Protocol and Generics
|
|
2
|
+
|
|
3
|
+
## Protocol design principles
|
|
4
|
+
|
|
5
|
+
### Minimal protocol requirements
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
// Good — focused contract
|
|
9
|
+
protocol Identifiable {
|
|
10
|
+
associatedtype ID: Hashable
|
|
11
|
+
var id: ID { get }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Bad — too many requirements force unnecessary implementations
|
|
15
|
+
protocol DataSource {
|
|
16
|
+
func count() -> Int
|
|
17
|
+
func item(at: Int) -> Any
|
|
18
|
+
func add(_ item: Any) // not all data sources are mutable
|
|
19
|
+
func remove(at: Int) // not all data sources support removal
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- Define the smallest set of requirements that captures the abstraction.
|
|
24
|
+
- Split large protocols into composable pieces (`Readable`, `Writable` instead of `ReadWriteStore`).
|
|
25
|
+
- Use protocol extensions for default implementations of non-essential behavior.
|
|
26
|
+
|
|
27
|
+
### Protocol composition
|
|
28
|
+
|
|
29
|
+
```swift
|
|
30
|
+
typealias UserStore = Identifiable & Codable & Sendable
|
|
31
|
+
|
|
32
|
+
func save<T: Identifiable & Encodable>(_ item: T) throws {
|
|
33
|
+
let data = try JSONEncoder().encode(item)
|
|
34
|
+
try storage.write(data, forKey: "\(T.ID.self):\(item.id)")
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Use `&` composition to combine protocols at use sites instead of creating combined protocols.
|
|
39
|
+
- Use `typealias` for frequently used compositions.
|
|
40
|
+
|
|
41
|
+
## Opaque types (`some`)
|
|
42
|
+
|
|
43
|
+
```swift
|
|
44
|
+
// Return type is known to the compiler, hidden from callers
|
|
45
|
+
func makeParser() -> some Parser {
|
|
46
|
+
JSONParser() // concrete type is fixed
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// In SwiftUI — the canonical use case
|
|
50
|
+
var body: some View {
|
|
51
|
+
VStack {
|
|
52
|
+
Text("Hello")
|
|
53
|
+
Button("Tap") { action() }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `some Protocol` preserves type identity — the compiler tracks the concrete type at compile time.
|
|
59
|
+
- Zero runtime overhead compared to existentials.
|
|
60
|
+
- Cannot return different concrete types from different code paths.
|
|
61
|
+
- Use `some` in return types and stored properties when the caller doesn't need to know the concrete type.
|
|
62
|
+
|
|
63
|
+
## Existential types (`any`)
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
// Dynamic dispatch — concrete type resolved at runtime
|
|
67
|
+
var repositories: [any Repository] = [
|
|
68
|
+
PostgresRepository(),
|
|
69
|
+
InMemoryRepository(),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
func process(repo: any Repository) {
|
|
73
|
+
// uses dynamic dispatch
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `any Protocol` has boxing overhead: heap allocation for the existential container.
|
|
78
|
+
- Required when you need heterogeneous collections of protocol-conforming types.
|
|
79
|
+
- Required when the concrete type varies at runtime.
|
|
80
|
+
- Cannot use protocols with `Self` requirements or associated types directly as `any` (use type erasure or constrained generics).
|
|
81
|
+
|
|
82
|
+
### Opening existentials
|
|
83
|
+
|
|
84
|
+
```swift
|
|
85
|
+
func processAny(_ item: any Identifiable) {
|
|
86
|
+
// Swift 5.7+ can "open" existentials into generics
|
|
87
|
+
process(item) // calls the generic version below
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func process<T: Identifiable>(_ item: T) {
|
|
91
|
+
print(item.id)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Associated types
|
|
96
|
+
|
|
97
|
+
```swift
|
|
98
|
+
protocol Collection {
|
|
99
|
+
associatedtype Element
|
|
100
|
+
associatedtype Index: Comparable
|
|
101
|
+
|
|
102
|
+
var startIndex: Index { get }
|
|
103
|
+
var endIndex: Index { get }
|
|
104
|
+
subscript(position: Index) -> Element { get }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Use associated types when the protocol needs to express relationships between types.
|
|
109
|
+
- Add constraints to associated types (`associatedtype Element: Sendable`) to propagate requirements.
|
|
110
|
+
- Use `where` clauses for complex constraints:
|
|
111
|
+
|
|
112
|
+
```swift
|
|
113
|
+
extension Collection where Element: Equatable {
|
|
114
|
+
func contains(_ element: Element) -> Bool {
|
|
115
|
+
// ...
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Generic constraint patterns
|
|
121
|
+
|
|
122
|
+
### Constrained extensions
|
|
123
|
+
|
|
124
|
+
```swift
|
|
125
|
+
extension Array where Element: Numeric {
|
|
126
|
+
var sum: Element {
|
|
127
|
+
reduce(.zero, +)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
extension Sequence where Element: Hashable {
|
|
132
|
+
var unique: [Element] {
|
|
133
|
+
var seen = Set<Element>()
|
|
134
|
+
return filter { seen.insert($0).inserted }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Generic type with constraints
|
|
140
|
+
|
|
141
|
+
```swift
|
|
142
|
+
struct Cache<Key: Hashable & Sendable, Value: Sendable> {
|
|
143
|
+
private var storage: [Key: Value] = [:]
|
|
144
|
+
|
|
145
|
+
mutating func set(_ value: Value, forKey key: Key) {
|
|
146
|
+
storage[key] = value
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func get(_ key: Key) -> Value? {
|
|
150
|
+
storage[key]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Primary associated types (Swift 5.7+)
|
|
156
|
+
|
|
157
|
+
```swift
|
|
158
|
+
protocol Repository<Model> {
|
|
159
|
+
associatedtype Model: Identifiable
|
|
160
|
+
|
|
161
|
+
func find(id: Model.ID) async throws -> Model?
|
|
162
|
+
func save(_ model: Model) async throws
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Use in constrained contexts without full generic signatures
|
|
166
|
+
func processRepo(_ repo: some Repository<User>) async throws {
|
|
167
|
+
let user = try await repo.find(id: userID)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Primary associated types simplify common generic usage with `some Protocol<ConcreteType>` syntax.
|
|
172
|
+
- Reduces the need for manual type erasure wrappers.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Sendable and Isolation
|
|
2
|
+
|
|
3
|
+
## Sendable conformance checklist
|
|
4
|
+
|
|
5
|
+
| Type | Sendable? | Notes |
|
|
6
|
+
| --------------------------------------------------------- | --------------------- | ------------------------------------------------- |
|
|
7
|
+
| `struct` with all `Sendable` stored properties | Automatic | No annotation needed in most cases. |
|
|
8
|
+
| `enum` with all `Sendable` associated values | Automatic | Including cases with no associated values. |
|
|
9
|
+
| `actor` | Always `Sendable` | Actors are `Sendable` by definition. |
|
|
10
|
+
| `final class` with immutable `Sendable` stored properties | Explicit | Must declare `: Sendable` conformance. |
|
|
11
|
+
| `class` with mutable state and manual synchronization | `@unchecked Sendable` | Requires manual audit of all access paths. |
|
|
12
|
+
| `class` with no synchronization | Not Sendable | Must be redesigned or isolated to a single actor. |
|
|
13
|
+
|
|
14
|
+
## Isolation boundaries
|
|
15
|
+
|
|
16
|
+
### Crossing isolation domains
|
|
17
|
+
|
|
18
|
+
```swift
|
|
19
|
+
actor DataStore {
|
|
20
|
+
private var items: [Item] = []
|
|
21
|
+
|
|
22
|
+
// Value types cross boundaries safely
|
|
23
|
+
func snapshot() -> [Item] {
|
|
24
|
+
items // [Item] is Sendable if Item is Sendable
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// From non-isolated context:
|
|
29
|
+
let store = DataStore()
|
|
30
|
+
let items = await store.snapshot() // crosses isolation boundary
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### The `sending` parameter (Swift 6)
|
|
34
|
+
|
|
35
|
+
```swift
|
|
36
|
+
// Transfer ownership of a non-Sendable value into an actor
|
|
37
|
+
func process(_ item: sending Item) async {
|
|
38
|
+
// item is now owned by this isolation domain
|
|
39
|
+
// caller cannot use item after this call
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `sending` allows passing non-Sendable values across boundaries when ownership is transferred.
|
|
44
|
+
- The compiler enforces that the caller does not retain a reference after the call.
|
|
45
|
+
- Prefer `sending` over `@unchecked Sendable` when transferring ownership is the real intent.
|
|
46
|
+
|
|
47
|
+
## Auditing @unchecked Sendable
|
|
48
|
+
|
|
49
|
+
When marking a class `@unchecked Sendable`, verify:
|
|
50
|
+
|
|
51
|
+
1. **All mutable state** is protected by a lock, serial queue, or other synchronization mechanism.
|
|
52
|
+
2. **No stored property** is a non-Sendable reference type accessed without synchronization.
|
|
53
|
+
3. **No method** exposes internal mutable state by reference.
|
|
54
|
+
4. **Subclasses** (if any) cannot break the synchronization contract.
|
|
55
|
+
|
|
56
|
+
```swift
|
|
57
|
+
final class AtomicCounter: @unchecked Sendable {
|
|
58
|
+
private let lock = NSLock()
|
|
59
|
+
private var _value: Int = 0
|
|
60
|
+
|
|
61
|
+
var value: Int {
|
|
62
|
+
lock.lock()
|
|
63
|
+
defer { lock.unlock() }
|
|
64
|
+
return _value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func increment() {
|
|
68
|
+
lock.lock()
|
|
69
|
+
defer { lock.unlock() }
|
|
70
|
+
_value += 1
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Always use `final class` with `@unchecked Sendable` to prevent subclass violations.
|
|
76
|
+
- Prefer `OSAllocatedUnfairLock` (iOS 16+/macOS 13+) over `NSLock` for better performance.
|
|
77
|
+
- Document the synchronization strategy in a comment next to the conformance.
|
|
78
|
+
|
|
79
|
+
## Closure isolation
|
|
80
|
+
|
|
81
|
+
```swift
|
|
82
|
+
// @Sendable closure — can only capture Sendable values
|
|
83
|
+
func runDetached(_ work: @escaping @Sendable () async -> Void) {
|
|
84
|
+
Task.detached { await work() }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Non-Sendable capture will produce a compiler error
|
|
88
|
+
class ViewController {
|
|
89
|
+
var state: [String] = [] // not Sendable
|
|
90
|
+
|
|
91
|
+
func bad() {
|
|
92
|
+
runDetached {
|
|
93
|
+
self.state.append("oops") // ERROR: captured non-Sendable self
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func good() {
|
|
98
|
+
let snapshot = state // copy to Sendable value
|
|
99
|
+
runDetached { [snapshot] in
|
|
100
|
+
print(snapshot) // OK — snapshot is Sendable
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `@Sendable` closures enforce Sendable captures at compile time.
|
|
107
|
+
- Capture value copies or Sendable references in the capture list.
|
|
108
|
+
- Use `@MainActor in` closure syntax to isolate closures to the main actor.
|
|
109
|
+
|
|
110
|
+
## Migration strategy
|
|
111
|
+
|
|
112
|
+
1. Enable `-strict-concurrency=targeted` first to see warnings without breaking the build.
|
|
113
|
+
2. Fix `Sendable` conformance for types that cross boundaries (DTOs, config, events).
|
|
114
|
+
3. Escalate to `-strict-concurrency=complete` once targeted warnings are resolved.
|
|
115
|
+
4. Convert `@unchecked Sendable` uses to proper actors where feasible.
|
|
116
|
+
5. Use `sending` parameters to eliminate remaining `@unchecked Sendable` workarounds.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Swift Concurrency and Protocols
|
|
2
|
+
|
|
3
|
+
## Actor design
|
|
4
|
+
|
|
5
|
+
### When to use actors
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
actor BankAccount {
|
|
9
|
+
private var balance: Decimal
|
|
10
|
+
|
|
11
|
+
init(initialBalance: Decimal) {
|
|
12
|
+
self.balance = initialBalance
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func deposit(_ amount: Decimal) {
|
|
16
|
+
balance += amount
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func withdraw(_ amount: Decimal) throws -> Decimal {
|
|
20
|
+
guard balance >= amount else {
|
|
21
|
+
throw BankError.insufficientFunds
|
|
22
|
+
}
|
|
23
|
+
balance -= amount
|
|
24
|
+
return amount
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// nonisolated for properties that don't need isolation
|
|
28
|
+
nonisolated var description: String { "BankAccount" }
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Use `actor` when mutable state needs thread-safe access from multiple callers.
|
|
33
|
+
- Actor methods are implicitly `async` when called from outside the actor.
|
|
34
|
+
- Use `nonisolated` for computed properties or methods that don't access mutable state.
|
|
35
|
+
- Keep actor methods fast — long-running work blocks other callers waiting for the actor.
|
|
36
|
+
|
|
37
|
+
### Global actors
|
|
38
|
+
|
|
39
|
+
```swift
|
|
40
|
+
@globalActor
|
|
41
|
+
actor DatabaseActor {
|
|
42
|
+
static let shared = DatabaseActor()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@DatabaseActor
|
|
46
|
+
class DatabaseManager {
|
|
47
|
+
func query(_ sql: String) async throws -> [Row] { /* ... */ }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Use `@MainActor` for all UI-bound code. Never dispatch to main queue manually.
|
|
52
|
+
- Create custom global actors for subsystems that need serial execution (database, file system).
|
|
53
|
+
- Annotate classes, methods, or properties — not entire modules.
|
|
54
|
+
|
|
55
|
+
## Sendable conformance
|
|
56
|
+
|
|
57
|
+
### Value types (automatic Sendable)
|
|
58
|
+
|
|
59
|
+
```swift
|
|
60
|
+
// Automatically Sendable — all stored properties are Sendable
|
|
61
|
+
struct UserDTO: Sendable {
|
|
62
|
+
let id: UUID
|
|
63
|
+
let name: String
|
|
64
|
+
let email: String
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Enums with Sendable associated values are Sendable
|
|
68
|
+
enum Result<Success: Sendable, Failure: Error>: Sendable {
|
|
69
|
+
case success(Success)
|
|
70
|
+
case failure(Failure)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Reference types (manual Sendable)
|
|
75
|
+
|
|
76
|
+
```swift
|
|
77
|
+
// Must prove thread safety manually
|
|
78
|
+
final class ThreadSafeCache: @unchecked Sendable {
|
|
79
|
+
private let lock = NSLock()
|
|
80
|
+
private var storage: [String: Any] = [:]
|
|
81
|
+
|
|
82
|
+
func get(_ key: String) -> Any? {
|
|
83
|
+
lock.lock()
|
|
84
|
+
defer { lock.unlock() }
|
|
85
|
+
return storage[key]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `@unchecked Sendable` requires manual audit — the compiler trusts your claim.
|
|
91
|
+
- Prefer actors over `@unchecked Sendable` classes when possible.
|
|
92
|
+
- Use `sending` parameter modifier (Swift 6) to transfer ownership without requiring `Sendable`.
|
|
93
|
+
|
|
94
|
+
### Closure Sendable rules
|
|
95
|
+
|
|
96
|
+
```swift
|
|
97
|
+
// Closure must capture only Sendable values
|
|
98
|
+
func runOnBackground(_ work: @Sendable () async -> Void) {
|
|
99
|
+
Task { await work() }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Capture list with Sendable values only
|
|
103
|
+
let name = "Alice" // String is Sendable
|
|
104
|
+
runOnBackground { [name] in
|
|
105
|
+
print(name) // OK — captured Sendable value
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Structured concurrency
|
|
110
|
+
|
|
111
|
+
### TaskGroup
|
|
112
|
+
|
|
113
|
+
```swift
|
|
114
|
+
func fetchAllUsers(ids: [Int]) async throws -> [User] {
|
|
115
|
+
try await withThrowingTaskGroup(of: User.self) { group in
|
|
116
|
+
for id in ids {
|
|
117
|
+
group.addTask {
|
|
118
|
+
try await api.fetchUser(id: id)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var users: [User] = []
|
|
122
|
+
for try await user in group {
|
|
123
|
+
users.append(user)
|
|
124
|
+
}
|
|
125
|
+
return users
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- Use `TaskGroup` for fan-out/fan-in patterns. Child tasks are automatically cancelled if the group scope exits.
|
|
131
|
+
- `withThrowingTaskGroup` propagates the first error and cancels remaining child tasks.
|
|
132
|
+
|
|
133
|
+
### Task cancellation
|
|
134
|
+
|
|
135
|
+
```swift
|
|
136
|
+
func processItems(_ items: [Item]) async throws {
|
|
137
|
+
for item in items {
|
|
138
|
+
// Check cancellation before each unit of work
|
|
139
|
+
try Task.checkCancellation()
|
|
140
|
+
await process(item)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Or check without throwing
|
|
145
|
+
if Task.isCancelled {
|
|
146
|
+
// Clean up and return partial results
|
|
147
|
+
return partialResults
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Always check `Task.isCancelled` or call `Task.checkCancellation()` in loops.
|
|
152
|
+
- Cancellation is cooperative — tasks are not killed automatically.
|
|
153
|
+
- Clean up resources in cancellation paths (close files, cancel network requests).
|
|
154
|
+
|
|
155
|
+
### AsyncSequence and AsyncStream
|
|
156
|
+
|
|
157
|
+
```swift
|
|
158
|
+
// Producing async events
|
|
159
|
+
func events() -> AsyncStream<Event> {
|
|
160
|
+
AsyncStream { continuation in
|
|
161
|
+
let observer = NotificationCenter.default.addObserver(...) { notification in
|
|
162
|
+
continuation.yield(Event(from: notification))
|
|
163
|
+
}
|
|
164
|
+
continuation.onTermination = { _ in
|
|
165
|
+
NotificationCenter.default.removeObserver(observer)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Consuming
|
|
171
|
+
for await event in events() {
|
|
172
|
+
handle(event)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Protocol-oriented architecture
|
|
177
|
+
|
|
178
|
+
### Protocol as capability contract
|
|
179
|
+
|
|
180
|
+
```swift
|
|
181
|
+
protocol UserRepository {
|
|
182
|
+
func find(id: UUID) async throws -> User?
|
|
183
|
+
func save(_ user: User) async throws
|
|
184
|
+
func delete(id: UUID) async throws
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Concrete implementation is internal
|
|
188
|
+
struct PostgresUserRepository: UserRepository {
|
|
189
|
+
func find(id: UUID) async throws -> User? { /* ... */ }
|
|
190
|
+
func save(_ user: User) async throws { /* ... */ }
|
|
191
|
+
func delete(id: UUID) async throws { /* ... */ }
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Protocol extensions for defaults
|
|
196
|
+
|
|
197
|
+
```swift
|
|
198
|
+
protocol Cacheable: Identifiable {
|
|
199
|
+
var cacheKey: String { get }
|
|
200
|
+
var ttl: TimeInterval { get }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
extension Cacheable {
|
|
204
|
+
var cacheKey: String { "\(Self.self):\(id)" }
|
|
205
|
+
var ttl: TimeInterval { 300 } // 5 minutes default
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- Use extensions for sensible defaults. Keep required methods minimal.
|
|
210
|
+
|
|
211
|
+
### Opaque vs existential types
|
|
212
|
+
|
|
213
|
+
```swift
|
|
214
|
+
// Opaque: compiler knows the concrete type (zero-cost)
|
|
215
|
+
func makeUser() -> some UserRepository {
|
|
216
|
+
PostgresUserRepository()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Existential: runtime type erasure (has boxing overhead)
|
|
220
|
+
func getRepository() -> any UserRepository {
|
|
221
|
+
condition ? PostgresUserRepository() : InMemoryUserRepository()
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
| Feature | `some Protocol` | `any Protocol` |
|
|
226
|
+
| ---------------------- | ------------------------------ | ---------------------------------- |
|
|
227
|
+
| Performance | Zero-cost (static dispatch) | Boxing overhead (dynamic dispatch) |
|
|
228
|
+
| Can vary return type | No — always same concrete type | Yes — different types allowed |
|
|
229
|
+
| Use as stored property | Only in generic context | Yes, directly |
|
|
230
|
+
|
|
231
|
+
- Prefer `some` by default. Use `any` only when you need heterogeneous collections or dynamic type selection.
|
|
232
|
+
- Use `any Protocol` in function parameters when multiple concrete types must be accepted.
|
|
233
|
+
|
|
234
|
+
## Testing with protocols
|
|
235
|
+
|
|
236
|
+
```swift
|
|
237
|
+
@Test
|
|
238
|
+
func testUserCreation() async throws {
|
|
239
|
+
let repo = MockUserRepository()
|
|
240
|
+
let service = UserService(repository: repo)
|
|
241
|
+
|
|
242
|
+
let user = try await service.createUser(name: "Alice")
|
|
243
|
+
|
|
244
|
+
#expect(user.name == "Alice")
|
|
245
|
+
#expect(repo.savedUsers.count == 1)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
struct MockUserRepository: UserRepository {
|
|
249
|
+
var savedUsers: [User] = []
|
|
250
|
+
|
|
251
|
+
mutating func save(_ user: User) async throws {
|
|
252
|
+
savedUsers.append(user)
|
|
253
|
+
}
|
|
254
|
+
// ...
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
- Inject protocols, test with mocks. Never test against real databases in unit tests.
|
|
259
|
+
- Use Swift Testing `@Test` and `#expect` for new test targets.
|
|
260
|
+
- Use `confirmation()` for testing actor-isolated code that emits events.
|