@sava-info-systems/api-maker-engine 0.0.0-stage → 4.0.0-beta.7
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 +312 -2
- package/bin/api-maker.js +497 -0
- package/package.json +55 -5
package/README.md
CHANGED
|
@@ -1,3 +1,313 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://apimaker.dev">
|
|
3
|
+
<img width="220" src="https://apimaker.dev/assets/images/logo.svg" alt="API Maker"/>
|
|
4
|
+
</a>
|
|
5
|
+
</div>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
<h1 align="center">API Maker® v4</h1>
|
|
8
|
+
|
|
9
|
+
<h3 align="center">The backend engine for the AI era</h3>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
AI writes the logic. API Maker builds, guards and runs the rest.<br/>
|
|
13
|
+
<sub>A self-hosted backend engine: one self-contained program, written in Rust.</sub>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="https://www.npmjs.com/package/@sava-info-systems/api-maker-engine"><img src="https://img.shields.io/npm/v/%40sava-info-systems%2Fapi-maker-engine?style=flat-square&label=npm&color=0C53A4" alt="npm version"/></a>
|
|
18
|
+
<img src="https://img.shields.io/badge/engine-Rust-DEA584?style=flat-square&logo=rust&logoColor=black" alt="Engine written in Rust"/>
|
|
19
|
+
<img src="https://img.shields.io/badge/Linux-x64%20%7C%20arm64-2FB36A?style=flat-square&logo=linux&logoColor=white" alt="Linux x64 and arm64"/>
|
|
20
|
+
<img src="https://img.shields.io/badge/macOS-Apple%20silicon%20%7C%20Intel-2FB36A?style=flat-square&logo=apple&logoColor=white" alt="macOS Apple silicon and Intel"/>
|
|
21
|
+
<img src="https://img.shields.io/badge/Windows-x64-2FB36A?style=flat-square&logo=windows&logoColor=white" alt="Windows x64"/>
|
|
22
|
+
<img src="https://img.shields.io/badge/databases-8-2FB36A?style=flat-square" alt="8 database types"/>
|
|
23
|
+
<img src="https://img.shields.io/badge/license-proprietary-64748b?style=flat-square" alt="Proprietary license"/>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#-install"><b>Install</b></a> ·
|
|
28
|
+
<a href="#-upgrade"><b>Upgrade</b></a> ·
|
|
29
|
+
<a href="https://docs.apimaker.dev/v4/docs/getting-started/first-api.html"><b>First API</b></a> ·
|
|
30
|
+
<a href="https://docs.apimaker.dev"><b>Documentation</b></a> ·
|
|
31
|
+
<a href="https://apimaker.dev/features"><b>Features</b></a> ·
|
|
32
|
+
<a href="https://apimaker.dev"><b>Website</b></a>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<hr/>
|
|
36
|
+
|
|
37
|
+
API Maker connects to your databases and gives every table secure REST APIs at once. You add your own logic in TypeScript — custom APIs, pre and post hooks, schedulers and events — and run it all from one admin panel, on your own servers. Caching, access control, logging, monitoring and Git deployment are built in.
|
|
38
|
+
|
|
39
|
+
> **New in v4** — API Maker is now **one program written in Rust**. A single process serves the APIs, the WebSockets **and** the admin panel on every core of the machine. Nothing of Node.js is installed on a server, and the code of your accounts runs in sandboxes with a native agent for each of its eight languages.
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<table>
|
|
43
|
+
<tr>
|
|
44
|
+
<td align="center"><b>~30</b><br/>APIs for each table</td>
|
|
45
|
+
<td align="center"><b>8</b><br/>database types</td>
|
|
46
|
+
<td align="center"><b>8</b><br/>languages for your code</td>
|
|
47
|
+
<td align="center"><b>1</b><br/>program to run</td>
|
|
48
|
+
</tr>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
Nobody wrote an endpoint for this call. The table was there, so the API is there:
|
|
53
|
+
|
|
54
|
+
```http
|
|
55
|
+
GET /api/gen/admin/shop/main/products?find={stock:{$gt:0}}&sort=-price&limit=10&select=name,price
|
|
56
|
+
x-am-authorization: <API user token>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"success": true,
|
|
62
|
+
"statusCode": 200,
|
|
63
|
+
"data": [
|
|
64
|
+
{ "_id": "66f1c3…", "name": "Keyboard", "price": 49 },
|
|
65
|
+
{ "_id": "66f1c2…", "name": "Mouse", "price": 19 }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`admin` is the API path of your account, `shop` the instance, `main` the database and `products` the table. Every API answers in this shape.
|
|
71
|
+
|
|
72
|
+
## 🚀 Install
|
|
73
|
+
|
|
74
|
+
Pick the way that fits. Each one installs the build for **your operating system and processor** on its own.
|
|
75
|
+
|
|
76
|
+
| | Where | Command |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| 🖥️ **Server** | A fresh Ubuntu server (x64 or arm64) | `curl -fsSL https://apimaker.dev/v4/install.sh -o install.sh && bash install.sh --license_key=YOUR_KEY` |
|
|
79
|
+
| 📦 **npm** | Any Linux or macOS machine, Windows x64 | `npm install -g @sava-info-systems/api-maker-engine` |
|
|
80
|
+
| 🗜️ **Archive** | A server without internet access | `bash install.sh --package=api-maker-<version>-linux-<arch>.tar.gz` |
|
|
81
|
+
|
|
82
|
+
### 🖥️ On a server — the install script
|
|
83
|
+
|
|
84
|
+
Run it as **root on a fresh Ubuntu server**, with your license key at the end:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
curl -fsSL https://apimaker.dev/v4/install.sh -o install.sh && \
|
|
88
|
+
bash install.sh --version=latest --license_key=YOUR_KEY
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It reads the processor of the server, downloads the matching release and checks it before anything changes. Then it installs everything around API Maker:
|
|
92
|
+
|
|
93
|
+
- **API Maker** itself, run by systemd as the service `api_maker_be` — the APIs, the WebSockets and the admin panel.
|
|
94
|
+
- **Docker** for the sandboxes, with **MongoDB** and **Redis** as containers.
|
|
95
|
+
- **Caddy** in front, with HTTPS certificates from Let's Encrypt that renew on their own.
|
|
96
|
+
|
|
97
|
+
It ends with the admin panel URL, the API and WebSocket URLs, and the commands to start, stop and watch API Maker. Run it again to upgrade, to change a setting or to repair an installation: a version that does not start is rolled back on its own.
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><b>What the server needs, and the flags of the installer</b></summary>
|
|
101
|
+
|
|
102
|
+
**The server**
|
|
103
|
+
|
|
104
|
+
- Ubuntu 22.04, 24.04 or 26.04, the root user, and a CPU with AVX (MongoDB needs it).
|
|
105
|
+
- x86-64 or ARM64, at least 1 CPU core, 1 GB of RAM (the installer adds swap) and 20 GB of storage.
|
|
106
|
+
|
|
107
|
+
**The flags** — every question has one, so the installer can run without questions too:
|
|
108
|
+
|
|
109
|
+
| Flag | What it does |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `--license_key=YOUR_KEY` | Your license key, written to `license.txt` and bound to the server. |
|
|
112
|
+
| `--version=latest` | The version to install, or an exact one like `--version=4.0.0`. |
|
|
113
|
+
| `--package=<file or URL>` | Install from a release archive (`.tar.gz`) or an npm `.tgz` instead of downloading. |
|
|
114
|
+
| `--default` | Keep the default ports and names, ask only a few questions. |
|
|
115
|
+
| `--primary` · `--secondary` | The first server of an installation, or one more server attached to it. |
|
|
116
|
+
| `--caddy=yes\|no` · `--ssl=yes\|no` | Caddy in front of API Maker, and HTTPS with `--api_domain`, `--ws_domain`, `--panel_domain`. |
|
|
117
|
+
| `--mongoVersion=8.0.10` | The MongoDB image to run. |
|
|
118
|
+
|
|
119
|
+
Secrets and connection strings are never flags: the variables `AM_PASS_COMMUNICATION`, `AM_PASS_JWT`, `AM_PASS_DB_ENCRYPT_DECRYPT`, `AM_MONGO_CONNECTION`, `AM_REDIS_INTERNAL` and `AM_REDIS_EXTERNAL` answer those questions.
|
|
120
|
+
|
|
121
|
+
**Note for AWS users**
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
chmod 400 ~/Desktop/aws_server.pem # avoid a permission error
|
|
125
|
+
ssh -i ~/Desktop/aws_server.pem ubuntu@__server_ip__ # sign in to the server
|
|
126
|
+
sudo -s # become root, then run the installer
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The whole guide: [Install on a server](https://docs.apimaker.dev/v4/docs/getting-started/install-on-server.html).
|
|
130
|
+
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
### 📦 With npm — Linux, macOS and Windows
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm install -g @sava-info-systems/api-maker-engine
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
npm reads your system and processor and downloads only the build that runs on your machine:
|
|
140
|
+
|
|
141
|
+
| Your machine | What npm installs |
|
|
142
|
+
|---|---|
|
|
143
|
+
| Linux x64 | `@sava-info-systems/api-maker-engine-linux-x64` |
|
|
144
|
+
| Linux arm64 (Graviton, Ampere, Raspberry Pi 5…) | `@sava-info-systems/api-maker-engine-linux-arm64` |
|
|
145
|
+
| macOS Apple silicon (M1 and newer) | `@sava-info-systems/api-maker-engine-darwin-arm64` + `@sava-info-systems/api-maker-engine-sandbox-linux-arm64`, the agents of its sandboxes |
|
|
146
|
+
| macOS Intel | `@sava-info-systems/api-maker-engine-darwin-x64` + `@sava-info-systems/api-maker-engine-sandbox-linux-x64`, the agents of its sandboxes |
|
|
147
|
+
| Windows x64 (10, 11, Server 2016 and newer) | `@sava-info-systems/api-maker-engine-win32-x64` + `@sava-info-systems/api-maker-engine-sandbox-linux-x64`, the agents of its sandboxes, which run in [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/) |
|
|
148
|
+
| Windows on Arm | Run the Linux build inside [WSL 2](https://learn.microsoft.com/windows/wsl/install) |
|
|
149
|
+
|
|
150
|
+
The sandboxes of a Mac and of Windows are Linux containers: their package holds only what runs inside them (the
|
|
151
|
+
`am_agent` launcher and the agent libraries), never the Linux server or its admin panel.
|
|
152
|
+
|
|
153
|
+
Then, in the folder your installation lives in:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
mkdir my-api-maker && cd my-api-maker
|
|
157
|
+
|
|
158
|
+
api-maker init --license_key=YOUR_KEY --docker # .env with new secrets, license.txt, MongoDB + Redis in Docker
|
|
159
|
+
api-maker # start API Maker
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Open the admin panel at **http://localhost:4626**. The APIs and the WebSockets answer on **http://localhost:48246**.
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary><b>The <code>api-maker</code> command</b></summary>
|
|
166
|
+
|
|
167
|
+
| Command | What it does |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `api-maker` | Starts API Maker in the current folder: `.env`, `license.txt` and `uploads/` are read and written there. Every option of the server works: `api-maker --am.port=5000`. |
|
|
170
|
+
| `api-maker init` | Writes a `.env` with new secrets for a local MongoDB and Redis. `--license_key=KEY` writes `license.txt`, `--docker` starts MongoDB (a replica set) and Redis in Docker, `--mongo_port` and `--redis_port` choose their ports, `--force` replaces an existing `.env`. |
|
|
171
|
+
| `api-maker info` | Shows the version, the platform, and the files API Maker runs from. |
|
|
172
|
+
| `api-maker --version` | Prints the version of the server. |
|
|
173
|
+
| `api-maker help` | Shows this help and the options of the server. |
|
|
174
|
+
|
|
175
|
+
Without `--docker`, point `.env` at your own MongoDB (it has to be a replica set: a Git pull is one transaction) and Redis.
|
|
176
|
+
|
|
177
|
+
</details>
|
|
178
|
+
|
|
179
|
+
### 🗜️ From an archive — no internet needed
|
|
180
|
+
|
|
181
|
+
Every release has one archive per processor for Linux servers: `api-maker-<version>-linux-x64.tar.gz` and `api-maker-<version>-linux-arm64.tar.gz`, each with its `.sha256`. Copy it to the server and give it to the installer:
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
bash install.sh --package=api-maker-4.0.0-linux-x64.tar.gz --license_key=YOUR_KEY
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
For Windows, `api-maker-<version>-win32-x64.zip` holds the server (`api_maker_be.exe`), the agents for code that runs on Windows itself, and the admin panel: unzip it and start `api_maker_be.exe` in the folder of your installation.
|
|
188
|
+
|
|
189
|
+
### 👤 First sign-in
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
admin@admin.com | Admin_123456789
|
|
193
|
+
root@root.com | R00t_123456789
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Change both passwords right after the first sign-in: the root user in **Root Settings**, the admin user in its profile. The admin account comes with a sample shop to read: an instance, schemas, custom APIs and hooks.
|
|
197
|
+
|
|
198
|
+
### 🔑 License key
|
|
199
|
+
|
|
200
|
+
API Maker needs a license key. Write to [contact@apimaker.dev](mailto:contact@apimaker.dev) for one. The key is the first line of `license.txt` in the folder API Maker runs in; the installer and `api-maker init --license_key=…` write it for you.
|
|
201
|
+
|
|
202
|
+
## ⬆️ Upgrade
|
|
203
|
+
|
|
204
|
+
| Installed with | Upgrade with |
|
|
205
|
+
|---|---|
|
|
206
|
+
| The install script | Run it again: `bash install.sh --version=latest` (or `--version=4.0.1`). |
|
|
207
|
+
| The install script, from the admin panel | **Deploy API Maker**: pick a version, or upload the release `.tar.gz`. Every server of the installation takes it, checks it and restarts, with a roll-back when the new version does not answer. |
|
|
208
|
+
| An archive, offline | `bash install.sh --package=api-maker-<version>-linux-<arch>.tar.gz` |
|
|
209
|
+
| npm | `npm install -g @sava-info-systems/api-maker-engine@latest` |
|
|
210
|
+
|
|
211
|
+
Each version keeps its own folder on a server (`~/projects/api_maker/releases/<version>`, the last three are kept), so going back is instant.
|
|
212
|
+
|
|
213
|
+
## 📦 What's inside
|
|
214
|
+
|
|
215
|
+
Every platform package is a complete API Maker: the server, the agents of its sandboxes, and the admin panel.
|
|
216
|
+
|
|
217
|
+
| File | What it is |
|
|
218
|
+
|---|---|
|
|
219
|
+
| `api_maker_be` | The server: the APIs, the WebSockets and the admin panel, in one program (`api_maker_be.exe` on Windows). |
|
|
220
|
+
| `admin-panel/` | The admin panel, served by the server itself on `am__adminPanel__port` (4626). |
|
|
221
|
+
| `am_agent` | The program of the sandboxes, copied into every sandbox image. |
|
|
222
|
+
| `am_agent.node` | The agent of **Node.js** and **Bun** code — a Node-API addon. |
|
|
223
|
+
| `am_agent.abi3.so` | The agent of **Python** code — an extension module, for every Python 3.9+. |
|
|
224
|
+
| `libam_agent_jni.so` | The agent of **Java** and **Kotlin** code — a JVM library. |
|
|
225
|
+
| `libam_agent.so` | The agent of **.NET, Go, Rust and PHP** code — a C library. |
|
|
226
|
+
| `libam_*.dylib` | On macOS: the same agents for code that runs on the Mac itself (`runOnNativeProcess`). |
|
|
227
|
+
| `am_*.dll` | On Windows: the same agents for code that runs on Windows itself (`runOnNativeProcess`). |
|
|
228
|
+
|
|
229
|
+
The agents are release builds of the same sources as the server. Code of an account calls `g.sys.db.getAll(...)` and the agent inside its sandbox reads the database itself — no round trip to the server. The server only takes agents built from its own sources, so a release always runs with the agents it was built with.
|
|
230
|
+
|
|
231
|
+
## ⚙️ Configuration
|
|
232
|
+
|
|
233
|
+
API Maker reads its settings from `.env` in the folder it runs in (or `--envPath=<file>`), then from the environment and the command line. A setting is its path in the `am` block, with `__` between the names.
|
|
234
|
+
|
|
235
|
+
| Setting | What it is |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `BE_HOST_PORT` | Where browsers reach the APIs: `http://__ip_address__:48246` or `https://api.example.com`. |
|
|
238
|
+
| `am__port` | The port of the APIs and the WebSockets. |
|
|
239
|
+
| `am__adminPanel__port` · `am__adminPanel__folder` | The admin panel: its port, and the folder it is served from. |
|
|
240
|
+
| `am__mongo_db_connection` | The MongoDB API Maker keeps its own data in (a replica set). |
|
|
241
|
+
| `am__redisInternal` | The Redis of the processes of the installation: `{"nodes": [{port:6379, host:"127.0.0.1", pass: ""}]}` |
|
|
242
|
+
| `am__passCommunication` · `am__passJWT` · `am__passDBEncryptDecrypt` | Three secrets of your own (`passDBEncryptDecrypt`: 32 characters). |
|
|
243
|
+
|
|
244
|
+
Every setting: [API Maker configurations](https://docs.apimaker.dev/v4/docs/am-resources/api-maker-configurations.html).
|
|
245
|
+
|
|
246
|
+
## ✨ Everything in the box
|
|
247
|
+
|
|
248
|
+
| Category | Features |
|
|
249
|
+
|---|---|
|
|
250
|
+
| **Database APIs** | [Auto Generated APIs](https://apimaker.dev/auto-generated-apis-from-database) · [Schema & Validation](https://apimaker.dev/schema-validation) · [Deep Populate](https://apimaker.dev/deep-populate) · [Find and Join](https://apimaker.dev/find-and-join) · [Master Save](https://apimaker.dev/master-save) · [Multi-Tenant](https://apimaker.dev/multi-tenant) · [JSON, XML & YAML Output](https://apimaker.dev/get-output) |
|
|
251
|
+
| **Your code** | [Custom APIs](https://apimaker.dev/custom-api) · [Pre & Post Hooks](https://apimaker.dev/hooks) · [Utility Classes](https://apimaker.dev/utility-classes) · [System APIs](https://apimaker.dev/inbuilt-system) · [Schedulers](https://apimaker.dev/schedule) · [Events & Listeners](https://apimaker.dev/events-management) · [WebSockets](https://apimaker.dev/websockets) · [Internationalization](https://apimaker.dev/internationalization) |
|
|
252
|
+
| **Security** | [Access Management](https://apimaker.dev/access-management) · [APIs Security Report & Actions](https://apimaker.dev/api-security-report) · [Single Sign-On](https://apimaker.dev/single-sign) · [Secrets Management](https://apimaker.dev/secrets-management) · [Secure Sandbox](https://apimaker.dev/secure-sandbox) · [Security Features](https://apimaker.dev/security) |
|
|
253
|
+
| **Performance** | [Automatic Caching](https://apimaker.dev/automatic-caching) · [Low Memory Footprint](https://apimaker.dev/low-memory-footprint) · [Index Maker](https://apimaker.dev/index-maker) |
|
|
254
|
+
| **Team & delivery** | [Git Deployment](https://apimaker.dev/git-deployment) · [Developer Accounts](https://apimaker.dev/developer-accounts) · [Database Migrations](https://apimaker.dev/database-migrations) · [Testing Framework](https://apimaker.dev/inbuilt-testing) · [Developer Tools](https://apimaker.dev/developer-tools) |
|
|
255
|
+
| **Operations** | [API Logging](https://apimaker.dev/logging-api) · [Monitoring](https://apimaker.dev/monitoring) · [Deploy API Maker from the panel](https://docs.apimaker.dev/v4/docs/features/deploy-api-maker.html) |
|
|
256
|
+
|
|
257
|
+
**Databases**: MongoDB · MySQL · MariaDB · PostgreSQL · SQL Server · Oracle · TiDB · Percona XtraDB — in one account at the same time, with queries and joins across them.
|
|
258
|
+
|
|
259
|
+
**Languages of your code**: TypeScript and JavaScript (Node.js, Bun) · Python · Java · Kotlin · C# (.NET) · Go · Rust · PHP.
|
|
260
|
+
|
|
261
|
+
## 🤖 Built for the AI era
|
|
262
|
+
|
|
263
|
+
An AI assistant writes code in seconds. On a traditional backend, all of that code is still yours to review, secure, test and scale. On API Maker, the assistant writes only the business logic, and the framework generates, checks and runs everything around it.
|
|
264
|
+
|
|
265
|
+
- **Every item is a file.** The local client keeps your account in sync with a Git repository: custom APIs, hooks, schemas, schedulers, tests and settings are plain files any assistant can edit.
|
|
266
|
+
- **Types come with the code.** `import * as T from 'types'` gives every interface of API Maker, `import * as db from 'db-interfaces'` the interfaces of your own tables.
|
|
267
|
+
- **Tests run from the terminal.** `npm run test` runs the test cases of the account and writes the coverage.
|
|
268
|
+
- **Documentation an assistant can read**: [docs.apimaker.dev/llms.txt](https://docs.apimaker.dev/llms.txt), and every page as Markdown.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import * as T from 'types';
|
|
272
|
+
import * as db from 'db-interfaces';
|
|
273
|
+
|
|
274
|
+
async function main(g: T.IAMGlobal) {
|
|
275
|
+
const products = await g.sys.db.getAll<db.shop.main.IProducts>({
|
|
276
|
+
instance: 'shop', database: 'main', collection: 'products',
|
|
277
|
+
});
|
|
278
|
+
return products.map(p => p.name); // p is typed
|
|
279
|
+
}
|
|
280
|
+
module.exports = main;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Save it, and the custom API is live: no build, no deployment.
|
|
284
|
+
|
|
285
|
+
## 🔐 Security, enforced by the framework
|
|
286
|
+
|
|
287
|
+
| Layer | What API Maker does |
|
|
288
|
+
|---|---|
|
|
289
|
+
| **Tokens by default** | Every API needs a token unless you make it public. No valid token: 401. No group that grants the call: 403, before anything runs. |
|
|
290
|
+
| **Access down to the field** | Groups decide the instances, databases, tables, APIs and fields of every user. |
|
|
291
|
+
| **Validated writes** | Schema APIs convert and validate every write; fields can be stored encrypted or hashed. |
|
|
292
|
+
| **Your code in a sandbox** | Custom code runs in Docker containers, apart from API Maker, with a time and a memory limit. |
|
|
293
|
+
| **An encrypted admin panel** | Every request of the panel travels inside an encrypted channel (ECDH P-256, AES-256-GCM), on top of HTTP or HTTPS. |
|
|
294
|
+
| **A security report** | API Maker reads your groups, users and hooks and shows where a person could read rows that are not theirs — with a fix in one click for many findings. |
|
|
295
|
+
|
|
296
|
+
## 🔗 Resources
|
|
297
|
+
|
|
298
|
+
- [Documentation](https://docs.apimaker.dev) · [Your first API](https://docs.apimaker.dev/v4/docs/getting-started/first-api.html) · [How it works](https://docs.apimaker.dev/v4/docs/getting-started/how-it-works.html) · [Cheat sheet](https://docs.apimaker.dev/v4/docs/cheat-sheet.html)
|
|
299
|
+
- [Install on a server](https://docs.apimaker.dev/v4/docs/getting-started/install-on-server.html) · [Deploy API Maker from the panel](https://docs.apimaker.dev/v4/docs/features/deploy-api-maker.html)
|
|
300
|
+
- [Release notes](https://docs.apimaker.dev/v4/docs/blog/release-notes/release_notes_summary.html) · [FAQ](https://apimaker.dev/faq)
|
|
301
|
+
- [Video tutorials on YouTube](https://www.youtube.com/@api_maker) · [LinkedIn](https://www.linkedin.com/company/api-maker) · [Twitter](https://twitter.com/api_maker)
|
|
302
|
+
|
|
303
|
+
## 💬 Support & license
|
|
304
|
+
|
|
305
|
+
- A license key, a question, a feature request or a bug: [contact@apimaker.dev](mailto:contact@apimaker.dev) or the [contact page](https://apimaker.dev/contact).
|
|
306
|
+
- API Maker is proprietary software. Its use needs a license key: see the [terms](https://apimaker.dev/terms).
|
|
307
|
+
- Working on this repository? The developer guide is [internal/README.md](internal/README.md).
|
|
308
|
+
|
|
309
|
+
<hr/>
|
|
310
|
+
|
|
311
|
+
<p align="center">
|
|
312
|
+
API Maker® is developed and maintained by <a href="https://savainfosystems.com">SAVA Info Systems Pvt. Ltd.</a>
|
|
313
|
+
</p>
|
package/bin/api-maker.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// `api-maker` : starts API Maker v4 for the system and the processor of this machine.
|
|
4
|
+
//
|
|
5
|
+
// npm installs this package with the one package of its platform (`optionalDependencies` : each platform package says
|
|
6
|
+
// its `os` and `cpu`, npm takes the one that matches) : the server `api_maker_be`, the agents of its sandboxes and the
|
|
7
|
+
// admin panel. This file finds that package and starts its server in the current folder, with what the server can not
|
|
8
|
+
// find by itself :
|
|
9
|
+
// - the admin panel of the package (`am__adminPanel__folder`), unless the settings already name one ;
|
|
10
|
+
// - on macOS and Windows, the agents of the sandboxes : the sandboxes are Linux containers, so their agents are the
|
|
11
|
+
// ones of the sandbox package of the same processor (`-sandbox-linux-<arch>` : the launcher am_agent and the four
|
|
12
|
+
// agent libraries of Linux, nothing of the server), which npm installs next to the one of the machine
|
|
13
|
+
// (`AM_SANDBOX_AGENT`, `AM_SANDBOX_CORE`). The agents for code which runs on the Mac or the Windows machine itself are
|
|
14
|
+
// next to its server.
|
|
15
|
+
//
|
|
16
|
+
// No dependency : only Node.js (18 or newer) itself. Built into the package by scripts/release/release.sh of
|
|
17
|
+
// api_maker_be_rust, tested by scripts/release/npm/test/api-maker.test.js.
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const crypto = require('node:crypto');
|
|
22
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
23
|
+
|
|
24
|
+
const ROOT = path.join(__dirname, '..');
|
|
25
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
26
|
+
const [SCOPE, BASE] = PKG.name.split('/');
|
|
27
|
+
|
|
28
|
+
/** The platforms a package is built for, as `<process.platform>-<process.arch>`. */
|
|
29
|
+
const PLATFORMS = ['linux-x64', 'linux-arm64', 'darwin-arm64', 'darwin-x64', 'win32-x64'];
|
|
30
|
+
const PROGRAM = 'api_maker_be';
|
|
31
|
+
/** The program of the sandboxes and the four agent libraries, as a sandbox package (and a Linux package) delivers them. */
|
|
32
|
+
const SANDBOX_FILES = ['am_agent', 'am_agent.node', 'am_agent.abi3.so', 'libam_agent_jni.so', 'libam_agent.so'];
|
|
33
|
+
/** The agent libraries for code which runs on a Mac itself, next to its server. */
|
|
34
|
+
const MAC_FILES = ['libam_napi.dylib', 'libam_pyo3.dylib', 'libam_jni.dylib', 'libam_ffi.dylib'];
|
|
35
|
+
/** The agent libraries for code which runs on a Windows machine itself, next to its server. */
|
|
36
|
+
const WINDOWS_FILES = ['am_napi.dll', 'am_pyo3.dll', 'am_jni.dll', 'am_ffi.dll'];
|
|
37
|
+
|
|
38
|
+
const DEFAULTS = { port: 48246, panelPort: 4626, mongoPort: 27017, redisPort: 6379 };
|
|
39
|
+
const MONGO_IMAGE = 'mongo:8.0.10';
|
|
40
|
+
const REDIS_IMAGE = 'redis:7.0.5-alpine';
|
|
41
|
+
|
|
42
|
+
/** An error that is told to the person as it is, without a stack. */
|
|
43
|
+
class Told extends Error {}
|
|
44
|
+
|
|
45
|
+
// ───────────────────────────────────────────── the platform
|
|
46
|
+
|
|
47
|
+
/** `linux-x64`… for this machine, `null` where no package is built. */
|
|
48
|
+
function platformKey(platform = process.platform, arch = process.arch) {
|
|
49
|
+
const key = `${platform}-${arch}`;
|
|
50
|
+
return PLATFORMS.includes(key) ? key : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The npm package of a platform : `@sava-info-systems/api-maker-engine-linux-x64`. */
|
|
54
|
+
function packageOf(key) {
|
|
55
|
+
return `${SCOPE}/${BASE}-${key}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The file of the server in the package of a system : `api_maker_be.exe` on Windows. */
|
|
59
|
+
function programOf(platform) {
|
|
60
|
+
return platform === 'win32' ? `${PROGRAM}.exe` : PROGRAM;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether the sandboxes of a platform take their agents from the sandbox package of its processor : a Mac and Windows
|
|
65
|
+
* run them as Linux containers.
|
|
66
|
+
*/
|
|
67
|
+
function sandboxesFromLinux(key) {
|
|
68
|
+
return !key.startsWith('linux-');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The sandbox package of a processor : what the sandboxes of a Mac and of Windows run, the launcher am_agent and the
|
|
73
|
+
* agent libraries of Linux alone (`@sava-info-systems/api-maker-engine-sandbox-linux-arm64`).
|
|
74
|
+
*/
|
|
75
|
+
function sandboxPackageOf(arch) {
|
|
76
|
+
return packageOf(`sandbox-linux-${arch}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The folder npm installed a package in, as Node.js finds it from this package, `null` when it is not installed. */
|
|
80
|
+
function folderOf(name, from = ROOT) {
|
|
81
|
+
try {
|
|
82
|
+
return path.dirname(require.resolve(`${name}/package.json`, { paths: [from] }));
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readVersion(folder) {
|
|
89
|
+
try {
|
|
90
|
+
return fs.readFileSync(path.join(folder, 'VERSION'), 'utf8').trim();
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function unsupported(platform = process.platform, arch = process.arch) {
|
|
97
|
+
const lines = [`API Maker v4 is not built for ${platform}-${arch}.`, `It runs on ${PLATFORMS.join(', ')}.`];
|
|
98
|
+
if (platform === 'win32') {
|
|
99
|
+
lines.push('On Windows, install it inside WSL 2 (Ubuntu) : https://learn.microsoft.com/windows/wsl/install');
|
|
100
|
+
}
|
|
101
|
+
return lines.join('\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What runs on this machine : the folder of its server, the folder of the agents of its sandboxes (the same one on
|
|
106
|
+
* Linux, the sandbox package of the same processor on a Mac and on Windows) and the admin panel. Throws a `Told` error
|
|
107
|
+
* when the package of the platform is not there.
|
|
108
|
+
*/
|
|
109
|
+
function resolveRuntime(platform = process.platform, arch = process.arch) {
|
|
110
|
+
const key = platformKey(platform, arch);
|
|
111
|
+
if (!key) throw new Told(unsupported(platform, arch));
|
|
112
|
+
const name = packageOf(key);
|
|
113
|
+
const server = folderOf(name);
|
|
114
|
+
if (!server) {
|
|
115
|
+
throw new Told(
|
|
116
|
+
`${name} is not installed : it holds API Maker for this machine.\n` +
|
|
117
|
+
'npm installs it with this package unless optional dependencies are left out (--omit=optional, ' +
|
|
118
|
+
`--no-optional). Install again : npm install -g ${PKG.name}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const program = path.join(server, programOf(platform));
|
|
122
|
+
if (!fs.existsSync(program)) throw new Told(`${name} has no ${programOf(platform)} (${server}) : install it again.`);
|
|
123
|
+
// On a Mac and on Windows, the sandbox package ; the Linux package holds the same files besides its server, and is
|
|
124
|
+
// taken when it is the one installed (a package of Linux installed by hand).
|
|
125
|
+
const sandboxes = sandboxesFromLinux(key) ? folderOf(sandboxPackageOf(arch)) || folderOf(packageOf(`linux-${arch}`)) : server;
|
|
126
|
+
const panel =
|
|
127
|
+
[server, sandboxes]
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.map((folder) => path.join(folder, 'admin-panel'))
|
|
130
|
+
.find((folder) => fs.existsSync(path.join(folder, 'index.html'))) || null;
|
|
131
|
+
return { key, name, server, program, sandboxes, panel, version: readVersion(server) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ───────────────────────────────────────────── the settings the person gave
|
|
135
|
+
|
|
136
|
+
/** The arguments of the server by name, as it reads them (`--name=value`, a bare `--name` is true). */
|
|
137
|
+
function argumentsByName(args) {
|
|
138
|
+
const named = new Map();
|
|
139
|
+
for (const argument of args) {
|
|
140
|
+
const at = argument.indexOf('=');
|
|
141
|
+
const name = (at < 0 ? argument : argument.slice(0, at)).replace(/^-+/, '');
|
|
142
|
+
named.set(name, at < 0 ? true : argument.slice(at + 1));
|
|
143
|
+
}
|
|
144
|
+
return named;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The `.env` file the server reads : `--envPath`, else `.env` of `--appRoot` (the current folder by default). */
|
|
148
|
+
function envFileOf(args, cwd = process.cwd()) {
|
|
149
|
+
const named = argumentsByName(args);
|
|
150
|
+
const envPath = named.get('envPath');
|
|
151
|
+
if (typeof envPath === 'string' && envPath) return path.resolve(cwd, envPath);
|
|
152
|
+
const appRoot = named.get('appRoot');
|
|
153
|
+
return path.join(typeof appRoot === 'string' && appRoot ? path.resolve(cwd, appRoot) : cwd, '.env');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Whether the text of a `.env` file sets one of these names (to anything, empty included). */
|
|
157
|
+
function envFileSets(text, names) {
|
|
158
|
+
return names.some((name) =>
|
|
159
|
+
new RegExp(`^\\s*(?:export\\s+)?${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=`, 'm').test(text),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Whether the person set a setting already : on the command line (`arguments`), in the environment, or in the `.env`
|
|
165
|
+
* file the server reads (`envNames`). What they set is never replaced.
|
|
166
|
+
*/
|
|
167
|
+
function isSet({ argNames, envNames }, args, env, envText) {
|
|
168
|
+
const named = argumentsByName(args);
|
|
169
|
+
return (
|
|
170
|
+
argNames.some((name) => named.has(name)) ||
|
|
171
|
+
envNames.some((name) => env[name] !== undefined) ||
|
|
172
|
+
envFileSets(envText, envNames)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const PANEL_SETTING = { argNames: ['am.adminPanel.folder', 'am__adminPanel__folder'], envNames: ['am__adminPanel__folder'] };
|
|
177
|
+
const AGENT_SETTING = { argNames: ['sandboxAgent'], envNames: ['AM_SANDBOX_AGENT'] };
|
|
178
|
+
const CORE_SETTING = { argNames: ['sandboxCore'], envNames: ['AM_SANDBOX_CORE'] };
|
|
179
|
+
|
|
180
|
+
/** The environment the server starts with : the one of this process, with what the package adds. */
|
|
181
|
+
function environmentFor(runtime, args, env = process.env, cwd = process.cwd()) {
|
|
182
|
+
const out = { ...env };
|
|
183
|
+
let envText = '';
|
|
184
|
+
try {
|
|
185
|
+
envText = fs.readFileSync(envFileOf(args, cwd), 'utf8');
|
|
186
|
+
} catch {
|
|
187
|
+
// No .env : the settings come from the environment and the arguments.
|
|
188
|
+
}
|
|
189
|
+
if (sandboxesFromLinux(runtime.key) && runtime.sandboxes) {
|
|
190
|
+
const agent = path.join(runtime.sandboxes, 'am_agent');
|
|
191
|
+
if (fs.existsSync(agent) && !isSet(AGENT_SETTING, args, env, envText)) out.AM_SANDBOX_AGENT = agent;
|
|
192
|
+
if (fs.existsSync(path.join(runtime.sandboxes, 'am_agent.node')) && !isSet(CORE_SETTING, args, env, envText)) {
|
|
193
|
+
out.AM_SANDBOX_CORE = runtime.sandboxes;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (runtime.panel && !isSet(PANEL_SETTING, args, env, envText)) out.am__adminPanel__folder = runtime.panel;
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ───────────────────────────────────────────── start
|
|
201
|
+
|
|
202
|
+
/** npm keeps the modes of the files of a package, a copy made another way may not : the programs are made executable. */
|
|
203
|
+
function ensureExecutable(file) {
|
|
204
|
+
// Windows runs a program by its name (.exe) : files have no executable mode there.
|
|
205
|
+
if (process.platform === 'win32') return;
|
|
206
|
+
try {
|
|
207
|
+
const mode = fs.statSync(file).mode;
|
|
208
|
+
if ((mode & 0o111) === 0) fs.chmodSync(file, mode | 0o755);
|
|
209
|
+
} catch {
|
|
210
|
+
// Read only (a global install of root) : the start says so if it can not run.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function start(args) {
|
|
215
|
+
const runtime = resolveRuntime();
|
|
216
|
+
ensureExecutable(runtime.program);
|
|
217
|
+
if (sandboxesFromLinux(runtime.key) && !runtime.sandboxes) {
|
|
218
|
+
process.stderr.write(
|
|
219
|
+
`⚠️ ${sandboxPackageOf(process.arch)} is not installed : the sandboxes have no agent of their own, so ` +
|
|
220
|
+
'API Maker answers what their code asks of g.sys itself. Install again to get it.\n',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const child = spawn(runtime.program, args, { stdio: 'inherit', env: environmentFor(runtime, args) });
|
|
224
|
+
// The signals a terminal or a service manager sends this process go to the server ; it stops on its own. Windows
|
|
225
|
+
// has no signals : Ctrl+C reaches the server itself, through the console both share, and a `kill` of Node.js would
|
|
226
|
+
// end it at once, whatever the signal. There this process only waits for the server to end.
|
|
227
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
228
|
+
process.on(signal, () => {
|
|
229
|
+
if (process.platform === 'win32') return;
|
|
230
|
+
if (child.exitCode === null && child.signalCode === null) child.kill(signal);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
child.on('error', (error) => {
|
|
234
|
+
process.stderr.write(`❌ ${runtime.program} could not be started : ${error.message}\n`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
|
237
|
+
child.on('exit', (code, signal) => {
|
|
238
|
+
if (signal) {
|
|
239
|
+
process.removeAllListeners(signal);
|
|
240
|
+
process.kill(process.pid, signal);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
process.exit(code === null ? 1 : code);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ───────────────────────────────────────────── init
|
|
248
|
+
|
|
249
|
+
/** Characters at random, as the installer makes its secrets : letters, digits and `_`. */
|
|
250
|
+
function randomText(length) {
|
|
251
|
+
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
|
|
252
|
+
let text = '';
|
|
253
|
+
while (text.length < length) {
|
|
254
|
+
for (const byte of crypto.randomBytes(length)) {
|
|
255
|
+
// 252 = 4 × 63 : no letter is more likely than another.
|
|
256
|
+
if (byte < 252 && text.length < length) text += alphabet[byte % alphabet.length];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return text;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function portOption(named, name, fallback) {
|
|
263
|
+
const value = named.get(name);
|
|
264
|
+
if (value === undefined) return fallback;
|
|
265
|
+
const port = Number(value);
|
|
266
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Told(`--${name} is a port (1 to 65535), not '${value}'.`);
|
|
267
|
+
return port;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The `.env` of a new installation on this machine : new secrets, a MongoDB and a Redis of this machine. */
|
|
271
|
+
function envText({ port, panelPort, mongoPort, redisPort }) {
|
|
272
|
+
return `# The settings of API Maker v4, written by \`api-maker init\`. A setting is its path in the "am" block, with "__"
|
|
273
|
+
# between the names (am__logs__enableLogs=false is am.logs.enableLogs). Every setting :
|
|
274
|
+
# https://docs.apimaker.dev/v4/docs/am-resources/api-maker-configurations.html
|
|
275
|
+
|
|
276
|
+
# Where the admin panel (and the browsers of your users) reach API Maker : its web sockets too, ws:// or wss://.
|
|
277
|
+
BE_HOST_PORT="http://__ip_address__:${port}"
|
|
278
|
+
|
|
279
|
+
am__serverName=server1
|
|
280
|
+
am__port=${port} # the APIs and the web sockets
|
|
281
|
+
am__adminPanel__port=${panelPort} # the admin panel (\`api-maker\` serves the one of its package)
|
|
282
|
+
|
|
283
|
+
# API Maker's own data : a MongoDB replica set (a pull of git is one transaction), and a Redis.
|
|
284
|
+
am__mongo_db_connection="mongodb://127.0.0.1:${mongoPort}/api_maker?replicaSet=rs0&directConnection=true"
|
|
285
|
+
am__redisInternal='{"nodes": [{port:${redisPort}, host:"127.0.0.1", pass: ""}]}'
|
|
286
|
+
|
|
287
|
+
# Three secrets of this installation. Keep them : what API Maker encrypts can only be read with them.
|
|
288
|
+
am__passCommunication="${randomText(20)}"
|
|
289
|
+
am__passJWT="${randomText(32)}"
|
|
290
|
+
am__passDBEncryptDecrypt="${randomText(32)}" # 👈 32 characters : the data of the databases and of the git repositories is encrypted with it.
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function writeSecretFile(file, text) {
|
|
295
|
+
fs.writeFileSync(file, text, { mode: 0o600 });
|
|
296
|
+
try {
|
|
297
|
+
fs.chmodSync(file, 0o600);
|
|
298
|
+
} catch {
|
|
299
|
+
// A file system without modes.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function docker(args, options = {}) {
|
|
304
|
+
return spawnSync('docker', args, { encoding: 'utf8', ...options });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function sleep(milliseconds) {
|
|
308
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function waitFor(what, test, seconds) {
|
|
312
|
+
const until = Date.now() + seconds * 1000;
|
|
313
|
+
while (Date.now() < until) {
|
|
314
|
+
if (test()) return;
|
|
315
|
+
sleep(1000);
|
|
316
|
+
}
|
|
317
|
+
throw new Told(`${what} did not answer within ${seconds} seconds.`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** A container of Docker by name : `running`, `stopped`, or `null` when there is none. */
|
|
321
|
+
function containerState(name) {
|
|
322
|
+
const found = docker(['ps', '-a', '--filter', `name=^/${name}$`, '--format', '{{.State}}']);
|
|
323
|
+
const state = (found.stdout || '').trim();
|
|
324
|
+
if (!state) return null;
|
|
325
|
+
return state === 'running' ? 'running' : 'stopped';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function ensureContainer(name, runArgs) {
|
|
329
|
+
const state = containerState(name);
|
|
330
|
+
if (state === 'running') return `${name} runs already`;
|
|
331
|
+
const done = state === 'stopped' ? docker(['start', name]) : docker(['run', '-d', '--name', name, ...runArgs]);
|
|
332
|
+
if (done.status !== 0) throw new Told(`Docker could not start ${name} :\n${(done.stderr || done.stdout || '').trim()}`);
|
|
333
|
+
return state === 'stopped' ? `${name} started again` : `${name} started`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** MongoDB (a replica set of one member) and Redis in Docker, on ports of this machine only. */
|
|
337
|
+
function startDatabases({ mongoPort, redisPort }) {
|
|
338
|
+
const info = docker(['info', '--format', '{{.ServerVersion}}']);
|
|
339
|
+
if (info.error || info.status !== 0) {
|
|
340
|
+
throw new Told('Docker does not answer : start Docker (Docker Desktop, OrbStack, or the docker service) and run this again.');
|
|
341
|
+
}
|
|
342
|
+
const mongo = `api_maker_mongodb_${mongoPort}`;
|
|
343
|
+
const redis = `api_maker_redis_${redisPort}`;
|
|
344
|
+
say(`🔶 ${ensureContainer(mongo, ['--restart', 'unless-stopped', '-p', `127.0.0.1:${mongoPort}:27017`, '-v', `${mongo}:/data/db`, MONGO_IMAGE, '--replSet', 'rs0', '--bind_ip_all'])}`);
|
|
345
|
+
const shell = (script) => docker(['exec', mongo, 'mongosh', '--quiet', '--eval', script]);
|
|
346
|
+
waitFor('MongoDB', () => (shell('db.runCommand({ ping: 1 }).ok').stdout || '').trim() === '1', 90);
|
|
347
|
+
// The member is named as the container sees itself : clients connect to it directly (directConnection=true).
|
|
348
|
+
shell("try { rs.status().ok } catch (e) { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: '127.0.0.1:27017' }] }).ok }");
|
|
349
|
+
waitFor('The replica set of MongoDB', () => (shell('db.hello().isWritablePrimary').stdout || '').trim() === 'true', 60);
|
|
350
|
+
say(`✅ MongoDB (replica set rs0) on 127.0.0.1:${mongoPort}`);
|
|
351
|
+
say(`🔶 ${ensureContainer(redis, ['--restart', 'unless-stopped', '-p', `127.0.0.1:${redisPort}:6379`, REDIS_IMAGE])}`);
|
|
352
|
+
waitFor('Redis', () => (docker(['exec', redis, 'redis-cli', 'ping']).stdout || '').trim() === 'PONG', 30);
|
|
353
|
+
say(`✅ Redis on 127.0.0.1:${redisPort}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function init(args, cwd = process.cwd()) {
|
|
357
|
+
const named = argumentsByName(args);
|
|
358
|
+
const known = new Set(['force', 'license_key', 'docker', 'port', 'panel_port', 'mongo_port', 'redis_port']);
|
|
359
|
+
for (const name of named.keys()) {
|
|
360
|
+
if (!known.has(name)) throw new Told(`api-maker init : unknown option --${name} (api-maker help).`);
|
|
361
|
+
}
|
|
362
|
+
const ports = {
|
|
363
|
+
port: portOption(named, 'port', DEFAULTS.port),
|
|
364
|
+
panelPort: portOption(named, 'panel_port', DEFAULTS.panelPort),
|
|
365
|
+
mongoPort: portOption(named, 'mongo_port', DEFAULTS.mongoPort),
|
|
366
|
+
redisPort: portOption(named, 'redis_port', DEFAULTS.redisPort),
|
|
367
|
+
};
|
|
368
|
+
const force = named.get('force') === true;
|
|
369
|
+
const envFile = path.join(cwd, '.env');
|
|
370
|
+
if (fs.existsSync(envFile) && !force) {
|
|
371
|
+
say(`👉 ${envFile} is there already : it is kept (--force writes a new one, with new secrets).`);
|
|
372
|
+
} else {
|
|
373
|
+
writeSecretFile(envFile, envText(ports));
|
|
374
|
+
say(`✅ Settings written : ${envFile}`);
|
|
375
|
+
}
|
|
376
|
+
const key = named.get('license_key');
|
|
377
|
+
if (key !== undefined) {
|
|
378
|
+
if (typeof key !== 'string' || !key.trim()) throw new Told('--license_key needs the key : --license_key=YOUR_KEY');
|
|
379
|
+
const licenseFile = path.join(cwd, 'license.txt');
|
|
380
|
+
if (fs.existsSync(licenseFile) && !force) {
|
|
381
|
+
say(`👉 ${licenseFile} is there already : it is kept (--force replaces it).`);
|
|
382
|
+
} else {
|
|
383
|
+
writeSecretFile(licenseFile, `${key.trim()}\n`);
|
|
384
|
+
say(`✅ License key written : ${licenseFile}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (named.get('docker') === true) startDatabases(ports);
|
|
388
|
+
say('');
|
|
389
|
+
if (named.get('docker') !== true) {
|
|
390
|
+
say('API Maker needs a MongoDB replica set and a Redis, as the .env names them. In Docker :');
|
|
391
|
+
say(` docker run -d --name api_maker_mongodb_${ports.mongoPort} -p 127.0.0.1:${ports.mongoPort}:27017 ${MONGO_IMAGE} --replSet rs0 --bind_ip_all`);
|
|
392
|
+
say(` docker exec api_maker_mongodb_${ports.mongoPort} mongosh --quiet --eval "rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]})"`);
|
|
393
|
+
say(` docker run -d --name api_maker_redis_${ports.redisPort} -p 127.0.0.1:${ports.redisPort}:6379 ${REDIS_IMAGE}`);
|
|
394
|
+
say('(or run `api-maker init --docker`, which does it)');
|
|
395
|
+
say('');
|
|
396
|
+
}
|
|
397
|
+
say('Start API Maker in this folder : api-maker');
|
|
398
|
+
say(`The admin panel : http://localhost:${ports.panelPort}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ───────────────────────────────────────────── info, help
|
|
402
|
+
|
|
403
|
+
function info() {
|
|
404
|
+
const key = platformKey();
|
|
405
|
+
say(`${PKG.name} ${PKG.version}`);
|
|
406
|
+
say(`Machine ${process.platform}-${process.arch}${key ? '' : ' (no build for it)'}`);
|
|
407
|
+
say(`Node.js ${process.version}`);
|
|
408
|
+
if (!key) {
|
|
409
|
+
say('');
|
|
410
|
+
say(unsupported());
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const runtime = resolveRuntime();
|
|
414
|
+
say(`Package ${runtime.name} ${runtime.version || '?'}${runtime.version && runtime.version !== PKG.version ? ` ⚠️ not ${PKG.version}` : ''}`);
|
|
415
|
+
say(`Server ${runtime.program}`);
|
|
416
|
+
say(`Admin panel ${runtime.panel || '(none)'}`);
|
|
417
|
+
const files = (folder, names) => names.map((name) => `${fs.existsSync(path.join(folder, name)) ? '✔' : '✘'} ${name}`).join(' ');
|
|
418
|
+
say(`Sandbox agents ${runtime.sandboxes ? `${runtime.sandboxes}\n ${files(runtime.sandboxes, SANDBOX_FILES)}` : `(${sandboxPackageOf(process.arch)} is not installed)`}`);
|
|
419
|
+
if (runtime.key.startsWith('darwin-')) say(`Mac agents ${files(runtime.server, MAC_FILES)}`);
|
|
420
|
+
if (runtime.key.startsWith('win32-')) say(`Windows agents ${files(runtime.server, WINDOWS_FILES)}`);
|
|
421
|
+
const version = spawnSync(runtime.program, ['--version'], { encoding: 'utf8' });
|
|
422
|
+
say(`Runs here ${version.status === 0 ? `yes : ${version.stdout.trim()}` : `no : ${(version.stderr || version.error?.message || '').trim()}`}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function help() {
|
|
426
|
+
say(`api-maker : API Maker v4 (${PKG.version}) for this machine.
|
|
427
|
+
|
|
428
|
+
api-maker [server options] Start API Maker in the current folder (.env, license.txt and uploads/ are there).
|
|
429
|
+
Every option of the server works, e.g. api-maker --am.port=5000
|
|
430
|
+
api-maker init [options] Write a .env with new secrets for a MongoDB and a Redis of this machine.
|
|
431
|
+
--license_key=KEY write license.txt with the key
|
|
432
|
+
--docker start MongoDB (a replica set) and Redis in Docker
|
|
433
|
+
--port=${DEFAULTS.port} --panel_port=${DEFAULTS.panelPort} --mongo_port=${DEFAULTS.mongoPort} --redis_port=${DEFAULTS.redisPort}
|
|
434
|
+
--force replace an existing .env and license.txt
|
|
435
|
+
api-maker info The version, the platform and the files API Maker runs from.
|
|
436
|
+
api-maker --version The version of the server.
|
|
437
|
+
api-maker help This help, then the options of the server.
|
|
438
|
+
|
|
439
|
+
Documentation : https://docs.apimaker.dev`);
|
|
440
|
+
if (!platformKey()) return;
|
|
441
|
+
try {
|
|
442
|
+
const runtime = resolveRuntime();
|
|
443
|
+
const server = spawnSync(runtime.program, ['--help'], { encoding: 'utf8' });
|
|
444
|
+
if (server.status === 0) say(`\nThe server :\n${server.stdout.trimEnd()}`);
|
|
445
|
+
} catch {
|
|
446
|
+
// The package of the platform is missing : `api-maker info` says so.
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function say(line) {
|
|
451
|
+
process.stdout.write(`${line}\n`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function main(argv) {
|
|
455
|
+
const [command, ...rest] = argv;
|
|
456
|
+
switch (command) {
|
|
457
|
+
case 'init':
|
|
458
|
+
return init(rest);
|
|
459
|
+
case 'info':
|
|
460
|
+
return info();
|
|
461
|
+
case 'help':
|
|
462
|
+
case '-h':
|
|
463
|
+
case '--help':
|
|
464
|
+
return help();
|
|
465
|
+
case 'start':
|
|
466
|
+
return start(rest);
|
|
467
|
+
default:
|
|
468
|
+
return start(argv);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (require.main === module) {
|
|
473
|
+
try {
|
|
474
|
+
main(process.argv.slice(2));
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (!(error instanceof Told)) throw error;
|
|
477
|
+
process.stderr.write(`❌ ${error.message}\n`);
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = {
|
|
483
|
+
PLATFORMS,
|
|
484
|
+
platformKey,
|
|
485
|
+
packageOf,
|
|
486
|
+
sandboxPackageOf,
|
|
487
|
+
folderOf,
|
|
488
|
+
resolveRuntime,
|
|
489
|
+
argumentsByName,
|
|
490
|
+
envFileOf,
|
|
491
|
+
envFileSets,
|
|
492
|
+
environmentFor,
|
|
493
|
+
envText,
|
|
494
|
+
randomText,
|
|
495
|
+
init,
|
|
496
|
+
Told,
|
|
497
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@sava-info-systems/api-maker-engine",
|
|
3
|
+
"version": "4.0.0-beta.7",
|
|
4
|
+
"description": "🚀 API Maker® v4 — the backend engine for the AI era. Connect a database, get every API, add your logic : API Maker locks every API and checks your code for data leaks. One program written in Rust : npm installs the build of your system and processor.",
|
|
5
|
+
"homepage": "https://apimaker.dev",
|
|
6
|
+
"author": "SAVA INFO SYSTEMS PRIVATE LIMITED",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"SAVA Info Systems Pvt. Ltd. <savainfosystems4@gmail.com> (https://savainfosystems.com)"
|
|
9
|
+
],
|
|
10
|
+
"license": "proprietary",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"api maker",
|
|
13
|
+
"backend solution",
|
|
14
|
+
"backend engine for the ai era",
|
|
15
|
+
"self-hosted backend",
|
|
16
|
+
"automatic api generator",
|
|
17
|
+
"rest api generator",
|
|
18
|
+
"custom apis",
|
|
19
|
+
"websocket notifications",
|
|
20
|
+
"schedulers",
|
|
21
|
+
"graphql alternative",
|
|
22
|
+
"supabase alternative",
|
|
23
|
+
"firebase alternative",
|
|
24
|
+
"xano alternative",
|
|
25
|
+
"automatic caching",
|
|
26
|
+
"database migration scripts",
|
|
27
|
+
"git integration",
|
|
28
|
+
"low code backend",
|
|
29
|
+
"mongodb apis generator",
|
|
30
|
+
"mysql apis generator",
|
|
31
|
+
"postgresql apis generator",
|
|
32
|
+
"sql server apis generator",
|
|
33
|
+
"oracle apis generator",
|
|
34
|
+
"mariadb apis generator",
|
|
35
|
+
"rust"
|
|
36
|
+
],
|
|
37
|
+
"bin": {
|
|
38
|
+
"api-maker": "bin/api-maker.js"
|
|
39
|
+
},
|
|
40
|
+
"os": ["linux", "darwin", "win32"],
|
|
41
|
+
"cpu": ["x64", "arm64"],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@sava-info-systems/api-maker-engine-linux-x64": "4.0.0-beta.7",
|
|
47
|
+
"@sava-info-systems/api-maker-engine-linux-arm64": "4.0.0-beta.7",
|
|
48
|
+
"@sava-info-systems/api-maker-engine-darwin-arm64": "4.0.0-beta.7",
|
|
49
|
+
"@sava-info-systems/api-maker-engine-darwin-x64": "4.0.0-beta.7",
|
|
50
|
+
"@sava-info-systems/api-maker-engine-win32-x64": "4.0.0-beta.7",
|
|
51
|
+
"@sava-info-systems/api-maker-engine-sandbox-linux-arm64": "4.0.0-beta.7",
|
|
52
|
+
"@sava-info-systems/api-maker-engine-sandbox-linux-x64": "4.0.0-beta.7"
|
|
53
|
+
},
|
|
54
|
+
"files": ["bin", "README.md"],
|
|
55
|
+
"publishConfig": { "access": "public" }
|
|
56
|
+
}
|