Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Items as files

Every item of an account is a folder of files in its Git repository and in the folder of the local client. An AI agent which adds a custom API, a hook or a scheduler writes these files : this page says which files each kind has, what is in each one, and what makes an item new. Pick your language in the tabs : the files of the code follow it.

In short

  • src/<kind>/<item>/ holds <item>.yaml (the item, without its code), <item>.<ts|py|php|cs|go|rs|java|kt> (its code : the extension is its language) and, for a few kinds, <item>.config.ts (its settings, TypeScript in every language).
  • A new item has no _id and no guid : API Maker gives them. Code next to the YAML is read when the YAML lists versions with one version "-1".
  • Run the local client (npm run start) : a saved folder is checked and saved the way the admin panel saves it, and ✗ Not applied on server : … says what is wrong.

The whole tree

A repository with one item of each kind, its code in the language of the tab :

A repository, the code in TypeScript
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.ts                                                  the code
    pre-hooks/check_weight.ts                                        a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.ts
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.ts
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.ts                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.ts
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.ts
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.ts
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.ts
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.ts                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg.ts                                           its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in Python
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
pyproject.toml                                                       the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.py                                                  the code
    pre-hooks/check_weight.py                                        a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.py
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.py
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.py                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.py
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.py
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.py
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.py
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.py                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg.py                                           its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in PHP
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
composer.json                                                        the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.php                                                 the code
    pre-hooks/check_weight.php                                       a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.php
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.php
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.php                                        who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.php
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.php
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.php
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.php
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.php                                                the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg.php                                          its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in C#
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
ApiMaker.csproj  am/dotnet/                                          the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.cs                                                  the code
    pre-hooks/check_weight.cs                                        a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.cs
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.cs
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.cs                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.cs
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.cs
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.cs
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.cs
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.cs                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg.cs                                           its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in Go
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
go.mod  am/am.go  am/methods.go                                      the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.go                                                  the code
    pre-hooks/check_weight/check_weight.go                           a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.go
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.go
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.go                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.go
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.go
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.go
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.go
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.go                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg/default_db_login.fg.go                       its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in Rust
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
Cargo.toml  am/rust/ (project.rs names every .rs file as a module)   the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.rs                                                  the code
    pre-hooks/check_weight.rs                                        a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.rs
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.rs
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.rs                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.rs
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.rs
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.rs
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.rs
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.rs                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg.rs                                           its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in Java
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
settings.gradle.kts  am/java/build.gradle.kts  am/jvm/am/            the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.java                                                the code
    pre-hooks/check_weight/check_weight.java                         a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.java
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.java
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.java                                       who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.java
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.java
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.java
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.java
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.java                                               the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg/default_db_login.fg.java                     its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker
A repository, the code in Kotlin
AGENTS.md                                                            the guide for AI agents, written by API Maker
package.json  tsconfig.json  am-types/types.ts  localClient.js       written by API Maker
settings.gradle.kts  am/kotlin/build.gradle.kts  am/jvm/am/          the project of the language, written by API Maker
src/
  custom_apis/shippingCost/
    shippingCost.yaml                                                name, path, versions and their hooks (no code)
    shippingCost.config.ts                                           path, method, access, schemas : TypeScript in every language
    shippingCost.kt                                                  the code
    pre-hooks/check_weight/check_weight.kt                           a hook of the API, named in versions[].preHooks
  collection_hooks/Row_scope-orders/                                 one folder per hook
    Row_scope-orders.yaml                                            its table, pre or post, its order
    Row_scope-orders.kt
  events/Order_placed/
    Order_placed.yaml                                                its listeners, its triggers
    listeners/Notify_the_shop/Notify_the_shop.kt
  web_socket_events/defaultorders-updated/                           the event default/orders-updated
    defaultorders-updated.yaml
    defaultorders-updated.kt                                         who may subscribe
  schedulers/Clean_old_carts/
    Clean_old_carts.yaml                                             intervals, isActive
    Clean_old_carts.kt
  database_migrations/Add_description/
    Add_description.yaml                                             label, order, active
    Add_description.kt
  process_initializers/Load_rates/
    Load_rates.yaml                                                  order
    Load_rates.kt
  utility_classes/utils---Money/                                     the class Money of the folder utils/
    Money.yaml
    Money.kt
  test_cases/Shipping_cost/
    Shipping_cost.yaml                                               what it tests, its mocks
    Shipping_cost.kt                                                 the tests
  schemas/<guid>/                                                    a table : its YAML and <guid>.config.ts
  instance_api_settings/orders/                                      settings of a table : orders.yaml, orders.config.ts
  system_api_settings/HASH_DATA/                                     settings of a system API : HASH_DATA.yaml, HASH_DATA.config.ts
  auth_providers/default_db_login/
    default_db_login.yaml
    default_db_login.ts                                              what the provider is : TypeScript in every language
    default_db_login.fg/default_db_login.fg.kt                       its fields generator
  groups/Shop_app/Shop_app.yaml
  api_users/shop_app/shop_app.yaml
  instances/mongodb/mongodb.yaml
  i18ns/hindi/hindi.yaml  hindi.ts
  admin_user/data.yaml                                               sandboxes : runtimes, versions, packages
  ssr/<website>/                                                     an SSR website, in TypeScript
  types/db-interfaces.d.ts  api_security_report/  auto_increments/   written by API Maker

The rules of every item

Folders and file names

Rule Example
One folder per kind in src, the kind in small letters with _ src/custom_apis, src/web_socket_events
One folder per item : its name, where what is no letter, digit, ., - or _ is written _ the scheduler Daily report is src/schedulers/Daily_report/
- and -- between the parts of a name lose their spaces the hook Row scope - orders is Row_scope-orders/
The files of an item are named as its folder Daily_report/Daily_report.yaml, Daily_report/Daily_report.py
The name itself is name: in the YAML : nothing is read back from the folder name: Daily report
  • A utility class is its folder with - for /, then --, then its name : the class Money of the folder utils/ is src/utility_classes/utils---Money/ (of cloud/db/ : cloud-db---Money/), its YAML Money.yaml, its code Money.<extension>.
  • A schema is the folder of its guid when API Maker writes it. A new one can have any folder (the name of its table) : the next commit writes it under its guid.
  • The settings of a system API are the folder of the API : src/system_api_settings/GET_TOKEN/. A WebSocket event is its name without / and @ : default/orders-updated is defaultorders-updated/.
  • Two hooks of one name : the second one gets its table, then a number (Check_access, Check_access--orders). See Hooks in Git.

The YAML

  • The item as the admin panel saves it, without its code and without the keys of its settings file. Write the keys this page shows for its kind ; API Maker writes the others back with their defaults.
  • A new item : no _id and no guid. A guid copied from another item replaces that item : never copy a folder with its guid.
  • The language of the code is the extension of its file : orders.py next to orders.yaml is Python. codeLanguage: python in the YAML says the same, and wins over the extension.

The code

  • One function main in each file, which gets g : what it returns is the answer. The code blocks of this page show it in each language ; the global object g has every call.
  • An API and all its hooks are one language. In Go, Java and Kotlin, a piece of code which shares a folder with other code (the hooks of a custom API, the functions of an auth provider) is in a folder named as its file : pre-hooks/check_weight/check_weight.go.
  • A file of Go never ends as Go reads names : orders_test is written orders_test_.go.

Versions

  • versions in the YAML is the list of the versions of the code. One version "-1" (named Original) : the code is <item>.<extension>, next to the YAML.
  • Several versions : versions/<version>/<Name_of_the_version>.<extension>, its hooks in versions/<version>/pre-hooks/ and post-hooks/, and isActive: true on the version which runs.

