@hivehub/rulebook 4.2.1 → 4.3.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.
- package/.claude/commands/continue.md +33 -33
- package/.claude/commands/ralph-config.md +112 -112
- package/.claude/commands/ralph-history.md +110 -110
- package/.claude/commands/ralph-init.md +72 -72
- package/.claude/commands/ralph-pause-resume.md +105 -105
- package/.claude/commands/ralph-run.md +101 -101
- package/.claude/commands/ralph-status.md +76 -76
- package/.claude/commands/rulebook-memory-save.md +48 -48
- package/.claude/commands/rulebook-memory-search.md +47 -47
- package/.claude/commands/rulebook-task-apply.md +67 -67
- package/.claude/commands/rulebook-task-archive.md +70 -70
- package/.claude/commands/rulebook-task-create.md +93 -93
- package/.claude/commands/rulebook-task-list.md +42 -42
- package/.claude/commands/rulebook-task-show.md +52 -52
- package/.claude/commands/rulebook-task-validate.md +53 -53
- package/.claude-plugin/marketplace.json +28 -28
- package/.claude-plugin/plugin.json +8 -8
- package/README.md +8 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +4 -4
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/claude-mcp.d.ts +8 -1
- package/dist/core/claude-mcp.d.ts.map +1 -1
- package/dist/core/claude-mcp.js +32 -1
- package/dist/core/claude-mcp.js.map +1 -1
- package/dist/core/generator.d.ts +13 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +283 -28
- package/dist/core/generator.js.map +1 -1
- package/dist/core/workspace/project-worker.d.ts.map +1 -1
- package/dist/core/workspace/project-worker.js +3 -0
- package/dist/core/workspace/project-worker.js.map +1 -1
- package/dist/core/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/core/workspace/workspace-manager.js +2 -6
- package/dist/core/workspace/workspace-manager.js.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +6 -5
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/package.json +21 -22
- package/templates/agents/accessibility-reviewer.md +43 -0
- package/templates/agents/api-designer.md +42 -0
- package/templates/agents/architect.md +51 -0
- package/templates/agents/build-engineer.md +36 -0
- package/templates/agents/code-reviewer.md +47 -0
- package/templates/agents/database-architect.md +41 -0
- package/templates/agents/devops-engineer.md +42 -0
- package/templates/agents/docs-writer.md +38 -0
- package/templates/agents/i18n-engineer.md +42 -0
- package/templates/agents/implementer.md +38 -35
- package/templates/agents/migration-engineer.md +42 -0
- package/templates/agents/performance-engineer.md +49 -0
- package/templates/agents/refactoring-agent.md +41 -0
- package/templates/agents/researcher.md +38 -34
- package/templates/agents/security-reviewer.md +40 -0
- package/templates/agents/team-lead.md +37 -34
- package/templates/agents/tester.md +45 -42
- package/templates/agents/ux-reviewer.md +43 -0
- package/templates/ci/rulebook-review.yml +26 -26
- package/templates/cli/AIDER.md +49 -49
- package/templates/cli/AMAZON_Q.md +25 -25
- package/templates/cli/AUGGIE.md +32 -32
- package/templates/cli/CLAUDE.md +117 -117
- package/templates/cli/CLINE.md +99 -99
- package/templates/cli/CODEBUDDY.md +20 -20
- package/templates/cli/CODEIUM.md +20 -20
- package/templates/cli/CODEX.md +21 -21
- package/templates/cli/CONTINUE.md +34 -34
- package/templates/cli/CURSOR_CLI.md +62 -62
- package/templates/cli/FACTORY.md +18 -18
- package/templates/cli/GEMINI.md +35 -35
- package/templates/cli/KILOCODE.md +18 -18
- package/templates/cli/OPENCODE.md +18 -18
- package/templates/cli/_GENERIC_TEMPLATE.md +29 -29
- package/templates/commands/rulebook-memory-save.md +48 -48
- package/templates/commands/rulebook-memory-search.md +47 -47
- package/templates/commands/rulebook-task-apply.md +67 -67
- package/templates/commands/rulebook-task-archive.md +94 -94
- package/templates/commands/rulebook-task-create.md +93 -93
- package/templates/commands/rulebook-task-list.md +42 -42
- package/templates/commands/rulebook-task-show.md +52 -52
- package/templates/commands/rulebook-task-validate.md +53 -53
- package/templates/core/AGENTS_LEAN.md +25 -25
- package/templates/core/AGENTS_OVERRIDE.md +16 -16
- package/templates/core/AGENT_AUTOMATION.md +288 -288
- package/templates/core/DAG.md +304 -304
- package/templates/core/DOCUMENTATION_RULES.md +36 -36
- package/templates/core/MULTI_AGENT.md +74 -74
- package/templates/core/PLANS.md +28 -28
- package/templates/core/QUALITY_ENFORCEMENT.md +68 -68
- package/templates/core/RALPH.md +471 -471
- package/templates/core/RULEBOOK.md +1935 -1935
- package/templates/frameworks/ANGULAR.md +36 -36
- package/templates/frameworks/DJANGO.md +83 -83
- package/templates/frameworks/ELECTRON.md +147 -147
- package/templates/frameworks/FLASK.md +38 -38
- package/templates/frameworks/FLUTTER.md +55 -55
- package/templates/frameworks/JQUERY.md +32 -32
- package/templates/frameworks/LARAVEL.md +38 -38
- package/templates/frameworks/NESTJS.md +43 -43
- package/templates/frameworks/NEXTJS.md +127 -127
- package/templates/frameworks/NUXT.md +40 -40
- package/templates/frameworks/RAILS.md +66 -66
- package/templates/frameworks/REACT.md +38 -38
- package/templates/frameworks/REACT_NATIVE.md +47 -47
- package/templates/frameworks/SPRING.md +39 -39
- package/templates/frameworks/SYMFONY.md +36 -36
- package/templates/frameworks/VUE.md +36 -36
- package/templates/frameworks/ZEND.md +35 -35
- package/templates/git/CI_CD_PATTERNS.md +661 -661
- package/templates/git/GITHUB_ACTIONS.md +728 -728
- package/templates/git/GITLAB_CI.md +730 -730
- package/templates/git/GIT_WORKFLOW.md +1157 -1157
- package/templates/git/SECRETS_MANAGEMENT.md +585 -585
- package/templates/hooks/COMMIT_MSG.md +530 -530
- package/templates/hooks/POST_CHECKOUT.md +546 -546
- package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -619
- package/templates/hooks/PRE_COMMIT.md +414 -414
- package/templates/hooks/PRE_PUSH.md +601 -601
- package/templates/ides/CONTINUE_RULES.md +16 -16
- package/templates/ides/COPILOT.md +37 -37
- package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -23
- package/templates/ides/CURSOR.md +43 -43
- package/templates/ides/GEMINI_RULES.md +17 -17
- package/templates/ides/JETBRAINS_AI.md +35 -35
- package/templates/ides/REPLIT.md +36 -36
- package/templates/ides/TABNINE.md +29 -29
- package/templates/ides/VSCODE.md +40 -40
- package/templates/ides/WINDSURF.md +36 -36
- package/templates/ides/WINDSURF_RULES.md +14 -14
- package/templates/ides/ZED.md +32 -32
- package/templates/ides/cursor-mdc/go.mdc +24 -24
- package/templates/ides/cursor-mdc/python.mdc +24 -24
- package/templates/ides/cursor-mdc/quality.mdc +25 -25
- package/templates/ides/cursor-mdc/ralph.mdc +39 -39
- package/templates/ides/cursor-mdc/rulebook.mdc +38 -38
- package/templates/ides/cursor-mdc/rust.mdc +24 -24
- package/templates/ides/cursor-mdc/typescript.mdc +25 -25
- package/templates/languages/C.md +333 -333
- package/templates/languages/CPP.md +743 -743
- package/templates/languages/CSHARP.md +417 -417
- package/templates/languages/ELIXIR.md +454 -454
- package/templates/languages/ERLANG.md +361 -361
- package/templates/languages/GO.md +645 -645
- package/templates/languages/HASKELL.md +177 -177
- package/templates/languages/JAVA.md +607 -607
- package/templates/languages/JAVASCRIPT.md +631 -631
- package/templates/languages/JULIA.md +97 -97
- package/templates/languages/KOTLIN.md +511 -511
- package/templates/languages/LISP.md +100 -100
- package/templates/languages/LUA.md +74 -74
- package/templates/languages/OBJECTIVEC.md +90 -90
- package/templates/languages/PHP.md +416 -416
- package/templates/languages/PYTHON.md +682 -682
- package/templates/languages/RUBY.md +421 -421
- package/templates/languages/RUST.md +477 -477
- package/templates/languages/SAS.md +73 -73
- package/templates/languages/SCALA.md +348 -348
- package/templates/languages/SOLIDITY.md +580 -580
- package/templates/languages/SQL.md +137 -137
- package/templates/languages/SWIFT.md +466 -466
- package/templates/languages/TYPESCRIPT.md +591 -591
- package/templates/languages/ZIG.md +265 -265
- package/templates/modules/ATLASSIAN.md +255 -255
- package/templates/modules/CONTEXT7.md +54 -54
- package/templates/modules/FIGMA.md +267 -267
- package/templates/modules/GITHUB_MCP.md +64 -64
- package/templates/modules/GRAFANA.md +328 -328
- package/templates/modules/MEMORY.md +126 -126
- package/templates/modules/NOTION.md +247 -247
- package/templates/modules/PLAYWRIGHT.md +90 -90
- package/templates/modules/RULEBOOK_MCP.md +156 -156
- package/templates/modules/SERENA.md +337 -337
- package/templates/modules/SUPABASE.md +223 -223
- package/templates/modules/SYNAP.md +69 -69
- package/templates/modules/VECTORIZER.md +63 -63
- package/templates/modules/sequential-thinking.md +42 -42
- package/templates/ralph/ralph-history.bat +4 -4
- package/templates/ralph/ralph-history.sh +5 -5
- package/templates/ralph/ralph-init.bat +5 -5
- package/templates/ralph/ralph-init.sh +5 -5
- package/templates/ralph/ralph-pause.bat +5 -5
- package/templates/ralph/ralph-pause.sh +5 -5
- package/templates/ralph/ralph-run.bat +5 -5
- package/templates/ralph/ralph-run.sh +5 -5
- package/templates/ralph/ralph-status.bat +4 -4
- package/templates/ralph/ralph-status.sh +5 -5
- package/templates/services/AZURE_BLOB.md +184 -184
- package/templates/services/CASSANDRA.md +239 -239
- package/templates/services/DATADOG.md +26 -26
- package/templates/services/DOCKER.md +124 -124
- package/templates/services/DOCKER_COMPOSE.md +168 -168
- package/templates/services/DYNAMODB.md +308 -308
- package/templates/services/ELASTICSEARCH.md +347 -347
- package/templates/services/GCS.md +178 -178
- package/templates/services/HELM.md +194 -194
- package/templates/services/INFLUXDB.md +265 -265
- package/templates/services/KAFKA.md +341 -341
- package/templates/services/KUBERNETES.md +208 -208
- package/templates/services/MARIADB.md +183 -183
- package/templates/services/MEMCACHED.md +242 -242
- package/templates/services/MINIO.md +201 -201
- package/templates/services/MONGODB.md +268 -268
- package/templates/services/MYSQL.md +358 -358
- package/templates/services/NEO4J.md +247 -247
- package/templates/services/OPENTELEMETRY.md +25 -25
- package/templates/services/ORACLE.md +290 -290
- package/templates/services/PINO.md +24 -24
- package/templates/services/POSTGRESQL.md +326 -326
- package/templates/services/PROMETHEUS.md +33 -33
- package/templates/services/RABBITMQ.md +286 -286
- package/templates/services/REDIS.md +292 -292
- package/templates/services/S3.md +298 -298
- package/templates/services/SENTRY.md +23 -23
- package/templates/services/SQLITE.md +294 -294
- package/templates/services/SQLSERVER.md +294 -294
- package/templates/services/WINSTON.md +30 -30
- package/templates/skills/cli/aider/SKILL.md +59 -59
- package/templates/skills/cli/amazon-q/SKILL.md +35 -35
- package/templates/skills/cli/auggie/SKILL.md +42 -42
- package/templates/skills/cli/claude/SKILL.md +42 -42
- package/templates/skills/cli/cline/SKILL.md +42 -42
- package/templates/skills/cli/codebuddy/SKILL.md +30 -30
- package/templates/skills/cli/codeium/SKILL.md +30 -30
- package/templates/skills/cli/codex/SKILL.md +31 -31
- package/templates/skills/cli/continue/SKILL.md +44 -44
- package/templates/skills/cli/cursor-cli/SKILL.md +38 -38
- package/templates/skills/cli/factory/SKILL.md +28 -28
- package/templates/skills/cli/gemini/SKILL.md +45 -45
- package/templates/skills/cli/kilocode/SKILL.md +28 -28
- package/templates/skills/cli/opencode/SKILL.md +28 -28
- package/templates/skills/core/agent-automation/SKILL.md +194 -194
- package/templates/skills/core/dag/SKILL.md +314 -314
- package/templates/skills/core/documentation-rules/SKILL.md +46 -46
- package/templates/skills/core/quality-enforcement/SKILL.md +78 -78
- package/templates/skills/core/rulebook/SKILL.md +176 -176
- package/templates/skills/dev/accessibility/SKILL.md +17 -0
- package/templates/skills/dev/api-design/SKILL.md +15 -0
- package/templates/skills/dev/architect/SKILL.md +17 -0
- package/templates/skills/dev/build-fix/SKILL.md +17 -0
- package/templates/skills/dev/db-design/SKILL.md +15 -0
- package/templates/skills/dev/debug/SKILL.md +16 -0
- package/templates/skills/dev/deploy/SKILL.md +17 -0
- package/templates/skills/dev/docs/SKILL.md +17 -0
- package/templates/skills/dev/migrate/SKILL.md +15 -0
- package/templates/skills/dev/perf/SKILL.md +17 -0
- package/templates/skills/dev/refactor/SKILL.md +17 -0
- package/templates/skills/dev/research/SKILL.md +14 -0
- package/templates/skills/dev/review/SKILL.md +18 -0
- package/templates/skills/dev/security-audit/SKILL.md +17 -0
- package/templates/skills/frameworks/angular/SKILL.md +46 -46
- package/templates/skills/frameworks/django/SKILL.md +93 -93
- package/templates/skills/frameworks/electron/SKILL.md +157 -157
- package/templates/skills/frameworks/flask/SKILL.md +48 -48
- package/templates/skills/frameworks/flutter/SKILL.md +65 -65
- package/templates/skills/frameworks/jquery/SKILL.md +42 -42
- package/templates/skills/frameworks/laravel/SKILL.md +48 -48
- package/templates/skills/frameworks/nestjs/SKILL.md +53 -53
- package/templates/skills/frameworks/nextjs/SKILL.md +137 -137
- package/templates/skills/frameworks/nuxt/SKILL.md +50 -50
- package/templates/skills/frameworks/rails/SKILL.md +76 -76
- package/templates/skills/frameworks/react/SKILL.md +48 -48
- package/templates/skills/frameworks/react-native/SKILL.md +57 -57
- package/templates/skills/frameworks/spring/SKILL.md +49 -49
- package/templates/skills/frameworks/symfony/SKILL.md +46 -46
- package/templates/skills/frameworks/vue/SKILL.md +46 -46
- package/templates/skills/frameworks/zend/SKILL.md +45 -45
- package/templates/skills/ides/copilot/SKILL.md +47 -47
- package/templates/skills/ides/cursor/SKILL.md +53 -53
- package/templates/skills/ides/jetbrains-ai/SKILL.md +45 -45
- package/templates/skills/ides/replit/SKILL.md +46 -46
- package/templates/skills/ides/tabnine/SKILL.md +39 -39
- package/templates/skills/ides/vscode/SKILL.md +50 -50
- package/templates/skills/ides/windsurf/SKILL.md +46 -46
- package/templates/skills/ides/zed/SKILL.md +42 -42
- package/templates/skills/languages/c/SKILL.md +343 -343
- package/templates/skills/languages/cpp/SKILL.md +753 -753
- package/templates/skills/languages/csharp/SKILL.md +427 -427
- package/templates/skills/languages/elixir/SKILL.md +464 -464
- package/templates/skills/languages/erlang/SKILL.md +371 -371
- package/templates/skills/languages/go/SKILL.md +655 -655
- package/templates/skills/languages/haskell/SKILL.md +187 -187
- package/templates/skills/languages/java/SKILL.md +617 -617
- package/templates/skills/languages/javascript/SKILL.md +641 -641
- package/templates/skills/languages/julia/SKILL.md +107 -107
- package/templates/skills/languages/kotlin/SKILL.md +521 -521
- package/templates/skills/languages/lisp/SKILL.md +110 -110
- package/templates/skills/languages/lua/SKILL.md +84 -84
- package/templates/skills/languages/objectivec/SKILL.md +100 -100
- package/templates/skills/languages/php/SKILL.md +426 -426
- package/templates/skills/languages/python/SKILL.md +692 -692
- package/templates/skills/languages/ruby/SKILL.md +431 -431
- package/templates/skills/languages/rust/SKILL.md +487 -487
- package/templates/skills/languages/sas/SKILL.md +83 -83
- package/templates/skills/languages/scala/SKILL.md +358 -358
- package/templates/skills/languages/solidity/SKILL.md +590 -590
- package/templates/skills/languages/sql/SKILL.md +147 -147
- package/templates/skills/languages/swift/SKILL.md +476 -476
- package/templates/skills/languages/typescript/SKILL.md +302 -302
- package/templates/skills/languages/zig/SKILL.md +275 -275
- package/templates/skills/modules/atlassian/SKILL.md +265 -265
- package/templates/skills/modules/context7/SKILL.md +64 -64
- package/templates/skills/modules/figma/SKILL.md +277 -277
- package/templates/skills/modules/github-mcp/SKILL.md +74 -74
- package/templates/skills/modules/grafana/SKILL.md +338 -338
- package/templates/skills/modules/memory/SKILL.md +73 -73
- package/templates/skills/modules/notion/SKILL.md +257 -257
- package/templates/skills/modules/playwright/SKILL.md +100 -100
- package/templates/skills/modules/rulebook-mcp/SKILL.md +166 -166
- package/templates/skills/modules/serena/SKILL.md +347 -347
- package/templates/skills/modules/supabase/SKILL.md +233 -233
- package/templates/skills/modules/synap/SKILL.md +79 -79
- package/templates/skills/modules/vectorizer/SKILL.md +73 -73
- package/templates/skills/services/azure-blob/SKILL.md +194 -194
- package/templates/skills/services/cassandra/SKILL.md +249 -249
- package/templates/skills/services/dynamodb/SKILL.md +318 -318
- package/templates/skills/services/elasticsearch/SKILL.md +357 -357
- package/templates/skills/services/gcs/SKILL.md +188 -188
- package/templates/skills/services/influxdb/SKILL.md +275 -275
- package/templates/skills/services/kafka/SKILL.md +351 -351
- package/templates/skills/services/mariadb/SKILL.md +193 -193
- package/templates/skills/services/memcached/SKILL.md +252 -252
- package/templates/skills/services/minio/SKILL.md +211 -211
- package/templates/skills/services/mongodb/SKILL.md +278 -278
- package/templates/skills/services/mysql/SKILL.md +368 -368
- package/templates/skills/services/neo4j/SKILL.md +257 -257
- package/templates/skills/services/oracle/SKILL.md +300 -300
- package/templates/skills/services/postgresql/SKILL.md +336 -336
- package/templates/skills/services/rabbitmq/SKILL.md +296 -296
- package/templates/skills/services/redis/SKILL.md +302 -302
- package/templates/skills/services/s3/SKILL.md +308 -308
- package/templates/skills/services/sqlite/SKILL.md +304 -304
- package/templates/skills/services/sqlserver/SKILL.md +304 -304
- package/templates/skills/workflows/ralph/SKILL.md +309 -309
- package/templates/skills/workflows/ralph/install.sh +87 -87
- package/templates/skills/workflows/ralph/manifest.json +158 -158
|
@@ -1,347 +1,347 @@
|
|
|
1
|
-
<!-- ELASTICSEARCH:START -->
|
|
2
|
-
# Elasticsearch Search Instructions
|
|
3
|
-
|
|
4
|
-
**CRITICAL**: Use Elasticsearch for full-text search, log aggregation, and real-time analytics with distributed architecture.
|
|
5
|
-
|
|
6
|
-
## Core Features
|
|
7
|
-
|
|
8
|
-
### Connection
|
|
9
|
-
```typescript
|
|
10
|
-
// Using @elastic/elasticsearch
|
|
11
|
-
import { Client } from '@elastic/elasticsearch'
|
|
12
|
-
|
|
13
|
-
const client = new Client({
|
|
14
|
-
node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200',
|
|
15
|
-
auth: {
|
|
16
|
-
username: process.env.ELASTICSEARCH_USER || 'elastic',
|
|
17
|
-
password: process.env.ELASTICSEARCH_PASSWORD || '',
|
|
18
|
-
},
|
|
19
|
-
maxRetries: 5,
|
|
20
|
-
requestTimeout: 60000,
|
|
21
|
-
sniffOnStart: true,
|
|
22
|
-
sniffInterval: 30000,
|
|
23
|
-
})
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Index Operations
|
|
27
|
-
```typescript
|
|
28
|
-
// Create index
|
|
29
|
-
await client.indices.create({
|
|
30
|
-
index: 'users',
|
|
31
|
-
body: {
|
|
32
|
-
mappings: {
|
|
33
|
-
properties: {
|
|
34
|
-
name: { type: 'text' },
|
|
35
|
-
email: { type: 'keyword' },
|
|
36
|
-
age: { type: 'integer' },
|
|
37
|
-
createdAt: { type: 'date' },
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
settings: {
|
|
41
|
-
number_of_shards: 1,
|
|
42
|
-
number_of_replicas: 1,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// Delete index
|
|
48
|
-
await client.indices.delete({ index: 'users' })
|
|
49
|
-
|
|
50
|
-
// Check if index exists
|
|
51
|
-
const exists = await client.indices.exists({ index: 'users' })
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Document Operations
|
|
55
|
-
```typescript
|
|
56
|
-
// Index document
|
|
57
|
-
await client.index({
|
|
58
|
-
index: 'users',
|
|
59
|
-
id: '1',
|
|
60
|
-
body: {
|
|
61
|
-
name: 'John Doe',
|
|
62
|
-
email: 'john@example.com',
|
|
63
|
-
age: 30,
|
|
64
|
-
createdAt: new Date(),
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Get document
|
|
69
|
-
const result = await client.get({
|
|
70
|
-
index: 'users',
|
|
71
|
-
id: '1',
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
// Update document
|
|
75
|
-
await client.update({
|
|
76
|
-
index: 'users',
|
|
77
|
-
id: '1',
|
|
78
|
-
body: {
|
|
79
|
-
doc: {
|
|
80
|
-
age: 31,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Delete document
|
|
86
|
-
await client.delete({
|
|
87
|
-
index: 'users',
|
|
88
|
-
id: '1',
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// Bulk operations
|
|
92
|
-
await client.bulk({
|
|
93
|
-
body: [
|
|
94
|
-
{ index: { _index: 'users', _id: '1' } },
|
|
95
|
-
{ name: 'User 1', email: 'user1@example.com' },
|
|
96
|
-
{ index: { _index: 'users', _id: '2' } },
|
|
97
|
-
{ name: 'User 2', email: 'user2@example.com' },
|
|
98
|
-
],
|
|
99
|
-
})
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Search Operations
|
|
103
|
-
```typescript
|
|
104
|
-
// Simple search
|
|
105
|
-
const result = await client.search({
|
|
106
|
-
index: 'users',
|
|
107
|
-
body: {
|
|
108
|
-
query: {
|
|
109
|
-
match: {
|
|
110
|
-
name: 'John',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// Multi-match search
|
|
117
|
-
const result = await client.search({
|
|
118
|
-
index: 'users',
|
|
119
|
-
body: {
|
|
120
|
-
query: {
|
|
121
|
-
multi_match: {
|
|
122
|
-
query: 'search term',
|
|
123
|
-
fields: ['name^2', 'email'], // name has 2x boost
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// Bool query (AND/OR/NOT)
|
|
130
|
-
const result = await client.search({
|
|
131
|
-
index: 'users',
|
|
132
|
-
body: {
|
|
133
|
-
query: {
|
|
134
|
-
bool: {
|
|
135
|
-
must: [
|
|
136
|
-
{ match: { name: 'John' } },
|
|
137
|
-
{ range: { age: { gte: 18, lte: 65 } } },
|
|
138
|
-
],
|
|
139
|
-
must_not: [
|
|
140
|
-
{ term: { status: 'inactive' } },
|
|
141
|
-
],
|
|
142
|
-
should: [
|
|
143
|
-
{ match: { tags: 'premium' } },
|
|
144
|
-
],
|
|
145
|
-
minimum_should_match: 1,
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// Aggregations
|
|
152
|
-
const result = await client.search({
|
|
153
|
-
index: 'users',
|
|
154
|
-
body: {
|
|
155
|
-
aggs: {
|
|
156
|
-
age_groups: {
|
|
157
|
-
range: {
|
|
158
|
-
field: 'age',
|
|
159
|
-
ranges: [
|
|
160
|
-
{ to: 25 },
|
|
161
|
-
{ from: 25, to: 50 },
|
|
162
|
-
{ from: 50 },
|
|
163
|
-
],
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
avg_age: {
|
|
167
|
-
avg: { field: 'age' },
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
})
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Common Patterns
|
|
175
|
-
|
|
176
|
-
### Full-Text Search
|
|
177
|
-
```typescript
|
|
178
|
-
async function searchUsers(query: string, filters?: any) {
|
|
179
|
-
const must: any[] = [
|
|
180
|
-
{
|
|
181
|
-
multi_match: {
|
|
182
|
-
query,
|
|
183
|
-
fields: ['name^2', 'email'],
|
|
184
|
-
fuzziness: 'AUTO',
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
if (filters) {
|
|
190
|
-
if (filters.age) {
|
|
191
|
-
must.push({ range: { age: filters.age } })
|
|
192
|
-
}
|
|
193
|
-
if (filters.status) {
|
|
194
|
-
must.push({ term: { status: filters.status } })
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const result = await client.search({
|
|
199
|
-
index: 'users',
|
|
200
|
-
body: {
|
|
201
|
-
query: {
|
|
202
|
-
bool: {
|
|
203
|
-
must,
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
highlight: {
|
|
207
|
-
fields: {
|
|
208
|
-
name: {},
|
|
209
|
-
email: {},
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
return result.body.hits.hits.map(hit => ({
|
|
216
|
-
...hit._source,
|
|
217
|
-
score: hit._score,
|
|
218
|
-
highlights: hit.highlight,
|
|
219
|
-
}))
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Auto-complete
|
|
224
|
-
```typescript
|
|
225
|
-
// Create index with completion suggester
|
|
226
|
-
await client.indices.create({
|
|
227
|
-
index: 'suggestions',
|
|
228
|
-
body: {
|
|
229
|
-
mappings: {
|
|
230
|
-
properties: {
|
|
231
|
-
name: {
|
|
232
|
-
type: 'text',
|
|
233
|
-
fields: {
|
|
234
|
-
suggest: {
|
|
235
|
-
type: 'completion',
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
// Search with suggestions
|
|
245
|
-
const result = await client.search({
|
|
246
|
-
index: 'suggestions',
|
|
247
|
-
body: {
|
|
248
|
-
suggest: {
|
|
249
|
-
name_suggest: {
|
|
250
|
-
prefix: 'joh',
|
|
251
|
-
completion: {
|
|
252
|
-
field: 'name.suggest',
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
})
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
## Best Practices
|
|
261
|
-
|
|
262
|
-
✅ **DO:**
|
|
263
|
-
- Use appropriate field types (text vs keyword)
|
|
264
|
-
- Create indexes with proper mappings
|
|
265
|
-
- Use bulk API for multiple operations
|
|
266
|
-
- Implement proper error handling
|
|
267
|
-
- Use aliases for zero-downtime reindexing
|
|
268
|
-
- Monitor cluster health
|
|
269
|
-
- Use appropriate shard and replica counts
|
|
270
|
-
- Implement search result pagination
|
|
271
|
-
- Use filters for exact matches (faster than queries)
|
|
272
|
-
- Enable slow query logging
|
|
273
|
-
|
|
274
|
-
❌ **DON'T:**
|
|
275
|
-
- Store large binary data (use external storage)
|
|
276
|
-
- Use too many shards (1 per 10-50GB)
|
|
277
|
-
- Skip mapping definitions
|
|
278
|
-
- Ignore cluster health
|
|
279
|
-
- Hardcode connection strings
|
|
280
|
-
- Use wildcard queries in production
|
|
281
|
-
- Skip error handling
|
|
282
|
-
- Ignore query performance
|
|
283
|
-
- Store sensitive data without encryption
|
|
284
|
-
- Use default refresh interval for real-time needs
|
|
285
|
-
|
|
286
|
-
## Configuration
|
|
287
|
-
|
|
288
|
-
### Environment Variables
|
|
289
|
-
```bash
|
|
290
|
-
ELASTICSEARCH_URL=http://localhost:9200
|
|
291
|
-
ELASTICSEARCH_USER=elastic
|
|
292
|
-
ELASTICSEARCH_PASSWORD=securepassword
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Docker Compose
|
|
296
|
-
```yaml
|
|
297
|
-
services:
|
|
298
|
-
elasticsearch:
|
|
299
|
-
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
|
|
300
|
-
environment:
|
|
301
|
-
- discovery.type=single-node
|
|
302
|
-
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
303
|
-
- xpack.security.enabled=false
|
|
304
|
-
ports:
|
|
305
|
-
- "9200:9200"
|
|
306
|
-
volumes:
|
|
307
|
-
- elasticsearch_data:/usr/share/elasticsearch/data
|
|
308
|
-
healthcheck:
|
|
309
|
-
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
|
|
310
|
-
interval: 30s
|
|
311
|
-
timeout: 10s
|
|
312
|
-
retries: 5
|
|
313
|
-
|
|
314
|
-
volumes:
|
|
315
|
-
elasticsearch_data:
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Integration with Development
|
|
319
|
-
|
|
320
|
-
### Testing
|
|
321
|
-
```typescript
|
|
322
|
-
// Use test index
|
|
323
|
-
const testIndex = 'test_users'
|
|
324
|
-
|
|
325
|
-
beforeEach(async () => {
|
|
326
|
-
await client.indices.create({ index: testIndex })
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
afterEach(async () => {
|
|
330
|
-
await client.indices.delete({ index: testIndex })
|
|
331
|
-
})
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Health Checks
|
|
335
|
-
```typescript
|
|
336
|
-
async function checkElasticsearchHealth(): Promise<boolean> {
|
|
337
|
-
try {
|
|
338
|
-
const response = await client.cluster.health()
|
|
339
|
-
return response.body.status !== 'red'
|
|
340
|
-
} catch {
|
|
341
|
-
return false
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
<!-- ELASTICSEARCH:END -->
|
|
347
|
-
|
|
1
|
+
<!-- ELASTICSEARCH:START -->
|
|
2
|
+
# Elasticsearch Search Instructions
|
|
3
|
+
|
|
4
|
+
**CRITICAL**: Use Elasticsearch for full-text search, log aggregation, and real-time analytics with distributed architecture.
|
|
5
|
+
|
|
6
|
+
## Core Features
|
|
7
|
+
|
|
8
|
+
### Connection
|
|
9
|
+
```typescript
|
|
10
|
+
// Using @elastic/elasticsearch
|
|
11
|
+
import { Client } from '@elastic/elasticsearch'
|
|
12
|
+
|
|
13
|
+
const client = new Client({
|
|
14
|
+
node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200',
|
|
15
|
+
auth: {
|
|
16
|
+
username: process.env.ELASTICSEARCH_USER || 'elastic',
|
|
17
|
+
password: process.env.ELASTICSEARCH_PASSWORD || '',
|
|
18
|
+
},
|
|
19
|
+
maxRetries: 5,
|
|
20
|
+
requestTimeout: 60000,
|
|
21
|
+
sniffOnStart: true,
|
|
22
|
+
sniffInterval: 30000,
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Index Operations
|
|
27
|
+
```typescript
|
|
28
|
+
// Create index
|
|
29
|
+
await client.indices.create({
|
|
30
|
+
index: 'users',
|
|
31
|
+
body: {
|
|
32
|
+
mappings: {
|
|
33
|
+
properties: {
|
|
34
|
+
name: { type: 'text' },
|
|
35
|
+
email: { type: 'keyword' },
|
|
36
|
+
age: { type: 'integer' },
|
|
37
|
+
createdAt: { type: 'date' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
settings: {
|
|
41
|
+
number_of_shards: 1,
|
|
42
|
+
number_of_replicas: 1,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Delete index
|
|
48
|
+
await client.indices.delete({ index: 'users' })
|
|
49
|
+
|
|
50
|
+
// Check if index exists
|
|
51
|
+
const exists = await client.indices.exists({ index: 'users' })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Document Operations
|
|
55
|
+
```typescript
|
|
56
|
+
// Index document
|
|
57
|
+
await client.index({
|
|
58
|
+
index: 'users',
|
|
59
|
+
id: '1',
|
|
60
|
+
body: {
|
|
61
|
+
name: 'John Doe',
|
|
62
|
+
email: 'john@example.com',
|
|
63
|
+
age: 30,
|
|
64
|
+
createdAt: new Date(),
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Get document
|
|
69
|
+
const result = await client.get({
|
|
70
|
+
index: 'users',
|
|
71
|
+
id: '1',
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Update document
|
|
75
|
+
await client.update({
|
|
76
|
+
index: 'users',
|
|
77
|
+
id: '1',
|
|
78
|
+
body: {
|
|
79
|
+
doc: {
|
|
80
|
+
age: 31,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Delete document
|
|
86
|
+
await client.delete({
|
|
87
|
+
index: 'users',
|
|
88
|
+
id: '1',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Bulk operations
|
|
92
|
+
await client.bulk({
|
|
93
|
+
body: [
|
|
94
|
+
{ index: { _index: 'users', _id: '1' } },
|
|
95
|
+
{ name: 'User 1', email: 'user1@example.com' },
|
|
96
|
+
{ index: { _index: 'users', _id: '2' } },
|
|
97
|
+
{ name: 'User 2', email: 'user2@example.com' },
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Search Operations
|
|
103
|
+
```typescript
|
|
104
|
+
// Simple search
|
|
105
|
+
const result = await client.search({
|
|
106
|
+
index: 'users',
|
|
107
|
+
body: {
|
|
108
|
+
query: {
|
|
109
|
+
match: {
|
|
110
|
+
name: 'John',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Multi-match search
|
|
117
|
+
const result = await client.search({
|
|
118
|
+
index: 'users',
|
|
119
|
+
body: {
|
|
120
|
+
query: {
|
|
121
|
+
multi_match: {
|
|
122
|
+
query: 'search term',
|
|
123
|
+
fields: ['name^2', 'email'], // name has 2x boost
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Bool query (AND/OR/NOT)
|
|
130
|
+
const result = await client.search({
|
|
131
|
+
index: 'users',
|
|
132
|
+
body: {
|
|
133
|
+
query: {
|
|
134
|
+
bool: {
|
|
135
|
+
must: [
|
|
136
|
+
{ match: { name: 'John' } },
|
|
137
|
+
{ range: { age: { gte: 18, lte: 65 } } },
|
|
138
|
+
],
|
|
139
|
+
must_not: [
|
|
140
|
+
{ term: { status: 'inactive' } },
|
|
141
|
+
],
|
|
142
|
+
should: [
|
|
143
|
+
{ match: { tags: 'premium' } },
|
|
144
|
+
],
|
|
145
|
+
minimum_should_match: 1,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Aggregations
|
|
152
|
+
const result = await client.search({
|
|
153
|
+
index: 'users',
|
|
154
|
+
body: {
|
|
155
|
+
aggs: {
|
|
156
|
+
age_groups: {
|
|
157
|
+
range: {
|
|
158
|
+
field: 'age',
|
|
159
|
+
ranges: [
|
|
160
|
+
{ to: 25 },
|
|
161
|
+
{ from: 25, to: 50 },
|
|
162
|
+
{ from: 50 },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
avg_age: {
|
|
167
|
+
avg: { field: 'age' },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Common Patterns
|
|
175
|
+
|
|
176
|
+
### Full-Text Search
|
|
177
|
+
```typescript
|
|
178
|
+
async function searchUsers(query: string, filters?: any) {
|
|
179
|
+
const must: any[] = [
|
|
180
|
+
{
|
|
181
|
+
multi_match: {
|
|
182
|
+
query,
|
|
183
|
+
fields: ['name^2', 'email'],
|
|
184
|
+
fuzziness: 'AUTO',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
if (filters) {
|
|
190
|
+
if (filters.age) {
|
|
191
|
+
must.push({ range: { age: filters.age } })
|
|
192
|
+
}
|
|
193
|
+
if (filters.status) {
|
|
194
|
+
must.push({ term: { status: filters.status } })
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await client.search({
|
|
199
|
+
index: 'users',
|
|
200
|
+
body: {
|
|
201
|
+
query: {
|
|
202
|
+
bool: {
|
|
203
|
+
must,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
highlight: {
|
|
207
|
+
fields: {
|
|
208
|
+
name: {},
|
|
209
|
+
email: {},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
return result.body.hits.hits.map(hit => ({
|
|
216
|
+
...hit._source,
|
|
217
|
+
score: hit._score,
|
|
218
|
+
highlights: hit.highlight,
|
|
219
|
+
}))
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Auto-complete
|
|
224
|
+
```typescript
|
|
225
|
+
// Create index with completion suggester
|
|
226
|
+
await client.indices.create({
|
|
227
|
+
index: 'suggestions',
|
|
228
|
+
body: {
|
|
229
|
+
mappings: {
|
|
230
|
+
properties: {
|
|
231
|
+
name: {
|
|
232
|
+
type: 'text',
|
|
233
|
+
fields: {
|
|
234
|
+
suggest: {
|
|
235
|
+
type: 'completion',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Search with suggestions
|
|
245
|
+
const result = await client.search({
|
|
246
|
+
index: 'suggestions',
|
|
247
|
+
body: {
|
|
248
|
+
suggest: {
|
|
249
|
+
name_suggest: {
|
|
250
|
+
prefix: 'joh',
|
|
251
|
+
completion: {
|
|
252
|
+
field: 'name.suggest',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Best Practices
|
|
261
|
+
|
|
262
|
+
✅ **DO:**
|
|
263
|
+
- Use appropriate field types (text vs keyword)
|
|
264
|
+
- Create indexes with proper mappings
|
|
265
|
+
- Use bulk API for multiple operations
|
|
266
|
+
- Implement proper error handling
|
|
267
|
+
- Use aliases for zero-downtime reindexing
|
|
268
|
+
- Monitor cluster health
|
|
269
|
+
- Use appropriate shard and replica counts
|
|
270
|
+
- Implement search result pagination
|
|
271
|
+
- Use filters for exact matches (faster than queries)
|
|
272
|
+
- Enable slow query logging
|
|
273
|
+
|
|
274
|
+
❌ **DON'T:**
|
|
275
|
+
- Store large binary data (use external storage)
|
|
276
|
+
- Use too many shards (1 per 10-50GB)
|
|
277
|
+
- Skip mapping definitions
|
|
278
|
+
- Ignore cluster health
|
|
279
|
+
- Hardcode connection strings
|
|
280
|
+
- Use wildcard queries in production
|
|
281
|
+
- Skip error handling
|
|
282
|
+
- Ignore query performance
|
|
283
|
+
- Store sensitive data without encryption
|
|
284
|
+
- Use default refresh interval for real-time needs
|
|
285
|
+
|
|
286
|
+
## Configuration
|
|
287
|
+
|
|
288
|
+
### Environment Variables
|
|
289
|
+
```bash
|
|
290
|
+
ELASTICSEARCH_URL=http://localhost:9200
|
|
291
|
+
ELASTICSEARCH_USER=elastic
|
|
292
|
+
ELASTICSEARCH_PASSWORD=securepassword
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Docker Compose
|
|
296
|
+
```yaml
|
|
297
|
+
services:
|
|
298
|
+
elasticsearch:
|
|
299
|
+
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
|
|
300
|
+
environment:
|
|
301
|
+
- discovery.type=single-node
|
|
302
|
+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
303
|
+
- xpack.security.enabled=false
|
|
304
|
+
ports:
|
|
305
|
+
- "9200:9200"
|
|
306
|
+
volumes:
|
|
307
|
+
- elasticsearch_data:/usr/share/elasticsearch/data
|
|
308
|
+
healthcheck:
|
|
309
|
+
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
|
|
310
|
+
interval: 30s
|
|
311
|
+
timeout: 10s
|
|
312
|
+
retries: 5
|
|
313
|
+
|
|
314
|
+
volumes:
|
|
315
|
+
elasticsearch_data:
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Integration with Development
|
|
319
|
+
|
|
320
|
+
### Testing
|
|
321
|
+
```typescript
|
|
322
|
+
// Use test index
|
|
323
|
+
const testIndex = 'test_users'
|
|
324
|
+
|
|
325
|
+
beforeEach(async () => {
|
|
326
|
+
await client.indices.create({ index: testIndex })
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
afterEach(async () => {
|
|
330
|
+
await client.indices.delete({ index: testIndex })
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Health Checks
|
|
335
|
+
```typescript
|
|
336
|
+
async function checkElasticsearchHealth(): Promise<boolean> {
|
|
337
|
+
try {
|
|
338
|
+
const response = await client.cluster.health()
|
|
339
|
+
return response.body.status !== 'red'
|
|
340
|
+
} catch {
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
<!-- ELASTICSEARCH:END -->
|
|
347
|
+
|