@cloudstreamsoftware/claude-tools 1.0.0 → 1.2.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/README.md +152 -37
- package/agents/INDEX.md +183 -0
- package/agents/architect.md +247 -0
- package/agents/build-error-resolver.md +555 -0
- package/agents/catalyst-deployer.md +132 -0
- package/agents/code-reviewer.md +121 -0
- package/agents/compliance-auditor.md +148 -0
- package/agents/creator-architect.md +395 -0
- package/agents/deluge-reviewer.md +98 -0
- package/agents/doc-updater.md +471 -0
- package/agents/e2e-runner.md +711 -0
- package/agents/planner.md +122 -0
- package/agents/refactor-cleaner.md +309 -0
- package/agents/security-reviewer.md +582 -0
- package/agents/tdd-guide.md +302 -0
- package/bin/cloudstream-setup.js +16 -6
- package/config/versions.json +63 -0
- package/dist/hooks/hooks.json +209 -0
- package/dist/index.js +47 -0
- package/dist/lib/asset-value.js +609 -0
- package/dist/lib/client-manager.js +300 -0
- package/dist/lib/command-matcher.js +242 -0
- package/dist/lib/cross-session-patterns.js +754 -0
- package/dist/lib/intent-classifier.js +1075 -0
- package/dist/lib/package-manager.js +374 -0
- package/dist/lib/recommendation-engine.js +597 -0
- package/dist/lib/session-memory.js +489 -0
- package/dist/lib/skill-effectiveness.js +486 -0
- package/dist/lib/skill-matcher.js +595 -0
- package/dist/lib/tutorial-metrics.js +242 -0
- package/dist/lib/tutorial-progress.js +209 -0
- package/dist/lib/tutorial-renderer.js +431 -0
- package/dist/lib/utils.js +380 -0
- package/dist/lib/verify-formatter.js +143 -0
- package/dist/lib/workflow-state.js +249 -0
- package/hooks/hooks.json +209 -0
- package/package.json +5 -1
- package/scripts/aggregate-sessions.js +290 -0
- package/scripts/branch-name-validator.js +291 -0
- package/scripts/build.js +101 -0
- package/scripts/commands/client-switch.js +231 -0
- package/scripts/deprecate-skill.js +610 -0
- package/scripts/diagnose.js +324 -0
- package/scripts/doc-freshness.js +168 -0
- package/scripts/generate-weekly-digest.js +393 -0
- package/scripts/health-check.js +270 -0
- package/scripts/hooks/credential-check.js +101 -0
- package/scripts/hooks/evaluate-session.js +81 -0
- package/scripts/hooks/pre-compact.js +66 -0
- package/scripts/hooks/prompt-analyzer.js +276 -0
- package/scripts/hooks/prompt-router.js +422 -0
- package/scripts/hooks/quality-gate-enforcer.js +371 -0
- package/scripts/hooks/session-end.js +156 -0
- package/scripts/hooks/session-start.js +195 -0
- package/scripts/hooks/skill-injector.js +333 -0
- package/scripts/hooks/suggest-compact.js +58 -0
- package/scripts/lib/asset-value.js +609 -0
- package/scripts/lib/client-manager.js +300 -0
- package/scripts/lib/command-matcher.js +242 -0
- package/scripts/lib/cross-session-patterns.js +754 -0
- package/scripts/lib/intent-classifier.js +1075 -0
- package/scripts/lib/package-manager.js +374 -0
- package/scripts/lib/recommendation-engine.js +597 -0
- package/scripts/lib/session-memory.js +489 -0
- package/scripts/lib/skill-effectiveness.js +486 -0
- package/scripts/lib/skill-matcher.js +595 -0
- package/scripts/lib/tutorial-metrics.js +242 -0
- package/scripts/lib/tutorial-progress.js +209 -0
- package/scripts/lib/tutorial-renderer.js +431 -0
- package/scripts/lib/utils.js +380 -0
- package/scripts/lib/verify-formatter.js +143 -0
- package/scripts/lib/workflow-state.js +249 -0
- package/scripts/onboard.js +363 -0
- package/scripts/quarterly-report.js +692 -0
- package/scripts/setup-package-manager.js +204 -0
- package/scripts/sync-upstream.js +391 -0
- package/scripts/test.js +108 -0
- package/scripts/tutorial-runner.js +351 -0
- package/scripts/validate-all.js +201 -0
- package/scripts/verifiers/agents.js +245 -0
- package/scripts/verifiers/config.js +186 -0
- package/scripts/verifiers/environment.js +123 -0
- package/scripts/verifiers/hooks.js +188 -0
- package/scripts/verifiers/index.js +38 -0
- package/scripts/verifiers/persistence.js +140 -0
- package/scripts/verifiers/plugin.js +215 -0
- package/scripts/verifiers/skills.js +209 -0
- package/scripts/verify-setup.js +164 -0
- package/skills/INDEX.md +157 -0
- package/skills/backend-patterns/SKILL.md +586 -0
- package/skills/backend-patterns/catalyst-patterns.md +128 -0
- package/skills/bigquery-patterns/SKILL.md +27 -0
- package/skills/bigquery-patterns/performance-optimization.md +518 -0
- package/skills/bigquery-patterns/query-patterns.md +372 -0
- package/skills/bigquery-patterns/schema-design.md +78 -0
- package/skills/cloudstream-project-template/SKILL.md +20 -0
- package/skills/cloudstream-project-template/structure.md +65 -0
- package/skills/coding-standards/SKILL.md +524 -0
- package/skills/coding-standards/deluge-standards.md +83 -0
- package/skills/compliance-patterns/SKILL.md +28 -0
- package/skills/compliance-patterns/hipaa/audit-requirements.md +251 -0
- package/skills/compliance-patterns/hipaa/baa-process.md +298 -0
- package/skills/compliance-patterns/hipaa/data-archival-strategy.md +387 -0
- package/skills/compliance-patterns/hipaa/phi-handling.md +52 -0
- package/skills/compliance-patterns/pci-dss/saq-a-requirements.md +307 -0
- package/skills/compliance-patterns/pci-dss/tokenization-patterns.md +382 -0
- package/skills/compliance-patterns/pci-dss/zoho-checkout-patterns.md +56 -0
- package/skills/compliance-patterns/soc2/access-controls.md +344 -0
- package/skills/compliance-patterns/soc2/audit-logging.md +458 -0
- package/skills/compliance-patterns/soc2/change-management.md +403 -0
- package/skills/compliance-patterns/soc2/deluge-execution-logging.md +407 -0
- package/skills/consultancy-workflows/SKILL.md +19 -0
- package/skills/consultancy-workflows/client-isolation.md +21 -0
- package/skills/consultancy-workflows/documentation-automation.md +454 -0
- package/skills/consultancy-workflows/handoff-procedures.md +257 -0
- package/skills/consultancy-workflows/knowledge-capture.md +513 -0
- package/skills/consultancy-workflows/time-tracking.md +26 -0
- package/skills/continuous-learning/SKILL.md +84 -0
- package/skills/continuous-learning/config.json +18 -0
- package/skills/continuous-learning/evaluate-session.sh +60 -0
- package/skills/continuous-learning-v2/SKILL.md +126 -0
- package/skills/continuous-learning-v2/config.json +61 -0
- package/skills/frontend-patterns/SKILL.md +635 -0
- package/skills/frontend-patterns/zoho-widget-patterns.md +103 -0
- package/skills/gcp-data-engineering/SKILL.md +36 -0
- package/skills/gcp-data-engineering/bigquery/performance-optimization.md +337 -0
- package/skills/gcp-data-engineering/dataflow/error-handling.md +496 -0
- package/skills/gcp-data-engineering/dataflow/pipeline-patterns.md +444 -0
- package/skills/gcp-data-engineering/dbt/model-organization.md +63 -0
- package/skills/gcp-data-engineering/dbt/testing-patterns.md +503 -0
- package/skills/gcp-data-engineering/medallion-architecture/bronze-layer.md +60 -0
- package/skills/gcp-data-engineering/medallion-architecture/gold-layer.md +311 -0
- package/skills/gcp-data-engineering/medallion-architecture/layer-transitions.md +517 -0
- package/skills/gcp-data-engineering/medallion-architecture/silver-layer.md +305 -0
- package/skills/gcp-data-engineering/zoho-to-gcp/data-extraction.md +543 -0
- package/skills/gcp-data-engineering/zoho-to-gcp/real-time-vs-batch.md +337 -0
- package/skills/security-review/SKILL.md +498 -0
- package/skills/security-review/compliance-checklist.md +53 -0
- package/skills/strategic-compact/SKILL.md +67 -0
- package/skills/tdd-workflow/SKILL.md +413 -0
- package/skills/tdd-workflow/zoho-testing.md +124 -0
- package/skills/tutorial/SKILL.md +249 -0
- package/skills/tutorial/docs/ACCESSIBILITY.md +169 -0
- package/skills/tutorial/lessons/00-philosophy-and-workflow.md +198 -0
- package/skills/tutorial/lessons/01-basics.md +81 -0
- package/skills/tutorial/lessons/02-training.md +86 -0
- package/skills/tutorial/lessons/03-commands.md +109 -0
- package/skills/tutorial/lessons/04-workflows.md +115 -0
- package/skills/tutorial/lessons/05-compliance.md +116 -0
- package/skills/tutorial/lessons/06-zoho.md +121 -0
- package/skills/tutorial/lessons/07-hooks-system.md +277 -0
- package/skills/tutorial/lessons/08-mcp-servers.md +316 -0
- package/skills/tutorial/lessons/09-client-management.md +215 -0
- package/skills/tutorial/lessons/10-testing-e2e.md +260 -0
- package/skills/tutorial/lessons/11-skills-deep-dive.md +272 -0
- package/skills/tutorial/lessons/12-rules-system.md +326 -0
- package/skills/tutorial/lessons/13-golden-standard-graduation.md +213 -0
- package/skills/tutorial/lessons/14-fork-setup-and-sync.md +312 -0
- package/skills/tutorial/lessons/15-living-examples-system.md +221 -0
- package/skills/tutorial/tracks/accelerated/README.md +134 -0
- package/skills/tutorial/tracks/accelerated/assessment/checkpoint-1.md +161 -0
- package/skills/tutorial/tracks/accelerated/assessment/checkpoint-2.md +175 -0
- package/skills/tutorial/tracks/accelerated/day-1-core-concepts.md +234 -0
- package/skills/tutorial/tracks/accelerated/day-2-essential-commands.md +270 -0
- package/skills/tutorial/tracks/accelerated/day-3-workflow-mastery.md +305 -0
- package/skills/tutorial/tracks/accelerated/day-4-compliance-zoho.md +304 -0
- package/skills/tutorial/tracks/accelerated/day-5-hooks-skills.md +344 -0
- package/skills/tutorial/tracks/accelerated/day-6-client-testing.md +386 -0
- package/skills/tutorial/tracks/accelerated/day-7-graduation.md +369 -0
- package/skills/zoho-patterns/CHANGELOG.md +108 -0
- package/skills/zoho-patterns/SKILL.md +446 -0
- package/skills/zoho-patterns/analytics/dashboard-patterns.md +352 -0
- package/skills/zoho-patterns/analytics/zoho-to-bigquery-pipeline.md +427 -0
- package/skills/zoho-patterns/catalyst/appsail-deployment.md +349 -0
- package/skills/zoho-patterns/catalyst/context-close-patterns.md +354 -0
- package/skills/zoho-patterns/catalyst/cron-batch-processing.md +374 -0
- package/skills/zoho-patterns/catalyst/function-patterns.md +439 -0
- package/skills/zoho-patterns/creator/form-design.md +304 -0
- package/skills/zoho-patterns/creator/publish-api-patterns.md +313 -0
- package/skills/zoho-patterns/creator/widget-integration.md +306 -0
- package/skills/zoho-patterns/creator/workflow-automation.md +253 -0
- package/skills/zoho-patterns/deluge/api-patterns.md +468 -0
- package/skills/zoho-patterns/deluge/batch-processing.md +403 -0
- package/skills/zoho-patterns/deluge/cross-app-integration.md +356 -0
- package/skills/zoho-patterns/deluge/error-handling.md +423 -0
- package/skills/zoho-patterns/deluge/syntax-reference.md +65 -0
- package/skills/zoho-patterns/integration/cors-proxy-architecture.md +426 -0
- package/skills/zoho-patterns/integration/crm-books-native-sync.md +277 -0
- package/skills/zoho-patterns/integration/oauth-token-management.md +461 -0
- package/skills/zoho-patterns/integration/zoho-flow-patterns.md +334 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Catalyst AppSail Deployment
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
AppSail provides full-stack application hosting on Zoho Catalyst with support for Node.js, Python, and Java runtimes. It handles SSL, scaling, and deployment via CLI.
|
|
6
|
+
|
|
7
|
+
## Supported Runtimes
|
|
8
|
+
|
|
9
|
+
| Runtime | Versions | Use Case |
|
|
10
|
+
|---------|----------|----------|
|
|
11
|
+
| Node.js | 16, 18, 20 | React widgets, Express APIs, Next.js |
|
|
12
|
+
| Python | 3.8, 3.9, 3.10 | Flask/FastAPI APIs, ML models |
|
|
13
|
+
| Java | 11, 17 | Spring Boot, enterprise integrations |
|
|
14
|
+
|
|
15
|
+
## Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
my-catalyst-app/
|
|
19
|
+
├── catalyst.json # Project configuration
|
|
20
|
+
├── app/ # AppSail application
|
|
21
|
+
│ ├── Dockerfile # Container configuration
|
|
22
|
+
│ ├── package.json # (Node.js) dependencies
|
|
23
|
+
│ ├── index.js # (Node.js) entry point
|
|
24
|
+
│ ├── requirements.txt # (Python) dependencies
|
|
25
|
+
│ ├── app.py # (Python) entry point
|
|
26
|
+
│ └── static/ # Static files
|
|
27
|
+
├── functions/ # Catalyst Functions (serverless)
|
|
28
|
+
│ └── my-function/
|
|
29
|
+
│ ├── index.js
|
|
30
|
+
│ └── catalyst-config.json
|
|
31
|
+
└── client/ # Client-side assets (optional)
|
|
32
|
+
└── index.html
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## PORT Binding (CRITICAL)
|
|
36
|
+
|
|
37
|
+
> **WARNING:** Your application MUST bind to the port specified by `process.env.X_ZOHO_CATALYST_LISTEN_PORT` (Node.js) or the equivalent environment variable. Hardcoding ports will cause deployment failure.
|
|
38
|
+
|
|
39
|
+
### Node.js (Express)
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const express = require("express");
|
|
43
|
+
const app = express();
|
|
44
|
+
|
|
45
|
+
// MANDATORY: Use the Catalyst-provided port
|
|
46
|
+
const PORT = process.env.X_ZOHO_CATALYST_LISTEN_PORT || 9000;
|
|
47
|
+
|
|
48
|
+
app.get("/health", (req, res) => {
|
|
49
|
+
res.status(200).json({ status: "healthy", timestamp: new Date().toISOString() });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
app.get("/api/data", async (req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
// Your business logic here
|
|
55
|
+
const data = await fetchData();
|
|
56
|
+
res.json({ success: true, data });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
res.status(500).json({ success: false, error: error.message });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
app.listen(PORT, () => {
|
|
63
|
+
console.log(`AppSail server running on port ${PORT}`);
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Python (Flask)
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import os
|
|
71
|
+
from flask import Flask, jsonify
|
|
72
|
+
|
|
73
|
+
app = Flask(__name__)
|
|
74
|
+
|
|
75
|
+
# MANDATORY: Use the Catalyst-provided port
|
|
76
|
+
PORT = int(os.environ.get("X_ZOHO_CATALYST_LISTEN_PORT", 9000))
|
|
77
|
+
|
|
78
|
+
@app.route("/health")
|
|
79
|
+
def health():
|
|
80
|
+
return jsonify({"status": "healthy"}), 200
|
|
81
|
+
|
|
82
|
+
@app.route("/api/data")
|
|
83
|
+
def get_data():
|
|
84
|
+
try:
|
|
85
|
+
data = fetch_data()
|
|
86
|
+
return jsonify({"success": True, "data": data}), 200
|
|
87
|
+
except Exception as e:
|
|
88
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
app.run(host="0.0.0.0", port=PORT)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Dockerfile Configuration
|
|
95
|
+
|
|
96
|
+
### Node.js Dockerfile
|
|
97
|
+
|
|
98
|
+
```dockerfile
|
|
99
|
+
FROM node:18-alpine
|
|
100
|
+
|
|
101
|
+
WORKDIR /app
|
|
102
|
+
|
|
103
|
+
# Copy package files first (layer caching)
|
|
104
|
+
COPY package*.json ./
|
|
105
|
+
RUN npm ci --only=production
|
|
106
|
+
|
|
107
|
+
# Copy application code
|
|
108
|
+
COPY . .
|
|
109
|
+
|
|
110
|
+
# Expose the Catalyst port
|
|
111
|
+
EXPOSE 9000
|
|
112
|
+
|
|
113
|
+
# Health check
|
|
114
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
115
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:${X_ZOHO_CATALYST_LISTEN_PORT}/health || exit 1
|
|
116
|
+
|
|
117
|
+
# Start command
|
|
118
|
+
CMD ["node", "index.js"]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Python Dockerfile
|
|
122
|
+
|
|
123
|
+
```dockerfile
|
|
124
|
+
FROM python:3.10-slim
|
|
125
|
+
|
|
126
|
+
WORKDIR /app
|
|
127
|
+
|
|
128
|
+
COPY requirements.txt .
|
|
129
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
130
|
+
|
|
131
|
+
COPY . .
|
|
132
|
+
|
|
133
|
+
EXPOSE 9000
|
|
134
|
+
|
|
135
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
136
|
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:${X_ZOHO_CATALYST_LISTEN_PORT}/health')" || exit 1
|
|
137
|
+
|
|
138
|
+
CMD ["python", "app.py"]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Environment Variables
|
|
142
|
+
|
|
143
|
+
Set via CLI or Catalyst Console:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Set environment variable
|
|
147
|
+
catalyst env:set MY_API_KEY=abc123 --environment production
|
|
148
|
+
|
|
149
|
+
# Set multiple variables
|
|
150
|
+
catalyst env:set DB_HOST=mysql.example.com DB_PORT=3306 DB_NAME=mydb
|
|
151
|
+
|
|
152
|
+
# List current variables
|
|
153
|
+
catalyst env:list
|
|
154
|
+
|
|
155
|
+
# Remove variable
|
|
156
|
+
catalyst env:unset MY_API_KEY
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Accessing in Code
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// Node.js
|
|
163
|
+
const apiKey = process.env.MY_API_KEY;
|
|
164
|
+
const dbConfig = {
|
|
165
|
+
host: process.env.DB_HOST,
|
|
166
|
+
port: parseInt(process.env.DB_PORT || "3306"),
|
|
167
|
+
database: process.env.DB_NAME
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
> **WARNING:** Never commit environment variables to git. Use `.env` locally and Catalyst environment settings for deployment.
|
|
172
|
+
|
|
173
|
+
## Static File Serving
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const express = require("express");
|
|
177
|
+
const path = require("path");
|
|
178
|
+
const app = express();
|
|
179
|
+
|
|
180
|
+
// Serve static files from /static directory
|
|
181
|
+
app.use("/static", express.static(path.join(__dirname, "static")));
|
|
182
|
+
|
|
183
|
+
// Serve React build for SPA
|
|
184
|
+
app.use(express.static(path.join(__dirname, "client/build")));
|
|
185
|
+
|
|
186
|
+
// SPA fallback - serve index.html for all non-API routes
|
|
187
|
+
app.get("*", (req, res) => {
|
|
188
|
+
if (!req.path.startsWith("/api")) {
|
|
189
|
+
res.sendFile(path.join(__dirname, "client/build", "index.html"));
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Custom Domains and SSL
|
|
195
|
+
|
|
196
|
+
1. **Add domain in Catalyst Console:** Settings > Custom Domain
|
|
197
|
+
2. **Configure DNS:** Add CNAME record pointing to your Catalyst subdomain
|
|
198
|
+
3. **SSL:** Automatically provisioned (Let's Encrypt) after DNS verification
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
# DNS Configuration
|
|
202
|
+
Type: CNAME
|
|
203
|
+
Name: app
|
|
204
|
+
Value: your-project-id.zohoappdev.com
|
|
205
|
+
TTL: 300
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> SSL certificates auto-renew. No manual intervention needed.
|
|
209
|
+
|
|
210
|
+
## Health Checks
|
|
211
|
+
|
|
212
|
+
Catalyst checks `/health` endpoint. Your app MUST respond with 200:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
app.get("/health", (req, res) => {
|
|
216
|
+
// Check dependencies
|
|
217
|
+
const checks = {
|
|
218
|
+
database: checkDbConnection(),
|
|
219
|
+
cache: checkCacheConnection(),
|
|
220
|
+
memory: process.memoryUsage().heapUsed < 500 * 1024 * 1024 // Under 500MB
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const allHealthy = Object.values(checks).every(v => v === true);
|
|
224
|
+
|
|
225
|
+
res.status(allHealthy ? 200 : 503).json({
|
|
226
|
+
status: allHealthy ? "healthy" : "degraded",
|
|
227
|
+
checks: checks,
|
|
228
|
+
uptime: process.uptime()
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Scaling Configuration
|
|
234
|
+
|
|
235
|
+
Configure in `catalyst.json`:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"appsail": {
|
|
240
|
+
"stack": "node18",
|
|
241
|
+
"memory": 512,
|
|
242
|
+
"scaling": {
|
|
243
|
+
"min_instances": 1,
|
|
244
|
+
"max_instances": 5,
|
|
245
|
+
"target_cpu_utilization": 70
|
|
246
|
+
},
|
|
247
|
+
"timeout": 120
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
| Setting | Default | Max | Notes |
|
|
253
|
+
|---------|---------|-----|-------|
|
|
254
|
+
| Memory (MB) | 256 | 1024 | Increase for data-heavy apps |
|
|
255
|
+
| Min Instances | 0 | 5 | Set 1+ to avoid cold starts |
|
|
256
|
+
| Max Instances | 1 | 10 | Plan-dependent |
|
|
257
|
+
| Request Timeout | 60s | 300s | Per-request limit |
|
|
258
|
+
|
|
259
|
+
## Deployment via CLI
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Install Catalyst CLI
|
|
263
|
+
npm install -g zcatalyst-cli
|
|
264
|
+
|
|
265
|
+
# Login to your Zoho account
|
|
266
|
+
catalyst login
|
|
267
|
+
|
|
268
|
+
# Initialize project (first time)
|
|
269
|
+
catalyst init
|
|
270
|
+
|
|
271
|
+
# Deploy AppSail app
|
|
272
|
+
catalyst deploy --only appsail
|
|
273
|
+
|
|
274
|
+
# Deploy everything (functions + appsail + client)
|
|
275
|
+
catalyst deploy
|
|
276
|
+
|
|
277
|
+
# Deploy to specific environment
|
|
278
|
+
catalyst deploy --env production
|
|
279
|
+
|
|
280
|
+
# View deployment logs
|
|
281
|
+
catalyst logs --type appsail --tail
|
|
282
|
+
|
|
283
|
+
# Rollback to previous deployment
|
|
284
|
+
catalyst rollback --version previous
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### CI/CD Integration (GitHub Actions)
|
|
288
|
+
|
|
289
|
+
```yaml
|
|
290
|
+
name: Deploy to Catalyst
|
|
291
|
+
on:
|
|
292
|
+
push:
|
|
293
|
+
branches: [main]
|
|
294
|
+
|
|
295
|
+
jobs:
|
|
296
|
+
deploy:
|
|
297
|
+
runs-on: ubuntu-latest
|
|
298
|
+
steps:
|
|
299
|
+
- uses: actions/checkout@v3
|
|
300
|
+
|
|
301
|
+
- name: Setup Node.js
|
|
302
|
+
uses: actions/setup-node@v3
|
|
303
|
+
with:
|
|
304
|
+
node-version: "18"
|
|
305
|
+
|
|
306
|
+
- name: Install Catalyst CLI
|
|
307
|
+
run: npm install -g zcatalyst-cli
|
|
308
|
+
|
|
309
|
+
- name: Install dependencies
|
|
310
|
+
run: cd app && npm ci
|
|
311
|
+
|
|
312
|
+
- name: Run tests
|
|
313
|
+
run: cd app && npm test
|
|
314
|
+
|
|
315
|
+
- name: Deploy to Catalyst
|
|
316
|
+
env:
|
|
317
|
+
CATALYST_AUTH_TOKEN: ${{ secrets.CATALYST_AUTH_TOKEN }}
|
|
318
|
+
CATALYST_PROJECT_ID: ${{ secrets.CATALYST_PROJECT_ID }}
|
|
319
|
+
run: |
|
|
320
|
+
catalyst deploy --only appsail --token $CATALYST_AUTH_TOKEN --project $CATALYST_PROJECT_ID
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Common Deployment Issues
|
|
324
|
+
|
|
325
|
+
| Issue | Cause | Fix |
|
|
326
|
+
|-------|-------|-----|
|
|
327
|
+
| Port binding failure | Hardcoded port | Use `process.env.X_ZOHO_CATALYST_LISTEN_PORT` |
|
|
328
|
+
| Build timeout | Large dependencies | Use multi-stage Dockerfile, prune dev deps |
|
|
329
|
+
| Health check failing | No `/health` endpoint | Add health check route |
|
|
330
|
+
| Cold start > 10s | Heavy initialization | Lazy-load modules, reduce bundle size |
|
|
331
|
+
| Memory exceeded | Memory leak or large payloads | Profile with `--inspect`, add memory limits |
|
|
332
|
+
| CORS errors from widget | Missing CORS headers | Add `cors` middleware with Zoho origins |
|
|
333
|
+
|
|
334
|
+
### CORS for Widget Integration
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
const cors = require("cors");
|
|
338
|
+
|
|
339
|
+
// Allow Creator widgets to call this AppSail app
|
|
340
|
+
app.use(cors({
|
|
341
|
+
origin: [
|
|
342
|
+
"https://creator.zoho.com",
|
|
343
|
+
"https://creator.zohocloud.ca",
|
|
344
|
+
"https://creatorapp.zohopublic.com",
|
|
345
|
+
/\.zoho\.(com|eu|in|com\.au|jp)$/
|
|
346
|
+
],
|
|
347
|
+
credentials: true
|
|
348
|
+
}));
|
|
349
|
+
```
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# Catalyst context.close() Patterns
|
|
2
|
+
|
|
3
|
+
## Why context.close() is MANDATORY
|
|
4
|
+
|
|
5
|
+
> **CRITICAL:** Every Catalyst function MUST call `context.close()` before exiting. If missed:
|
|
6
|
+
> - The function will hang until timeout (30s for I/O, 15min for Cron)
|
|
7
|
+
> - Resources (DB connections, file handles) will leak
|
|
8
|
+
> - You will be billed for the full timeout duration
|
|
9
|
+
> - Concurrent execution limits will be consumed by hung functions
|
|
10
|
+
|
|
11
|
+
## The Golden Rule
|
|
12
|
+
|
|
13
|
+
**Every code path that can exit the function MUST call `context.close()`.** This includes:
|
|
14
|
+
- Normal completion
|
|
15
|
+
- Error/exception paths
|
|
16
|
+
- Early returns
|
|
17
|
+
- Conditional branches
|
|
18
|
+
- After async operations complete or fail
|
|
19
|
+
|
|
20
|
+
## Pattern 1: Try-Finally (Recommended)
|
|
21
|
+
|
|
22
|
+
The safest pattern - `finally` block ALWAYS executes:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const catalyst = require("zcatalyst-sdk-node");
|
|
26
|
+
|
|
27
|
+
module.exports = async (req, res, context) => {
|
|
28
|
+
const app = catalyst.initialize(context);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const data = await app.zcql().executeZCQLQuery("SELECT * FROM Users");
|
|
32
|
+
res.status(200).json({ success: true, data });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
res.status(500).json({ success: false, error: error.message });
|
|
35
|
+
} finally {
|
|
36
|
+
context.close(); // ALWAYS executes, regardless of success or failure
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Pattern 2: Multiple Return Points
|
|
42
|
+
|
|
43
|
+
> **WARNING:** This is the most common source of missed `context.close()` calls. Every early return needs its own close.
|
|
44
|
+
|
|
45
|
+
### WRONG (Missing context.close on early return):
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// BAD - context.close() missed on validation failure
|
|
49
|
+
module.exports = async (req, res, context) => {
|
|
50
|
+
if (!req.body.email) {
|
|
51
|
+
res.status(400).json({ error: "Email required" });
|
|
52
|
+
return; // LEAKED! context.close() never called
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ... processing ...
|
|
56
|
+
res.status(200).json({ success: true });
|
|
57
|
+
context.close();
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### CORRECT (Try-finally covers all paths):
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// GOOD - finally block covers all exits
|
|
65
|
+
module.exports = async (req, res, context) => {
|
|
66
|
+
try {
|
|
67
|
+
if (!req.body.email) {
|
|
68
|
+
res.status(400).json({ error: "Email required" });
|
|
69
|
+
return; // Safe - finally still executes
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!req.body.name) {
|
|
73
|
+
res.status(400).json({ error: "Name required" });
|
|
74
|
+
return; // Safe - finally still executes
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = await processData(req.body);
|
|
78
|
+
res.status(200).json({ success: true, data: result });
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
res.status(500).json({ success: false, error: error.message });
|
|
82
|
+
} finally {
|
|
83
|
+
context.close(); // Covers ALL paths above
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Pattern 3: Async/Await with Error Handling
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
module.exports = async (req, res, context) => {
|
|
92
|
+
const app = catalyst.initialize(context);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Multiple async operations
|
|
96
|
+
const user = await getUser(app, req.params.userId);
|
|
97
|
+
const orders = await getOrders(app, user.ROWID);
|
|
98
|
+
const analytics = await computeAnalytics(orders);
|
|
99
|
+
|
|
100
|
+
res.status(200).json({
|
|
101
|
+
user,
|
|
102
|
+
orders,
|
|
103
|
+
analytics
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Differentiate error types
|
|
108
|
+
if (error.code === "NOT_FOUND") {
|
|
109
|
+
res.status(404).json({ error: "User not found" });
|
|
110
|
+
} else if (error.code === "TIMEOUT") {
|
|
111
|
+
res.status(504).json({ error: "Database timeout" });
|
|
112
|
+
} else {
|
|
113
|
+
res.status(500).json({ error: "Internal error" });
|
|
114
|
+
console.error("Unhandled:", error);
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
context.close();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Pattern 4: Cron Functions
|
|
123
|
+
|
|
124
|
+
Cron functions have different signatures but same requirement:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
module.exports = async (cronDetails, context) => {
|
|
128
|
+
const app = catalyst.initialize(context);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
console.log("Cron started:", cronDetails.cron_name);
|
|
132
|
+
|
|
133
|
+
const results = await performBatchOperation(app);
|
|
134
|
+
console.log("Cron completed:", results);
|
|
135
|
+
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error("Cron failed:", error.message);
|
|
138
|
+
// Notify admin - but don't let notification failure block close
|
|
139
|
+
try {
|
|
140
|
+
await notifyAdmin(app, error);
|
|
141
|
+
} catch (notifyError) {
|
|
142
|
+
console.error("Notification also failed:", notifyError.message);
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
context.close();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Pattern 5: Event Functions
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
module.exports = async (eventData, context) => {
|
|
154
|
+
const app = catalyst.initialize(context);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const { event_type, data } = eventData;
|
|
158
|
+
|
|
159
|
+
switch (event_type) {
|
|
160
|
+
case "user_created":
|
|
161
|
+
await handleUserCreated(app, data);
|
|
162
|
+
break;
|
|
163
|
+
case "order_placed":
|
|
164
|
+
await handleOrderPlaced(app, data);
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
console.warn("Unknown event type:", event_type);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error("Event processing failed:", error);
|
|
172
|
+
} finally {
|
|
173
|
+
context.close();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Pattern 6: Nested Async Operations
|
|
179
|
+
|
|
180
|
+
When you have nested promises or callbacks:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
module.exports = async (req, res, context) => {
|
|
184
|
+
const app = catalyst.initialize(context);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Parallel async operations
|
|
188
|
+
const [users, products, orders] = await Promise.all([
|
|
189
|
+
fetchUsers(app),
|
|
190
|
+
fetchProducts(app),
|
|
191
|
+
fetchOrders(app)
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
// Sequential dependent operations
|
|
195
|
+
const enrichedOrders = [];
|
|
196
|
+
for (const order of orders) {
|
|
197
|
+
const enriched = await enrichOrder(app, order, users, products);
|
|
198
|
+
enrichedOrders.push(enriched);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
res.status(200).json({ data: enrichedOrders });
|
|
202
|
+
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// Promise.all rejects on first failure
|
|
205
|
+
res.status(500).json({ error: error.message });
|
|
206
|
+
} finally {
|
|
207
|
+
context.close();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Pattern 7: Streaming/Chunked Response
|
|
213
|
+
|
|
214
|
+
For long-running operations that stream responses:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
module.exports = async (req, res, context) => {
|
|
218
|
+
const app = catalyst.initialize(context);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
222
|
+
|
|
223
|
+
const stream = await getLargeDataset(app);
|
|
224
|
+
let first = true;
|
|
225
|
+
|
|
226
|
+
res.write("[");
|
|
227
|
+
for await (const chunk of stream) {
|
|
228
|
+
if (!first) res.write(",");
|
|
229
|
+
res.write(JSON.stringify(chunk));
|
|
230
|
+
first = false;
|
|
231
|
+
}
|
|
232
|
+
res.write("]");
|
|
233
|
+
res.end();
|
|
234
|
+
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// If headers already sent, can't change status code
|
|
237
|
+
if (!res.headersSent) {
|
|
238
|
+
res.status(500).json({ error: error.message });
|
|
239
|
+
} else {
|
|
240
|
+
res.end(); // Close the stream
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
context.close();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Anti-Patterns to Avoid
|
|
249
|
+
|
|
250
|
+
### 1. context.close() in Callback (May Not Execute)
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
// BAD - if callback never fires, context.close() never called
|
|
254
|
+
someAsyncOperation((error, result) => {
|
|
255
|
+
if (error) { res.status(500).send(error); }
|
|
256
|
+
else { res.status(200).send(result); }
|
|
257
|
+
context.close();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// GOOD - wrap in promise, use try-finally
|
|
261
|
+
try {
|
|
262
|
+
const result = await new Promise((resolve, reject) => {
|
|
263
|
+
someAsyncOperation((error, result) => {
|
|
264
|
+
if (error) reject(error);
|
|
265
|
+
else resolve(result);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
res.status(200).send(result);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
res.status(500).send(error.message);
|
|
271
|
+
} finally {
|
|
272
|
+
context.close();
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 2. Conditional context.close()
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// BAD - context.close() only on success path
|
|
280
|
+
if (success) {
|
|
281
|
+
res.status(200).send("OK");
|
|
282
|
+
context.close();
|
|
283
|
+
}
|
|
284
|
+
// What if success is false? Function hangs!
|
|
285
|
+
|
|
286
|
+
// GOOD - unconditional in finally
|
|
287
|
+
try {
|
|
288
|
+
if (success) {
|
|
289
|
+
res.status(200).send("OK");
|
|
290
|
+
} else {
|
|
291
|
+
res.status(400).send("Failed");
|
|
292
|
+
}
|
|
293
|
+
} finally {
|
|
294
|
+
context.close();
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 3. After setTimeout (Race Condition)
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// BAD - setTimeout may fire after function timeout
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
context.close(); // May be too late!
|
|
304
|
+
}, 5000);
|
|
305
|
+
|
|
306
|
+
// GOOD - use await with promise-based delay
|
|
307
|
+
try {
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
309
|
+
// Do work after delay
|
|
310
|
+
res.status(200).send("Done");
|
|
311
|
+
} finally {
|
|
312
|
+
context.close();
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Testing for context.close() Coverage
|
|
317
|
+
|
|
318
|
+
Before deploying, verify every function has proper close coverage:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// Add this wrapper to catch missing context.close() in development
|
|
322
|
+
function wrapHandler(handler) {
|
|
323
|
+
return async (req, res, context) => {
|
|
324
|
+
let closeCalled = false;
|
|
325
|
+
const originalClose = context.close.bind(context);
|
|
326
|
+
|
|
327
|
+
context.close = () => {
|
|
328
|
+
closeCalled = true;
|
|
329
|
+
originalClose();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
await handler(req, res, context);
|
|
333
|
+
|
|
334
|
+
if (!closeCalled) {
|
|
335
|
+
console.error("WARNING: context.close() was not called!");
|
|
336
|
+
originalClose(); // Safety net
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Usage in development
|
|
342
|
+
module.exports = wrapHandler(async (req, res, context) => {
|
|
343
|
+
// Your handler code
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Checklist Before Deployment
|
|
348
|
+
|
|
349
|
+
- [ ] Every function has `try-finally` with `context.close()` in `finally`
|
|
350
|
+
- [ ] No early `return` statements outside the try block
|
|
351
|
+
- [ ] No `context.close()` inside `setTimeout` or unresolved callbacks
|
|
352
|
+
- [ ] Error notification code is wrapped in its own try-catch (won't block close)
|
|
353
|
+
- [ ] All `Promise.all()` calls are inside the main try block
|
|
354
|
+
- [ ] Cron functions close even on lock-acquisition failure
|