The settings

  • <item>.config.ts for a custom API, a schema and the settings of a database, a table, an API or a system API ; <item>.ts for an auth provider and for texts (i18n). They are TypeScript in every language : API Maker runs them itself when it saves. Each exports one object : module.exports = customApi;.
  • The settings file wins over the YAML for the keys it has : the name, path, requestMethod, apiAccessType… of a custom API, the columns of an auth provider. Keep name the same in both : it names the folder.

Files API Maker writes : never edit them

AGENTS.md (the block of API Maker), am-types/types.ts, src/types/db-interfaces.d.ts, localClient.js, package.json, tsconfig.json, pyproject.toml, composer.json, go.mod, Cargo.toml, ApiMaker.csproj, settings.gradle.kts, am/, src/api_security_report/, src/auto_increments/. A package goes into src/admin_user/data.yaml : see Packages.

Check a change

  • With the local client : npm run start sends each saved folder, and the server answers ✓ Saved on server or ✗ Not applied on server : <errors>. Deleting the folder of an item deletes the item.
  • npm run test runs the test cases of the account with the code of the folder.
  • The compiler of the language, offline :
npx tsc --noEmit
python -m compileall src
php -l src/custom_apis/shippingCost/shippingCost.php
dotnet build
go build ./... && go vet ./...
cargo check
gradle check
gradle check

Every kind at a glance

Kind Folder in src Files Code
Custom API custom_apis/ YAML, .config.ts, code, .docs.json, pre-hooks/, post-hooks/, versions/ main, the answer
Hook of an instance, a database, a table, an API, a system API instance_hooks/, database_hooks/, collection_hooks/, instance_api_hooks/, system_api_hooks/ YAML, code : one folder per hook main, before or after the APIs
Event events/ YAML, listeners/<listener>/<listener>.<ext> one main per listener
WebSocket event web_socket_events/ YAML, code who may subscribe
Scheduler schedulers/ YAML, code the job
Database migration database_migrations/ YAML, code runs once per environment, after a pull
Process initializer process_initializers/ YAML, code runs once per runner, when it starts
Utility class utility_classes/ YAML, code shared by the code of its language
Test case test_cases/ YAML, code the tests
Schema schemas/ YAML, .config.ts none : TypeScript settings
Settings of a database, a table, an API instance_api_settings/ YAML, .config.ts none
Settings of a system API system_api_settings/ YAML, .config.ts none
Auth provider auth_providers/ YAML, .ts, .fg.<ext>, .tg.<ext>, .tv.<ext> the functions of the provider
Group, API user, instance groups/, api_users/, instances/ YAML none
Texts, log profiles, saved requests i18ns/, log_profiles/, api_testing_states/ YAML, and .ts for texts none
Packages and sandboxes admin_user/ data.yaml, sandbox/<family>/Dockerfile none
SSR website ssr/<website>/ a project of TypeScript pages

Custom API

A function behind a path and a method : see Custom APIs. The custom API shippingCost gives the cost of a parcel from the rates table ; the test case below tests it.

src/custom_apis/shippingCost/shippingCost.yaml
name: shippingCost          # the same name as in shippingCost.config.ts : it names the folder and the files
path: /shipping-cost        # as in the settings file, which wins
versions:
  - name: Original
    version: "-1"           # "-1" : the code is the file next to this one, shippingCost.<extension>
    isActive: true
    preHooks: []            # the hooks of the API, see below
    postHooks: []
src/custom_apis/shippingCost/shippingCost.config.ts
import * as T from 'types';

// The settings of the API : TypeScript, whatever the language of its code.
const customApi: T.ICustomApiSettingsTypes = {
    name: 'shippingCost',
    path: '/shipping-cost',                         // POST /api/custom-api/<user path>/shipping-cost
    requestMethod: T.ERequestMethod.POST,
    apiAccessType: T.EAPIAccessType.TOKEN_ACCESS,   // IS_PUBLIC : no token ; NO_ACCESS : from code only
    errorList: ['Too heavy to ship.'],              // the messages it throws, for i18n and its docs
    reqBodySchema: {                                // checked before the code runs
        weight: { __type: T.EType.number, validations: { required: true, min: 0 } },
    },
};
module.exports = customApi;
src/custom_apis/shippingCost/shippingCost.ts
1
2
3
4
5
6
7
8
9
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const weight = Number(g.req.body.weight || 0);
    if (weight > 30) throw [{ code: 422, message: 'Too heavy to ship.' }];
    const rates = await g.sys.db.getAll({ instance: 'mysql_8', database: 'shop', collection: 'rates' });
    return { cost: weight * rates[0].per_kg };
}
module.exports = main;
src/custom_apis/shippingCost/shippingCost.py
1
2
3
4
5
6
7
8
9
from am import Errors, G


async def main(g: G):
    weight = float(g.req.body.weight or 0)
    if weight > 30:
        raise Errors('Too heavy to ship.', code=422)
    rates = await g.sys.db.get_all('mysql_8', 'shop', 'rates')
    return {'cost': weight * rates[0]['per_kg']}
src/custom_apis/shippingCost/shippingCost.php
<?php
function main(AmG $g)
{
    $weight = (float) ($g->req->body->weight ?? 0);
    if ($weight > 30) {
        throw new AmErrors('Too heavy to ship.', 422);
    }
    $rates = $g->sys->db->getAll('mysql_8', 'shop', 'rates');
    return ['cost' => $weight * $rates[0]->per_kg];
}
src/custom_apis/shippingCost/shippingCost.cs
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var weight = g.Req.Body.GetDouble("weight");
        if (weight > 30) throw new AmErrors("Too heavy to ship.", 422);
        var rates = await g.Sys.Db.GetAll(new GetAll("mysql_8", "shop", "rates"));
        return new { cost = weight * rates[0].GetDouble("per_kg") };
    }
}
src/custom_apis/shippingCost/shippingCost.go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
    weight := g.Req.Body().Float("weight")
    if weight > 30 {
        return nil, am.Fail(422, "Too heavy to ship.")
    }
    rates, err := g.Sys.Db.GetAll(am.GetAll{Instance: "mysql_8", Database: "shop", Collection: "rates"})
    if err != nil {
        return nil, err
    }
    return am.M{"cost": weight * rates[0].Float("per_kg")}, nil
}
src/custom_apis/shippingCost/shippingCost.rs
use am::{Errors, G, GetAll, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let weight = g.req.body()["weight"].as_f64().unwrap_or(0.0);
    if weight > 30.0 {
        return Err(am::fail(422, "Too heavy to ship."));
    }
    let rates = g.sys.db.get_all(GetAll::new("mysql_8", "shop", "rates"))?;
    let per_kg = rates.first().and_then(|rate| rate["per_kg"].as_f64()).unwrap_or(0.0);
    Ok(json!({ "cost": weight * per_kg }))
}
src/custom_apis/shippingCost/shippingCost.java
import am.AmErrors;
import am.G;
import am.GetAll;
import am.JsonObject;
import java.util.List;
import java.util.Map;

class Api {
    static Object main(G g) {
        double weight = g.req.getBody().getDouble("weight");
        if (weight > 30) throw new AmErrors("Too heavy to ship.", 422);
        List<JsonObject> rates = g.sys.db.getAll(new GetAll("mysql_8", "shop", "rates"));
        return Map.of("cost", weight * rates.get(0).getDouble("per_kg"));
    }
}
src/custom_apis/shippingCost/shippingCost.kt
import am.AmErrors
import am.G
import am.GetAll

