Osy#betaRun it on Osyrin: osyrin.com

Reference / Local

Importing a data source's schema

osy import-schema <source> [path] [--table <name>] [--schema <name>] [--as <entity>] [-o <file>] [--force] [--list] [--check] [--json]

Writes an entity for every table a registered data source holds — at its address, with its key — as ordinary source you review, grant and commit. The provider describes itself on the local platform, inside your compiled app.

stable1 example compiled by CIlocalcliauthoringdata

Summary#

Once an app registers a data source, osy import-schema asks that source what it holds and writes an entity for each table: [DataSource], its [ExternalName] address, a member per column with its type, and [Key] on the columns that identify a row. What it writes is source like any other — Order is then a type your functions and pages use, that osy model shows and the editor completes.

Signature#

osy import-schema <source> [path] [--table <name>] [--schema <name>] [--as <entity>] [-o <file>] [--force] [--list] [--json]

Description#

What it reads#

The source's own DescribeSchema() — the provider class the app registered, built from its entry in app.DataSources with its secrets, running on the local platform. So the source has to be in the compiled app: add it to app.DataSources, osy compile, then import. The CLI never holds the credentials.

What it writes#

One entity per table, into <source>.osy in the project (or -o <file>):

// Generated by `osy import-schema Analytics` from what data source `Analytics` describes.
// Ordinary source: review it, trim it, grant reads, and commit it. A later import never overwrites it.

[DataSource(DataSource.Analytics), ExternalName("SALES.ORDERS")]
entity Orders {
  [Key, ExternalName("ORDER_ID")] string OrderId;
  [ExternalName("REGION")] string? Region;
  [ExternalName("TOTAL")] decimal Total;
  [ExternalName("CREATED_AT")] DateTimeOffset CreatedAt;

  // Nobody can read these rows until you say who may — for example:
  //   security { allow read when IsAuthenticated; }
}
  • Names are PascalCase, with [ExternalName] carrying the exact column whenever the two differ. The entity is named after the table exactly — ORDERS becomes Orders, never a guessed singular — and --as Order renames it. A column named ID becomes IdColumn, because every entity's Id is the platform's.
  • Types are the ones a source hands values back in: string, bool, int, long, decimal, bigdecimal, double, DateTime, DateOnly, TimeOnly, DateTimeOffset, Guid and Geography. A nullable column is written T?.
  • A column of any other type is never dropped silently: it is written as a ⚠ SKIPPED comment naming it, and listed when the command finishes.
  • Keys come from the source marking its key columns. A table with none is written with a ⚠ NO KEY comment and does not compile until you mark the column or columns that identify a row with [Key].

Nothing is granted#

The generated entities have no security { } block, so under secure-by-default nobody can read them. Who may see a source's data is your decision, not a generator's: write the rule in each entity before you ship it.

Choosing tables#

--list prints the tables the source holds and writes nothing. --table <name> imports one table — by its full name or its last part, so ORDERS finds SALES.ORDERS — and repeats for more. --schema SALES limits the import to that schema. --json prints the generated source (or, with --list, the tables) instead of writing.

It never overwrites#

The file is yours once it is written: you trim columns, rename members and add security. So an import refuses to replace an existing file. -o a different file to compare, or --force when you mean to start again.

Asking whether it is still true#

Because the file is yours, a second import cannot tell you anything useful by rewriting it — the interesting question is whether what you wrote is still true of the source. --check answers exactly that and writes nothing:

osy import-schema Analytics --check

It reports, per entity:

✗ a column that is gonewith the nearest name at the source, because a drop and a RENAME look identical from here and the remedies are opposites
✗ a table that is gone, a type that no longer fits, a column that started admitting NULL, a key that movedeach one meaning a read will fail, or already does on some row
ⓘ a column you do not read, one that stopped admitting NULLinformation, never a problem — trimming the import is the point of it being source

--json gives the same findings with a kind and a refusal flag per entry. The exit code is non-zero when anything in the first two rows was found.

⚠ Widening is not drift. A decimal column read into a bigdecimal member, or an int into a long, is fine and is not reported — an author who widened a member ahead of the warehouse has not broken anything. Nor is a member typed as your own enum over a code column: that is a hand-edit the generator could not have made, and the check does not punish it.

osy compile refuses drift#

The same comparison runs as a pre-flight on every osy compile, before a version is minted, and fails the compile naming the column. Without it the first sign of a dropped column is a read failing in a provider's words, on whoever opens the page first.

⚠ A source that cannot be reached never fails a compile. A warehouse is somebody else's machine, and it is down sometimes; a gate that turned "I could not ask" into "your app is broken" would stop the whole team compiling — including the fix — for an outage they did not cause. Only an answer refuses.

⚠ And osy validate does not run it, because validate is offline by design. Its footer says so rather than letting a clean local run read as schema-checked.

Examples#

What an import leaves you with, beside the provider it came from — with the read the author then granted:

using Osysharp.Sources;

class Warehouse : IEntitySource {
  public const QueryFeatures Pushdown = QueryFeatures.Filter | QueryFeatures.OrderBy | QueryFeatures.Paging;
  public const string SecurityKitVersion = "1";

  public stream<SourceRow> Query(QueryPlan plan) { yield return new SourceRow(); }

  public SourceSchema DescribeSchema() {
    return new SourceSchema { Tables = [
      new SourceTable { Name = "SALES.ORDERS", Columns = [
        new SourceColumn { Name = "ORDER_ID", Type = "string", Key = true },
        new SourceColumn { Name = "TOTAL", Type = "decimal" },
      ] },
    ] };
  }
}

app.DataSources = [ new Warehouse("Analytics") ];

// Generated by `osy import-schema Analytics`, then reviewed: the author added the read.
[DataSource(DataSource.Analytics), ExternalName("SALES.ORDERS")]
entity Orders {
  [Key, ExternalName("ORDER_ID")] string OrderId;
  [ExternalName("TOTAL")] decimal Total;

  security { allow read when IsAuthenticated; }
}

And the commands that write it:

osy import-schema Analytics --list
osy import-schema Analytics --schema SALES --table ORDERS --as Order -o model/orders.osy
osy import-schema Analytics --table ORDERS --json
osy import-schema Analytics --check

See also#

Related

data held somewhere else (app.DataSources)

`app.DataSources` registers the places an app's data can live besides its own database — a warehouse, a SaaS table API…

Importing data

Loads your app's own data — the JSON files that live beside its source — into the app on the local platform. Rows are…

Compiling your app

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