@chaprola/mcp-server 1.6.4 → 1.8.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/dist/index.js +108 -54
- package/package.json +2 -2
- package/references/auth.md +33 -0
- package/references/cookbook.md +185 -43
- package/references/gotchas.md +53 -7
- package/references/ref-apps.md +195 -0
- package/references/ref-gotchas.md +6 -1
- package/references/ref-huldra.md +1 -1
- package/references/ref-import.md +37 -0
- package/references/ref-pivot.md +11 -0
- package/references/ref-programs.md +125 -18
- package/references/ref-query.md +51 -0
package/references/cookbook.md
CHANGED
|
@@ -19,56 +19,67 @@ POST /run {userid, project, name: "REPORT", primary_file: "STAFF", record: 1}
|
|
|
19
19
|
|-------|---------|--------------------------|
|
|
20
20
|
| R1–R20 | HULDRA elements (parameters) | No — HULDRA overwrites these |
|
|
21
21
|
| R21–R40 | HULDRA objectives (error metrics) | No — HULDRA reads these |
|
|
22
|
-
| R41–
|
|
22
|
+
| R41–R99 | Scratch space | **Yes — always use R41–R99 for DEFINE VARIABLE** |
|
|
23
23
|
|
|
24
|
-
For non-HULDRA programs, R1–R40 are technically available but using R41–
|
|
24
|
+
For non-HULDRA programs, R1–R40 are technically available but using R41–R99 is a good habit.
|
|
25
25
|
|
|
26
|
-
## PRINT: Output
|
|
26
|
+
## PRINT: Preferred Output Methods
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
PRINT
|
|
28
|
+
**Concatenation (preferred):**
|
|
29
|
+
```chaprola
|
|
30
|
+
PRINT P.name + " — " + P.department + " — $" + R41
|
|
31
|
+
PRINT "Total: " + R42
|
|
32
|
+
PRINT P.last_name // single field, auto-trimmed
|
|
33
|
+
PRINT "Hello from Chaprola!" // literal string
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
- String literals are copied as-is.
|
|
37
|
+
- P./S./U./X. fields are auto-trimmed (trailing spaces removed).
|
|
38
|
+
- R-variables print as integers when no fractional part, otherwise as floats.
|
|
39
|
+
- Concatenation auto-flushes the line.
|
|
34
40
|
|
|
41
|
+
**U buffer output (for fixed-width columnar reports only):**
|
|
35
42
|
```chaprola
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
CLEAR U
|
|
44
|
+
MOVE P.name U.1 20
|
|
45
|
+
PUT sal INTO U.22 10 D 2
|
|
46
|
+
PRINT 0 // output entire U buffer, then clear
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
## Hello World (no data file)
|
|
41
50
|
|
|
42
51
|
```chaprola
|
|
43
|
-
|
|
44
|
-
PRINT 0
|
|
52
|
+
PRINT "Hello from Chaprola!"
|
|
45
53
|
END
|
|
46
54
|
```
|
|
47
55
|
|
|
48
56
|
## Loop Through All Records
|
|
49
57
|
|
|
58
|
+
Always start programs with `OPEN PRIMARY` to declare the data file. This makes the program self-documenting and eliminates the need for `primary_format` on compile.
|
|
59
|
+
|
|
50
60
|
```chaprola
|
|
51
|
-
|
|
61
|
+
OPEN PRIMARY "STAFF" 0
|
|
62
|
+
DEFINE VARIABLE rec R41
|
|
52
63
|
LET rec = 1
|
|
53
64
|
100 SEEK rec
|
|
54
65
|
IF EOF GOTO 900
|
|
55
|
-
|
|
56
|
-
MOVE P.name U.1 8
|
|
57
|
-
MOVE P.salary U.12 6
|
|
58
|
-
PRINT 0
|
|
66
|
+
PRINT P.name + " — " + P.salary
|
|
59
67
|
LET rec = rec + 1
|
|
60
68
|
GOTO 100
|
|
61
69
|
900 END
|
|
62
70
|
```
|
|
63
71
|
|
|
72
|
+
Compile without `primary_format` — the compiler reads the format from `OPEN PRIMARY`:
|
|
73
|
+
```bash
|
|
74
|
+
POST /compile {userid, project, name: "REPORT", source: "OPEN PRIMARY \"STAFF\" 0\n..."}
|
|
75
|
+
```
|
|
76
|
+
|
|
64
77
|
## Filtered Report
|
|
65
78
|
|
|
66
79
|
```chaprola
|
|
67
80
|
GET sal FROM P.salary
|
|
68
|
-
IF sal LT 80000 GOTO 200
|
|
69
|
-
|
|
70
|
-
PUT sal INTO U.12 10 D 0 // D=dollar format
|
|
71
|
-
PRINT 0
|
|
81
|
+
IF sal LT 80000 GOTO 200 // skip low earners
|
|
82
|
+
PRINT P.name + " — " + R41
|
|
72
83
|
200 LET rec = rec + 1
|
|
73
84
|
```
|
|
74
85
|
|
|
@@ -76,10 +87,10 @@ PRINT 0
|
|
|
76
87
|
|
|
77
88
|
```chaprola
|
|
78
89
|
OPEN "DEPARTMENTS" 0
|
|
79
|
-
FIND match FROM S.dept_code
|
|
80
|
-
IF match EQ 0 GOTO 200
|
|
81
|
-
READ match
|
|
82
|
-
|
|
90
|
+
FIND match FROM S.dept_code USING P.dept_code
|
|
91
|
+
IF match EQ 0 GOTO 200 // no match
|
|
92
|
+
READ match // load matched secondary record
|
|
93
|
+
PRINT P.name + " — " + S.dept_name
|
|
83
94
|
```
|
|
84
95
|
|
|
85
96
|
Compile with both formats so the compiler resolves fields from both files:
|
|
@@ -105,14 +116,47 @@ IF EQUAL U.200 U.180 12 GOTO 200 // match — jump to handler
|
|
|
105
116
|
## Read-Modify-Write (UPDATE)
|
|
106
117
|
|
|
107
118
|
```chaprola
|
|
108
|
-
READ match
|
|
109
|
-
GET bal FROM S.balance
|
|
110
|
-
LET bal = bal + amt
|
|
111
|
-
PUT bal INTO S.balance
|
|
112
|
-
WRITE match
|
|
113
|
-
CLOSE
|
|
119
|
+
READ match // load record
|
|
120
|
+
GET bal FROM S.balance // read current value
|
|
121
|
+
LET bal = bal + amt // modify
|
|
122
|
+
PUT bal INTO S.balance F 0 // write back to S memory (length auto-filled)
|
|
123
|
+
WRITE match // flush to disk
|
|
124
|
+
CLOSE // flush all at end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Date Arithmetic
|
|
128
|
+
|
|
129
|
+
```chaprola
|
|
130
|
+
GET DATE R41 FROM X.primary_modified // when was file last changed?
|
|
131
|
+
GET DATE R42 FROM X.utc_time // what time is it now?
|
|
132
|
+
LET R43 = R42 - R41 // difference in seconds
|
|
133
|
+
LET R43 = R43 / 86400 // convert to days
|
|
134
|
+
IF R43 GT 30 PRINT "WARNING: file is over 30 days old" ;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Get Current User
|
|
138
|
+
|
|
139
|
+
```chaprola
|
|
140
|
+
PRINT "Logged in as: " + X.username
|
|
114
141
|
```
|
|
115
142
|
|
|
143
|
+
## System Text Properties (X.)
|
|
144
|
+
|
|
145
|
+
Access system metadata by property name — no numeric positions needed:
|
|
146
|
+
|
|
147
|
+
| Property | Description |
|
|
148
|
+
|----------|-------------|
|
|
149
|
+
| `X.year` | Year (four digits) |
|
|
150
|
+
| `X.julian` | Julian date (1–366) |
|
|
151
|
+
| `X.hour` | Hour (military time) |
|
|
152
|
+
| `X.minute` | Minute (0–59) |
|
|
153
|
+
| `X.username` | Authenticated user |
|
|
154
|
+
| `X.record_num` | Primary file record number |
|
|
155
|
+
| `X.utc_time` | UTC datetime (ISO 8601) |
|
|
156
|
+
| `X.elapsed` | Elapsed execution time |
|
|
157
|
+
| `X.primary_modified` | Primary file Last-Modified |
|
|
158
|
+
| `X.secondary_modified` | Secondary file Last-Modified |
|
|
159
|
+
|
|
116
160
|
## Async for Large Datasets
|
|
117
161
|
|
|
118
162
|
```bash
|
|
@@ -140,9 +184,7 @@ SEEK 1
|
|
|
140
184
|
GOTO 300
|
|
141
185
|
200 GET cardlvl FROM P.level
|
|
142
186
|
IF cardlvl NE lvl GOTO 300 // filter by level param
|
|
143
|
-
|
|
144
|
-
MOVE P.reading U.6 10
|
|
145
|
-
PRINT 0
|
|
187
|
+
PRINT P.kanji + " — " + P.reading
|
|
146
188
|
300 LET rec = rec + 1
|
|
147
189
|
SEEK rec
|
|
148
190
|
GOTO 100
|
|
@@ -208,7 +250,7 @@ POST /query {
|
|
|
208
250
|
| `I` | Integer (right-justified) | ` 1234` |
|
|
209
251
|
| `E` | Scientific notation | `1.23E+03` |
|
|
210
252
|
|
|
211
|
-
Syntax: `PUT
|
|
253
|
+
Syntax: `PUT R41 INTO P.salary D 2` (R-var, field name, format, decimals — length auto-filled)
|
|
212
254
|
|
|
213
255
|
## Common Field Widths
|
|
214
256
|
|
|
@@ -227,19 +269,19 @@ Use these when sizing MOVE lengths and U buffer positions.
|
|
|
227
269
|
|
|
228
270
|
| Prefix | Description |
|
|
229
271
|
|--------|-------------|
|
|
230
|
-
| `P` | Primary data file
|
|
231
|
-
| `S` | Secondary data file
|
|
272
|
+
| `P` | Primary data file — use field names: `P.salary`, `P.name` |
|
|
273
|
+
| `S` | Secondary data file — use field names: `S.dept`, `S.emp_id` |
|
|
232
274
|
| `U` | User buffer (scratch for output) |
|
|
233
|
-
| `X` | System text
|
|
275
|
+
| `X` | System text — use property names: `X.username`, `X.utc_time` |
|
|
234
276
|
|
|
235
277
|
## Math Intrinsics
|
|
236
278
|
|
|
237
279
|
```chaprola
|
|
238
|
-
LET
|
|
239
|
-
LET
|
|
240
|
-
LET
|
|
241
|
-
LET
|
|
242
|
-
LET
|
|
280
|
+
LET R42 = EXP R41 // e^R41
|
|
281
|
+
LET R42 = LOG R41 // ln(R41)
|
|
282
|
+
LET R42 = SQRT R41 // √R41
|
|
283
|
+
LET R42 = ABS R41 // |R41|
|
|
284
|
+
LET R43 = POW R41 R42 // R41^R42
|
|
243
285
|
```
|
|
244
286
|
|
|
245
287
|
## Import-Download: URL → Dataset (Parquet, Excel, CSV, JSON)
|
|
@@ -281,7 +323,7 @@ HULDRA finds the best parameter values for a mathematical model by minimizing th
|
|
|
281
323
|
|-------|---------|-------------|
|
|
282
324
|
| R1–R20 | **Elements** (parameters to optimize) | HULDRA sets these before each VM run |
|
|
283
325
|
| R21–R40 | **Objectives** (error metrics) | Your program computes and stores these |
|
|
284
|
-
| R41–
|
|
326
|
+
| R41–R99 | **Scratch space** | Your program uses these for temp variables |
|
|
285
327
|
|
|
286
328
|
### Complete Example: Fit a Linear Model
|
|
287
329
|
|
|
@@ -521,6 +563,106 @@ LET PRED = PRED + R2
|
|
|
521
563
|
|
|
522
564
|
All patterns follow the same loop structure: SEEK records, GET fields, compute PRED, accumulate `(PRED - OBS)^2` in SSR, store SSR in R21 at the end.
|
|
523
565
|
|
|
566
|
+
## Parameterized Report Endpoint
|
|
567
|
+
|
|
568
|
+
Combine QUERY with PARAM to build a dynamic JSON API from a published program. QUERY output is a .QR file (read-only). R20 = matched record count. Missing PARAMs are silently replaced with blank (string) or 0.0 (numeric) — check param warnings in the response for diagnostics.
|
|
569
|
+
|
|
570
|
+
```chaprola
|
|
571
|
+
// STAFF_BY_DEPT.CS — call via /report?publisher=admin&program=STAFF_BY_DEPT&dept=Engineering
|
|
572
|
+
QUERY STAFF FIELDS name, salary, title INTO dept_staff WHERE department EQ PARAM.dept ORDER BY salary DESC
|
|
573
|
+
OPEN SECONDARY dept_staff
|
|
574
|
+
DEFINE name = S.name
|
|
575
|
+
DEFINE salary = S.salary
|
|
576
|
+
DEFINE title = S.title
|
|
577
|
+
PRINT "["
|
|
578
|
+
READ SECONDARY
|
|
579
|
+
IF FINI GOTO done
|
|
580
|
+
100 PRINT TRIM "{\"name\":\"" + name + "\",\"title\":\"" + title + "\",\"salary\":" + salary + "}"
|
|
581
|
+
READ SECONDARY
|
|
582
|
+
IF FINI GOTO done
|
|
583
|
+
PRINT ","
|
|
584
|
+
GOTO 100
|
|
585
|
+
done.
|
|
586
|
+
PRINT "]"
|
|
587
|
+
STOP
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Publish with: `POST /publish {userid, project, name: "STAFF_BY_DEPT", primary_file: "STAFF", acl: "authenticated"}`
|
|
591
|
+
Call with: `POST /report?publisher=admin&program=STAFF_BY_DEPT&dept=Engineering`
|
|
592
|
+
|
|
593
|
+
## Cross-File Filtering (IN/NOT IN)
|
|
594
|
+
|
|
595
|
+
Use QUERY with NOT IN to find records in one file that don't appear in another. This is the flashcard review pattern — find unreviewed cards by excluding already-reviewed ones. One IN/NOT IN per QUERY.
|
|
596
|
+
|
|
597
|
+
If the IN-file doesn't exist (e.g., new user with no progress), NOT IN treats it as empty — all records pass. This is correct: "kanji not in (nothing)" = all kanji.
|
|
598
|
+
|
|
599
|
+
```chaprola
|
|
600
|
+
// Step 1: Get the list of kanji the user has already reviewed
|
|
601
|
+
QUERY progress INTO reviewed WHERE username EQ PARAM.username
|
|
602
|
+
|
|
603
|
+
// Step 2: Filter flashcards to only those NOT in the reviewed set
|
|
604
|
+
// If progress doesn't exist (new user), all flashcards are returned
|
|
605
|
+
QUERY flashcards INTO new_cards WHERE kanji NOT IN reviewed.kanji
|
|
606
|
+
|
|
607
|
+
// Step 3: Loop through unreviewed cards
|
|
608
|
+
OPEN SECONDARY new_cards
|
|
609
|
+
READ SECONDARY
|
|
610
|
+
IF FINI GOTO empty
|
|
611
|
+
100 PRINT S.kanji + " — " + S.reading + " — " + S.meaning
|
|
612
|
+
READ SECONDARY
|
|
613
|
+
IF FINI GOTO done
|
|
614
|
+
GOTO 100
|
|
615
|
+
empty.
|
|
616
|
+
PRINT "All cards reviewed!"
|
|
617
|
+
done.
|
|
618
|
+
STOP
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Public Apps: Use /report, Not /query
|
|
622
|
+
|
|
623
|
+
Site keys require BAA signing. For public-facing web apps, use published reports (`/report`) instead of `/query` for all read operations. `/report` is public — no auth or BAA needed.
|
|
624
|
+
|
|
625
|
+
**Pattern:** Move data logic into Chaprola programs (QUERY, TABULATE), publish them, and call `/report` from the frontend. Reserve site keys for write operations (`/insert-record`) only.
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
// GOOD: public report — no auth needed, works for anyone
|
|
629
|
+
const url = `${API}/report?userid=myapp&project=data&name=RESULTS&poll_id=${id}`;
|
|
630
|
+
const response = await fetch(url);
|
|
631
|
+
|
|
632
|
+
// BAD: /query with site key — fails if BAA not signed (403 Forbidden)
|
|
633
|
+
const response = await fetch(`${API}/query`, {
|
|
634
|
+
headers: { 'Authorization': `Bearer ${SITE_KEY}` },
|
|
635
|
+
body: JSON.stringify({ userid: 'myapp', project: 'data', file: 'votes', where: [...] })
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Why this is better:** The program runs server-side with full access. The frontend gets clean output. No API keys exposed for reads. QUERY + TABULATE in a program replaces client-side pivot logic.
|
|
640
|
+
|
|
641
|
+
## Chart Data with TABULATE
|
|
642
|
+
|
|
643
|
+
Use TABULATE to produce CSV output suitable for charting. This example cross-tabulates mortality data by cause and year.
|
|
644
|
+
|
|
645
|
+
```chaprola
|
|
646
|
+
// Generate a pivot table of death counts by cause and year
|
|
647
|
+
TABULATE mortality SUM deaths FOR cause VS year WHERE year GE "2020" INTO trends
|
|
648
|
+
|
|
649
|
+
// Output as CSV — ready for any charting library
|
|
650
|
+
PRINT TABULATE trends AS CSV
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Output:
|
|
654
|
+
```
|
|
655
|
+
cause,2020,2021,2022,2023,total
|
|
656
|
+
Heart disease,690882,693021,699659,702000,2785562
|
|
657
|
+
Cancer,598932,602350,608371,611000,2420653
|
|
658
|
+
...
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
For web apps, use JSON output instead:
|
|
662
|
+
```chaprola
|
|
663
|
+
PRINT TABULATE trends AS JSON
|
|
664
|
+
```
|
|
665
|
+
|
|
524
666
|
### Agent Workflow Summary
|
|
525
667
|
|
|
526
668
|
1. **Inspect** — Call `/format` to see what fields exist
|
package/references/gotchas.md
CHANGED
|
@@ -11,21 +11,60 @@ LET temp = qty + bonus
|
|
|
11
11
|
LET result = price * temp
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
### No built-in functions
|
|
15
|
+
There are NO functions with parentheses. No STR(), INT(), ABS(), LEN(), TRIM(), SUBSTR(), CONCAT(), FORMAT(), TOSTRING(), etc.
|
|
16
|
+
- To convert number to text for output: `PRINT "Total: " + R41`
|
|
17
|
+
- To write number to a field: `PUT R41 INTO P.salary D 2`
|
|
18
|
+
- To convert text to number: `GET R41 FROM P.salary`
|
|
19
|
+
- To clear a field: `MOVE BLANKS P.notes` or `CLEAR U`
|
|
20
|
+
|
|
21
|
+
### Use field names, not numeric positions
|
|
22
|
+
Always use `P.salary`, `S.dept`, `X.username` — not `P.63 10`, `S.30 4`, `X.28 8`. The compiler auto-fills positions and lengths from the format file.
|
|
23
|
+
```chaprola
|
|
24
|
+
// PREFERRED: field names — readable, resilient to format changes
|
|
25
|
+
GET R41 FROM P.salary
|
|
26
|
+
PRINT P.name + " — " + P.department
|
|
27
|
+
MOVE X.username U.1
|
|
28
|
+
|
|
29
|
+
// AVOID: numeric positions — fragile, unreadable
|
|
30
|
+
GET R41 FROM P.63 10
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Use PRINT concatenation, not MOVE + PRINT 0
|
|
34
|
+
```chaprola
|
|
35
|
+
// PREFERRED: direct concatenation
|
|
36
|
+
PRINT P.name + " earns $" + R41
|
|
37
|
+
|
|
38
|
+
// AVOID: old MOVE-to-buffer pattern
|
|
39
|
+
MOVE BLANKS U.1 80
|
|
40
|
+
MOVE P.name U.1 20
|
|
41
|
+
PUT R41 INTO U.22 10 D 2
|
|
42
|
+
PRINT 0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Use CLEAR, not MOVE BLANKS for full regions
|
|
46
|
+
```chaprola
|
|
47
|
+
CLEAR U // clear entire user buffer
|
|
48
|
+
CLEAR P // clear entire primary region
|
|
49
|
+
CLEAR S // clear entire secondary region
|
|
50
|
+
MOVE BLANKS P.notes // clear a single field (length auto-filled)
|
|
51
|
+
```
|
|
52
|
+
|
|
14
53
|
### IF EQUAL compares a literal to a location
|
|
15
|
-
Cannot compare two memory locations. Copy to U buffer first.
|
|
54
|
+
Cannot compare two memory locations directly. Copy to U buffer first.
|
|
16
55
|
```chaprola
|
|
17
56
|
MOVE P.txn_type U.76 6
|
|
18
57
|
IF EQUAL "CREDIT" U.76 GOTO 200
|
|
19
58
|
```
|
|
20
59
|
|
|
21
|
-
### MOVE
|
|
22
|
-
`MOVE P.name
|
|
60
|
+
### MOVE literal auto-pads to field width
|
|
61
|
+
`MOVE "Jones" P.name` auto-fills the rest of the field with blanks. No need to clear first.
|
|
23
62
|
|
|
24
63
|
### DEFINE VARIABLE names must not collide with field names
|
|
25
|
-
If the format has a `balance` field, don't `DEFINE VARIABLE balance
|
|
64
|
+
If the format has a `balance` field, don't `DEFINE VARIABLE balance R41`. Use `bal` instead. The compiler confuses the alias with the field name.
|
|
26
65
|
|
|
27
66
|
### R-variables are floating point
|
|
28
|
-
All R1–
|
|
67
|
+
All R1–R99 are 64-bit floats. `7 / 2 = 3.5`. Use PUT with `I` format to display as integer.
|
|
29
68
|
|
|
30
69
|
### Statement numbers are labels, not line numbers
|
|
31
70
|
Only number lines that are GOTO/CALL targets. Don't number every line.
|
|
@@ -36,6 +75,13 @@ Always check `IF match EQ 0` after FIND before calling READ.
|
|
|
36
75
|
### PRINT 0 clears the U buffer
|
|
37
76
|
After PRINT 0, the buffer is empty. No need to manually clear between prints unless reusing specific positions.
|
|
38
77
|
|
|
78
|
+
### GET DATE / PUT DATE — no FOR keyword needed with property names
|
|
79
|
+
```chaprola
|
|
80
|
+
GET DATE R41 FROM X.utc_time // correct — length auto-filled
|
|
81
|
+
GET DATE R42 FROM X.primary_modified // correct — length auto-filled
|
|
82
|
+
PUT DATE R41 INTO U.1 20 // U buffer needs explicit length
|
|
83
|
+
```
|
|
84
|
+
|
|
39
85
|
## Import
|
|
40
86
|
|
|
41
87
|
### Field widths come from the longest value
|
|
@@ -74,8 +120,8 @@ Always CLOSE before END if you wrote to the secondary file. Unflushed writes are
|
|
|
74
120
|
|
|
75
121
|
## HULDRA Optimization
|
|
76
122
|
|
|
77
|
-
### Use R41–
|
|
78
|
-
R1–R20 are reserved for HULDRA elements. R21–R40 are reserved for objectives. Your VALUE program's DEFINE VARIABLE declarations must use R41–
|
|
123
|
+
### Use R41–R99 for scratch variables, not R1–R20
|
|
124
|
+
R1–R20 are reserved for HULDRA elements. R21–R40 are reserved for objectives. Your VALUE program's DEFINE VARIABLE declarations must use R41–R99 only.
|
|
79
125
|
```chaprola
|
|
80
126
|
// WRONG: DEFINE VARIABLE counter R1 (HULDRA will overwrite this)
|
|
81
127
|
// RIGHT: DEFINE VARIABLE counter R41
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Building Apps on Chaprola — Architecture Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Chaprola is a backend for frontend apps. Your React, Vue, Svelte, or Laravel app calls `api.chaprola.org` directly from the browser. No proxy server, no middleware, no infrastructure to manage.
|
|
6
|
+
|
|
7
|
+
## Two Architectures
|
|
8
|
+
|
|
9
|
+
### 1. Single-Owner App (simplest)
|
|
10
|
+
|
|
11
|
+
One Chaprola account owns all the data. The app uses a **site key** locked to its domain. All users see the same data.
|
|
12
|
+
|
|
13
|
+
**Use cases:** dashboards, public reports, internal tools, data viewers, portfolio sites.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Browser → React App → api.chaprola.org (site key in Authorization header)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Setup:**
|
|
20
|
+
1. Register a Chaprola account: `POST /register`
|
|
21
|
+
2. Sign the BAA if handling health data: `POST /sign-baa`
|
|
22
|
+
3. Create a site key locked to your domain:
|
|
23
|
+
```json
|
|
24
|
+
POST /create-site-key
|
|
25
|
+
{"userid": "myapp", "origin": "https://myapp.example.com", "label": "production"}
|
|
26
|
+
```
|
|
27
|
+
Response: `{"site_key": "site_a1b2c3..."}`
|
|
28
|
+
4. Use the site key in your frontend:
|
|
29
|
+
```javascript
|
|
30
|
+
const resp = await fetch('https://api.chaprola.org/query', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Authorization': 'Bearer site_a1b2c3...',
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({ userid: 'myapp', project: 'main', file: 'products', where: [] })
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**BAA requirement:** All data endpoints (including `/query`) require BAA signing. For public-facing apps that haven't signed a BAA, use published reports (`/report`) for reads instead of `/query`. Move filtering and aggregation into Chaprola programs (QUERY + TABULATE commands), publish them, and call `/report` from the frontend. Reserve the site key for write-only operations like `/insert-record`.
|
|
41
|
+
|
|
42
|
+
**Security model:** The site key is checked against the `Origin` HTTP header, which browsers set automatically. This prevents other websites from using your key (CORS-level protection). However, Origin headers are trivially spoofable from non-browser clients (curl, Postman, scripts). Anyone who extracts the site key from your JavaScript has full access to the account's data. **Use this pattern only for public or semi-public data** — dashboards, product catalogs, published reports. For private data, use the multi-user pattern (each user authenticates individually) or the enterprise proxy pattern.
|
|
43
|
+
|
|
44
|
+
### 2. Multi-User App (each user has their own account)
|
|
45
|
+
|
|
46
|
+
Each app user registers their own Chaprola account. The app stores their API key in the browser session. Each user has their own data silo.
|
|
47
|
+
|
|
48
|
+
**Use cases:** SaaS apps, multi-tenant platforms, apps where users own their data.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Browser → React App → api.chaprola.org (user's own API key)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Setup:**
|
|
55
|
+
1. Your app's registration form calls Chaprola directly:
|
|
56
|
+
```javascript
|
|
57
|
+
// User signs up
|
|
58
|
+
const resp = await fetch('https://api.chaprola.org/register', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ username: 'alice', passcode: 'their-secure-passcode' })
|
|
62
|
+
})
|
|
63
|
+
const { api_key } = await resp.json()
|
|
64
|
+
sessionStorage.setItem('chaprola_key', api_key)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. Your app's login form:
|
|
68
|
+
```javascript
|
|
69
|
+
const resp = await fetch('https://api.chaprola.org/login', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: JSON.stringify({ username: 'alice', passcode: 'their-secure-passcode' })
|
|
73
|
+
})
|
|
74
|
+
const { api_key } = await resp.json()
|
|
75
|
+
sessionStorage.setItem('chaprola_key', api_key)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. All subsequent API calls use the user's key:
|
|
79
|
+
```javascript
|
|
80
|
+
const key = sessionStorage.getItem('chaprola_key')
|
|
81
|
+
const resp = await fetch('https://api.chaprola.org/query', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
'Authorization': `Bearer ${key}`,
|
|
85
|
+
'Content-Type': 'application/json'
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ userid: 'alice', project: 'mydata', file: 'tasks', where: [] })
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Security model:** Each user authenticates individually. User A cannot access User B's data (userid enforcement). No shared secrets in the frontend. API keys are per-user, stored in the browser session.
|
|
92
|
+
|
|
93
|
+
**Team data sharing:** If users need to share a project, the project owner creates an `access.json` file listing writers. See the auth reference for details.
|
|
94
|
+
|
|
95
|
+
## Which Architecture Should I Use?
|
|
96
|
+
|
|
97
|
+
| Question | Single-Owner | Multi-User |
|
|
98
|
+
|----------|:---:|:---:|
|
|
99
|
+
| One person/org owns all the data? | Yes | |
|
|
100
|
+
| Users create accounts and own their data? | | Yes |
|
|
101
|
+
| Public dashboard or report viewer? | Yes | |
|
|
102
|
+
| SaaS with multiple tenants? | | Yes |
|
|
103
|
+
| Internal tool for a small team? | Yes | |
|
|
104
|
+
| App where privacy between users matters? | | Yes |
|
|
105
|
+
| Data is sensitive or private? | No — use Multi-User or Enterprise | Yes |
|
|
106
|
+
|
|
107
|
+
**Rule of thumb:** If you'd be uncomfortable with someone viewing all the data in that Chaprola account, don't use the single-owner pattern. The site key will be in your JavaScript source — assume it's public.
|
|
108
|
+
|
|
109
|
+
## Enterprise Customers
|
|
110
|
+
|
|
111
|
+
If your enterprise requires that API keys never touch the browser:
|
|
112
|
+
|
|
113
|
+
Run your own backend proxy. Your React/Laravel frontend authenticates users through your own auth system (OAuth, SSO, SAML). Your backend holds the Chaprola API key server-side and proxies requests.
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
User → Enterprise App → Enterprise Backend (holds API key) → api.chaprola.org
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This is the same pattern used with Stripe, Twilio, and other APIs. Chaprola does not provide a managed proxy — your infrastructure team owns this layer.
|
|
120
|
+
|
|
121
|
+
**Most apps do not need this.** The site key + per-user auth model handles the vast majority of use cases without any backend infrastructure.
|
|
122
|
+
|
|
123
|
+
## Site Keys Reference
|
|
124
|
+
|
|
125
|
+
Site keys are API keys locked to a specific browser origin. They prevent your key from being used on other websites.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
// Create
|
|
129
|
+
POST /create-site-key
|
|
130
|
+
{"userid": "myapp", "origin": "https://myapp.example.com", "label": "production"}
|
|
131
|
+
// Response: {"site_key": "site_...", "origin": "https://myapp.example.com"}
|
|
132
|
+
|
|
133
|
+
// List
|
|
134
|
+
POST /list-site-keys
|
|
135
|
+
{"userid": "myapp"}
|
|
136
|
+
|
|
137
|
+
// Delete
|
|
138
|
+
POST /delete-site-key
|
|
139
|
+
{"userid": "myapp", "site_key": "site_..."}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Key facts:**
|
|
143
|
+
- Format: `site_` prefix + 64 hex chars
|
|
144
|
+
- Locked to one origin (exact match on the `Origin` HTTP header)
|
|
145
|
+
- Never expire — persist until explicitly deleted
|
|
146
|
+
- Not affected by `/login` (login rotates the main API key, not site keys)
|
|
147
|
+
- Same permissions as the main API key for that account
|
|
148
|
+
- Create multiple site keys for different environments (dev, staging, production)
|
|
149
|
+
|
|
150
|
+
## Deploying Your App
|
|
151
|
+
|
|
152
|
+
For static React/Vue/Svelte apps, Chaprola can host them:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
POST /app/deploy
|
|
156
|
+
{"userid": "myapp", "project": "main", "app_name": "myapp"}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Your app is served at `https://chaprola.org/apps/{userid}/{app_name}/`. Or deploy anywhere (Vercel, Netlify, S3) and use a site key locked to that domain.
|
|
160
|
+
|
|
161
|
+
## Common Patterns
|
|
162
|
+
|
|
163
|
+
### Import data, then query it
|
|
164
|
+
```javascript
|
|
165
|
+
// Import
|
|
166
|
+
await fetch('https://api.chaprola.org/import', {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
169
|
+
body: JSON.stringify({ userid: 'myapp', project: 'main', name: 'products', data: [...] })
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Query
|
|
173
|
+
const resp = await fetch('https://api.chaprola.org/query', {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
176
|
+
body: JSON.stringify({ userid: 'myapp', project: 'main', file: 'products', where: [{ field: 'category', op: 'eq', value: 'electronics' }] })
|
|
177
|
+
})
|
|
178
|
+
const { records } = await resp.json()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Insert a record
|
|
182
|
+
```javascript
|
|
183
|
+
await fetch('https://api.chaprola.org/insert-record', {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
186
|
+
body: JSON.stringify({ userid: 'myapp', project: 'main', file: 'tasks', record: { title: 'New task', status: 'open', due: '2026-04-15' } })
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Run a published report
|
|
191
|
+
```javascript
|
|
192
|
+
// No auth needed for public reports
|
|
193
|
+
const resp = await fetch('https://api.chaprola.org/report?userid=myapp&project=main&name=DASHBOARD&format=html')
|
|
194
|
+
const html = await resp.text()
|
|
195
|
+
```
|
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Language
|
|
4
4
|
- **No parentheses in LET.** `LET result = price * qty` only. For `price * (qty + bonus)`: use `LET temp = qty + bonus` then `LET result = price * temp`.
|
|
5
|
+
- **No built-in functions.** No STR(), INT(), ABS(), LEN(), TRIM(), SUBSTR(), etc. Use PUT/GET/MOVE/PRINT concatenation instead.
|
|
6
|
+
- **Use field names, not numeric positions.** `P.salary` not `P.63 10`. `X.username` not `X.15 10`. The compiler auto-fills positions and lengths.
|
|
7
|
+
- **Use PRINT concatenation for output.** `PRINT P.name + " — " + R41` instead of MOVE-to-U-buffer + PRINT 0.
|
|
8
|
+
- **Use CLEAR for full regions.** `CLEAR U` instead of `MOVE BLANKS U.1 65536`. Also `CLEAR P`, `CLEAR S`.
|
|
9
|
+
- **MOVE literal auto-pads.** `MOVE "Jones" P.name` fills the rest of the field with blanks. No need to clear first.
|
|
5
10
|
- **IF EQUAL compares literal to location.** To compare two locations, copy both to U buffer first.
|
|
6
|
-
- **MOVE length must match field width.** If `name` is 8 chars wide, `MOVE P.name U.1 20` bleeds into adjacent fields.
|
|
7
11
|
- **DEFINE VARIABLE names must not collide with field names.** If format has `balance`, don't `DEFINE VARIABLE balance R41`.
|
|
8
12
|
- **R-variables are 64-bit floats.** `7 / 2 = 3.5`. Use PUT with `I` format for integer display.
|
|
9
13
|
- **FIND returns 0 on no match.** Always check `IF match EQ 0` before READ.
|
|
10
14
|
- **PRINT 0 clears the U buffer.** No need to manually clear between prints.
|
|
11
15
|
- **Statement numbers are labels, not line numbers.** Only number GOTO/CALL targets.
|
|
16
|
+
- **GET DATE / PUT DATE need no FOR keyword** with property names: `GET DATE R41 FROM X.utc_time`
|
|
12
17
|
|
|
13
18
|
## API
|
|
14
19
|
- **NEVER use `/import` to change field widths.** `/import` REPLACES existing data. Use `/alter` to widen/narrow fields while preserving data. See `chaprola://ref/schema`.
|
package/references/ref-huldra.md
CHANGED
|
@@ -8,7 +8,7 @@ HULDRA finds optimal parameter values for a mathematical model by minimizing the
|
|
|
8
8
|
|-------|---------|-------------|
|
|
9
9
|
| R1–R20 | Elements (parameters to optimize) | HULDRA sets before each run |
|
|
10
10
|
| R21–R40 | Objectives (error metrics) | Your program computes these |
|
|
11
|
-
| R41–
|
|
11
|
+
| R41–R99 | Scratch space | Your program's temp variables |
|
|
12
12
|
|
|
13
13
|
## POST /optimize
|
|
14
14
|
```json
|
package/references/ref-import.md
CHANGED
|
@@ -44,3 +44,40 @@ Optional `format: "fhir"` for FHIR JSON reconstruction.
|
|
|
44
44
|
## POST /download
|
|
45
45
|
`{userid, project, file, type}` → `{download_url, expires_in, size_bytes}`
|
|
46
46
|
Type: `data`, `format`, `source`, `proc`, `output`.
|
|
47
|
+
|
|
48
|
+
## sort_columns
|
|
49
|
+
|
|
50
|
+
Reorder fields and physically sort data at import time, creating a self-indexing (clustered) data file.
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
POST /import {
|
|
54
|
+
"userid": "...", "project": "...", "name": "STAFF",
|
|
55
|
+
"sort_columns": ["username", "kanji"],
|
|
56
|
+
"data": [...]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Reorders fields so sort columns come first in the format file
|
|
61
|
+
- Sorts data by those columns at import time
|
|
62
|
+
- Marks `KEY:1`, `KEY:2` in `.F` metadata
|
|
63
|
+
- Enables binary search on the clustered key during QUERY
|
|
64
|
+
|
|
65
|
+
## split_by
|
|
66
|
+
|
|
67
|
+
Split a dataset into per-group data files at import time. One `.DA` file per distinct value of the split field, with a shared `.F` format file.
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
POST /import {
|
|
71
|
+
"userid": "...", "project": "...", "name": "orders",
|
|
72
|
+
"split_by": "region",
|
|
73
|
+
"data": [...]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Creates one `.DA` per distinct value of the split field
|
|
78
|
+
- Shared `.F` format file
|
|
79
|
+
- Response includes `files_created` and `groups` object
|
|
80
|
+
|
|
81
|
+
## 5GB File Size Limit
|
|
82
|
+
|
|
83
|
+
Maximum 5GB per data file. Returns 413 error if exceeded. Use `split_by` for larger datasets.
|