fun main(g: G): Any? {
    val weight = g.req.body.getDouble("weight")
    if (weight > 30) throw AmErrors("Too heavy to ship.", 422)
    val rates = g.sys.db.getAll(GetAll("mysql_8", "shop", "rates"))
    return mapOf("cost" to weight * rates[0].getDouble("per_kg"))
}
  • Its hooks are named in the version they run with, and their code is in pre-hooks/ and post-hooks/ of the folder of the API : preHooks: [{ name: check_weight, isActive: true }] is pre-hooks/check_weight.<extension> (in Go, Java and Kotlin pre-hooks/check_weight/check_weight.<extension>). A hook of an API is in the language of the API ; groupNames on a hook runs it for the API users of those groups only.
  • Its docs (the Docs tab) are shippingCost.docs.json, which the panel writes. An API without docs has no such file.
  • Its path is unique with its method, and its name is unique in the account.

Hooks

The hooks of an instance, a database, a table, an API of a table and a system API are a folder each, named by the hook. See Pre hooks and Post hooks.

Hook of Folder in src Where it runs : keys of its YAML
An instance instance_hooks/ instanceName
A database database_hooks/ instanceName, databaseName
A table collection_hooks/ instanceName, databaseName, collectionName
One API of a table instance_api_hooks/ instanceName, databaseName, collectionName, apiId (SCHEMA_GET_ALL, GEN_POST_QUERY…)
A system API system_api_hooks/ apiId (GET_TOKEN…)
src/collection_hooks/Row_scope-orders/Row_scope-orders.yaml
name: Row scope - orders    # the folder : Row_scope-orders
instanceName: mongodb       # where it runs : the table orders
databaseName: shop
collectionName: orders
hookType: pre               # pre : before the APIs of the table ; post : after them
order: 1                    # its place among the pre hooks of the table, from 1
isActive: true
groupNames: [Shop app]      # only for the API users of these groups ; leave it out for all of them
  • A new hook needs no _id and no guid : it joins the other hooks of its place. Delete its folder and the hook goes.
  • The code is Row_scope-orders.<extension>, next to its YAML, in every language : a hook of a table is alone in its folder.
src/collection_hooks/Row_scope-orders/Row_scope-orders.ts
import * as T from 'types';
import * as db from 'db-interfaces';

async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return; // calls made by your own code : leave them alone

    // g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    // g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    // g.res.shared : values for the API and the post hooks of the same request
}
module.exports = main;
src/collection_hooks/Row_scope-orders/Row_scope-orders.py
from am import G


async def main(g: G):
    if not g.req.is_api_request_from_user:
        return  # calls made by your own code : leave them alone

    # g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    # g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    # g.res.shared : values for the API and the post hooks of the same request
src/collection_hooks/Row_scope-orders/Row_scope-orders.php
<?php
function main(AmG $g)
{
    if (!$g->req->isApiRequestFromUser) {
        return null; // calls made by your own code : leave them alone
    }

    // $g->req->query, $g->req->body, $g->req->params, $g->req->headers : the request, changeable
    // $g->req->auth->authAMUser, $g->req->auth->authAMDB… : who calls
    // $g->res->shared : values for the API and the post hooks of the same request
    return null;
}
src/collection_hooks/Row_scope-orders/Row_scope-orders.cs
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (!g.Req.IsApiRequestFromUser) return null; // calls made by your own code : leave them alone

        // g.Req.Query, g.Req.Body, g.Req.Params, g.Req.Headers : the request, changeable
        // g.Req.Auth["authAMUser"], g.Req.Auth["authAMDB"]… : who calls
        // g.Res.Shared : values for the API and the post hooks of the same request
        return null;
    }
}
src/collection_hooks/Row_scope-orders/Row_scope-orders.go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
    if !g.Req.IsApiRequestFromUser() {
        return nil, nil // calls made by your own code : leave them alone
    }

    // g.Req.Query(), g.Req.Body(), g.Req.Params(), g.Req.Headers() : the request, changeable
    // g.Req.Auth().Map("authAMUser"), g.Req.Auth().Map("authAMDB")… : who calls
    // g.Res.Shared() : values for the API and the post hooks of the same request
    return nil, nil
}
src/collection_hooks/Row_scope-orders/Row_scope-orders.rs
use am::{Errors, G, Value};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    if !g.req.is_api_request_from_user() {
        return Ok(Value::Null); // calls made by your own code : leave them alone
    }

    // g.req.query_mut(), g.req.body_mut(), g.req.params_mut(), g.req.headers_mut() : the request, changeable
    // g.req.auth()["authAMUser"], g.req.auth()["authAMDB"]… : who calls
    // g.res.shared_mut() : values for the API and the post hooks of the same request
    Ok(Value::Null)
}
src/collection_hooks/Row_scope-orders/Row_scope-orders.java
import am.G;

class Api {
    static Object main(G g) {
        if (!g.req.isApiRequestFromUser()) return null; // calls made by your own code : leave them alone

        // g.req.getQuery(), g.req.getBody(), g.req.getParams(), g.req.getHeaders() : the request, changeable
        // g.req.getAuth().getObject("authAMUser"), g.req.getAuth().getObject("authAMDB")… : who calls
        // g.res.getShared() : values for the API and the post hooks of the same request
        return null;
    }
}
src/collection_hooks/Row_scope-orders/Row_scope-orders.kt
import am.G

fun main(g: G): Any? {
    if (!g.req.isApiRequestFromUser) return null // calls made by your own code : leave them alone

    // g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    // g.req.auth.getObject("authAMUser"), g.req.auth.getObject("authAMDB")… : who calls
    // g.res.shared : values for the API and the post hooks of the same request
    return null
}

Event

An event has listeners, each one a folder of code under listeners/. See Events.

src/events/Order_placed/Order_placed.yaml
name: Order placed                       # g.sys.system.emitEvent('Order placed', data) emits it
isAutomaticallyTriggerOnApiHit: false    # true : emitted after each successful call of the APIs of triggerOnApis
triggerOnApis: []
eventListeners:
  - name: Notify the shop                # its code : listeners/Notify_the_shop/Notify_the_shop.<extension>
    versions:
      - name: Original
        version: "-1"
        isActive: true
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.ts
import * as T from 'types';
import * as db from 'db-interfaces';

async function main(g: T.IAMGlobal) {
    const data = g.req.eventData;   // what was emitted
    await g.sys.db.saveSingleOrMultiple({
        instance: 'mongodb', database: 'shop', collection: 'notifications',
        saveData: { order_id: data.id, text: `Order ${data.id} placed` },
    });
    return { notified: true };      // returned to the emitter
}
module.exports = main;
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.py
from am import G


async def main(g: G):
    data = g.req.event_data   # what was emitted
    await g.sys.db.save_single_or_multiple(
        'mongodb', 'shop', 'notifications',
        save_data={'order_id': data.id, 'text': f'Order {data.id} placed'},
    )
    return {'notified': True}  # returned to the emitter
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.php
<?php
function main(AmG $g)
{
    $data = $g->req->eventData;   // what was emitted
    $g->sys->db->saveSingleOrMultiple(
        'mongodb', 'shop', 'notifications',
        saveData: ['order_id' => $data->id, 'text' => "Order {$data->id} placed"],
    );
    return ['notified' => true];  // returned to the emitter
}
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.cs
1
2
3
4
5
6
7
8
9
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var orderId = g.Req.EventData?["id"]?.GetValue<long>();   // what was emitted
        await g.Sys.Db.SaveSingleOrMultiple(new Save("mongodb", "shop", "notifications", new { order_id = orderId, text = $"Order {orderId} placed" }));
        return new { notified = true };  // returned to the emitter
    }
}
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.go
package code

import (
    "fmt"

    "apimaker/am"
)

