@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
# /how-to:create-child-entities
|
|
2
|
+
|
|
3
|
+
Interactive guide to implementing parent-child entity relationships in NextSpark.
|
|
4
|
+
|
|
5
|
+
**Aliases:** `/how-to:child-entities`, `/how-to:nested-entities`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Required Skills
|
|
10
|
+
|
|
11
|
+
Before executing, these skills provide deeper context:
|
|
12
|
+
- `.claude/skills/entity-system/SKILL.md` - Entity configuration and types
|
|
13
|
+
- `.claude/skills/database-migrations/SKILL.md` - Creating tables with foreign keys
|
|
14
|
+
- `.claude/skills/entity-api/SKILL.md` - Dynamic entity API endpoints
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Syntax
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
/how-to:create-child-entities
|
|
22
|
+
/how-to:create-child-entities --parent tasks
|
|
23
|
+
/how-to:create-child-entities --example
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Behavior
|
|
29
|
+
|
|
30
|
+
Guides the user through creating parent-child entity relationships with proper configuration, migrations, and API patterns.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Tutorial Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
STEPS OVERVIEW (5 steps)
|
|
38
|
+
|
|
39
|
+
Step 1: Understanding Parent-Child
|
|
40
|
+
└── Conceptual model and use cases
|
|
41
|
+
|
|
42
|
+
Step 2: Configure Child Entities
|
|
43
|
+
└── Entity config childEntities array
|
|
44
|
+
|
|
45
|
+
Step 3: Database Structure
|
|
46
|
+
└── Foreign keys and migrations
|
|
47
|
+
|
|
48
|
+
Step 4: API Pattern
|
|
49
|
+
└── GET/POST /entity/[id]/child/[type]
|
|
50
|
+
|
|
51
|
+
Step 5: UI Integration
|
|
52
|
+
└── Displaying child entities
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Step 1: Understanding Parent-Child
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
61
|
+
📚 HOW TO: CREATE CHILD ENTITIES
|
|
62
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
63
|
+
|
|
64
|
+
STEP 1 OF 5: Understanding Parent-Child
|
|
65
|
+
|
|
66
|
+
Child entities belong to a parent entity and
|
|
67
|
+
are managed through nested API endpoints.
|
|
68
|
+
|
|
69
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**📋 What are Child Entities?**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
┌─────────────────────────────────────────────┐
|
|
76
|
+
│ PARENT-CHILD RELATIONSHIP │
|
|
77
|
+
│ ───────────────────────────────────────── │
|
|
78
|
+
│ │
|
|
79
|
+
│ PARENT (e.g., Task) │
|
|
80
|
+
│ └── CHILDREN (e.g., Subtasks) │
|
|
81
|
+
│ ├── Subtask 1 │
|
|
82
|
+
│ ├── Subtask 2 │
|
|
83
|
+
│ └── Subtask 3 │
|
|
84
|
+
│ │
|
|
85
|
+
│ • Child belongs to ONE parent │
|
|
86
|
+
│ • Deleting parent deletes children │
|
|
87
|
+
│ • Children accessed via parent context │
|
|
88
|
+
└─────────────────────────────────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**📋 Common Use Cases:**
|
|
92
|
+
|
|
93
|
+
| Parent | Child | Use Case |
|
|
94
|
+
|--------|-------|----------|
|
|
95
|
+
| Task | Subtask | Breaking tasks into steps |
|
|
96
|
+
| Order | OrderItem | Line items in an order |
|
|
97
|
+
| Project | Milestone | Project milestones |
|
|
98
|
+
| Invoice | LineItem | Invoice line items |
|
|
99
|
+
| Post | Comment | Blog comments |
|
|
100
|
+
| Product | Variant | Product variations |
|
|
101
|
+
|
|
102
|
+
**📋 Parent vs Relation Field:**
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
CHILD ENTITY (parentId):
|
|
106
|
+
• Strong ownership
|
|
107
|
+
• Cascade delete
|
|
108
|
+
• Nested API endpoints
|
|
109
|
+
• Child can't exist without parent
|
|
110
|
+
|
|
111
|
+
RELATION FIELD (foreign key):
|
|
112
|
+
• Weak reference
|
|
113
|
+
• No cascade delete
|
|
114
|
+
• Separate API endpoints
|
|
115
|
+
• Related entity is independent
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**📋 API Pattern:**
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
# Parent entity
|
|
122
|
+
GET /api/v1/tasks
|
|
123
|
+
GET /api/v1/tasks/:taskId
|
|
124
|
+
|
|
125
|
+
# Child entities (nested under parent)
|
|
126
|
+
GET /api/v1/tasks/:taskId/child/subtasks
|
|
127
|
+
POST /api/v1/tasks/:taskId/child/subtasks
|
|
128
|
+
GET /api/v1/tasks/:taskId/child/subtasks/:subtaskId
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
133
|
+
|
|
134
|
+
What would you like to do?
|
|
135
|
+
|
|
136
|
+
[1] Continue to Step 2 (Configure Child Entities)
|
|
137
|
+
[2] What's the difference from many-to-many?
|
|
138
|
+
[3] Can a child have its own children?
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Step 2: Configure Child Entities
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
147
|
+
STEP 2 OF 5: Configure Child Entities
|
|
148
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
149
|
+
|
|
150
|
+
Define child entities in your parent entity's
|
|
151
|
+
configuration file.
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**📋 Parent Entity Config:**
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// entities/tasks/tasks.config.ts
|
|
158
|
+
import type { EntityConfig } from '@/core/types/entity'
|
|
159
|
+
|
|
160
|
+
export const tasksConfig: EntityConfig = {
|
|
161
|
+
name: 'tasks',
|
|
162
|
+
displayName: 'entities.tasks.name',
|
|
163
|
+
slug: 'tasks',
|
|
164
|
+
|
|
165
|
+
// ... other config ...
|
|
166
|
+
|
|
167
|
+
// Define child entities
|
|
168
|
+
childEntities: [
|
|
169
|
+
{
|
|
170
|
+
name: 'subtasks',
|
|
171
|
+
displayName: 'entities.subtasks.name',
|
|
172
|
+
foreignKey: 'parentId', // Column in child table
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'comments',
|
|
176
|
+
displayName: 'entities.comments.name',
|
|
177
|
+
foreignKey: 'taskId',
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**📋 Child Entity Config:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// entities/subtasks/subtasks.config.ts
|
|
187
|
+
import type { EntityConfig } from '@/core/types/entity'
|
|
188
|
+
|
|
189
|
+
export const subtasksConfig: EntityConfig = {
|
|
190
|
+
name: 'subtasks',
|
|
191
|
+
displayName: 'entities.subtasks.name',
|
|
192
|
+
slug: 'subtasks',
|
|
193
|
+
|
|
194
|
+
// Mark as child entity
|
|
195
|
+
isChildEntity: true,
|
|
196
|
+
parentEntity: 'tasks',
|
|
197
|
+
parentForeignKey: 'parentId',
|
|
198
|
+
|
|
199
|
+
// ... fields, schema, etc ...
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**📋 Child Entity Fields:**
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// entities/subtasks/subtasks.fields.ts
|
|
207
|
+
export const subtasksFields: EntityField[] = [
|
|
208
|
+
{
|
|
209
|
+
name: 'parentId',
|
|
210
|
+
type: 'relation',
|
|
211
|
+
required: true,
|
|
212
|
+
display: {
|
|
213
|
+
label: 'Parent Task',
|
|
214
|
+
showInForm: false, // Auto-set from URL
|
|
215
|
+
showInList: false,
|
|
216
|
+
},
|
|
217
|
+
relation: {
|
|
218
|
+
entity: 'tasks',
|
|
219
|
+
field: 'id'
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'title',
|
|
224
|
+
type: 'text',
|
|
225
|
+
required: true,
|
|
226
|
+
display: {
|
|
227
|
+
label: 'Subtask Title',
|
|
228
|
+
showInList: true,
|
|
229
|
+
showInForm: true,
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'completed',
|
|
234
|
+
type: 'boolean',
|
|
235
|
+
required: false,
|
|
236
|
+
display: {
|
|
237
|
+
label: 'Completed',
|
|
238
|
+
showInList: true,
|
|
239
|
+
showInForm: true,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**📋 ChildEntityDefinition Type:**
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
interface ChildEntityDefinition {
|
|
249
|
+
name: string // Entity slug (e.g., 'subtasks')
|
|
250
|
+
displayName: string // i18n key for display
|
|
251
|
+
foreignKey: string // Column name in child table
|
|
252
|
+
orderBy?: string // Default sort column
|
|
253
|
+
orderDirection?: 'asc' | 'desc'
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
259
|
+
|
|
260
|
+
What would you like to do?
|
|
261
|
+
|
|
262
|
+
[1] Continue to Step 3 (Database Structure)
|
|
263
|
+
[2] Can I have multiple child types?
|
|
264
|
+
[3] How do I hide the parent field?
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Step 3: Database Structure
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
273
|
+
STEP 3 OF 5: Database Structure
|
|
274
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
275
|
+
|
|
276
|
+
Create the child entity table with proper
|
|
277
|
+
foreign key references.
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**📋 Migration Example:**
|
|
281
|
+
|
|
282
|
+
```sql
|
|
283
|
+
-- migrations/xxx_subtasks_table.sql
|
|
284
|
+
|
|
285
|
+
-- Create subtasks table
|
|
286
|
+
CREATE TABLE subtasks (
|
|
287
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
288
|
+
|
|
289
|
+
-- Foreign key to parent
|
|
290
|
+
parentId TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
291
|
+
|
|
292
|
+
-- Team context (inherited from parent conceptually)
|
|
293
|
+
teamId TEXT NOT NULL REFERENCES teams(id),
|
|
294
|
+
|
|
295
|
+
-- Child-specific fields
|
|
296
|
+
title TEXT NOT NULL,
|
|
297
|
+
description TEXT,
|
|
298
|
+
completed BOOLEAN DEFAULT false,
|
|
299
|
+
"order" INTEGER DEFAULT 0,
|
|
300
|
+
|
|
301
|
+
-- Timestamps
|
|
302
|
+
createdAt TIMESTAMPTZ DEFAULT now(),
|
|
303
|
+
updatedAt TIMESTAMPTZ DEFAULT now(),
|
|
304
|
+
deletedAt TIMESTAMPTZ -- Soft delete
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
-- Enable RLS
|
|
308
|
+
ALTER TABLE subtasks ENABLE ROW LEVEL SECURITY;
|
|
309
|
+
|
|
310
|
+
-- RLS Policy: Team members can access subtasks of tasks in their team
|
|
311
|
+
CREATE POLICY subtasks_team_policy ON subtasks
|
|
312
|
+
FOR ALL
|
|
313
|
+
USING (
|
|
314
|
+
teamId IN (
|
|
315
|
+
SELECT team_id FROM team_members
|
|
316
|
+
WHERE user_id = auth.uid()
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
-- Indexes
|
|
321
|
+
CREATE INDEX idx_subtasks_parent_id ON subtasks(parentId);
|
|
322
|
+
CREATE INDEX idx_subtasks_team_id ON subtasks(teamId);
|
|
323
|
+
CREATE INDEX idx_subtasks_order ON subtasks("order");
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**📋 Cascade Delete:**
|
|
327
|
+
|
|
328
|
+
```sql
|
|
329
|
+
-- ON DELETE CASCADE ensures:
|
|
330
|
+
-- When parent task is deleted, all subtasks are deleted
|
|
331
|
+
|
|
332
|
+
-- Alternative: ON DELETE SET NULL (if child can exist independently)
|
|
333
|
+
parentId TEXT REFERENCES tasks(id) ON DELETE SET NULL
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**📋 Ordering Children:**
|
|
337
|
+
|
|
338
|
+
```sql
|
|
339
|
+
-- Add order column for manual sorting
|
|
340
|
+
"order" INTEGER DEFAULT 0
|
|
341
|
+
|
|
342
|
+
-- Or use createdAt for automatic ordering
|
|
343
|
+
-- API returns ORDER BY createdAt DESC by default
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**📋 Sample Data Migration:**
|
|
347
|
+
|
|
348
|
+
```sql
|
|
349
|
+
-- migrations/xxx_subtasks_sample.sql
|
|
350
|
+
|
|
351
|
+
INSERT INTO subtasks (id, parentId, teamId, title, completed, "order")
|
|
352
|
+
SELECT
|
|
353
|
+
'subtask-' || generate_series,
|
|
354
|
+
(SELECT id FROM tasks WHERE title LIKE '%Sample%' LIMIT 1),
|
|
355
|
+
(SELECT id FROM teams LIMIT 1),
|
|
356
|
+
'Sample Subtask ' || generate_series,
|
|
357
|
+
generate_series % 2 = 0,
|
|
358
|
+
generate_series
|
|
359
|
+
FROM generate_series(1, 5);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
364
|
+
|
|
365
|
+
What would you like to do?
|
|
366
|
+
|
|
367
|
+
[1] Continue to Step 4 (API Pattern)
|
|
368
|
+
[2] What about soft delete for children?
|
|
369
|
+
[3] How do I inherit team from parent?
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Step 4: API Pattern
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
378
|
+
STEP 4 OF 5: API Pattern
|
|
379
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
380
|
+
|
|
381
|
+
Child entities are accessed through nested
|
|
382
|
+
API endpoints under the parent.
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**📋 API Endpoint Structure:**
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
# List child entities
|
|
389
|
+
GET /api/v1/[entity]/[id]/child/[childType]
|
|
390
|
+
|
|
391
|
+
# Create child entity
|
|
392
|
+
POST /api/v1/[entity]/[id]/child/[childType]
|
|
393
|
+
|
|
394
|
+
# Get single child
|
|
395
|
+
GET /api/v1/[entity]/[id]/child/[childType]/[childId]
|
|
396
|
+
|
|
397
|
+
# Update child
|
|
398
|
+
PATCH /api/v1/[entity]/[id]/child/[childType]/[childId]
|
|
399
|
+
|
|
400
|
+
# Delete child
|
|
401
|
+
DELETE /api/v1/[entity]/[id]/child/[childType]/[childId]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**📋 Route Handler Location:**
|
|
405
|
+
|
|
406
|
+
```
|
|
407
|
+
app/api/v1/[entity]/[id]/child/[childType]/
|
|
408
|
+
├── route.ts # List and Create (GET, POST)
|
|
409
|
+
└── [childId]/
|
|
410
|
+
└── route.ts # Single item (GET, PATCH, DELETE)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**📋 List Children Example:**
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// GET /api/v1/tasks/task-123/child/subtasks
|
|
417
|
+
|
|
418
|
+
// Request
|
|
419
|
+
fetch('/api/v1/tasks/task-123/child/subtasks', {
|
|
420
|
+
headers: { 'Authorization': 'Bearer ...' }
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Response
|
|
424
|
+
{
|
|
425
|
+
"success": true,
|
|
426
|
+
"data": [
|
|
427
|
+
{
|
|
428
|
+
"id": "subtask-1",
|
|
429
|
+
"parentId": "task-123",
|
|
430
|
+
"title": "First subtask",
|
|
431
|
+
"completed": false,
|
|
432
|
+
"createdAt": "2024-01-15T10:00:00Z"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"id": "subtask-2",
|
|
436
|
+
"parentId": "task-123",
|
|
437
|
+
"title": "Second subtask",
|
|
438
|
+
"completed": true,
|
|
439
|
+
"createdAt": "2024-01-15T11:00:00Z"
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**📋 Create Child Example:**
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// POST /api/v1/tasks/task-123/child/subtasks
|
|
449
|
+
|
|
450
|
+
// Request
|
|
451
|
+
fetch('/api/v1/tasks/task-123/child/subtasks', {
|
|
452
|
+
method: 'POST',
|
|
453
|
+
headers: {
|
|
454
|
+
'Authorization': 'Bearer ...',
|
|
455
|
+
'Content-Type': 'application/json'
|
|
456
|
+
},
|
|
457
|
+
body: JSON.stringify({
|
|
458
|
+
title: 'New subtask',
|
|
459
|
+
completed: false
|
|
460
|
+
})
|
|
461
|
+
// Note: parentId is automatically set from URL
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
// Response
|
|
465
|
+
{
|
|
466
|
+
"success": true,
|
|
467
|
+
"data": {
|
|
468
|
+
"id": "subtask-new",
|
|
469
|
+
"parentId": "task-123",
|
|
470
|
+
"title": "New subtask",
|
|
471
|
+
"completed": false,
|
|
472
|
+
"createdAt": "2024-01-15T12:00:00Z"
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**📋 API Handler Implementation:**
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// From core/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts
|
|
481
|
+
|
|
482
|
+
export async function GET(request, { params }) {
|
|
483
|
+
const { entity, id, childType } = params
|
|
484
|
+
|
|
485
|
+
// 1. Resolve entity from URL
|
|
486
|
+
const resolution = await resolveEntityFromUrl(pathname)
|
|
487
|
+
|
|
488
|
+
// 2. Check if child entity exists
|
|
489
|
+
const childEntities = getChildEntities(entity)
|
|
490
|
+
const childEntity = childEntities.find(c => c.name === childType)
|
|
491
|
+
|
|
492
|
+
if (!childEntity) {
|
|
493
|
+
return createApiError('Child entity not found', 404)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 3. Query child entities
|
|
497
|
+
const children = await db.query(
|
|
498
|
+
`SELECT * FROM "${childEntity.tableName}"
|
|
499
|
+
WHERE "${childEntity.foreignKey}" = $1
|
|
500
|
+
ORDER BY "createdAt" DESC`,
|
|
501
|
+
[id]
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return createApiResponse({ data: children })
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
```
|
|
509
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
510
|
+
|
|
511
|
+
What would you like to do?
|
|
512
|
+
|
|
513
|
+
[1] Continue to Step 5 (UI Integration)
|
|
514
|
+
[2] How do I paginate children?
|
|
515
|
+
[3] Can I filter children?
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Step 5: UI Integration
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
524
|
+
STEP 5 OF 5: UI Integration
|
|
525
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
526
|
+
|
|
527
|
+
Display child entities in your parent entity's
|
|
528
|
+
detail view.
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**📋 TanStack Query Pattern:**
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
// hooks/useChildEntities.ts
|
|
535
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
536
|
+
|
|
537
|
+
export function useChildEntities(
|
|
538
|
+
parentEntity: string,
|
|
539
|
+
parentId: string,
|
|
540
|
+
childType: string
|
|
541
|
+
) {
|
|
542
|
+
return useQuery({
|
|
543
|
+
queryKey: ['child', parentEntity, parentId, childType],
|
|
544
|
+
queryFn: async () => {
|
|
545
|
+
const response = await fetch(
|
|
546
|
+
`/api/v1/${parentEntity}/${parentId}/child/${childType}`
|
|
547
|
+
)
|
|
548
|
+
const data = await response.json()
|
|
549
|
+
return data.data
|
|
550
|
+
},
|
|
551
|
+
enabled: !!parentId
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function useCreateChildEntity(
|
|
556
|
+
parentEntity: string,
|
|
557
|
+
parentId: string,
|
|
558
|
+
childType: string
|
|
559
|
+
) {
|
|
560
|
+
const queryClient = useQueryClient()
|
|
561
|
+
|
|
562
|
+
return useMutation({
|
|
563
|
+
mutationFn: async (data: Record<string, unknown>) => {
|
|
564
|
+
const response = await fetch(
|
|
565
|
+
`/api/v1/${parentEntity}/${parentId}/child/${childType}`,
|
|
566
|
+
{
|
|
567
|
+
method: 'POST',
|
|
568
|
+
headers: { 'Content-Type': 'application/json' },
|
|
569
|
+
body: JSON.stringify(data)
|
|
570
|
+
}
|
|
571
|
+
)
|
|
572
|
+
return response.json()
|
|
573
|
+
},
|
|
574
|
+
onSuccess: () => {
|
|
575
|
+
queryClient.invalidateQueries({
|
|
576
|
+
queryKey: ['child', parentEntity, parentId, childType]
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**📋 Child Entities Component:**
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// components/ChildEntitiesList.tsx
|
|
587
|
+
'use client'
|
|
588
|
+
|
|
589
|
+
import { useChildEntities, useCreateChildEntity } from '@/hooks/useChildEntities'
|
|
590
|
+
|
|
591
|
+
interface Props {
|
|
592
|
+
parentEntity: string
|
|
593
|
+
parentId: string
|
|
594
|
+
childType: string
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function ChildEntitiesList({ parentEntity, parentId, childType }: Props) {
|
|
598
|
+
const { data: children, isLoading } = useChildEntities(
|
|
599
|
+
parentEntity, parentId, childType
|
|
600
|
+
)
|
|
601
|
+
const createMutation = useCreateChildEntity(
|
|
602
|
+
parentEntity, parentId, childType
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
if (isLoading) return <div>Loading...</div>
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div className="space-y-4">
|
|
609
|
+
<div className="flex justify-between items-center">
|
|
610
|
+
<h3 className="text-lg font-medium">
|
|
611
|
+
{childType}
|
|
612
|
+
</h3>
|
|
613
|
+
<Button onClick={() => setShowForm(true)}>
|
|
614
|
+
Add {childType}
|
|
615
|
+
</Button>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<ul className="divide-y">
|
|
619
|
+
{children?.map((child) => (
|
|
620
|
+
<li key={child.id} className="py-2">
|
|
621
|
+
{child.title}
|
|
622
|
+
</li>
|
|
623
|
+
))}
|
|
624
|
+
</ul>
|
|
625
|
+
|
|
626
|
+
{children?.length === 0 && (
|
|
627
|
+
<p className="text-muted-foreground">
|
|
628
|
+
No {childType} yet
|
|
629
|
+
</p>
|
|
630
|
+
)}
|
|
631
|
+
</div>
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**📋 In Parent Detail View:**
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// In task detail page
|
|
640
|
+
export default function TaskDetailPage({ params }) {
|
|
641
|
+
const { id } = params
|
|
642
|
+
|
|
643
|
+
return (
|
|
644
|
+
<div className="space-y-8">
|
|
645
|
+
{/* Parent task details */}
|
|
646
|
+
<TaskDetails id={id} />
|
|
647
|
+
|
|
648
|
+
{/* Child entities */}
|
|
649
|
+
<ChildEntitiesList
|
|
650
|
+
parentEntity="tasks"
|
|
651
|
+
parentId={id}
|
|
652
|
+
childType="subtasks"
|
|
653
|
+
/>
|
|
654
|
+
|
|
655
|
+
<ChildEntitiesList
|
|
656
|
+
parentEntity="tasks"
|
|
657
|
+
parentId={id}
|
|
658
|
+
childType="comments"
|
|
659
|
+
/>
|
|
660
|
+
</div>
|
|
661
|
+
)
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
```
|
|
666
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
667
|
+
|
|
668
|
+
✅ TUTORIAL STORY!
|
|
669
|
+
|
|
670
|
+
You've learned:
|
|
671
|
+
• Parent-child entity relationships
|
|
672
|
+
• Entity configuration for children
|
|
673
|
+
• Database structure with foreign keys
|
|
674
|
+
• Nested API endpoints
|
|
675
|
+
• UI integration with TanStack Query
|
|
676
|
+
|
|
677
|
+
📚 Related tutorials:
|
|
678
|
+
• /how-to:create-entity - Create the parent entity
|
|
679
|
+
• /how-to:create-migrations - Database migrations
|
|
680
|
+
• /how-to:add-metadata - Metadata for child entities
|
|
681
|
+
|
|
682
|
+
🔙 Back to menu: /how-to:start
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## Interactive Options
|
|
688
|
+
|
|
689
|
+
### "Can a child have its own children?"
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
📋 Multi-Level Nesting:
|
|
693
|
+
|
|
694
|
+
Yes, children can have their own children!
|
|
695
|
+
|
|
696
|
+
Example: Project → Task → Subtask → Checklist Item
|
|
697
|
+
|
|
698
|
+
However, keep in mind:
|
|
699
|
+
|
|
700
|
+
CONSIDERATIONS:
|
|
701
|
+
• API endpoints become longer
|
|
702
|
+
/api/v1/projects/1/child/tasks/2/child/subtasks/3/child/items
|
|
703
|
+
|
|
704
|
+
• Query complexity increases
|
|
705
|
+
• Performance considerations
|
|
706
|
+
|
|
707
|
+
RECOMMENDATION:
|
|
708
|
+
• Limit to 2-3 levels of nesting
|
|
709
|
+
• For deeper hierarchies, consider:
|
|
710
|
+
- Self-referential entities (parentId on same table)
|
|
711
|
+
- Materialized path pattern
|
|
712
|
+
- Nested set pattern
|
|
713
|
+
|
|
714
|
+
EXAMPLE: Self-referential
|
|
715
|
+
// comments table
|
|
716
|
+
CREATE TABLE comments (
|
|
717
|
+
id TEXT PRIMARY KEY,
|
|
718
|
+
parentCommentId TEXT REFERENCES comments(id),
|
|
719
|
+
postId TEXT REFERENCES posts(id),
|
|
720
|
+
content TEXT
|
|
721
|
+
);
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### "How do I paginate children?"
|
|
725
|
+
|
|
726
|
+
```
|
|
727
|
+
📋 Child Entity Pagination:
|
|
728
|
+
|
|
729
|
+
Add query parameters to the child endpoint:
|
|
730
|
+
|
|
731
|
+
# Paginated request
|
|
732
|
+
GET /api/v1/tasks/task-123/child/subtasks?page=1&limit=10
|
|
733
|
+
|
|
734
|
+
# Implementation in route handler
|
|
735
|
+
const page = parseInt(searchParams.get('page') || '1')
|
|
736
|
+
const limit = parseInt(searchParams.get('limit') || '20')
|
|
737
|
+
const offset = (page - 1) * limit
|
|
738
|
+
|
|
739
|
+
const children = await db.query(`
|
|
740
|
+
SELECT * FROM subtasks
|
|
741
|
+
WHERE "parentId" = $1
|
|
742
|
+
ORDER BY "createdAt" DESC
|
|
743
|
+
LIMIT $2 OFFSET $3
|
|
744
|
+
`, [parentId, limit, offset])
|
|
745
|
+
|
|
746
|
+
const total = await db.query(`
|
|
747
|
+
SELECT COUNT(*) FROM subtasks
|
|
748
|
+
WHERE "parentId" = $1
|
|
749
|
+
`, [parentId])
|
|
750
|
+
|
|
751
|
+
return createApiResponse({
|
|
752
|
+
data: children,
|
|
753
|
+
meta: {
|
|
754
|
+
page,
|
|
755
|
+
limit,
|
|
756
|
+
total: total[0].count,
|
|
757
|
+
totalPages: Math.ceil(total[0].count / limit)
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## Related Commands
|
|
765
|
+
|
|
766
|
+
| Command | Description |
|
|
767
|
+
|---------|-------------|
|
|
768
|
+
| `/how-to:create-entity` | Create parent entities |
|
|
769
|
+
| `/how-to:create-migrations` | Database migrations |
|
|
770
|
+
| `/how-to:add-metadata` | Add metadata to children |
|
|
771
|
+
| `/how-to:add-taxonomies` | Add tags to children |
|