Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Local

Importing data

osy import [path] [--file <path>] [--replace] [--as <login>] [--password <pw>] [--json]

Loads your app's own data — the JSON files that live beside its source — into the app on the local platform. Rows are matched by key and updated, so running it twice converges instead of doubling.

stablelocalcliauthoring

Summary#

Loads the rows your app ships with — a catalogue, a country list, tax rates, whatever it needs to be useful on first open — from JSON files beside its source into the app on the local platform.

osy compile ships your app's code; this ships its rows. Together they make "copy this app and it works" true of both halves, instead of opening on an empty page.

Signature#

osy import [path] [--file <path>] [--replace] [--as <login>] [--password <pw>] [--json]

Description#

Where the files come from#

Your manifest declares them, the same way it declares source:

data "data/**/*.json";

Files load in path order, and that order matters: a row that references another resolves against rows already imported, so data/suppliers.json loads before data/products.json. Naming your files is how you control it. --file <path> imports just one file (and repeats), ignoring the glob.

Running it twice is the point#

Every file names the property that identifies a row, and import upserts by it. Running the same import again updates what changed and leaves the rest alone — which is what makes it usable in a build loop and in CI, rather than a one-shot that doubles your catalogue.

key also takes a LIST, which is how you seed a row that no single property identifies. A link between two things — a membership, a dependency, an assignment — is identified by both ends together and by neither alone, so one property cannot key it:

{
  "entity": "Dependency",
  "key": ["Dependent", "Prerequisite"],

  "lookups": { "Dependent": "Task[Code]", "Prerequisite": "Task[Code]" },

  "rows": [
    { "Dependent": "SHIP-2", "Prerequisite": "SHIP-1", "Kind": "Blocks" }
  ]
}

The rule is the same one as for a single key — the combination has to identify the row — so it upserts and re-runs clean. Without this, a join entity has to grow a synthetic identifier that exists only to satisfy the importer, which is a schema bent around a tool.

--replace deletes every existing row of each imported entity first. It destroys rows your files do not mention, so it is never the default.

An upsert needs update granted, and the first run will not tell you. An entity whose security {} grants create but not update imports perfectly the first time and fails the SECOND, when the rows already exist:

✗ data/03-policies.json row 1: Update of 'ApprovalPolicy' denied — it declares a `security { }` block, and that
  block grants no `update`. An entity with a block denies every operation the block does not name.

The fix is a rule in the model, never a flag here — reference data an operator re-seeds is data an operator may edit, so say so: allow update when IsOps. ⚑ The same shape bites in the other direction: an entity that grants create but not read cannot be MATCHED, so every run creates and the second one doubles the table. Both come from the same place — an upsert is a read, then a create or an update, and a block that names only create grants one third of it.

Files a row points at#

A row can reference an image or a document by a path relative to its own data file. Those bytes travel with the import, so an imported product photo is served from the app afterwards rather than 404ing.

Who it imports as#

An import is an ordinary data write, so your app's security { } decides every row, on every run. --as chooses which principal the rows are written as; it never chooses whether the rules apply. There is no privileged import.

With no --as, the import acts as anonymous inside your app. Being the app's developer authorises you to call the import — it is not an identity inside the app, so it grants nothing to the rows themselves. An entity that admits anonymous writes is seeded; one that does not is refused, row by row, with the reason.

$ osy import
✗ data/notes.json row 1: Create of 'Note' denied — it declares a `security` block

That is the common case for a real app, and --as <login> is the answer: it resolves one of your app's own users and writes as them, so the rows land exactly where that user may write. <login> is whatever app.Auth binds as its login field. Add the user first — Adding an account writes a real account through the app's own declared fields, with the roles the import will need.

--as is a login, not a label: it takes that user's own password, because naming a principal must never be enough to become one. Omit --password and you are prompted; there is no waiver, in dev or anywhere else.

$ osy user add ops@example.com --password 's3cret' --role Admin
$ osy import --as ops@example.com --password 's3cret'
✓ data/notes.json: 3 created   (ran as ops@example.com)

Every run reports which principal it used, so an import that seeded as an admin can never be mistaken for one that proved an ordinary user could do it. That second reading is the other reason to use --as: pointing it at a customer's own login checks that an import they will run is one they are allowed to run.

Against a deployed app#

osyrin app import is the same command against a platform you are logged in to.

Examples#

osy import                                  # everything the manifest's data glob matches, as anonymous
osy import --file data/products.json        # just this file
osy import --as ops@example.com --password 's3cret'   # as one of the app's own users — needed for anything it gates
osy import --replace --json                 # start from scratch, machine-readable result

See also#

Running a function — run one of the app's functions, the other half of putting it in a known state.

Compiling your app — ship the code these rows belong to.

The inner loop — where this sits in the build-run-look loop.

Related

Running a function

Runs one of your app's own functions from the command line — to put the app in a known state, backfill a column, or…

Compiling your app

Compiles your app's source into the app on the local platform — the inner-loop compile-and-apply. It applies additive…

The inner loop

The local develop-and-debug loop for an Osy# app — for the person in the editor and the coding agent at the CLI alike…