@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,692 @@
|
|
|
1
|
+
# /how-to:add-metadata
|
|
2
|
+
|
|
3
|
+
Interactive guide to working with entity metadata in NextSpark.
|
|
4
|
+
|
|
5
|
+
**Aliases:** `/how-to:metadata`, `/how-to:entity-metadata`
|
|
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 metadata tables
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Syntax
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/how-to:add-metadata
|
|
21
|
+
/how-to:add-metadata --entity users
|
|
22
|
+
/how-to:add-metadata --api
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
Guides the user through understanding and implementing the metadata system for entities.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Tutorial Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
STEPS OVERVIEW (5 steps)
|
|
37
|
+
|
|
38
|
+
Step 1: Understanding Metadata
|
|
39
|
+
└── Conceptual framework
|
|
40
|
+
|
|
41
|
+
Step 2: Metadata Tables
|
|
42
|
+
└── users_metas, entity_metas structure
|
|
43
|
+
|
|
44
|
+
Step 3: Using MetaService
|
|
45
|
+
└── getEntityMetas, getBulkEntityMetas
|
|
46
|
+
|
|
47
|
+
Step 4: Helper Functions
|
|
48
|
+
└── withMeta, copyEntityMetas
|
|
49
|
+
|
|
50
|
+
Step 5: API Patterns
|
|
51
|
+
└── Reading/writing metadata via API
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Step 1: Understanding Metadata
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
60
|
+
📚 HOW TO: ADD METADATA
|
|
61
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
62
|
+
|
|
63
|
+
STEP 1 OF 5: Understanding Metadata
|
|
64
|
+
|
|
65
|
+
Metadata allows you to extend entities with
|
|
66
|
+
flexible key-value data without schema changes.
|
|
67
|
+
|
|
68
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**📋 What is Metadata?**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌─────────────────────────────────────────────┐
|
|
75
|
+
│ METADATA = Flexible Extension Data │
|
|
76
|
+
│ ───────────────────────────────────────── │
|
|
77
|
+
│ │
|
|
78
|
+
│ • Key-value pairs attached to entities │
|
|
79
|
+
│ • No schema changes required │
|
|
80
|
+
│ • Stored in separate _metas tables │
|
|
81
|
+
│ • Supports any data type (JSON) │
|
|
82
|
+
│ • Can be public or private │
|
|
83
|
+
│ • Searchable/filterable │
|
|
84
|
+
└─────────────────────────────────────────────┘
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**📋 When to Use Metadata:**
|
|
88
|
+
|
|
89
|
+
| Use Metadata | Use Schema Fields |
|
|
90
|
+
|--------------|-------------------|
|
|
91
|
+
| Optional/rare data | Required/common data |
|
|
92
|
+
| User-defined fields | Core business fields |
|
|
93
|
+
| Plugin extensions | Standard CRUD fields |
|
|
94
|
+
| A/B test data | Type-safe fields |
|
|
95
|
+
| Feature flags | Searchable fields |
|
|
96
|
+
| Temporary data | Indexed fields |
|
|
97
|
+
|
|
98
|
+
**📋 Metadata vs Schema Fields:**
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Schema field (in entity table)
|
|
102
|
+
// ✓ Type-safe, indexed, required
|
|
103
|
+
interface Task {
|
|
104
|
+
id: string
|
|
105
|
+
title: string // Schema field
|
|
106
|
+
status: string // Schema field
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Metadata (in _metas table)
|
|
110
|
+
// ✓ Flexible, optional, extensible
|
|
111
|
+
const taskMeta = {
|
|
112
|
+
customPriority: 5,
|
|
113
|
+
userNotes: 'Remember to review',
|
|
114
|
+
featureFlag: true,
|
|
115
|
+
pluginData: { ... }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**📋 Entity Meta Configuration:**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Core entity configs in types/meta.types.ts
|
|
123
|
+
const CORE_ENTITY_CONFIGS = {
|
|
124
|
+
user: {
|
|
125
|
+
entityType: 'user',
|
|
126
|
+
tableName: 'users',
|
|
127
|
+
metaTableName: 'users_metas',
|
|
128
|
+
idColumn: 'userId',
|
|
129
|
+
apiPath: 'users'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
136
|
+
|
|
137
|
+
What would you like to do?
|
|
138
|
+
|
|
139
|
+
[1] Continue to Step 2 (Metadata Tables)
|
|
140
|
+
[2] What are good use cases for metadata?
|
|
141
|
+
[3] How is metadata different from JSONB columns?
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Step 2: Metadata Tables
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
150
|
+
STEP 2 OF 5: Metadata Tables
|
|
151
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
152
|
+
|
|
153
|
+
Metadata is stored in separate _metas tables
|
|
154
|
+
following a standard structure.
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**📋 Metadata Table Schema:**
|
|
158
|
+
|
|
159
|
+
```sql
|
|
160
|
+
-- Example: users_metas table
|
|
161
|
+
CREATE TABLE users_metas (
|
|
162
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
163
|
+
userId TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
164
|
+
|
|
165
|
+
-- Meta key-value
|
|
166
|
+
metaKey TEXT NOT NULL,
|
|
167
|
+
metaValue JSONB NOT NULL,
|
|
168
|
+
|
|
169
|
+
-- Type information
|
|
170
|
+
dataType TEXT NOT NULL DEFAULT 'string',
|
|
171
|
+
-- 'string' | 'number' | 'boolean' | 'json' | 'array'
|
|
172
|
+
|
|
173
|
+
-- Visibility & search
|
|
174
|
+
isPublic BOOLEAN DEFAULT false,
|
|
175
|
+
isSearchable BOOLEAN DEFAULT false,
|
|
176
|
+
|
|
177
|
+
-- Timestamps
|
|
178
|
+
createdAt TIMESTAMPTZ DEFAULT now(),
|
|
179
|
+
updatedAt TIMESTAMPTZ DEFAULT now(),
|
|
180
|
+
|
|
181
|
+
-- Constraints
|
|
182
|
+
UNIQUE(userId, metaKey)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
-- Indexes for performance
|
|
186
|
+
CREATE INDEX idx_users_metas_user_id ON users_metas(userId);
|
|
187
|
+
CREATE INDEX idx_users_metas_key ON users_metas(metaKey);
|
|
188
|
+
CREATE INDEX idx_users_metas_searchable ON users_metas(isSearchable)
|
|
189
|
+
WHERE isSearchable = true;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**📋 Creating Metadata Table for Custom Entity:**
|
|
193
|
+
|
|
194
|
+
```sql
|
|
195
|
+
-- For a 'tasks' entity
|
|
196
|
+
CREATE TABLE tasks_metas (
|
|
197
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
198
|
+
entityId TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
199
|
+
|
|
200
|
+
metaKey TEXT NOT NULL,
|
|
201
|
+
metaValue JSONB NOT NULL,
|
|
202
|
+
dataType TEXT NOT NULL DEFAULT 'string',
|
|
203
|
+
|
|
204
|
+
isPublic BOOLEAN DEFAULT false,
|
|
205
|
+
isSearchable BOOLEAN DEFAULT false,
|
|
206
|
+
|
|
207
|
+
createdAt TIMESTAMPTZ DEFAULT now(),
|
|
208
|
+
updatedAt TIMESTAMPTZ DEFAULT now(),
|
|
209
|
+
|
|
210
|
+
UNIQUE(entityId, metaKey)
|
|
211
|
+
);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**📋 EntityMeta Interface:**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// From types/meta.types.ts
|
|
218
|
+
interface EntityMeta {
|
|
219
|
+
id: string
|
|
220
|
+
metaKey: string
|
|
221
|
+
metaValue: unknown // Flexible value
|
|
222
|
+
dataType: MetaDataType // 'string' | 'number' | 'boolean' | 'json' | 'array'
|
|
223
|
+
isPublic: boolean // Visibility control
|
|
224
|
+
isSearchable: boolean // Can be searched
|
|
225
|
+
createdAt: string
|
|
226
|
+
updatedAt: string
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**📋 Data Types:**
|
|
231
|
+
|
|
232
|
+
| Type | Description | Example |
|
|
233
|
+
|------|-------------|---------|
|
|
234
|
+
| `string` | Text value | `"hello"` |
|
|
235
|
+
| `number` | Numeric value | `42` |
|
|
236
|
+
| `boolean` | True/false | `true` |
|
|
237
|
+
| `json` | Object | `{ "key": "value" }` |
|
|
238
|
+
| `array` | Array | `[1, 2, 3]` |
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
242
|
+
|
|
243
|
+
What would you like to do?
|
|
244
|
+
|
|
245
|
+
[1] Continue to Step 3 (Using MetaService)
|
|
246
|
+
[2] How do I add RLS to metadata tables?
|
|
247
|
+
[3] Can I have metadata without a table?
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Step 3: Using MetaService
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
256
|
+
STEP 3 OF 5: Using MetaService
|
|
257
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
258
|
+
|
|
259
|
+
MetaService provides methods for reading and
|
|
260
|
+
writing entity metadata.
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**📋 MetaService Methods:**
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// From lib/services/meta.service.ts
|
|
267
|
+
import { MetaService } from '@/core/lib/services/meta.service'
|
|
268
|
+
|
|
269
|
+
// Get metadata for single entity
|
|
270
|
+
const metas = await MetaService.getEntityMetas(
|
|
271
|
+
'user', // entityType
|
|
272
|
+
'user-123', // entityId
|
|
273
|
+
'current-user', // userId (for RLS)
|
|
274
|
+
false // includePrivate
|
|
275
|
+
)
|
|
276
|
+
// Returns: { preference: 'dark', language: 'en' }
|
|
277
|
+
|
|
278
|
+
// Get metadata for multiple entities (bulk)
|
|
279
|
+
const bulkMetas = await MetaService.getBulkEntityMetas(
|
|
280
|
+
'task', // entityType
|
|
281
|
+
['task-1', 'task-2', 'task-3'], // entityIds
|
|
282
|
+
'current-user', // userId
|
|
283
|
+
false // includePrivate
|
|
284
|
+
)
|
|
285
|
+
// Returns: {
|
|
286
|
+
// 'task-1': { priority: 5, notes: '...' },
|
|
287
|
+
// 'task-2': { priority: 3 },
|
|
288
|
+
// 'task-3': {}
|
|
289
|
+
// }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**📋 Why Bulk Queries?**
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
┌─────────────────────────────────────────────┐
|
|
296
|
+
│ N+1 PROBLEM SOLVED │
|
|
297
|
+
│ ───────────────────────────────────────── │
|
|
298
|
+
│ │
|
|
299
|
+
│ WITHOUT bulk: │
|
|
300
|
+
│ 1 query for entity list │
|
|
301
|
+
│ + N queries for N entity metas │
|
|
302
|
+
│ = N+1 total queries (slow!) │
|
|
303
|
+
│ │
|
|
304
|
+
│ WITH bulk: │
|
|
305
|
+
│ 1 query for entity list │
|
|
306
|
+
│ + 1 query for ALL metas │
|
|
307
|
+
│ = 2 total queries (fast!) │
|
|
308
|
+
└─────────────────────────────────────────────┘
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**📋 Writing Metadata:**
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Create/Update metadata payload
|
|
315
|
+
interface CreateMetaPayload {
|
|
316
|
+
metaKey: string
|
|
317
|
+
metaValue: unknown
|
|
318
|
+
dataType?: MetaDataType
|
|
319
|
+
isPublic?: boolean
|
|
320
|
+
isSearchable?: boolean
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Example: Save user preferences
|
|
324
|
+
await MetaService.setEntityMeta(
|
|
325
|
+
'user',
|
|
326
|
+
'user-123',
|
|
327
|
+
{
|
|
328
|
+
metaKey: 'theme',
|
|
329
|
+
metaValue: 'dark',
|
|
330
|
+
dataType: 'string',
|
|
331
|
+
isPublic: true
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
// Delete metadata
|
|
336
|
+
await MetaService.deleteEntityMeta(
|
|
337
|
+
'user',
|
|
338
|
+
'user-123',
|
|
339
|
+
'theme' // metaKey
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**📋 Visibility Control:**
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// isPublic controls who can see the metadata
|
|
347
|
+
|
|
348
|
+
// Public metadata (isPublic: true)
|
|
349
|
+
// - Visible to all users who can access the entity
|
|
350
|
+
// - Good for: preferences, display settings
|
|
351
|
+
|
|
352
|
+
// Private metadata (isPublic: false)
|
|
353
|
+
// - Only visible to entity owner or admin
|
|
354
|
+
// - Good for: internal notes, plugin data
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
359
|
+
|
|
360
|
+
What would you like to do?
|
|
361
|
+
|
|
362
|
+
[1] Continue to Step 4 (Helper Functions)
|
|
363
|
+
[2] How do I query searchable metadata?
|
|
364
|
+
[3] Can I use metadata in RLS policies?
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Step 4: Helper Functions
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
373
|
+
STEP 4 OF 5: Helper Functions
|
|
374
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
375
|
+
|
|
376
|
+
Helper functions make it easy to work with
|
|
377
|
+
metadata in common scenarios.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**📋 withMeta Helper:**
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// From lib/helpers/entity-meta.helpers.ts
|
|
384
|
+
import { withMeta } from '@/core/lib/helpers/entity-meta.helpers'
|
|
385
|
+
|
|
386
|
+
// Add metadata to single entity
|
|
387
|
+
const task = await getTask(taskId)
|
|
388
|
+
const taskWithMeta = await withMeta(
|
|
389
|
+
task,
|
|
390
|
+
'task',
|
|
391
|
+
userId,
|
|
392
|
+
false // includePrivate
|
|
393
|
+
)
|
|
394
|
+
// Result: { id, title, status, meta: { priority: 5, notes: '...' } }
|
|
395
|
+
|
|
396
|
+
// Add metadata to array of entities
|
|
397
|
+
const tasks = await getTasks()
|
|
398
|
+
const tasksWithMeta = await withMeta(
|
|
399
|
+
tasks,
|
|
400
|
+
'task',
|
|
401
|
+
userId,
|
|
402
|
+
false
|
|
403
|
+
)
|
|
404
|
+
// Result: [
|
|
405
|
+
// { id: '1', title: '...', meta: { ... } },
|
|
406
|
+
// { id: '2', title: '...', meta: { ... } }
|
|
407
|
+
// ]
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**📋 copyEntityMetas Helper:**
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { copyEntityMetas } from '@/core/lib/helpers/entity-meta.helpers'
|
|
414
|
+
|
|
415
|
+
// Copy metadata from one entity to another
|
|
416
|
+
await copyEntityMetas(
|
|
417
|
+
'task', // entityType
|
|
418
|
+
'source-task-id', // sourceEntityId
|
|
419
|
+
'target-task-id', // targetEntityId
|
|
420
|
+
userId,
|
|
421
|
+
['priority', 'notes'] // specific keys (optional)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
// Use case: Duplicating a task with its metadata
|
|
425
|
+
const newTask = await duplicateTask(originalTaskId)
|
|
426
|
+
await copyEntityMetas('task', originalTaskId, newTask.id, userId)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**📋 validateBasicMetas Helper:**
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import { validateBasicMetas } from '@/core/lib/helpers/entity-meta.helpers'
|
|
433
|
+
|
|
434
|
+
// Validate metadata before saving
|
|
435
|
+
const metas = {
|
|
436
|
+
preference: 'dark',
|
|
437
|
+
count: 42,
|
|
438
|
+
enabled: true
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
validateBasicMetas(metas)
|
|
443
|
+
// Valid - proceed to save
|
|
444
|
+
} catch (error) {
|
|
445
|
+
// Invalid metadata structure
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**📋 Common Pattern: Entity with Meta:**
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// In your service layer
|
|
453
|
+
class TaskService {
|
|
454
|
+
static async getById(taskId: string, userId: string) {
|
|
455
|
+
const task = await db.query(
|
|
456
|
+
'SELECT * FROM tasks WHERE id = $1',
|
|
457
|
+
[taskId]
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
// Add metadata
|
|
461
|
+
return withMeta(task, 'task', userId)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
static async list(userId: string) {
|
|
465
|
+
const tasks = await db.query(
|
|
466
|
+
'SELECT * FROM tasks WHERE team_id = $1',
|
|
467
|
+
[teamId]
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
// Bulk add metadata (efficient!)
|
|
471
|
+
return withMeta(tasks, 'task', userId)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
478
|
+
|
|
479
|
+
What would you like to do?
|
|
480
|
+
|
|
481
|
+
[1] Continue to Step 5 (API Patterns)
|
|
482
|
+
[2] Show me the full helper file
|
|
483
|
+
[3] How do I create a custom helper?
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Step 5: API Patterns
|
|
489
|
+
|
|
490
|
+
```
|
|
491
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
492
|
+
STEP 5 OF 5: API Patterns
|
|
493
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
494
|
+
|
|
495
|
+
Expose metadata through your API endpoints.
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**📋 Including Meta in Entity Response:**
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// In your API route handler
|
|
502
|
+
export async function GET(request: NextRequest) {
|
|
503
|
+
const auth = await authenticateRequest(request)
|
|
504
|
+
const { searchParams } = new URL(request.url)
|
|
505
|
+
const includeMeta = searchParams.get('include_meta') === 'true'
|
|
506
|
+
|
|
507
|
+
const tasks = await TaskService.list(auth.teamId)
|
|
508
|
+
|
|
509
|
+
if (includeMeta) {
|
|
510
|
+
const tasksWithMeta = await withMeta(tasks, 'task', auth.userId)
|
|
511
|
+
return createApiResponse({ data: tasksWithMeta })
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return createApiResponse({ data: tasks })
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Client usage
|
|
518
|
+
// GET /api/v1/tasks?include_meta=true
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**📋 Metadata CRUD Endpoints:**
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// GET /api/v1/tasks/:id/meta
|
|
525
|
+
export async function GET(request, { params }) {
|
|
526
|
+
const { id } = params
|
|
527
|
+
const auth = await authenticateRequest(request)
|
|
528
|
+
|
|
529
|
+
const metas = await MetaService.getEntityMetas(
|
|
530
|
+
'task', id, auth.userId
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
return createApiResponse({ data: metas })
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// POST /api/v1/tasks/:id/meta
|
|
537
|
+
export async function POST(request, { params }) {
|
|
538
|
+
const { id } = params
|
|
539
|
+
const auth = await authenticateRequest(request)
|
|
540
|
+
const body = await request.json()
|
|
541
|
+
|
|
542
|
+
await MetaService.setEntityMeta('task', id, {
|
|
543
|
+
metaKey: body.key,
|
|
544
|
+
metaValue: body.value,
|
|
545
|
+
dataType: body.type || 'string',
|
|
546
|
+
isPublic: body.isPublic || false
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
return createApiResponse({ message: 'Metadata saved' })
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// DELETE /api/v1/tasks/:id/meta/:key
|
|
553
|
+
export async function DELETE(request, { params }) {
|
|
554
|
+
const { id, key } = params
|
|
555
|
+
const auth = await authenticateRequest(request)
|
|
556
|
+
|
|
557
|
+
await MetaService.deleteEntityMeta('task', id, key)
|
|
558
|
+
|
|
559
|
+
return createApiResponse({ message: 'Metadata deleted' })
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**📋 Query String Pattern:**
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
# Get entity with metadata
|
|
567
|
+
GET /api/v1/tasks?include_meta=true
|
|
568
|
+
|
|
569
|
+
# Get only public metadata
|
|
570
|
+
GET /api/v1/tasks?include_meta=public
|
|
571
|
+
|
|
572
|
+
# Get specific metadata keys
|
|
573
|
+
GET /api/v1/tasks?include_meta=true&meta_keys=priority,notes
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**📋 Response Format:**
|
|
577
|
+
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"success": true,
|
|
581
|
+
"data": {
|
|
582
|
+
"id": "task-123",
|
|
583
|
+
"title": "My Task",
|
|
584
|
+
"status": "pending",
|
|
585
|
+
"meta": {
|
|
586
|
+
"priority": 5,
|
|
587
|
+
"notes": "Important task",
|
|
588
|
+
"customField": { "nested": "value" }
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
```
|
|
595
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
596
|
+
|
|
597
|
+
✅ TUTORIAL STORY!
|
|
598
|
+
|
|
599
|
+
You've learned:
|
|
600
|
+
• Metadata conceptual framework
|
|
601
|
+
• Metadata table structure
|
|
602
|
+
• Using MetaService
|
|
603
|
+
• Helper functions (withMeta, copyEntityMetas)
|
|
604
|
+
• API patterns for metadata
|
|
605
|
+
|
|
606
|
+
📚 Related tutorials:
|
|
607
|
+
• /how-to:create-entity - Create entities with metadata support
|
|
608
|
+
• /how-to:create-migrations - Create metadata tables
|
|
609
|
+
• /how-to:create-api - Create metadata API endpoints
|
|
610
|
+
|
|
611
|
+
🔙 Back to menu: /how-to:start
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Interactive Options
|
|
617
|
+
|
|
618
|
+
### "What are good use cases for metadata?"
|
|
619
|
+
|
|
620
|
+
```
|
|
621
|
+
📋 Common Metadata Use Cases:
|
|
622
|
+
|
|
623
|
+
USER PREFERENCES:
|
|
624
|
+
• Theme preference (dark/light)
|
|
625
|
+
• Language preference
|
|
626
|
+
• Notification settings
|
|
627
|
+
• Dashboard layout
|
|
628
|
+
|
|
629
|
+
PLUGIN EXTENSIONS:
|
|
630
|
+
• Plugin-specific data
|
|
631
|
+
• Integration tokens
|
|
632
|
+
• Custom fields from plugins
|
|
633
|
+
|
|
634
|
+
FEATURE FLAGS:
|
|
635
|
+
• Beta feature access
|
|
636
|
+
• A/B test groups
|
|
637
|
+
• Feature enablement
|
|
638
|
+
|
|
639
|
+
TEMPORARY DATA:
|
|
640
|
+
• Onboarding progress
|
|
641
|
+
• Tutorial completion
|
|
642
|
+
• Wizard state
|
|
643
|
+
|
|
644
|
+
CUSTOM FIELDS:
|
|
645
|
+
• User-defined fields
|
|
646
|
+
• Dynamic form data
|
|
647
|
+
• Flexible attributes
|
|
648
|
+
|
|
649
|
+
ANALYTICS:
|
|
650
|
+
• Usage counters
|
|
651
|
+
• Last activity timestamps
|
|
652
|
+
• Engagement metrics
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### "How is metadata different from JSONB columns?"
|
|
656
|
+
|
|
657
|
+
```
|
|
658
|
+
📋 Metadata vs JSONB Column:
|
|
659
|
+
|
|
660
|
+
JSONB COLUMN (on main table):
|
|
661
|
+
✓ Part of main entity table
|
|
662
|
+
✓ Single query with entity
|
|
663
|
+
✓ Can be indexed (specific paths)
|
|
664
|
+
✗ Schema changes to modify
|
|
665
|
+
✗ All-or-nothing visibility
|
|
666
|
+
✗ Hard to manage permissions
|
|
667
|
+
|
|
668
|
+
METADATA TABLE (separate):
|
|
669
|
+
✓ No schema changes needed
|
|
670
|
+
✓ Per-key visibility control
|
|
671
|
+
✓ Per-key searchability
|
|
672
|
+
✓ Easy to extend by plugins
|
|
673
|
+
✓ Independent lifecycle
|
|
674
|
+
✗ Extra query needed
|
|
675
|
+
✗ More complex joins
|
|
676
|
+
|
|
677
|
+
RECOMMENDATION:
|
|
678
|
+
• Use JSONB for structured, known data
|
|
679
|
+
• Use metadata for flexible, extensible data
|
|
680
|
+
• Use metadata for plugin/theme extensions
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Related Commands
|
|
686
|
+
|
|
687
|
+
| Command | Description |
|
|
688
|
+
|---------|-------------|
|
|
689
|
+
| `/how-to:create-entity` | Create entities with metadata |
|
|
690
|
+
| `/how-to:create-migrations` | Create metadata tables |
|
|
691
|
+
| `/how-to:create-api` | Create metadata endpoints |
|
|
692
|
+
| `/how-to:create-child-entities` | Child entities with metadata |
|