func Main(g *am.G) (any, error) {
    data := am.AsM(g.Req.EventData()) // what was emitted
    _, err := g.Sys.Db.SaveSingleOrMultiple(am.Save{
        Instance: "mongodb", Database: "shop", Collection: "notifications",
        SaveData: am.M{"order_id": data.Get("id"), "text": fmt.Sprintf("Order %v placed", data.Get("id"))},
    })
    if err != nil {
        return nil, err
    }
    return am.M{"notified": true}, nil // returned to the emitter
}
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.rs
use am::{Errors, G, Save, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let order_id = g.req.event_data()["id"].clone(); // what was emitted
    g.sys.db.save_single_or_multiple(Save::new(
        "mongodb",
        "shop",
        "notifications",
        json!({ "order_id": order_id, "text": format!("Order {order_id} placed") }),
    ))?;
    Ok(json!({ "notified": true })) // returned to the emitter
}
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.java
import am.G;
import am.JsonObject;
import am.Save;
import java.util.Map;

class Api {
    static Object main(G g) {
        JsonObject data = JsonObject.from(g.req.getEventData());   // what was emitted
        g.sys.db.saveSingleOrMultiple(new Save("mongodb", "shop", "notifications",
            Map.of("order_id", data.getLong("id"), "text", "Order " + data.getLong("id") + " placed")));
        return Map.of("notified", true);  // returned to the emitter
    }
}
src/events/Order_placed/listeners/Notify_the_shop/Notify_the_shop.kt
import am.G
import am.JsonObject
import am.Save

fun main(g: G): Any? {
    val data = JsonObject.from(g.req.eventData)   // what was emitted
    g.sys.db.saveSingleOrMultiple(
        Save("mongodb", "shop", "notifications", mapOf("order_id" to data.getLong("id"), "text" to "Order ${data.getLong("id")} placed")),
    )
    return mapOf("notified" to true)  // returned to the emitter
}
  • A listener is a folder of its own in every language : listeners/<listener>/<listener>.<extension>.
  • To emit it after a call of an API instead of from code : isAutomaticallyTriggerOnApiHit: true, and the API in triggerOnApis ({ apiCategory: CUSTOM_APIS, customApiName: shippingCost }, or an API of a table with instanceName, databaseName, collectionName, instanceApiName).

WebSocket event

A custom WebSocket event, emitted by g.sys.system.emitEventWS. Its code says who may subscribe. See WebSocket events.

src/web_socket_events/defaultorders-updated/defaultorders-updated.yaml
name: default/orders-updated   # the folder is the name without its /
apiCategory: WEB_SOCKET_EVENT
authProviders: [default_db_login]   # a client subscribes with a person token of these providers
versions:
  - name: Original
    version: "-1"
    isActive: true
src/web_socket_events/defaultorders-updated/defaultorders-updated.ts
1
2
3
4
5
6
7
8
import * as T from 'types';

// Who may subscribe : a person of the users table.
async function main(g: T.IAMGlobal) {
    if (!g.req.auth.authAMDB) return { canConnect: false, errorText: 'Please sign in.' };
    return { canConnect: true, errorText: '' };
}
module.exports = main;
src/web_socket_events/defaultorders-updated/defaultorders-updated.py
1
2
3
4
5
6
7
8
from am import G


# Who may subscribe : a person of the users table.
async def main(g: G):
    if not g.req.auth.authAMDB:
        return {'canConnect': False, 'errorText': 'Please sign in.'}
    return {'canConnect': True, 'errorText': ''}
src/web_socket_events/defaultorders-updated/defaultorders-updated.php
1
2
3
4
5
6
7
8
9
<?php
// Who may subscribe : a person of the users table.
function main(AmG $g)
{
    if (empty($g->req->auth->authAMDB)) {
        return ['canConnect' => false, 'errorText' => 'Please sign in.'];
    }
    return ['canConnect' => true, 'errorText' => ''];
}
src/web_socket_events/defaultorders-updated/defaultorders-updated.cs
1
2
3
4
5
6
7
8
9
// Who may subscribe : a person of the users table.
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (g.Req.Auth.GetObject("authAMDB").Count == 0) return new { canConnect = false, errorText = "Please sign in." };
        return new { canConnect = true, errorText = "" };
    }
}
src/web_socket_events/defaultorders-updated/defaultorders-updated.go
package code

import "apimaker/am"

// Main says who may subscribe : a person of the users table.
func Main(g *am.G) (any, error) {
    if len(g.Req.Auth().Map("authAMDB")) == 0 {
        return am.M{"canConnect": false, "errorText": "Please sign in."}, nil
    }
    return am.M{"canConnect": true, "errorText": ""}, nil
}
src/web_socket_events/defaultorders-updated/defaultorders-updated.rs
1
2
3
4
5
6
7
8
9
use am::{Errors, G, Value, json};

// Who may subscribe : a person of the users table.
pub fn main(g: &mut G) -> Result<Value, Errors> {
    if g.req.auth()["authAMDB"].is_null() {
        return Ok(json!({ "canConnect": false, "errorText": "Please sign in." }));
    }
    Ok(json!({ "canConnect": true, "errorText": "" }))
}
src/web_socket_events/defaultorders-updated/defaultorders-updated.java
import am.G;
import java.util.Map;

// Who may subscribe : a person of the users table.
class Api {
    static Object main(G g) {
        if (g.req.getAuth().getObject("authAMDB").isEmpty()) return Map.of("canConnect", false, "errorText", "Please sign in.");
        return Map.of("canConnect", true, "errorText", "");
    }
}
src/web_socket_events/defaultorders-updated/defaultorders-updated.kt
1
2
3
4
5
6
7
import am.G

// Who may subscribe : a person of the users table.
fun main(g: G): Any? {
    if (g.req.auth.getObject("authAMDB").isEmpty()) return mapOf("canConnect" to false, "errorText" to "Please sign in.")
    return mapOf("canConnect" to true, "errorText" to "")
}

Scheduler

Code which runs on a timetable. See Schedulers.

src/schedulers/Clean_old_carts/Clean_old_carts.yaml
name: Clean old carts
isActive: true                    # the switch of the whole scheduler
intervalArr:
  - timeInterval: 0 30 2 * * *    # cron, seconds first : every day at 02:30
    timeZone: Asia/Kolkata
    active: true                  # each interval has its own switch
schedulerTimeoutInMinutes: 10
versions:
  - name: Original
    version: "-1"
    isActive: true
src/schedulers/Clean_old_carts/Clean_old_carts.ts
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const before = Date.now() - 7 * 86400000;   // a week ago, in milliseconds since 1970 : what updated_at holds
    const removed = await g.sys.db.removeByQuery({
        instance: 'mongodb', database: 'shop', collection: 'carts',
        find: { updated_at: { $lt: before } },
    });
    g.logger.log(`${removed.deletedRowsCount} carts removed`);
    return { removed: removed.deletedRowsCount };
}
module.exports = main;
src/schedulers/Clean_old_carts/Clean_old_carts.py
import time

from am import G


async def main(g: G):
    before = int(time.time() * 1000) - 7 * 86400000   # a week ago, in milliseconds since 1970 : what updated_at holds
    removed = await g.sys.db.remove_by_query('mongodb', 'shop', 'carts', find={'updated_at': {'$lt': before}})
    g.logger.log(f'{removed.deleted_rows_count} carts removed')
    return {'removed': removed.deleted_rows_count}
