Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal — Datasette JSON API changes for 1.0 #2360

Open
asg017 opened this issue Jun 22, 2024 · 4 comments
Open

Proposal — Datasette JSON API changes for 1.0 #2360

asg017 opened this issue Jun 22, 2024 · 4 comments
Labels

Comments

@asg017
Copy link
Collaborator

asg017 commented Jun 22, 2024

refs #1509

To prepare for Datasette 1.0, we want to standardize and document all the HTTP API endpoints that every Datasette instance offers. This proposal goes over single single standard JSON API endpoint we should implement + document for Datasette 1.0.

In general, there are very few breaking changes:

  • The awkward /.json, (ie https://latest.datasette.io/.json) endpoint will be removed.
  • The /database/table.json?sql=select... endpoint for getting JSON results of an SQL query would be replaced with /database/-/query , but we will redirect the former to the latter so it shouldn't be "breaking"
  • the keys/values in a few seldom-used JSON endpoints will be updated

(I will be editing this top-level comment while the spec changes)

Instance-level endpoints

/.json

Will be deprecated.

/-/instance.json

Info about the entire Datasette instance.
- Instance-level metadata (title, description, etc.)
- A "initial look" at databases, tables, queries for the entire Datasette instance (first 100)

Show interface
interface Instance {

  // only provided when _extra=metadata
  metadata?: {
    title?: string;
    description?: string;
  }
  databases: {
    count: number;
    entries: InstanceDatabaseEntry[];
  }
  tables: {
    count: number;
    entries: InstanceTableEntry[];
  }
  views: {
    count: number;
    entries: InstanceViewEntry[];
  }
  queries: {
    count: number;
    entries: InstanceQueryEntry[];
  }
}

interface InstanceDatabaseEntry {
  database: string;
  name: string;
  // TODO
}
interface InstanceTableEntry {
  database: string;
  name: string;
  // TODO
}
interface InstanceViewEntry {
  database: string;
  name: string;
  // TODO
}
interface InstanceQueryEntry {
  database: string;
  name: string;
  // TODO
}

/-/databases.json

A paginated view of all the databases in the instance

Show interface
interface InstanceDatabases {
  databases: [];
  // only when _extra=metadata
  metadata?: DatabaseMetadata;
  // URL params to next page, if any
  next?: string;
  // full URL to next page, if any
  next_url?: string;
}

interface InstanceDatabase {
  name: string;
}

/-/resources.json

A paginated view of all the tables, views, and queries in each database for the entire instance.

Show interface
interface XXX {
  // URL params to next page, if any
  next?: string;
  // full URL to next page, if any
  next_url?: string;
}

Open question: Since /-/resources is a super-set of tables/views/queries, do we even need them?

These other endpoints would be unaffected:

  • /-/versions.json
  • /-/plugins.json
  • /-/settings.json
  • /-/config.json
  • /-/threads.json
  • /-/actor.json

Database Endpoints

/$DB.json

Returns info about a specific database named $DB for the Datasette instance.

Show interface
interface Database {
  private: boolean;

  // provided only if _extra=metadata
  metadata?: DatabaseMetadata; 

  tables: {
    count: number;
    truncated: boolean;
    
    // first 100 tables
    entries: DatabaseTableEntry[];
  };
  
  views: {
    count: number;
    truncated: boolean;
    
    // first 100 views
    entries: DatabaseViewEntry[];
  };
  
  queries: {
    count: number;
    truncated: boolean;
    
    // first 100 canned queries
    entries: DatabaseQueryEntry[];
  };
}

interface DatabaseTableEntry {
  // TODO
}
interface DatabaseViewEntry {
  // TODO
}
interface DatabaseQueryEntry {
  // TODO
}

/$DB/-/resources.json

Returns "resources" (tables, views, canned queries) for the given database.

interface XXX {
  // URL params to next page, if any
  next?: string;
  // full URL to next page, if any
  next_url?: string;
}

/$DB/-/query.json?sql=...

Perform an SQL query on the specific database and return the results.

interface DatabaseQuery {
  ok: boolean;
  truncated: boolean;
  rows: QueryRow[];
  
  // URL params to next page, if any
  next?: string;
  // full URL to next page, if any
  next_url?: string;
}
interface QueryRow {
  [column_name: string]: any;
};

"Resource" (Table, View, and Query) endpoints

/$DB/$RESOURCE.json

Returns info about the specified resource (table, view, query, etc.).

Show interface
interface Resource {
  ok: boolean;
  name: string;
  resource_type: 'table' | 'view' | 'query';
  database: string; 

  // num of row entries in resource. null when too large to count
  count?: number;
  
  primary_keys: string[];
  primary_key_values: string[];
  
  rows: ResourceRow[];
  
  // URL params to next page of rows, if any
  next?: string;
  // full URL to next page of rows, if any
  next_url?: string;
}

interface ResourceRow {
  [column_name: string]: any;
}

_extra=database_metadata

_labels=on

Row endpoints

Returns info about a specific row, and optionally about the table/database of the row..

/-/$DB/$TABLE/$ID[s].json

interface Row {
  ok: boolean;
  truncated: boolean;
  row: QueryRow;

  // TODO: foreign keys
  // TODO: labels

  // only when _extra=instance_metadata
  instance_metadata?: InstanceMetadata;
  // only when _extra=database_metadata
  database_metadata?: DatabaseMetadata;
  // only when _extra=table_metadata
  table_metadata?: TableMetadata;
}
interface QueryRow {
  [column_name: string]: any;
};
@phildini
Copy link

My one and maybe only comment here is that there doesn't seem to be any logic I can determine around when an api starts with /$DB/ and when it starts with /-/ and when it starts with /-/$DB/

This feels like an implementation detail getting hoisted to the interface, and if that could be different it would aid in comprehension and the inevitable countless "arg, right, /-/" that devs are likely to run into.

@simonw simonw added the design label Jun 24, 2024
@simonw
Copy link
Owner

simonw commented Jul 2, 2024

Datasette's core URL design works like this:

  • / - the homepage
  • /database - stuff relating to that database
  • /database/resource - stuff relating to that table, SQL view or configured canned query (they share a namespace)
  • /database/table/rowids - the page for a specific row in a table

The /-/ prefix is then used for other endpoints relating to those concepts.

So we have a family of things under /-/ relating to the instance:

  • /-/settings
  • /-/databases

Then things under /database/-/ relating to that database:

  • /database/-/query
  • /database/-/tables

And things under /database/table/-/ relating to that table:

We may even have /database/table/rowids/-/... endpoints at some point in the future.

@phildini
Copy link

phildini commented Jul 2, 2024

Is there a verb or noun that /-/ represents?

When I look at the proposal, how should I "read" /$DB/$RESOURCE.json vs /-/$DB/$TABLE/$ID[s].json?

It seems like it "should" be /$DB/$TABLE/$ID[s].json, so that makes me think I'm misunderstanding the use of /-/.

(if this is an API standard I'm not aware of, happy to read those docs instead, but I couldn't find a reference on the JSON API page of datasette)

@brandonrobertz
Copy link
Contributor

Is there a verb or noun that /-/ represents?

I think of it as "datasette function" as everything that follows a dash isn't a database name or a resource inside the DB, but a method provided by datasette that works on the resource before it.

simonw added a commit that referenced this issue Aug 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants