dzql 0.2.0 → 0.2.2
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 +20 -4
- package/docs/README.md +71 -0
- package/docs/compiler/README.md +84 -0
- package/docs/{CLAUDE.md → for-ai/claude-guide.md} +2 -2
- package/docs/getting-started/subscriptions-quick-start.md +203 -0
- package/docs/{GETTING_STARTED.md → getting-started/tutorial.md} +1 -1
- package/docs/guides/subscriptions.md +535 -0
- package/docs/{REFERENCE.md → reference/api.md} +9 -9
- package/docs/{CLIENT-QUICK-START.md → reference/client.md} +2 -2
- package/package.json +2 -3
- package/src/compiler/cli/compile-example.js +33 -0
- package/src/compiler/cli/compile-subscribable.js +43 -0
- package/src/compiler/cli/debug-compile.js +44 -0
- package/src/compiler/cli/debug-parse.js +26 -0
- package/src/compiler/cli/debug-path-parser.js +18 -0
- package/src/compiler/cli/debug-subscribable-parser.js +21 -0
- package/src/compiler/codegen/subscribable-codegen.js +52 -2
- package/src/database/migrations/008a_meta.sql +7 -0
- package/src/server/db.js +7 -0
- package/src/client/stores/README.md +0 -95
- /package/docs/{CLIENT-STORES.md → guides/client-stores.md} +0 -0
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@ PostgreSQL-powered framework with automatic CRUD operations, live query subscrip
|
|
|
4
4
|
|
|
5
5
|
## Documentation
|
|
6
6
|
|
|
7
|
-
- **[
|
|
8
|
-
- **[
|
|
9
|
-
- **[
|
|
7
|
+
- **[Documentation Hub](docs/)** - Complete documentation index
|
|
8
|
+
- **[Getting Started Tutorial](docs/getting-started/tutorial.md)** - Complete tutorial with working todo app
|
|
9
|
+
- **[API Reference](docs/reference/api.md)** - Complete API documentation
|
|
10
|
+
- **[Live Query Subscriptions](docs/getting-started/subscriptions-quick-start.md)** - Real-time denormalized documents (NEW in v0.2.0)
|
|
10
11
|
- **[Compiler Documentation](docs/compiler/)** - Entity compilation guide and coding standards
|
|
11
|
-
- **[Claude Guide](docs/
|
|
12
|
+
- **[Claude Guide](docs/for-ai/claude-guide.md)** - Development guide for AI assistants
|
|
12
13
|
- **[Venues Example](../venues/)** - Full working application
|
|
13
14
|
|
|
14
15
|
## Quick Install
|
|
@@ -55,6 +56,21 @@ const result = compiler.compileFromSQL(sqlContent);
|
|
|
55
56
|
|
|
56
57
|
See **[Compiler Documentation](docs/compiler/)** for complete usage guide, coding standards, and advanced features.
|
|
57
58
|
|
|
59
|
+
## Testing
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Start test database
|
|
63
|
+
cd tests/test-utils && docker compose up -d
|
|
64
|
+
|
|
65
|
+
# Run tests
|
|
66
|
+
bun test
|
|
67
|
+
|
|
68
|
+
# Stop database
|
|
69
|
+
cd tests/test-utils && docker compose down
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All tests use `bun:test` framework with automatic database setup/teardown. See **[tests/test-utils/README.md](tests/test-utils/README.md)** for details.
|
|
73
|
+
|
|
58
74
|
## License
|
|
59
75
|
|
|
60
76
|
MIT
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# DZQL Documentation
|
|
2
|
+
|
|
3
|
+
Complete documentation for the DZQL PostgreSQL-powered framework.
|
|
4
|
+
|
|
5
|
+
## 📚 Getting Started
|
|
6
|
+
|
|
7
|
+
New to DZQL? Start here:
|
|
8
|
+
|
|
9
|
+
- **[Tutorial](getting-started/tutorial.md)** - Complete step-by-step guide with a working todo app
|
|
10
|
+
- **[Subscriptions Quick Start](getting-started/subscriptions-quick-start.md)** - Get real-time subscriptions working in 5 minutes
|
|
11
|
+
|
|
12
|
+
## 📖 Guides
|
|
13
|
+
|
|
14
|
+
Feature-specific guides and how-tos:
|
|
15
|
+
|
|
16
|
+
- **[Live Query Subscriptions](guides/subscriptions.md)** - Real-time denormalized documents
|
|
17
|
+
- **[Client Stores](guides/client-stores.md)** - Pinia store patterns for Vue.js
|
|
18
|
+
|
|
19
|
+
## 📘 Reference
|
|
20
|
+
|
|
21
|
+
Complete API documentation:
|
|
22
|
+
|
|
23
|
+
- **[API Reference](reference/api.md)** - The 5 operations, entities, permissions, graph rules
|
|
24
|
+
- **[Client API](reference/client.md)** - WebSocket client and connection management
|
|
25
|
+
- **[Compiler](compiler/)** - Entity compilation, code generation, and coding standards
|
|
26
|
+
|
|
27
|
+
### Compiler Documentation
|
|
28
|
+
|
|
29
|
+
- [Quickstart](compiler/QUICKSTART.md) - Get started with the DZQL compiler
|
|
30
|
+
- [Advanced Filters](compiler/ADVANCED_FILTERS.md) - Complex search operators
|
|
31
|
+
- [Coding Standards](compiler/CODING_STANDARDS.md) - Best practices for DZQL code
|
|
32
|
+
- [Comparison](compiler/COMPARISON.md) - DZQL vs other approaches
|
|
33
|
+
|
|
34
|
+
## 🤖 For AI Assistants
|
|
35
|
+
|
|
36
|
+
- **[Claude Guide](for-ai/claude-guide.md)** - Complete guide for AI-assisted DZQL development
|
|
37
|
+
|
|
38
|
+
## 🔗 Quick Links
|
|
39
|
+
|
|
40
|
+
- [npm Package](https://www.npmjs.com/package/dzql)
|
|
41
|
+
- [GitHub Repository](https://github.com/blueshed/dzql)
|
|
42
|
+
- [Issue Tracker](https://github.com/blueshed/dzql/issues)
|
|
43
|
+
- [Changelog](../../../CHANGELOG.md)
|
|
44
|
+
- [Contributing](../../../CONTRIBUTING.md)
|
|
45
|
+
|
|
46
|
+
## 🏗️ Architecture
|
|
47
|
+
|
|
48
|
+
Looking for architecture and design docs? See the [repository docs](../../../docs/):
|
|
49
|
+
|
|
50
|
+
- [Permissions System](../../../docs/architecture/PERMISSIONS.md)
|
|
51
|
+
- [Project Roadmap](../../../docs/architecture/ROADMAP.md)
|
|
52
|
+
- [Subscription Architecture](../../../docs/architecture/SUBSCRIPTIONS_STRATEGY.md)
|
|
53
|
+
|
|
54
|
+
## 🧪 Development
|
|
55
|
+
|
|
56
|
+
Contributing to DZQL? See development documentation:
|
|
57
|
+
|
|
58
|
+
- [TDD Workflow](../../../docs/development/TDD_WORKFLOW.md)
|
|
59
|
+
- [WebSocket Testing](../../../docs/development/WEBSOCKET_TESTING.md)
|
|
60
|
+
- [Claude Web Setup](../../../docs/development/CLAUDE-WEB.md)
|
|
61
|
+
|
|
62
|
+
## 📦 Package Contents
|
|
63
|
+
|
|
64
|
+
This documentation is published with the npm package. For repository-wide documentation (contributors, development workflow, architecture), see [`/docs/`](../../../docs/) in the repository root.
|
|
65
|
+
|
|
66
|
+
## Need Help?
|
|
67
|
+
|
|
68
|
+
- 📖 Check the guides above
|
|
69
|
+
- 🐛 [Report an issue](https://github.com/blueshed/dzql/issues)
|
|
70
|
+
- 💬 [Start a discussion](https://github.com/blueshed/dzql/discussions)
|
|
71
|
+
- 🤖 Ask your AI assistant (they have access to this documentation!)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# DZQL Compiler Documentation
|
|
2
|
+
|
|
3
|
+
The DZQL Compiler transforms declarative entity definitions into optimized PostgreSQL stored procedures.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
- **[Quickstart Guide](QUICKSTART.md)** - Get started with the compiler in 5 minutes
|
|
8
|
+
|
|
9
|
+
## Guides
|
|
10
|
+
|
|
11
|
+
- **[Advanced Filters](ADVANCED_FILTERS.md)** - Complex search operators and patterns
|
|
12
|
+
- **[Coding Standards](CODING_STANDARDS.md)** - Best practices for DZQL code
|
|
13
|
+
|
|
14
|
+
## Reference
|
|
15
|
+
|
|
16
|
+
- **[Comparison](COMPARISON.md)** - How DZQL compares to other approaches
|
|
17
|
+
- **[Session Summary](SESSION_SUMMARY.md)** - Development session documentation
|
|
18
|
+
- **[Summary](SUMMARY.md)** - Compiler overview and architecture
|
|
19
|
+
- **[Overnight Build](OVERNIGHT_BUILD.md)** - Batch compilation process
|
|
20
|
+
|
|
21
|
+
## Using the Compiler
|
|
22
|
+
|
|
23
|
+
### Via CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
dzql compile database/domain.sql -o compiled/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Programmatically
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import { DZQLCompiler } from 'dzql/compiler';
|
|
33
|
+
|
|
34
|
+
const compiler = new DZQLCompiler();
|
|
35
|
+
const result = compiler.compileFromSQL(sqlContent);
|
|
36
|
+
|
|
37
|
+
console.log(result.sql); // Generated PostgreSQL
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Registering Entities
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
SELECT dzql.register_entity(
|
|
44
|
+
'todos', -- Table name
|
|
45
|
+
'title', -- Label field
|
|
46
|
+
array['title', 'description'], -- Searchable fields
|
|
47
|
+
'{}'::jsonb, -- FK includes
|
|
48
|
+
false, -- Soft delete
|
|
49
|
+
'{}'::jsonb, -- Graph rules
|
|
50
|
+
jsonb_build_object( -- Notification paths
|
|
51
|
+
'owner', array['@user_id']
|
|
52
|
+
),
|
|
53
|
+
jsonb_build_object( -- Permission paths
|
|
54
|
+
'view', array['@user_id'],
|
|
55
|
+
'create', array['@user_id'],
|
|
56
|
+
'update', array['@user_id'],
|
|
57
|
+
'delete', array['@user_id']
|
|
58
|
+
),
|
|
59
|
+
'{}'::jsonb -- Temporal config
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This generates 5 PostgreSQL functions:
|
|
64
|
+
- `get_todos(params, user_id)` - Retrieve single record
|
|
65
|
+
- `save_todos(params, user_id)` - Create or update
|
|
66
|
+
- `delete_todos(params, user_id)` - Delete record
|
|
67
|
+
- `lookup_todos(params, user_id)` - Autocomplete
|
|
68
|
+
- `search_todos(params, user_id)` - Search with filters
|
|
69
|
+
|
|
70
|
+
## Architecture
|
|
71
|
+
|
|
72
|
+
The compiler uses a three-phase approach:
|
|
73
|
+
|
|
74
|
+
1. **Parse** - Extract entity definitions from SQL
|
|
75
|
+
2. **Generate** - Create optimized PostgreSQL functions
|
|
76
|
+
3. **Deploy** - Execute generated SQL
|
|
77
|
+
|
|
78
|
+
All business logic runs in PostgreSQL, not application code.
|
|
79
|
+
|
|
80
|
+
## See Also
|
|
81
|
+
|
|
82
|
+
- [Main Documentation](../) - Full DZQL documentation
|
|
83
|
+
- [API Reference](../reference/api.md) - The 5 operations
|
|
84
|
+
- [For AI](../for-ai/claude-guide.md) - AI-assisted development
|
|
@@ -1163,7 +1163,7 @@ array['name', 'address', 'city', 'description', 'notes', 'tags', 'metadata']
|
|
|
1163
1163
|
|
|
1164
1164
|
## Additional Resources
|
|
1165
1165
|
|
|
1166
|
-
- **API Reference**: See [
|
|
1167
|
-
- **Tutorial**: See [
|
|
1166
|
+
- **API Reference**: See [API Reference](../reference/api.md) for complete API documentation
|
|
1167
|
+
- **Tutorial**: See [Getting Started Tutorial](../getting-started/tutorial.md) for hands-on guide
|
|
1168
1168
|
- **Examples**: See `packages/venues/` for complete working application
|
|
1169
1169
|
- **Tests**: See `packages/venues/tests/` for comprehensive test patterns
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Live Query Subscriptions - Quick Start
|
|
2
|
+
|
|
3
|
+
Get up and running with live query subscriptions in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## Step 1: Create a Subscribable (2 min)
|
|
6
|
+
|
|
7
|
+
Create `my_subscribable.sql`:
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
SELECT dzql.register_subscribable(
|
|
11
|
+
'venue_detail', -- Name (use in API)
|
|
12
|
+
'{"subscribe": ["@org_id->acts_for[org_id=$]{active}.user_id"]}'::jsonb, -- Who can subscribe
|
|
13
|
+
'{"venue_id": "int"}'::jsonb, -- Subscription parameters
|
|
14
|
+
'venues', -- Root table
|
|
15
|
+
'{"org": "organisations", "sites": {"entity": "sites", "filter": "venue_id=$venue_id"}}'::jsonb -- Related data
|
|
16
|
+
);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Step 2: Compile and Deploy (1 min)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Compile to PostgreSQL functions
|
|
23
|
+
bun packages/dzql/src/compiler/cli/compile-subscribable.js my_subscribable.sql | psql $DATABASE_URL
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This creates 3 functions:
|
|
27
|
+
- `venue_detail_can_subscribe(user_id, params)` - permission check
|
|
28
|
+
- `get_venue_detail(params, user_id)` - query builder
|
|
29
|
+
- `venue_detail_affected_documents(table, op, old, new)` - change detector
|
|
30
|
+
|
|
31
|
+
## Step 3: Subscribe from Client (2 min)
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { WebSocketManager } from '@dzql/client';
|
|
35
|
+
|
|
36
|
+
const ws = new WebSocketManager('ws://localhost:3000/ws');
|
|
37
|
+
await ws.connect();
|
|
38
|
+
|
|
39
|
+
// Subscribe - get initial data + live updates
|
|
40
|
+
const { data, unsubscribe } = await ws.api.subscribe_venue_detail(
|
|
41
|
+
{ venue_id: 123 },
|
|
42
|
+
(updatedData) => {
|
|
43
|
+
console.log('Venue changed!', updatedData);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log('Initial data:', data);
|
|
48
|
+
|
|
49
|
+
// Later: cleanup
|
|
50
|
+
await unsubscribe();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## That's It!
|
|
54
|
+
|
|
55
|
+
Your client now receives real-time updates whenever:
|
|
56
|
+
- The venue record changes
|
|
57
|
+
- Related organisation changes
|
|
58
|
+
- Related sites change
|
|
59
|
+
|
|
60
|
+
All change detection happens in PostgreSQL - zero configuration needed on the server!
|
|
61
|
+
|
|
62
|
+
## Next Steps
|
|
63
|
+
|
|
64
|
+
- [Full Documentation](../guides/subscriptions.md)
|
|
65
|
+
- [Permission Paths Guide](../../../../docs/architecture/PERMISSIONS.md)
|
|
66
|
+
- [API Reference](../reference/api.md)
|
|
67
|
+
|
|
68
|
+
## Common Patterns
|
|
69
|
+
|
|
70
|
+
### Simple Document (Single Table)
|
|
71
|
+
|
|
72
|
+
```sql
|
|
73
|
+
SELECT dzql.register_subscribable(
|
|
74
|
+
'user_settings',
|
|
75
|
+
'{"subscribe": ["@user_id"]}'::jsonb, -- Only owner
|
|
76
|
+
'{"user_id": "int"}'::jsonb,
|
|
77
|
+
'user_settings',
|
|
78
|
+
'{}'::jsonb -- No relations
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### With One Relation
|
|
83
|
+
|
|
84
|
+
```sql
|
|
85
|
+
SELECT dzql.register_subscribable(
|
|
86
|
+
'booking_summary',
|
|
87
|
+
'{"subscribe": ["@user_id"]}'::jsonb,
|
|
88
|
+
'{"booking_id": "int"}'::jsonb,
|
|
89
|
+
'bookings',
|
|
90
|
+
'{"venue": "venues"}'::jsonb -- Include venue
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### With Filtered Relations
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
SELECT dzql.register_subscribable(
|
|
98
|
+
'organisation_dashboard',
|
|
99
|
+
'{"subscribe": ["@id->acts_for[org_id=$]{active}.user_id"]}'::jsonb,
|
|
100
|
+
'{"org_id": "int"}'::jsonb,
|
|
101
|
+
'organisations',
|
|
102
|
+
'{
|
|
103
|
+
"members": {
|
|
104
|
+
"entity": "acts_for",
|
|
105
|
+
"filter": "org_id=$org_id AND valid_to IS NULL"
|
|
106
|
+
},
|
|
107
|
+
"venues": {
|
|
108
|
+
"entity": "venues",
|
|
109
|
+
"filter": "org_id=$org_id"
|
|
110
|
+
}
|
|
111
|
+
}'::jsonb
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Multiple Permission Paths (OR logic)
|
|
116
|
+
|
|
117
|
+
```sql
|
|
118
|
+
SELECT dzql.register_subscribable(
|
|
119
|
+
'venue_admin',
|
|
120
|
+
'{
|
|
121
|
+
"subscribe": [
|
|
122
|
+
"@owner_id", -- Direct owner
|
|
123
|
+
"@org_id->acts_for[org_id=$]{active}.user_id" -- OR org member
|
|
124
|
+
]
|
|
125
|
+
}'::jsonb,
|
|
126
|
+
'{"venue_id": "int"}'::jsonb,
|
|
127
|
+
'venues',
|
|
128
|
+
'{"sites": {"entity": "sites", "filter": "venue_id=$venue_id"}}'::jsonb
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Debugging Tips
|
|
133
|
+
|
|
134
|
+
### Test the functions manually:
|
|
135
|
+
|
|
136
|
+
```sql
|
|
137
|
+
-- Check permission
|
|
138
|
+
SELECT venue_detail_can_subscribe(1, '{"venue_id": 123}'::jsonb);
|
|
139
|
+
|
|
140
|
+
-- Get data
|
|
141
|
+
SELECT get_venue_detail('{"venue_id": 123}'::jsonb, 1);
|
|
142
|
+
|
|
143
|
+
-- Test change detection
|
|
144
|
+
SELECT venue_detail_affected_documents(
|
|
145
|
+
'venues',
|
|
146
|
+
'update',
|
|
147
|
+
'{"id": 123}'::jsonb,
|
|
148
|
+
'{"id": 123, "name": "New"}'::jsonb
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Check active subscriptions:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Client-side
|
|
156
|
+
console.log('My subscriptions:', ws.subscriptions.size);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## FAQ
|
|
160
|
+
|
|
161
|
+
**Q: When should I use subscriptions vs. simple queries?**
|
|
162
|
+
A: Use subscriptions when data changes frequently and client needs to stay in sync. Use simple queries for one-time lookups.
|
|
163
|
+
|
|
164
|
+
**Q: What happens when client disconnects?**
|
|
165
|
+
A: Server automatically cleans up all subscriptions for that connection.
|
|
166
|
+
|
|
167
|
+
**Q: Can multiple clients subscribe to the same data?**
|
|
168
|
+
A: Yes! Each subscription is independent. All will receive updates.
|
|
169
|
+
|
|
170
|
+
**Q: How do I update the subscribable definition?**
|
|
171
|
+
A: Re-compile and deploy. The `register_subscribable()` call uses `ON CONFLICT UPDATE`, so it's safe to run repeatedly.
|
|
172
|
+
|
|
173
|
+
**Q: What if the underlying data is deleted?**
|
|
174
|
+
A: The `get_<name>()` function returns `null`. Handle this in your callback:
|
|
175
|
+
```javascript
|
|
176
|
+
(data) => {
|
|
177
|
+
if (!data) {
|
|
178
|
+
console.log('Record was deleted');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
updateUI(data);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Q: How do I subscribe to a list of items?**
|
|
186
|
+
A: Create a subscribable with array parameters or use multiple subscriptions. For dashboard-style views, consider a single subscribable that returns an array.
|
|
187
|
+
|
|
188
|
+
## Performance Tips
|
|
189
|
+
|
|
190
|
+
1. **Index your joins**: Make sure foreign keys are indexed
|
|
191
|
+
2. **Keep _affected_documents() simple**: Early return for unrelated tables
|
|
192
|
+
3. **Limit relation depth**: Avoid deeply nested relations (max 2-3 levels)
|
|
193
|
+
4. **Use specific subscription keys**: `venue_id` is better than `org_id` (fewer false positives)
|
|
194
|
+
5. **Unsubscribe when done**: Always cleanup to free server resources
|
|
195
|
+
|
|
196
|
+
## Architecture Benefits
|
|
197
|
+
|
|
198
|
+
- ✅ **PostgreSQL-First**: All logic in database, not application code
|
|
199
|
+
- ✅ **Zero Configuration**: No server changes needed for new subscribables
|
|
200
|
+
- ✅ **Type Safe**: Compiled functions validated at deploy time
|
|
201
|
+
- ✅ **Efficient**: In-memory registry, PostgreSQL does matching
|
|
202
|
+
- ✅ **Secure**: Permission paths enforced at database level
|
|
203
|
+
- ✅ **Scalable**: Stateless server, can add instances freely
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
DZQL is a PostgreSQL framework that gives you **atomic real-time updates** via WebSocket. Every database change broadcasts instantly to all connected clients. Zero boilerplate.
|
|
4
4
|
|
|
5
|
-
> **See also:** [
|
|
5
|
+
> **See also:** [API Reference](../reference/api.md) for complete API documentation | [Claude Guide](../for-ai/claude-guide.md) for AI development guide
|
|
6
6
|
|
|
7
7
|
## The Core Pattern
|
|
8
8
|
|