src/schedulers/Clean_old_carts/Clean_old_carts.php
1
2
3
4
5
6
7
8
<?php
function main(AmG $g)
{
    $before = (int) (microtime(true) * 1000) - 7 * 86400000;   // a week ago, in milliseconds since 1970 : what updated_at holds
    $removed = $g->sys->db->removeByQuery('mongodb', 'shop', 'carts', find: ['updated_at' => ['$lt' => $before]]);
    $g->logger->log("{$removed->deletedRowsCount} carts removed");
    return ['removed' => $removed->deletedRowsCount];
}
src/schedulers/Clean_old_carts/Clean_old_carts.cs
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var before = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - 7 * 86400000L;   // a week ago, in milliseconds since 1970 : what updated_at holds
        var removed = await g.Sys.Db.RemoveByQuery(new RemoveByQuery("mongodb", "shop", "carts")
        {
            Find = new JsonObject { ["updated_at"] = new JsonObject { ["$lt"] = before } },
        });
        g.Logger.Log($"{removed.DeletedRowsCount} carts removed");
        return new { removed = removed.DeletedRowsCount };
    }
}
src/schedulers/Clean_old_carts/Clean_old_carts.go
package code

import (
    "fmt"
    "time"

    "apimaker/am"
)

func Main(g *am.G) (any, error) {
    before := time.Now().UnixMilli() - 7*86400000 // a week ago, in milliseconds since 1970 : what updated_at holds
    removed, err := g.Sys.Db.RemoveByQuery(am.RemoveByQuery{
        Instance: "mongodb", Database: "shop", Collection: "carts",
        Find: am.M{"updated_at": am.M{"$lt": before}},
    })
    if err != nil {
        return nil, err
    }
    g.Logger.Log(fmt.Sprintf("%d carts removed", removed.DeletedRowsCount))
    return am.M{"removed": removed.DeletedRowsCount}, nil
}
src/schedulers/Clean_old_carts/Clean_old_carts.rs
use std::time::{SystemTime, UNIX_EPOCH};

use am::{Errors, G, RemoveByQuery, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |since| since.as_millis() as i64);
    let before = now - 7 * 86_400_000; // a week ago, in milliseconds since 1970 : what updated_at holds
    let removed = g.sys.db.remove_by_query(RemoveByQuery::new("mongodb", "shop", "carts").find(json!({ "updated_at": { "$lt": before } })))?;
    g.logger.log(format!("{} carts removed", removed.deleted_rows_count));
    Ok(json!({ "removed": removed.deleted_rows_count }))
}
src/schedulers/Clean_old_carts/Clean_old_carts.java
import am.G;
import am.RemoveByQuery;
import am.RemoveByQueryResult;
import java.util.Map;

class Api {
    static Object main(G g) {
        long before = System.currentTimeMillis() - 7 * 86400000L;   // a week ago, in milliseconds since 1970 : what updated_at holds
        RemoveByQueryResult removed = g.sys.db.removeByQuery(new RemoveByQuery("mongodb", "shop", "carts")
            .find(Map.of("updated_at", Map.of("$lt", before))));
        g.logger.log(removed.getDeletedRowsCount() + " carts removed");
        return Map.of("removed", removed.getDeletedRowsCount());
    }
}
src/schedulers/Clean_old_carts/Clean_old_carts.kt
1
2
3
4
5
6
7
8
9
import am.G
import am.RemoveByQuery

fun main(g: G): Any? {
    val before = System.currentTimeMillis() - 7 * 86400000L   // a week ago, in milliseconds since 1970 : what updated_at holds
    val removed = g.sys.db.removeByQuery(RemoveByQuery("mongodb", "shop", "carts").find(mapOf("updated_at" to mapOf("\$lt" to before))))
    g.logger.log("${removed.deletedRowsCount} carts removed")
    return mapOf("removed" to removed.deletedRowsCount)
}

Database migration

A script which runs once on each environment, after the pull which brings it. See Database migration.

src/database_migrations/Add_description/Add_description.yaml
name: Add description
label: Add the column description to order_transactions   # what the list of scripts shows
order: 1                         # the pull runs the scripts which never ran here, from the lowest order
active: true
dbMigrationTimeoutInMinutes: 10
src/database_migrations/Add_description/Add_description.ts
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const meta = await g.sys.system.getTableMeta({ instance: 'mysql8', database: 'inventory', table: 'order_transactions' });
    const hasDescription = meta.some(column => column.name === 'description');

    if (!hasDescription) {
        await g.sys.system.executeQuery({
            instance: 'mysql8',
            query: 'ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`',
        });
    }
    return { hasDescription };
}
module.exports = main;
src/database_migrations/Add_description/Add_description.py
from am import G


async def main(g: G):
    meta = await g.sys.system.get_table_meta(instance='mysql8', database='inventory', table='order_transactions')
    has_description = any(column.name == 'description' for column in meta)

    if not has_description:
        await g.sys.system.execute_query(
            'mysql8',
            'ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`',
        )
    return {'hasDescription': has_description}
src/database_migrations/Add_description/Add_description.php
<?php
function main(AmG $g)
{
    $meta = $g->sys->system->getTableMeta(instance: 'mysql8', database: 'inventory', table: 'order_transactions');
    $hasDescription = (bool) array_filter($meta, fn ($column) => $column->name === 'description');

    if (!$hasDescription) {
        $g->sys->system->executeQuery(
            'mysql8',
            'ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`',
        );
    }
    return ['hasDescription' => $hasDescription];
}
src/database_migrations/Add_description/Add_description.cs
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var meta = await g.Sys.System.GetTableMeta(new GetTableMeta { Instance = "mysql8", Database = "inventory", Table = "order_transactions" });
        var hasDescription = meta.Any(column => column.Name == "description");

        if (!hasDescription)
        {
            await g.Sys.System.ExecuteQuery(new ExecuteQuery("mysql8",
                "ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`"));
        }
        return new { hasDescription };
    }
}
src/database_migrations/Add_description/Add_description.go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
    meta, err := g.Sys.System.GetTableMeta(am.GetTableMeta{Instance: "mysql8", Database: "inventory", Table: "order_transactions"})
    if err != nil {
        return nil, err
    }
    hasDescription := false
    for _, column := range meta {
        hasDescription = hasDescription || column.Name == "description"
    }

    if !hasDescription {
        _, err := g.Sys.System.ExecuteQuery(am.ExecuteQuery{
            Instance: "mysql8",
            Query:    "ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`",
        })
        if err != nil {
            return nil, err
        }
    }
    return am.M{"hasDescription": hasDescription}, nil
}
src/database_migrations/Add_description/Add_description.rs
use am::{Errors, ExecuteQuery, G, GetTableMeta, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let meta = g.sys.system.get_table_meta(GetTableMeta::default().instance("mysql8").database("inventory").table("order_transactions"))?;
    let has_description = meta.iter().any(|column| column.name == "description");

    if !has_description {
        g.sys.system.execute_query(ExecuteQuery::new(
            "mysql8",
            "ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`",
        ))?;
    }
    Ok(json!({ "hasDescription": has_description }))
}
src/database_migrations/Add_description/Add_description.java
import am.ExecuteQuery;
import am.G;
import am.GetTableMeta;
import am.TableMetaField;
import java.util.List;
import java.util.Map;

class Api {
    static Object main(G g) {
        List<TableMetaField> meta = g.sys.system.getTableMeta(new GetTableMeta().instance("mysql8").database("inventory").table("order_transactions"));
        boolean hasDescription = meta.stream().anyMatch(column -> "description".equals(column.getName()));

        if (!hasDescription) {
            g.sys.system.executeQuery(new ExecuteQuery("mysql8",
                "ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`"));
        }
        return Map.of("hasDescription", hasDescription);
    }
}
src/database_migrations/Add_description/Add_description.kt
import am.ExecuteQuery
import am.G
import am.GetTableMeta

fun main(g: G): Any? {
    val meta = g.sys.system.getTableMeta(GetTableMeta().instance("mysql8").database("inventory").table("order_transactions"))
    val hasDescription = meta.any { it.name == "description" }

    if (!hasDescription) {
        g.sys.system.executeQuery(
            ExecuteQuery("mysql8", "ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`"),
        )
    }
    return mapOf("hasDescription" to hasDescription)
}
  • Write a script which can run twice without harm : a script which failed half way runs again after the fix.

Process initializer

Code which runs once in each runner of the sandboxes of the account, when it starts. See Process initializers.

src/process_initializers/Load_rates/Load_rates.yaml
name: Load rates
order: 1                                 # the initializers run one after the other, from the lowest order
active: true
processInitializerTimeoutInMinutes: 1
src/process_initializers/Load_rates/Load_rates.ts
1
2
3
4
5
6
7
8
9
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    // Read once per process : every request this process serves finds them in g.shared.
    const rates = await g.sys.db.getAll({ instance: 'mysql_8', database: 'shop', collection: 'rates' });
    g.shared.rates = rates;
    g.logger.log(`${rates.length} rates loaded`);
}
module.exports = main;
src/process_initializers/Load_rates/Load_rates.py
1
2
3
4
5
6
7
8
from am import G


async def main(g: G):
    # Read once per process : every request this process serves finds them in g.shared.
    rates = await g.sys.db.get_all('mysql_8', 'shop', 'rates')
    g.shared['rates'] = rates
    g.logger.log(len(rates), 'rates loaded')
src/process_initializers/Load_rates/Load_rates.php
1
2
3
4
5
6
7
8
9
<?php
function main(AmG $g)
{
    // Read once per process : every request this process serves finds them in $g->shared.
    $rates = $g->sys->db->getAll('mysql_8', 'shop', 'rates');
    $g->shared->rates = $rates;
    $g->logger->log(count($rates), 'rates loaded');
    return null;
}
src/process_initializers/Load_rates/Load_rates.cs
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        // Read once per process : every request this process serves finds them in g.Shared.
        var rates = await g.Sys.Db.GetAll(new GetAll("mysql_8", "shop", "rates"));
        g.Shared["rates"] = rates;
        g.Logger.Log(rates.Count, "rates loaded");
        return null;
    }
}
src/process_initializers/Load_rates/Load_rates.go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
    // Read once per process : every request this process serves finds them in g.Shared.
    rates, err := g.Sys.Db.GetAll(am.GetAll{Instance: "mysql_8", Database: "shop", Collection: "rates"})
    if err != nil {
        return nil, err
    }
    g.Shared.Set("rates", rates)
    g.Logger.Log(len(rates), "rates loaded")
    return nil, nil
}
src/process_initializers/Load_rates/Load_rates.rs
1
2
3
4
5
6
7
8
9
use am::{Errors, G, GetAll, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    // Read once per process : every request this process serves finds them in g.shared.
    let rates = g.sys.db.get_all(GetAll::new("mysql_8", "shop", "rates"))?;
    g.logger.log(format!("{} rates loaded", rates.len()));
    g.shared.set("rates", json!(rates));
    Ok(Value::Null)
}
src/process_initializers/Load_rates/Load_rates.java
import am.G;
import am.GetAll;
import am.JsonObject;
import java.util.List;

class Api {
    static Object main(G g) {
        // Read once per process : every request this process serves finds them in g.shared.
        List<JsonObject> rates = g.sys.db.getAll(new GetAll("mysql_8", "shop", "rates"));
        g.shared.put("rates", rates);
        g.logger.log(rates.size(), "rates loaded");
        return null;
    }
}
src/process_initializers/Load_rates/Load_rates.kt
import am.G
import am.GetAll

fun main(g: G): Any? {
    // Read once per process : every request this process serves finds them in g.shared.
    val rates = g.sys.db.getAll(GetAll("mysql_8", "shop", "rates"))
    g.shared["rates"] = rates
    g.logger.log(rates.size, "rates loaded")
    return null
}

Utility class

Code which the other code of its language imports. See Utility classes.

src/utility_classes/utils---Money/Money.yaml
name: Money                 # what the code imports ; the file is named Money.yaml, not as the folder
folderPath: utils/          # its folder in the list : the folder in git is utils---Money
versions:
  - name: Original
    version: "-1"           # the code is Money.<extension>, next to this file
    isActive: true

The class is src/utility_classes/utils---Money/Money.ts, imported as utils/Money.

The utility class Money, in the folder utils
1
2
3
export function round(amount: number): number {
    return Math.round(amount * 100) / 100;
}
A custom API which uses it
1
2
3
4
5
6
7
import * as T from 'types';
import * as Money from 'utils/Money';

async function main(g: T.IAMGlobal) {
    return { total: Money.round(g.req.body.amount * 1.18) };
}
module.exports = main;

The class is src/utility_classes/utils---Money/Money.py, the module Money.

The utility class Money, in the folder utils
def round_amount(amount):
    return round(amount, 2)
A custom API which uses it
1
2
3
4
5
from Money import round_amount


async def main(g):
    return {'total': round_amount(g.req.body.amount * 1.18)}

The class is src/utility_classes/utils---Money/Money.php : what it declares is there for the code of PHP.

The utility class Money, in the folder utils
1
2
3
4
5
6
7
8
<?php
class Money
{
    public static function round($amount)
    {
        return round($amount, 2);
    }
}
A custom API which uses it
1
2
3
4
5
<?php
function main($g)
{
    return ['total' => Money::round($g->req->body->amount * 1.18)];
}

The class is src/utility_classes/utils---Money/Money.cs, compiled with the code.

The utility class Money, in the folder utils
1
2
3
4
public static class Money
{
    public static double Round(double amount) => Math.Round(amount, 2);
}
A custom API which uses it
1
2
3
4
5
6
7
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        return new { total = Money.Round(g.Req.Body.GetDouble("amount") * 1.18) };
    }
}

The class is src/utility_classes/utils---Money/Money.go, a package imported by its folder.

The utility class Money, in the folder utils
1
2
3
4
5
6
7
package money

import "math"

func Round(amount float64) float64 {
    return math.Round(amount*100) / 100
}
A custom API which uses it
package code

import (
    "apimaker/am"
    money "apimaker/src/utility_classes/utils---Money"
)

func Main(g *am.G) (any, error) {
    return am.M{"total": money.Round(g.Req.Body().Float("amount") * 1.18)}, nil
}

The class is src/utility_classes/utils---Money/Money.rs, the module Money of the crate of the code.

The utility class Money, in the folder utils
1
2
3
pub fn round(amount: f64) -> f64 {
    (amount * 100.0).round() / 100.0
}
A custom API which uses it
1
2
3
4
5
6
7
use am::{Errors, G, Value, json};

use crate::Money;

pub fn main(g: &mut G) -> Result<Value, Errors> {
    Ok(json!({ "total": Money::round(g.req.body()["amount"].as_f64().unwrap_or(0.0) * 1.18) }))
}

The class is src/utility_classes/utils---Money/Money.java, compiled with the other classes of Java.

The utility class Money, in the folder utils
1
2
3
4
5
public class Money {
    public static double round(double amount) {
        return Math.round(amount * 100) / 100.0;
    }
}
A custom API which uses it
1
2
3
4
5
6
7
8
import am.G;
import java.util.Map;

class Api {
    static Object main(G g) {
        return Map.of("total", Money.round(g.req.getBody().getDouble("amount") * 1.18));
    }
}

The class is src/utility_classes/utils---Money/Money.kt, compiled with the other classes of Kotlin.

The utility class Money, in the folder utils
1
2
3
object Money {
    fun round(amount: Double): Double = Math.round(amount * 100) / 100.0
}
A custom API which uses it
1
2
3
import am.G

