@protobase/cli 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,12 +12,18 @@ const byTable = (rows) => {
12
12
  }
13
13
  return groups;
14
14
  };
15
- const introspect = async (sql) => {
15
+ const kinds = {
16
+ r: "table",
17
+ p: "table",
18
+ v: "view",
19
+ m: "materialized view"
20
+ };
21
+ const introspect = async (sql, relations = {}) => {
16
22
  const [columns, foreignKeys, checks, indexes] = await Promise.all([
17
- selectColumns(sql),
18
- selectForeignKeys(sql),
19
- selectChecks(sql),
20
- selectIndexes(sql)
23
+ selectColumns(sql, relations),
24
+ selectForeignKeys(sql, relations),
25
+ selectChecks(sql, relations),
26
+ selectIndexes(sql, relations)
21
27
  ]);
22
28
  const fks = byTable(foreignKeys);
23
29
  const chks = byTable(checks);
@@ -25,7 +31,8 @@ const introspect = async (sql) => {
25
31
  return [...byTable(columns)].map(([key, rows]) => ({
26
32
  schema: rows[0].schema,
27
33
  name: rows[0].table,
28
- columns: rows.map(({ schema, table, enumValues, defaultExpr, ...column }) => ({
34
+ kind: kinds[rows[0].relkind],
35
+ columns: rows.map(({ schema, table, relkind, enumValues, defaultExpr, ...column }) => ({
29
36
  ...column,
30
37
  enumValues: enumValues ?? [],
31
38
  ...defaultExpr !== null && { defaultExpr }
@@ -1,11 +1,11 @@
1
1
  //#region src/db/queries.ts
2
- const userTables = `
3
- c.relkind in ('r', 'p')
2
+ const userRelations = ({ views = false }) => `
3
+ c.relkind in ('r', 'p'${views ? ", 'v', 'm'" : ""})
4
4
  and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')
5
5
  and n.nspname not like 'pg_temp%'
6
6
  `;
7
- const selectColumns = (sql) => sql`
8
- select n.nspname as schema, c.relname as table, a.attname as name, a.attnum::int as position,
7
+ const selectColumns = (sql, relations) => sql`
8
+ select n.nspname as schema, c.relname as table, c.relkind as relkind, a.attname as name, a.attnum::int as position,
9
9
  bt.typname as "typeName", format_type(a.atttypid, a.atttypmod) as formatted,
10
10
  (bt.typtype = 'e') as "isEnum",
11
11
  (select array_agg(e.enumlabel::text order by e.enumsortorder) from pg_enum e where e.enumtypid = bt.oid) as "enumValues",
@@ -17,10 +17,10 @@ const selectColumns = (sql) => sql`
17
17
  join pg_type t on t.oid = a.atttypid
18
18
  join pg_type bt on bt.oid = case when t.typtype = 'd' then t.typbasetype else t.oid end
19
19
  left join pg_attrdef d on d.adrelid = a.attrelid and d.adnum = a.attnum
20
- where ${sql.unsafe(userTables)} and a.attnum > 0 and not a.attisdropped
20
+ where ${sql.unsafe(userRelations(relations))} and a.attnum > 0 and not a.attisdropped
21
21
  order by n.nspname, c.relname, a.attnum
22
22
  `;
23
- const selectForeignKeys = (sql) => sql`
23
+ const selectForeignKeys = (sql, relations) => sql`
24
24
  select n.nspname as schema, c.relname as table,
25
25
  (select array_agg(a.attname::text order by k.ord) from unnest(con.conkey) with ordinality k(attnum, ord)
26
26
  join pg_attribute a on a.attrelid = con.conrelid and a.attnum = k.attnum) as columns,
@@ -32,9 +32,9 @@ const selectForeignKeys = (sql) => sql`
32
32
  join pg_namespace n on n.oid = c.relnamespace
33
33
  join pg_class rc on rc.oid = con.confrelid
34
34
  join pg_namespace rn on rn.oid = rc.relnamespace
35
- where con.contype = 'f' and ${sql.unsafe(userTables)}
35
+ where con.contype = 'f' and ${sql.unsafe(userRelations(relations))}
36
36
  `;
37
- const selectChecks = (sql) => sql`
37
+ const selectChecks = (sql, relations) => sql`
38
38
  select n.nspname as schema, c.relname as table,
39
39
  (select array_agg(a.attname::text order by k.ord) from unnest(con.conkey) with ordinality k(attnum, ord)
40
40
  join pg_attribute a on a.attrelid = con.conrelid and a.attnum = k.attnum) as columns,
@@ -42,9 +42,9 @@ const selectChecks = (sql) => sql`
42
42
  from pg_constraint con
43
43
  join pg_class c on c.oid = con.conrelid
44
44
  join pg_namespace n on n.oid = c.relnamespace
45
- where con.contype = 'c' and con.conkey is not null and ${sql.unsafe(userTables)}
45
+ where con.contype = 'c' and con.conkey is not null and ${sql.unsafe(userRelations(relations))}
46
46
  `;
47
- const selectIndexes = (sql) => sql`
47
+ const selectIndexes = (sql, relations) => sql`
48
48
  select n.nspname as schema, c.relname as table, ic.relname as name,
49
49
  (select array_agg(case when k.attnum = 0 then null else a.attname::text end order by k.ord)
50
50
  from unnest(i.indkey::int2[]) with ordinality k(attnum, ord)
@@ -56,7 +56,7 @@ const selectIndexes = (sql) => sql`
56
56
  join pg_namespace n on n.oid = c.relnamespace
57
57
  join pg_class ic on ic.oid = i.indexrelid
58
58
  join pg_am am on am.oid = ic.relam
59
- where i.indisvalid and ${sql.unsafe(userTables)}
59
+ where i.indisvalid and ${sql.unsafe(userRelations(relations))}
60
60
  `;
61
61
  //#endregion
62
62
  export { selectChecks, selectColumns, selectForeignKeys, selectIndexes };
@@ -39,7 +39,7 @@ const checkField = (model, table, name) => {
39
39
  resource: model.name,
40
40
  message: `${name}: declared .dbDefault() but ${column.name} has no default the database computes`
41
41
  });
42
- if (field.filterable && !leadingIndex(table, column.name, model.tenant && model.fields[model.tenant]?.column)) issues.push({
42
+ if (field.filterable && table.kind !== "view" && !leadingIndex(table, column.name, model.tenant && model.fields[model.tenant]?.column)) issues.push({
43
43
  severity: "warning",
44
44
  resource: model.name,
45
45
  message: `${name}: filterable but ${column.name} has no index`,
@@ -48,7 +48,8 @@ const checkField = (model, table, name) => {
48
48
  return issues;
49
49
  };
50
50
  const checkKey = (model, table) => {
51
- if (!keyColumns(table)) return [{
51
+ if (table.kind === "view") return [];
52
+ if (table.kind === "table" && !keyColumns(table)) return [{
52
53
  severity: "error",
53
54
  resource: model.name,
54
55
  message: `${tableKey(table)} has no primary key or unique NOT NULL index`,
@@ -8,7 +8,7 @@ const runDoctor = async (options, out) => {
8
8
  const models = await loadResourceModels(options.configDir);
9
9
  if (models.length === 0) throw new Error(`No resources exported from ${options.configDir}/index.ts`);
10
10
  const sql = connect(options.url);
11
- const tables = await introspect(sql).finally(() => sql.end());
11
+ const tables = await introspect(sql, { views: true }).finally(() => sql.end());
12
12
  const issues = models.flatMap((model) => checkResource(model, tables));
13
13
  for (const issue of issues) out(format(issue));
14
14
  const errors = issues.filter((i) => i.severity === "error").length;
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.1.1";
2
+ var version = "0.1.3";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -46,11 +46,11 @@
46
46
  "tsx": "^4.23.15",
47
47
  "vite": "^8.3.3",
48
48
  "zod": "^4.6.5",
49
- "@protobase/layout": "0.1.1",
50
- "@protobase/server": "0.1.1",
51
- "@protobase/ui": "0.1.1",
52
- "@protobase/schema": "0.1.1",
53
- "@protobase/query": "0.1.1"
49
+ "@protobase/layout": "0.1.3",
50
+ "@protobase/query": "0.1.3",
51
+ "@protobase/server": "0.1.3",
52
+ "@protobase/ui": "0.1.3",
53
+ "@protobase/schema": "0.1.3"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsdown"