@jgamaraalv/ts-dev-kit 1.0.0

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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
@@ -0,0 +1,257 @@
1
+ # PostgreSQL Indexes Reference
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [Index type decision guide](#decision-guide)
6
+ 2. [B-tree](#b-tree)
7
+ 3. [Hash](#hash)
8
+ 4. [GIN](#gin)
9
+ 5. [GiST](#gist)
10
+ 6. [BRIN](#brin)
11
+ 7. [Partial indexes](#partial-indexes)
12
+ 8. [Expression indexes](#expression-indexes)
13
+ 9. [Multicolumn indexes](#multicolumn-indexes)
14
+ 10. [Index creation options](#creation-options)
15
+ 11. [Inspecting indexes](#inspecting)
16
+
17
+ ---
18
+
19
+ ## Decision guide
20
+
21
+ | Situation | Use |
22
+ | ---------------------------------------------------------------- | -------------------------------------------------- |
23
+ | Equality / range on any sortable type (int, text, date, numeric) | **B-tree** (default) |
24
+ | Equality-only on large values where hash fits in memory | **Hash** |
25
+ | `LIKE 'foo%'` prefix match | **B-tree** with `text_pattern_ops` if non-C locale |
26
+ | `LIKE '%foo'` suffix / `LIKE '%foo%'` | **GIN** with `pg_trgm` extension |
27
+ | Full-text search (`@@` tsvector) | **GIN** |
28
+ | Array containment (`@>`, `<@`, `&&`) | **GIN** |
29
+ | JSONB containment / key existence | **GIN** |
30
+ | Geometric / PostGIS spatial queries | **GiST** |
31
+ | Nearest-neighbor (`ORDER BY location <-> point`) | **GiST** |
32
+ | `tsvector` (alternative to GIN, smaller, faster build) | **GiST** |
33
+ | Range types (`int4range`, `tstzrange`) | **GiST** |
34
+ | Append-only / time-series, very large tables, loose range filter | **BRIN** |
35
+ | Index only non-null or subset of rows | **Partial index** |
36
+ | Index on expression / function result | **Expression index** |
37
+
38
+ ---
39
+
40
+ ## B-tree
41
+
42
+ Default type. Use for: `<`, `<=`, `=`, `>=`, `>`, `BETWEEN`, `IN`, `IS NULL`, `IS NOT NULL`, `LIKE 'prefix%'`.
43
+
44
+ ```sql
45
+ -- Basic
46
+ CREATE INDEX idx_users_email ON users (email);
47
+
48
+ -- Descending (useful when queries ORDER BY col DESC)
49
+ CREATE INDEX idx_orders_created_desc ON orders (created_at DESC);
50
+
51
+ -- Include (covering index — avoid heap fetch)
52
+ CREATE INDEX idx_orders_customer ON orders (customer_id) INCLUDE (status, total);
53
+
54
+ -- Pattern matching (non-C locale requires operator class)
55
+ CREATE INDEX idx_users_name_pattern ON users (name text_pattern_ops);
56
+ -- Then: WHERE name LIKE 'Alice%' uses this index
57
+ ```
58
+
59
+ B-tree index can support `ORDER BY` without a sort step if the index column order matches.
60
+
61
+ ---
62
+
63
+ ## Hash
64
+
65
+ Only for `=` comparisons. Faster than B-tree for pure equality on large keys, but cannot support range queries or sorting.
66
+
67
+ ```sql
68
+ CREATE INDEX idx_sessions_token ON sessions USING HASH (token);
69
+ ```
70
+
71
+ Note: Hash indexes are WAL-logged (crash-safe) since PostgreSQL 10.
72
+
73
+ ---
74
+
75
+ ## GIN (Generalized Inverted Index)
76
+
77
+ Best for multi-valued columns: arrays, JSONB, full-text (`tsvector`). Slower to build and update than B-tree; query-time is faster than GiST for containment checks.
78
+
79
+ ```sql
80
+ -- Full-text search
81
+ CREATE INDEX idx_posts_search ON posts USING GIN (to_tsvector('portuguese', content));
82
+
83
+ -- JSONB — supports @>, ?, ?|, ?&
84
+ CREATE INDEX idx_profiles_data ON profiles USING GIN (data);
85
+ -- Specific path (jsonb_path_ops — smaller, only @> operator)
86
+ CREATE INDEX idx_profiles_data_path ON profiles USING GIN (data jsonb_path_ops);
87
+
88
+ -- Array containment
89
+ CREATE INDEX idx_products_tags ON products USING GIN (tags);
90
+
91
+ -- Trigram (requires pg_trgm extension) — enables LIKE '%foo%', ILIKE, similarity
92
+ CREATE EXTENSION IF NOT EXISTS pg_trgm;
93
+ CREATE INDEX idx_users_name_trgm ON users USING GIN (name gin_trgm_ops);
94
+ -- Now: WHERE name ILIKE '%alice%' uses the index
95
+ ```
96
+
97
+ GIN vs `jsonb_path_ops`:
98
+
99
+ - Default `jsonb_ops`: supports `@>`, `?`, `?|`, `?&`
100
+ - `jsonb_path_ops`: supports only `@>` but index is smaller and faster
101
+
102
+ ---
103
+
104
+ ## GiST (Generalized Search Tree)
105
+
106
+ Infrastructure for custom index types. Built-in support for: geometric types, `tsvector`, range types, PostGIS geometry.
107
+
108
+ ```sql
109
+ -- Geometric nearest-neighbor
110
+ CREATE INDEX idx_locations_point ON locations USING GIST (coordinates);
111
+ -- Query: SELECT * FROM locations ORDER BY coordinates <-> '(0,0)'::point LIMIT 10;
112
+
113
+ -- Range type (no overlap)
114
+ CREATE INDEX idx_reservations_period ON reservations USING GIST (period);
115
+ -- Query: WHERE period && '[2025-01-01, 2025-01-07)'::daterange
116
+
117
+ -- Full-text (alternative to GIN — smaller index, slower search)
118
+ CREATE INDEX idx_posts_fts ON posts USING GIST (to_tsvector('english', body));
119
+
120
+ -- PostGIS
121
+ CREATE INDEX idx_geom ON places USING GIST (geom);
122
+ ```
123
+
124
+ ---
125
+
126
+ ## BRIN (Block Range INdex)
127
+
128
+ Tiny footprint index. Stores min/max per block range. Effective when column values are physically correlated with insertion order (e.g., `created_at` on append-only tables, sensor readings, log IDs).
129
+
130
+ ```sql
131
+ CREATE INDEX idx_logs_created ON logs USING BRIN (created_at);
132
+ -- pages_per_range controls granularity (default 128)
133
+ CREATE INDEX idx_metrics_time ON metrics USING BRIN (recorded_at) WITH (pages_per_range = 32);
134
+ ```
135
+
136
+ Do NOT use BRIN if rows are frequently updated or if values are randomly distributed — the index will be ineffective.
137
+
138
+ ---
139
+
140
+ ## Partial indexes
141
+
142
+ Index only a subset of rows. Reduces index size and maintenance cost. The WHERE clause must be satisfied by the query for the index to be used.
143
+
144
+ ```sql
145
+ -- Index only active users (most queries filter by status = 'active')
146
+ CREATE INDEX idx_users_active_email ON users (email) WHERE status = 'active';
147
+
148
+ -- Index only unprocessed jobs
149
+ CREATE INDEX idx_jobs_pending ON jobs (created_at) WHERE processed_at IS NULL;
150
+
151
+ -- Unique partial index — email unique only among active users
152
+ CREATE UNIQUE INDEX idx_users_unique_email_active ON users (email) WHERE deleted_at IS NULL;
153
+ ```
154
+
155
+ The query WHERE clause must be _implied_ by the partial index's condition for the planner to use it. Example: `WHERE status = 'active' AND email = $1` will use `WHERE status = 'active'` partial index.
156
+
157
+ ---
158
+
159
+ ## Expression indexes
160
+
161
+ Index on the result of an expression or function. The query must use the exact same expression.
162
+
163
+ ```sql
164
+ -- Case-insensitive unique email
165
+ CREATE UNIQUE INDEX idx_users_email_lower ON users (lower(email));
166
+ -- Query must use: WHERE lower(email) = lower($1)
167
+
168
+ -- Date part
169
+ CREATE INDEX idx_orders_year ON orders (EXTRACT(YEAR FROM created_at));
170
+
171
+ -- Computed column (immutable functions only)
172
+ CREATE INDEX idx_products_slug ON products (regexp_replace(lower(name), '\s+', '-', 'g'));
173
+ ```
174
+
175
+ Only **immutable** functions can be indexed (same input always yields same output). `NOW()` is volatile — cannot be indexed.
176
+
177
+ ---
178
+
179
+ ## Multicolumn indexes
180
+
181
+ ```sql
182
+ CREATE INDEX idx_orders_user_status ON orders (user_id, status, created_at DESC);
183
+ ```
184
+
185
+ Rules:
186
+
187
+ - Columns are used left-to-right. An index on `(a, b, c)` supports queries on `a`, `(a, b)`, and `(a, b, c)`.
188
+ - A query on just `b` or `c` cannot use this index (unless the planner can bitmap-and with other indexes).
189
+ - Leading equality columns are most important; put high-selectivity equality columns first.
190
+ - Put the range/sort column last.
191
+
192
+ Example query that uses `(user_id, status, created_at DESC)`:
193
+
194
+ ```sql
195
+ WHERE user_id = $1 AND status = 'pending' ORDER BY created_at DESC LIMIT 10
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Creation options
201
+
202
+ ```sql
203
+ -- Non-blocking creation (cannot be in a transaction block)
204
+ CREATE INDEX CONCURRENTLY idx_name ON table (col);
205
+
206
+ -- Named index
207
+ CREATE INDEX idx_orders_customer_id ON orders (customer_id);
208
+
209
+ -- Unique
210
+ CREATE UNIQUE INDEX idx_users_email ON users (email);
211
+
212
+ -- Tablespace
213
+ CREATE INDEX idx_name ON table (col) TABLESPACE fast_ssd;
214
+
215
+ -- Fill factor (leave space for HOT updates)
216
+ CREATE INDEX idx_name ON table (col) WITH (fillfactor = 70);
217
+
218
+ -- Drop
219
+ DROP INDEX idx_name;
220
+ DROP INDEX CONCURRENTLY idx_name; -- non-blocking
221
+
222
+ -- Rebuild (e.g., after bloat)
223
+ REINDEX INDEX idx_name;
224
+ REINDEX TABLE tablename; -- rebuilds all indexes on table
225
+ REINDEX TABLE CONCURRENTLY tablename;
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Inspecting
231
+
232
+ ```sql
233
+ -- List indexes on a table
234
+ \di+ tablename -- in psql
235
+ -- or:
236
+ SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'orders';
237
+
238
+ -- Index usage stats (reset on restart)
239
+ SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch
240
+ FROM pg_stat_user_indexes
241
+ ORDER BY idx_scan;
242
+
243
+ -- Find unused indexes (idx_scan = 0 after some time in prod)
244
+ SELECT indexrelid::regclass, idx_scan
245
+ FROM pg_stat_user_indexes
246
+ WHERE idx_scan = 0 AND schemaname = 'public';
247
+
248
+ -- Index bloat check
249
+ SELECT relname, pg_size_pretty(pg_relation_size(indexrelid)) AS size
250
+ FROM pg_stat_user_indexes
251
+ ORDER BY pg_relation_size(indexrelid) DESC;
252
+
253
+ -- Check if a query uses an index
254
+ EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
255
+ -- Look for: "Index Scan", "Index Only Scan", "Bitmap Index Scan"
256
+ -- "Seq Scan" means no index was used
257
+ ```
@@ -0,0 +1,261 @@
1
+ # PostgreSQL JSONB Reference
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [json vs jsonb](#json-vs-jsonb)
6
+ 2. [Operators](#operators)
7
+ 3. [Functions](#functions)
8
+ 4. [Modifying JSONB](#modifying)
9
+ 5. [GIN indexing](#gin-indexing)
10
+ 6. [jsonpath](#jsonpath)
11
+ 7. [Common patterns](#patterns)
12
+
13
+ ---
14
+
15
+ ## json vs jsonb
16
+
17
+ | Feature | `json` | `jsonb` |
18
+ | ---------------- | --------------------------- | ------------------------- |
19
+ | Storage | Raw text (input preserved) | Binary parsed |
20
+ | Write speed | Faster | Slower (parsing overhead) |
21
+ | Read/query speed | Slower (re-parse each time) | **Faster** |
22
+ | Indexing | Not possible | **GIN indexable** |
23
+ | Key order | Preserved | **Not preserved** |
24
+ | Duplicate keys | Last one wins at read | First one kept |
25
+ | Whitespace | Preserved | Stripped |
26
+
27
+ **Always use `jsonb`** unless you need exact input preservation.
28
+
29
+ ---
30
+
31
+ ## Operators
32
+
33
+ ### Field access
34
+
35
+ ```sql
36
+ data -> 'key' -- returns jsonb (preserves type)
37
+ data ->> 'key' -- returns text
38
+ data -> 2 -- array element by index (0-based)
39
+ data ->> 2 -- array element as text
40
+ data #> '{a,b,c}' -- nested path → jsonb
41
+ data #>> '{a,b,c}' -- nested path → text
42
+
43
+ -- Examples
44
+ SELECT data -> 'name' FROM profiles; -- jsonb: "Alice"
45
+ SELECT data ->> 'name' FROM profiles; -- text: Alice
46
+ SELECT data #>> '{address,city}' FROM profiles; -- text: New York
47
+ SELECT data -> 'tags' -> 0 FROM profiles; -- first tag (jsonb)
48
+ ```
49
+
50
+ ### Containment
51
+
52
+ ```sql
53
+ -- @> left contains right (right is a subset of left)
54
+ SELECT * FROM products WHERE attributes @> '{"color": "red"}';
55
+ SELECT * FROM products WHERE attributes @> '{"tags": ["sale"]}';
56
+
57
+ -- <@ left is contained in right
58
+ SELECT * FROM products WHERE '{"color": "red"}' <@ attributes;
59
+ ```
60
+
61
+ ### Key existence
62
+
63
+ ```sql
64
+ -- ? key exists at top level
65
+ SELECT * FROM profiles WHERE data ? 'email';
66
+
67
+ -- ?| any of these keys exist
68
+ SELECT * FROM profiles WHERE data ?| ARRAY['email', 'phone'];
69
+
70
+ -- ?& all of these keys exist
71
+ SELECT * FROM profiles WHERE data ?& ARRAY['email', 'phone'];
72
+ ```
73
+
74
+ ### Concatenation & deletion
75
+
76
+ ```sql
77
+ -- || merge two jsonb values (right overwrites left on conflict)
78
+ SELECT '{"a":1}'::jsonb || '{"b":2}'::jsonb; -- {"a":1,"b":2}
79
+ SELECT data || '{"verified":true}'::jsonb FROM users;
80
+
81
+ -- - delete key or array element
82
+ SELECT '{"a":1,"b":2}'::jsonb - 'a'; -- {"b":2}
83
+ SELECT '{"a":1,"b":2}'::jsonb - ARRAY['a','b']; -- {}
84
+ SELECT '[1,2,3]'::jsonb - 1; -- removes index 1 → [1,3]
85
+
86
+ -- #- delete at path
87
+ SELECT '{"a":{"b":1}}'::jsonb #- '{a,b}'; -- {"a":{}}
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Functions
93
+
94
+ ```sql
95
+ -- Build
96
+ jsonb_build_object('key', value, 'key2', value2)
97
+ jsonb_build_array(1, 2, 3)
98
+ to_jsonb(any_value)
99
+ row_to_json(row)
100
+
101
+ -- Inspect
102
+ jsonb_typeof(data) -- 'object', 'array', 'string', 'number', 'boolean', 'null'
103
+ jsonb_array_length(data) -- length of JSON array
104
+ jsonb_object_keys(data) -- set of top-level keys
105
+
106
+ -- Array elements as rows
107
+ SELECT elem FROM jsonb_array_elements('["a","b","c"]'::jsonb) AS elem;
108
+ SELECT elem ->> 0 FROM jsonb_array_elements_text('["a","b","c"]'::jsonb) AS elem;
109
+
110
+ -- Object as key-value rows
111
+ SELECT key, value FROM jsonb_each('{"a":1,"b":2}'::jsonb);
112
+ SELECT key, value FROM jsonb_each_text('{"a":1,"b":2}'::jsonb);
113
+
114
+ -- Strip nulls
115
+ jsonb_strip_nulls('{"a":1,"b":null}'::jsonb) -- {"a":1}
116
+
117
+ -- Pretty print
118
+ jsonb_pretty('{"a":1}'::jsonb)
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Modifying
124
+
125
+ ```sql
126
+ -- jsonb_set(target, path, new_value, create_if_missing = true)
127
+ SELECT jsonb_set(data, '{address,city}', '"Rio de Janeiro"') FROM profiles;
128
+ SELECT jsonb_set(data, '{score}', '42', true) FROM profiles; -- creates if missing
129
+
130
+ -- jsonb_insert(target, path, value, insert_after = false)
131
+ -- Inserts into array (doesn't overwrite existing)
132
+ SELECT jsonb_insert('{"a":[1,2,3]}'::jsonb, '{a,1}', '99'); -- [1,99,2,3]
133
+
134
+ -- UPDATE with jsonb_set
135
+ UPDATE users
136
+ SET metadata = jsonb_set(metadata, '{last_login}', to_jsonb(NOW()))
137
+ WHERE id = $1;
138
+
139
+ -- Merge/update top-level key
140
+ UPDATE users
141
+ SET preferences = preferences || '{"theme":"dark"}'::jsonb
142
+ WHERE id = $1;
143
+
144
+ -- Delete a key
145
+ UPDATE users
146
+ SET metadata = metadata - 'temp_token'
147
+ WHERE id = $1;
148
+ ```
149
+
150
+ ---
151
+
152
+ ## GIN indexing
153
+
154
+ ```sql
155
+ -- Default operator class: supports @>, ?, ?|, ?&
156
+ CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
157
+
158
+ -- jsonb_path_ops: only supports @>, but smaller and faster
159
+ CREATE INDEX idx_products_attrs_path ON products USING GIN (attributes jsonb_path_ops);
160
+
161
+ -- Index a specific extracted value (B-tree on expression)
162
+ CREATE INDEX idx_users_role ON users ((metadata ->> 'role'));
163
+ ```
164
+
165
+ For detailed GIN indexing, see [indexes.md](indexes.md).
166
+
167
+ ---
168
+
169
+ ## jsonpath
170
+
171
+ jsonpath is a query language for JSONB (similar to XPath for XML). Available since PG 12.
172
+
173
+ ```sql
174
+ -- @ represents the current node
175
+ -- $ represents the root
176
+
177
+ -- Check if path matches
178
+ SELECT jsonb_path_exists('{"a":{"b":1}}', '$.a.b'); -- true
179
+
180
+ -- Extract matching values
181
+ SELECT jsonb_path_query('{"items":[1,2,3]}', '$.items[*]');
182
+ -- Returns 1, 2, 3 as separate rows
183
+
184
+ -- Query first match
185
+ SELECT jsonb_path_query_first('{"users":[{"name":"Alice"},{"name":"Bob"}]}', '$.users[0].name');
186
+ -- "Alice"
187
+
188
+ -- Filter array elements
189
+ SELECT jsonb_path_query('[{"n":1},{"n":5},{"n":2}]'::jsonb, '$[*] ? (@.n > 3)');
190
+ -- {"n":5}
191
+
192
+ -- Arithmetic
193
+ SELECT jsonb_path_query('{"price":100}', '$.price * 1.1');
194
+ -- 110.0
195
+
196
+ -- String operations
197
+ SELECT jsonb_path_query_array('{"tags":["dog","cat","bird"]}', '$.tags[*] ? (@ starts with "d")');
198
+ ```
199
+
200
+ jsonpath in WHERE clause:
201
+
202
+ ```sql
203
+ SELECT * FROM products WHERE jsonb_path_exists(data, '$.price ? (@ > 100)');
204
+ -- Equivalent to: WHERE (data ->> 'price')::numeric > 100
205
+ -- But jsonpath is more powerful for nested structures
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Common patterns
211
+
212
+ ### Validate JSON structure before insert
213
+
214
+ ```sql
215
+ ALTER TABLE configs ADD CONSTRAINT valid_schema
216
+ CHECK (jsonb_typeof(settings) = 'object' AND settings ? 'version');
217
+ ```
218
+
219
+ ### Aggregate rows into JSON array
220
+
221
+ ```sql
222
+ SELECT user_id, jsonb_agg(order_id ORDER BY created_at) AS order_ids
223
+ FROM orders
224
+ GROUP BY user_id;
225
+
226
+ SELECT jsonb_object_agg(key, value) FROM kv_table;
227
+ ```
228
+
229
+ ### Expand JSONB array to rows for JOINs
230
+
231
+ ```sql
232
+ SELECT p.id, tag
233
+ FROM products p
234
+ CROSS JOIN LATERAL jsonb_array_elements_text(p.tags) AS tag
235
+ WHERE tag = 'sale';
236
+ ```
237
+
238
+ ### Store and query flexible attributes
239
+
240
+ ```sql
241
+ CREATE TABLE products (
242
+ id bigint PRIMARY KEY,
243
+ name text NOT NULL,
244
+ attributes jsonb NOT NULL DEFAULT '{}'
245
+ );
246
+ CREATE INDEX idx_products_attrs ON products USING GIN (attributes);
247
+
248
+ -- Query by any attribute:
249
+ SELECT * FROM products WHERE attributes @> '{"color":"red","size":"M"}';
250
+ -- Add new attribute without schema change:
251
+ UPDATE products SET attributes = attributes || '{"material":"cotton"}' WHERE id = 1;
252
+ ```
253
+
254
+ ### Upsert into JSONB key
255
+
256
+ ```sql
257
+ -- Atomic "set if not exists"
258
+ UPDATE users
259
+ SET metadata = jsonb_set(metadata, '{onboarding_completed}', 'true', true)
260
+ WHERE id = $1 AND NOT (metadata ? 'onboarding_completed');
261
+ ```