fun main(g: G): Any? = mapOf("total" to Money.round(g.req.body.getDouble("amount") * 1.18))
  • A class is used by the code of its own language only.

Test case

The tests of a custom API or of a utility class, run by npm run test and the Test Cases page. See Test cases.

src/test_cases/Shipping_cost/Shipping_cost.yaml
name: Shipping cost
testCaseType: CUSTOM_APIS       # UTILITY_CLASS with utilityClassName for a utility class
customApiName: shippingCost     # the custom API it tests
mocks: []                       # the mocks of the Mocks tab ; the code adds its own

The test case is src/test_cases/Shipping_cost/Shipping_cost.ts.

A test case
import * as T from 'types';
import * as assert from 'node:assert';

module.exports = [{
    name: 'the cost of a parcel',
    mocks: [{ method: 'db.getAll', return: [{ per_kg: 2.5 }] }],
    code: async (g: T.IAMGlobal) => {
        assert.deepStrictEqual(await g.sys.test.runCustomApi({ name: 'shippingCost', body: { weight: 4 } }), { cost: 10 });
        assert.strictEqual(g.sys.test.calls('db.getAll').length, 1);
        await assert.rejects(g.sys.test.runCustomApi({ name: 'shippingCost', body: { weight: 40 } }), (errors: any) => errors[0].code === 422);
    },
}];

The test case is src/test_cases/Shipping_cost/Shipping_cost.py.

A test case
async def test_the_cost_of_a_parcel(g):
    await g.sys.test.mock("db.getAll", when={"collection": "rates"}, returns=[{"per_kg": 2.5}])
    out = await g.sys.test.run_custom_api("shippingCost", body={"weight": 4})
    assert out == {"cost": 10}, out
    assert len(await g.sys.test.calls("db.getAll")) == 1


async def test_too_heavy_is_refused(g):
    try:
        await g.sys.test.run_custom_api("shippingCost", body={"weight": 40})
    except g.Errors as refused:
        assert refused.code == 422
    else:
        raise AssertionError("a parcel of 40 kg was accepted")

The test case is src/test_cases/Shipping_cost/Shipping_cost.php.

A test case
<?php
function test_the_cost_of_a_parcel(AmG $g)
{
    $g->sys->test->mock('db.getAll', when: ['collection' => 'rates'], returns: [['per_kg' => 2.5]]);
    $out = $g->sys->test->runCustomApi('shippingCost', body: ['weight' => 4]);
    if ($out->cost != 10) {
        throw new Exception("the cost is {$out->cost}");
    }
}

function test_too_heavy_is_refused(AmG $g)
{
    try {
        $g->sys->test->runCustomApi('shippingCost', body: ['weight' => 40]);
    } catch (AmErrors $refused) {
        if ($refused->getCode() !== 422) {
            throw new Exception("refused with {$refused->getCode()}");
        }
        return;
    }
    throw new Exception('a parcel of 40 kg was accepted');
}

The test case is src/test_cases/Shipping_cost/Shipping_cost.cs.

A test case
public static class ShippingTests
{
    public static async Task TestTheCostOfAParcel(G g)
    {
        await g.Sys.Test.Mock(new Mock("db.getAll") { When = new { collection = "rates" }, Return = new[] { new { per_kg = 2.5 } } });
        var output = await g.Sys.Test.RunCustomApi(new RunCustomApi("shippingCost") { Body = new { weight = 4 } });
        if (output.GetDouble("cost") != 10) throw new Exception("the cost is " + output?["cost"]);
    }

    public static async Task TestTooHeavyIsRefused(G g)
    {
        try
        {
            await g.Sys.Test.RunCustomApi(new RunCustomApi("shippingCost") { Body = new { weight = 40 } });
        }
        catch (AmErrors refused) when (refused.Code == 422)
        {
            return;
        }
        throw new Exception("a parcel of 40 kg was accepted");
    }
}

The test case is src/test_cases/Shipping_cost/Shipping_cost.go.

A test case
package code

import (
    "errors"
    "fmt"

    "apimaker/am"
)

func theCostOfAParcel(g *am.G) error {
    if err := g.Sys.Test.Mock(am.Mock{Method: "db.getAll", When: am.M{"collection": "rates"}, Return: []any{am.M{"per_kg": 2.5}}}); err != nil {
        return err
    }
    out, err := g.Sys.Test.RunCustomApi(am.RunCustomApi{Name: "shippingCost", Body: am.M{"weight": 4}})
    if err != nil {
        return err
    }
    if cost := am.AsM(out).Float("cost"); cost != 10 {
        return fmt.Errorf("the cost is %v", cost)
    }
    return nil
}

func tooHeavyIsRefused(g *am.G) error {
    _, err := g.Sys.Test.RunCustomApi(am.RunCustomApi{Name: "shippingCost", Body: am.M{"weight": 40}})
    var refused *am.Errors
    if errors.As(err, &refused) && refused.Code() == 422 {
        return nil
    }
    return fmt.Errorf("a parcel of 40 kg was not refused : %v", err)
}

var Tests = []am.Test{
    {Name: "the cost of a parcel", Run: theCostOfAParcel},
    {Name: "too heavy is refused", Run: tooHeavyIsRefused},
}

The test case is src/test_cases/Shipping_cost/Shipping_cost.rs.

A test case
use am::{Errors, G, Mock, RunCustomApi, json};

fn the_cost_of_a_parcel(g: &mut G) -> Result<(), Errors> {
    g.sys.test.mock(Mock::new("db.getAll").when(json!({ "collection": "rates" })).returns(json!([{ "per_kg": 2.5 }])))?;
    let out = g.sys.test.run_custom_api(RunCustomApi::new("shippingCost").body(json!({ "weight": 4 })))?;
    assert_eq!(out["cost"].as_f64(), Some(10.0));
    Ok(())
}

fn too_heavy_is_refused(g: &mut G) -> Result<(), Errors> {
    match g.sys.test.run_custom_api(RunCustomApi::new("shippingCost").body(json!({ "weight": 40 }))) {
        Err(refused) if refused.code() == 422 => Ok(()),
        other => Err(am::fail(500, &format!("a parcel of 40 kg was not refused : {other:?}"))),
    }
}

am::tests!(the_cost_of_a_parcel, too_heavy_is_refused);

The test case is src/test_cases/Shipping_cost/Shipping_cost.java.

A test case
import am.AmErrors;
import am.G;
import am.JsonObject;
import am.Mock;
import am.RunCustomApi;
import java.util.List;
import java.util.Map;

class ShippingTests {
    static void testTheCostOfAParcel(G g) {
        g.sys.test.mock(new Mock("db.getAll").when(Map.of("collection", "rates")).returns(List.of(Map.of("per_kg", 2.5))));
        JsonObject output = JsonObject.from(g.sys.test.runCustomApi(new RunCustomApi("shippingCost").body(Map.of("weight", 4))));
        if (output.getDouble("cost") != 10) throw new IllegalStateException("the cost is " + output.get("cost"));
    }

    static void testTooHeavyIsRefused(G g) {
        try {
            g.sys.test.runCustomApi(new RunCustomApi("shippingCost").body(Map.of("weight", 40)));
        } catch (AmErrors refused) {
            if (refused.code == 422) return;
            throw refused;
        }
        throw new IllegalStateException("a parcel of 40 kg was accepted");
    }
}

The test case is src/test_cases/Shipping_cost/Shipping_cost.kt.

A test case
import am.AmErrors
import am.G
import am.JsonObject
import am.Mock
import am.RunCustomApi

