Skip to content
VylocoreBeta
Menu
Documentation

Server-side protection

Keep selected code off the user's device.

Invite-only browser beta. Separate access is required.

Choose functions#

In Advanced / Compiled V3, choose reviewed async functions, online execution and public input/output types. Confirm private modules belong to you.

Downloads include client connection code. Selected server code, private values and control keys stay private. See supported types.

Connect your app#

Keep client files together. Calls need Gateway login and permission; the default uses the broker. Your own server needs an approved setup you maintain.

Handle login, network delays and failures. Avoid calls every animation frame. Expiry, revoked access or account closure stop new calls.

Restrict repeated probing#

New broker builds use account/session request limits. Choose suitable limits under Protect against repeated probing; normal cached retries do not consume another query. Limits continue across relogin and new builds for the same project/account.

For stronger protection, choose Approve each action on my server. Install/connect your Gateway, copy its generated application.private.json and included Node or PHP action helper into your private server code, and add one application POST route. That route performs your normal login, CSRF and order/turn ownership checks, compares the request with a stored approved action, and calls the helper using its stored inputs and stable action ID. Enter the route in the protection form and build normally. Users receive no extra prompts.

Keep the config and key out of your web root and source uploads. The helper's README supplies the exact configuration and request format. Existing Gateways need the matching binary/SDK update first; existing builds retain their original permissions.

Once the new app is tested and in use, revoke older freely queryable builds of the same logic.

Approvals start within 30 seconds by default. Repeated deliveries use the same action/request ID and inputs. After the 24-hour retry horizon, reconcile from your business records rather than run the action again. Private-state revisions stay hidden in action mode unless you enable them for explicit state synchronization.

What stays visible#

Inputs and results stay visible; someone may copy the behavior. Keep permissions and sensitive decisions on the server.

Approved transactions still disclose their results. A server route which approves arbitrary quotes or endlessly creates new actions remains an oracle. Account limits reduce its volume; they cannot prove business intent on their own.

Strings and bytes#

Online strings allow 128 UTF-16 units each; strings and arrays allow 256 elements combined per call. u8_array needs a fixed ordinary Uint8Array with its own attached buffer; changes copy back after success. Objects, dynamic properties and some string operations are unsupported.

Shared private state#

Functions share values only within one build and user. Without a shared namespace, each function has separate state.

Define the private record's fields and set state_mode: true:

{
  "file": "rules.mjs",
  "function": "quote",
  "params": ["record", "number"],
  "result": "number",
  "state_mode": true,
  "record_schema": {
    "parameter": 0,
    "fields": [
      { "name": "tier", "type": "u32" },
      { "name": "discount", "type": "number" },
      { "name": "credits", "type": "number" },
      { "name": "active", "type": "boolean" }
    ]
  }
}

Functions read and update named fields:

export async function quote(customer, total) {
  return total * (1 - customer.discount);
}
export async function updateCustomer(customer, discount) {
  customer.discount = discount;
  return customer.discount;
}

Give both functions matching fields. In Private initial state, set parameter zero's namespace and starting values:

{
  "file": "rules.mjs",
  "function": "quote",
  "parameter": 0,
  "type": "record",
  "namespace": "customer-pricing",
  "initial": { "tier": 1, "discount": 0.15, "credits": 100, "active": true }
}

Repeat for updateCustomer. Fields and starting values must match. Private values stay out of downloads, policies and reports. Arrays can also share a namespace.

Fields support number, i32, u32, boolean, named reads and updates. Declared records may nest up to four levels, with 128 scalar fields total. For example:

{ "name": "wallet", "type": "record", "fields": [
  { "name": "credits", "type": "number" },
  { "name": "active", "type": "boolean" }
] }

Use customer.wallet.credits in code and { "wallet": { "credits": 100, "active": true } } in its starting values. Every shared function needs the same full schema and starting values.

Private helpers can receive a nested record directly:

function canSpend(wallet, amount) {
  return wallet.credits >= amount;
}
export async function eligible(customer, amount) {
  return canSpend(customer.wallet, amount);
}

Keep each helper's record parameter tied to one schema path within a selection. A helper called with both customer.wallet and a different record is rejected. Nested records cannot be returned or stored in aliases.

General objects, aliases to nested records, prototypes, getters, proxies, dynamic fields and record results are unsupported. Records are private server values.

Inventory, tables and history#

Private records can contain fixed typed arrays:

{ "name": "stock", "type": "u32_array", "length": 8 }

Supply eight numbers in stock when setting the starting values. Use state.stock[index] in your code; its type is Uint32Array. f64_array, i32_array and u8_array work the same way. Each array has 1–128 cells, and the complete record has at most 128 cells, including nested fields. Float values must be finite and cannot be negative zero.

Selected functions can share stock, prices and transaction history through the same namespace. Reads, updates and supported array methods keep their usual typed-array behavior. Replacing an array, changing its length or returning it is unsupported.

Keep a complete operation together—for example, checking a basket and updating stock in one checkout call. Private read-only tables are reused within that call. Remote calls still require the network; keep them outside animation and other hot loops.

Handle conflicting-update errors and retry appropriately. Moving state between builds requires an operator; it is not automatic.

Adapt the public calls#

Leave server-supplied private parameters out of calls:

await updateCustomer(0.2);
const discounted = await quote(100);

Omit customer, without an undefined placeholder. Update and test calls yourself. Selected functions must already be async.

Contact support

Documentation