fun testTheCostOfAParcel(g: G) {
    g.sys.test.mock(Mock("db.getAll").whenever(mapOf("collection" to "rates")).returns(listOf(mapOf("per_kg" to 2.5))))
    val output = JsonObject.from(g.sys.test.runCustomApi(RunCustomApi("shippingCost").body(mapOf("weight" to 4))))
    check(output.getDouble("cost") == 10.0) { "the cost is " + output["cost"] }
}

fun testTooHeavyIsRefused(g: G) {
    try {
        g.sys.test.runCustomApi(RunCustomApi("shippingCost").body(mapOf("weight" to 40)))
    } catch (refused: AmErrors) {
        check(refused.code == 422) { "refused with ${refused.code}" }
        return
    }
    error("a parcel of 40 kg was accepted")
}

Schema

The columns of a table, their checks and conversions, and the relations to other tables. TypeScript in every language. See Table schema.

src/schemas/orders/orders.yaml
instanceName: mongodb       # the table the schema is for
databaseName: shop
collectionName: orders
active: true
src/schemas/orders/orders.config.ts
import { EType, ISchemaType, ISchemaProperty } from 'types';

const schema: ISchemaType = {
    _id: <ISchemaProperty>{
        __type: EType.objectId,
        isPrimaryKey: true,
        isAutoGenerateByAM: { valueGeneratorType: 'ObjectID' },
    },
    customer_id: <ISchemaProperty>{          // a relation : orders.customer_id is customers.customer_id
        __type: EType.number,
        validations: { required: true },
        database: 'shop', collection: 'customers', column: 'customer_id',
    },
    status: <ISchemaProperty>{
        __type: EType.string,
        validations: { enum: ['PENDING', 'PAID', 'SHIPPED', 'CANCELLED'] },
        conversions: { toUpperCase: true, defaults: { defaultValue: 'PENDING' } },
    },
    total_cents: <ISchemaProperty>{ __type: EType.number, validations: { min: 0 } },
};
module.exports = { schema };
  • The YAML is named as the folder. API Maker writes a schema in the folder of its guid (src/schemas/01M6NW232XSTGXXD4222CXZDM7/) : a new one goes in any folder, and the next commit writes it under its guid.
  • src/types/db-interfaces.d.ts gets the interface of the table once the schema is saved (db.mongodb.shop.IOrders).

Settings of a database, a table, an API

Who may call the APIs of a table, their caching and their auth providers. See Collection settings.

src/instance_api_settings/orders/orders.yaml
name: orders                       # names the folder ; the guid of the settings when it has none
instanceName: mongodb
databaseName: shop
collectionName: orders
apiId: __COLLECTION_SETTINGS__     # the table ; SCHEMA_GET_ALL… : one API of it ;
                                   # __DATABASE_SETTINGS__ here and in collectionName : the whole database
src/instance_api_settings/orders/orders.config.ts
1
2
3
4
5
6
7
8
9
import * as T from 'types';

let instanceColSetting: T.IInstanceApiSettingsTypes = {
    enableCaching: true,                       // the reads are cached until a write of the table
    acceptOnlyEncryptedData: false,
    apiAccessType: T.EAPIAccessType.TOKEN_ACCESS,
    authProviders: ['default_db_login'],       // every API of the table needs a person token of this provider
};
module.exports = instanceColSetting;
  • A key left out is taken from the level above : API settings, then the table, then the database, then the secret.

Settings of a system API

src/system_api_settings/HASH_DATA/HASH_DATA.yaml
apiId: HASH_DATA                   # the folder and the files are named by the API
src/system_api_settings/HASH_DATA/HASH_DATA.config.ts
1
2
3
4
5
6
import * as T from 'types';

let systemApi: T.ISystemApiSettingsTypes = {
    apiAccessType: T.EAPIAccessType.TOKEN_ACCESS,   // NO_ACCESS without settings : only g.sys reaches it
};
module.exports = systemApi;

Auth provider

Who the person behind a request is : a row of your users table, Google, Azure AD, AWS Cognito or your own code. See Auth of a database user.

src/auth_providers/default_db_login/default_db_login.yaml
name: default_db_login
generatorType: DB_TOKEN_GENERATOR   # CUSTOM_TOKEN_GENERATOR, GOOGLE, AZURE, AWS : the other kinds
src/auth_providers/default_db_login/default_db_login.ts
import * as T from 'types';

// What the provider is : TypeScript in every language. It wins over the YAML.
const provider: T.IAuthTokenAMDB = {
    name: 'default_db_login',
    instance: 'mongodb',
    database: 'shop',
    collection: 'customers',        // table: '…' for SQL
    usernameColumn: 'email',
    passwordColumn: 'password',     // a column the schema hashes
    expiresInSeconds: 259200,
};
module.exports = provider;
  • Its functions are in the language of the provider, next to it : default_db_login.fg.<extension> (the fields it adds to the token, may be empty), and for a provider of your own code <name>.tg.<extension> (it makes the token) and <name>.tv.<extension> (it checks one). In Go, Java and Kotlin each one is in a folder named as its file : my_tokens.tg/my_tokens.tg.go. Their code : A provider of your own.

Group, API user, instance

src/groups/Shop_app/Shop_app.yaml
name: Shop app
apisRegex:                          # true : every item of a kind ; false : only the ones named below
  allInstances: false
  allDatabases: false
  allCollections: false
  allInstanceApis: false
  allFields: true
  allCustomApis: false
  allSystemApis: false
  allWebSocketEvents: false
queryParams:                        # the grants, by name
  instanceNames: [mongodb]
  databaseNames: [shop]
  collectionNames: [products, orders]
  supportedApiNames: [SCHEMA_GET_ALL, SCHEMA_GET_BY_ID, SCHEMA_POST_QUERY]
  customApiNames: [shippingCost]
  systemApiNames: []
  webSocketEventNames: []
src/api_users/shop_app/shop_app.yaml
name: shop_app
username: shop_app
groupNames: [Shop app]
enablePasswordPath: true
apiUserPasswordPath: common.apiUserPasswords.shop_app   # where the password is in the secret : never write password
src/instances/mongodb/mongodb.yaml
name: mongodb
type: MONGO_DB                      # MYSQL_DB, MARIA_DB, POSTGRE_SQL_DB, SQL_SERVER_DB, ORACLE_DB
connectionString: common.connectionString.mongodb          # a path in the secret, never the string itself
connectionStringSandbox: common.connectionString.mongodb   # the one the sandboxes use
maxPoolConnections: 10
  • Secrets are not in the repository : ask the person to add what a path names (common.apiUserPasswords.shop_app) to the secret of each environment. See Secrets and API group permission.

Texts, log profiles, saved requests

  • Texts of a language (src/i18ns/<name>/) : <name>.yaml (name) and <name>.ts, module.exports = { "message": "translation" }. See Internationalization.
  • Log profiles (src/log_profiles/<name>/<name>.yaml) and the shared saved requests of API Testing (src/api_testing_states/<name>/ : YAML, .code.ts, .response.txt) : made in the admin panel, kept as they are.

Packages and sandboxes

src/admin_user/data.yaml holds the sandboxes of the account : the runtime, the version and the packages of each family of languages (javascript, python, php, dotnet, go, rust, java, kotlin), under settings.sandbox.environments. A package of a new library goes there :

src/admin_user/data.yaml (a part)
settings:
  sandbox:
    environments:
      python:
        packages:
          - name: requests
            version: 2.32.3
  • The Dockerfile written for a family is src/admin_user/sandbox/<family>/Dockerfile. See Sandbox settings.

SSR website

src/ssr/<website>/ is a project of TypeScript : website.yaml, site.config.ts, pages/, components/, public/. Read the guide for AI assistants of the SSR websites first.