@geekmidas/db 10.0.0-alpha.16 → 10.0.0-alpha.161

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.
Files changed (90) hide show
  1. package/README.md +17 -0
  2. package/dist/introspect/DataBrowser.cjs +117 -0
  3. package/dist/introspect/DataBrowser.cjs.map +1 -0
  4. package/dist/introspect/DataBrowser.d.cts +51 -0
  5. package/dist/introspect/DataBrowser.d.cts.map +1 -0
  6. package/dist/introspect/DataBrowser.d.mts +51 -0
  7. package/dist/introspect/DataBrowser.d.mts.map +1 -0
  8. package/dist/introspect/DataBrowser.mjs +116 -0
  9. package/dist/introspect/DataBrowser.mjs.map +1 -0
  10. package/dist/introspect/errors.cjs +48 -0
  11. package/dist/introspect/errors.cjs.map +1 -0
  12. package/dist/introspect/errors.d.cts +27 -0
  13. package/dist/introspect/errors.d.cts.map +1 -0
  14. package/dist/introspect/errors.d.mts +27 -0
  15. package/dist/introspect/errors.d.mts.map +1 -0
  16. package/dist/introspect/errors.mjs +45 -0
  17. package/dist/introspect/errors.mjs.map +1 -0
  18. package/dist/introspect/filtering.cjs +101 -0
  19. package/dist/introspect/filtering.cjs.map +1 -0
  20. package/dist/introspect/filtering.d.cts +29 -0
  21. package/dist/introspect/filtering.d.cts.map +1 -0
  22. package/dist/introspect/filtering.d.mts +29 -0
  23. package/dist/introspect/filtering.d.mts.map +1 -0
  24. package/dist/introspect/filtering.mjs +98 -0
  25. package/dist/introspect/filtering.mjs.map +1 -0
  26. package/dist/introspect/handler.cjs +159 -0
  27. package/dist/introspect/handler.cjs.map +1 -0
  28. package/dist/introspect/handler.d.cts +43 -0
  29. package/dist/introspect/handler.d.cts.map +1 -0
  30. package/dist/introspect/handler.d.mts +43 -0
  31. package/dist/introspect/handler.d.mts.map +1 -0
  32. package/dist/introspect/handler.mjs +158 -0
  33. package/dist/introspect/handler.mjs.map +1 -0
  34. package/dist/introspect/index.cjs +22 -0
  35. package/dist/introspect/index.d.cts +8 -0
  36. package/dist/introspect/index.d.mts +8 -0
  37. package/dist/introspect/index.mjs +8 -0
  38. package/dist/introspect/introspection.cjs +149 -0
  39. package/dist/introspect/introspection.cjs.map +1 -0
  40. package/dist/introspect/introspection.d.cts +25 -0
  41. package/dist/introspect/introspection.d.cts.map +1 -0
  42. package/dist/introspect/introspection.d.mts +25 -0
  43. package/dist/introspect/introspection.d.mts.map +1 -0
  44. package/dist/introspect/introspection.mjs +146 -0
  45. package/dist/introspect/introspection.mjs.map +1 -0
  46. package/dist/introspect/types.cjs +24 -0
  47. package/dist/introspect/types.cjs.map +1 -0
  48. package/dist/introspect/types.d.cts +113 -0
  49. package/dist/introspect/types.d.cts.map +1 -0
  50. package/dist/introspect/types.d.mts +113 -0
  51. package/dist/introspect/types.d.mts.map +1 -0
  52. package/dist/introspect/types.mjs +23 -0
  53. package/dist/introspect/types.mjs.map +1 -0
  54. package/dist/kysely/pagination.cjs +1 -0
  55. package/dist/kysely/pagination.cjs.map +1 -1
  56. package/dist/kysely/pagination.d.cts +2 -2
  57. package/dist/kysely/pagination.d.cts.map +1 -1
  58. package/dist/kysely/pagination.d.mts +2 -2
  59. package/dist/kysely/pagination.d.mts.map +1 -1
  60. package/dist/kysely/pagination.mjs +2 -2
  61. package/dist/kysely/pagination.mjs.map +1 -1
  62. package/dist/objection/pagination.cjs +1 -0
  63. package/dist/objection/pagination.cjs.map +1 -1
  64. package/dist/objection/pagination.d.cts +2 -2
  65. package/dist/objection/pagination.d.cts.map +1 -1
  66. package/dist/objection/pagination.d.mts +2 -2
  67. package/dist/objection/pagination.d.mts.map +1 -1
  68. package/dist/objection/pagination.mjs +2 -2
  69. package/dist/objection/pagination.mjs.map +1 -1
  70. package/dist/pagination-CrU89125.d.cts +42 -0
  71. package/dist/pagination-CrU89125.d.cts.map +1 -0
  72. package/dist/pagination-CrU89125.d.mts +42 -0
  73. package/dist/pagination-CrU89125.d.mts.map +1 -0
  74. package/dist/pagination.cjs +17 -1
  75. package/dist/pagination.cjs.map +1 -1
  76. package/dist/pagination.d.cts +2 -30
  77. package/dist/pagination.d.mts +2 -30
  78. package/dist/pagination.mjs +17 -2
  79. package/dist/pagination.mjs.map +1 -1
  80. package/dist/pg/roles.cjs +42 -4
  81. package/dist/pg/roles.cjs.map +1 -1
  82. package/dist/pg/roles.d.cts +19 -0
  83. package/dist/pg/roles.d.cts.map +1 -1
  84. package/dist/pg/roles.d.mts +19 -0
  85. package/dist/pg/roles.d.mts.map +1 -1
  86. package/dist/pg/roles.mjs +42 -4
  87. package/dist/pg/roles.mjs.map +1 -1
  88. package/package.json +13 -2
  89. package/dist/pagination.d.cts.map +0 -1
  90. package/dist/pagination.d.mts.map +0 -1
@@ -0,0 +1,158 @@
1
+ import { InvalidCursor } from "../pagination.mjs";
2
+ import { ColumnNotFound, TableNotFound, UnsupportedFilterOperator } from "./errors.mjs";
3
+ import { FilterOperator } from "./types.mjs";
4
+ import { DataBrowser } from "./DataBrowser.mjs";
5
+ //#region src/introspect/handler.ts
6
+ const OPERATORS = new Set(Object.values(FilterOperator));
7
+ /**
8
+ * A read-only JSON API over a Postgres database's structure and rows.
9
+ *
10
+ * A plain `(Request) => Promise<Response>` rather than a router, so it mounts
11
+ * in anything that speaks web `Request`s — Hono, Bun, Deno, a Node adapter —
12
+ * and `@geekmidas/db` takes on no HTTP framework to provide it.
13
+ *
14
+ * | Route | Returns |
15
+ * | ----------------------------- | ----------------------------------------------- |
16
+ * | `GET /schemas` | `{ schemas, browsable }` |
17
+ * | `GET /tables?schema=&refresh=`| `{ tables }` with columns and foreign keys |
18
+ * | `GET /tables/:name?schema=` | one table |
19
+ * | `GET /tables/:name/rows` | a page of rows: `{ rows, hasMore, nextCursor }` |
20
+ *
21
+ * Rows take `pageSize` (≤ 100), `cursor`, `sort=col:asc,col2:desc` and filters
22
+ * as `filter[column][operator]=value` — `in`/`nin` take a comma-separated
23
+ * list, `is_null`/`is_not_null` take no value.
24
+ *
25
+ * It exposes every row of every browsable table to whoever can reach it. Mount
26
+ * it only where that is the developer: `gkm dev` serves it at `/__gkm/db` and
27
+ * nothing in a production build does.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const handler = createIntrospectionHandler({ db, basePath: '/__gkm/db' });
32
+ * app.all('/__gkm/db/*', (c) => handler(c.req.raw));
33
+ * ```
34
+ */
35
+ function createIntrospectionHandler(options) {
36
+ const browser = new DataBrowser(options);
37
+ const basePath = (options.basePath ?? "").replace(/\/+$/, "");
38
+ return async (request) => {
39
+ if (request.method !== "GET") return json({
40
+ error: "MethodNotAllowed",
41
+ message: "The database API is read-only; only GET is served."
42
+ }, 405, { allow: "GET" });
43
+ const url = new URL(request.url);
44
+ const path = url.pathname.startsWith(basePath) ? url.pathname.slice(basePath.length) : url.pathname;
45
+ const segments = path.split("/").filter(Boolean).map(decodeURIComponent);
46
+ const schema = url.searchParams.get("schema") ?? void 0;
47
+ try {
48
+ if (segments.length === 1 && segments[0] === "schemas") return json({
49
+ schemas: await browser.listSchemas(),
50
+ browsable: browser.schemas
51
+ });
52
+ if (segments.length === 1 && segments[0] === "tables") {
53
+ const refresh = url.searchParams.get("refresh") === "true";
54
+ const { tables, updatedAt } = await browser.getSchema(refresh);
55
+ return json({
56
+ tables: schema ? tables.filter((t) => t.schema === schema) : tables,
57
+ updatedAt
58
+ });
59
+ }
60
+ if (segments[0] === "tables" && segments[1] && segments.length === 2) {
61
+ const table = await browser.getTableInfo(segments[1], schema);
62
+ if (!table) throw new TableNotFound(segments[1], schema ?? browser.schemas[0] ?? "public");
63
+ return json(table);
64
+ }
65
+ if (segments[0] === "tables" && segments[1] && segments[2] === "rows" && segments.length === 3) {
66
+ const pageSize = Number.parseInt(url.searchParams.get("pageSize") ?? "", 10);
67
+ const filters = parseFilters(url.searchParams);
68
+ const sort = parseSort(url.searchParams.get("sort"));
69
+ return json(await browser.query({
70
+ table: segments[1],
71
+ schema,
72
+ pageSize: Number.isNaN(pageSize) ? void 0 : pageSize,
73
+ cursor: url.searchParams.get("cursor") || void 0,
74
+ filters: filters.length > 0 ? filters : void 0,
75
+ sort: sort.length > 0 ? sort : void 0
76
+ }));
77
+ }
78
+ return json({
79
+ error: "NotFound",
80
+ message: `No database route at '${path || "/"}'. Routes: /schemas, /tables, /tables/:name, /tables/:name/rows.`
81
+ }, 404);
82
+ } catch (error) {
83
+ const status = statusFor(error);
84
+ if (status === void 0) throw error;
85
+ return json({
86
+ error: error.name,
87
+ message: error.message
88
+ }, status);
89
+ }
90
+ };
91
+ }
92
+ /** The caller's mistakes, answered; anything else is the server's and thrown. */
93
+ function statusFor(error) {
94
+ if (error instanceof TableNotFound) return 404;
95
+ if (error instanceof ColumnNotFound || error instanceof UnsupportedFilterOperator || error instanceof InvalidCursor) return 400;
96
+ }
97
+ /**
98
+ * `filter[column][operator]=value`. An unknown operator is ignored rather than
99
+ * rejected, as a misspelt one in a hand-typed URL is easier to see in the
100
+ * results than to debug from an error.
101
+ */
102
+ function parseFilters(params) {
103
+ const filters = [];
104
+ for (const [key, value] of params) {
105
+ const match = key.match(/^filter\[(\w+)\]\[(\w+)\]$/);
106
+ const column = match?.[1];
107
+ const operator = match?.[2];
108
+ if (!column || !operator || !OPERATORS.has(operator)) continue;
109
+ const op = operator;
110
+ if (op === "in" || op === "nin") filters.push({
111
+ column,
112
+ operator: op,
113
+ value: value.split(",")
114
+ });
115
+ else if (op === "is_null" || op === "is_not_null") filters.push({
116
+ column,
117
+ operator: op
118
+ });
119
+ else filters.push({
120
+ column,
121
+ operator: op,
122
+ value: parseScalar(value)
123
+ });
124
+ }
125
+ return filters;
126
+ }
127
+ function parseScalar(value) {
128
+ if (value === "true") return true;
129
+ if (value === "false") return false;
130
+ if (value !== "" && !Number.isNaN(Number(value))) return Number(value);
131
+ return value;
132
+ }
133
+ /** `sort=name:asc,created_at:desc`; a column with no direction is ascending. */
134
+ function parseSort(param) {
135
+ if (!param) return [];
136
+ return param.split(",").flatMap((part) => {
137
+ const [column, direction] = part.split(":");
138
+ if (!column) return [];
139
+ return [{
140
+ column,
141
+ direction: direction === "desc" ? "desc" : "asc"
142
+ }];
143
+ });
144
+ }
145
+ function json(body, status = 200, headers = {}) {
146
+ const text = JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value);
147
+ return new Response(text, {
148
+ status,
149
+ headers: {
150
+ "content-type": "application/json; charset=utf-8",
151
+ ...headers
152
+ }
153
+ });
154
+ }
155
+ //#endregion
156
+ export { createIntrospectionHandler };
157
+
158
+ //# sourceMappingURL=handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.mjs","names":[],"sources":["../../src/introspect/handler.ts"],"sourcesContent":["import { Direction, InvalidCursor } from '../pagination';\nimport { DataBrowser } from './DataBrowser';\nimport {\n\tColumnNotFound,\n\tTableNotFound,\n\tUnsupportedFilterOperator,\n} from './errors';\nimport {\n\ttype DataBrowserOptions,\n\ttype FilterCondition,\n\tFilterOperator,\n\ttype SortConfig,\n} from './types';\n\nexport interface IntrospectionHandlerOptions<DB = unknown>\n\textends DataBrowserOptions<DB> {\n\t/**\n\t * The path the handler is mounted under, e.g. `/__gkm/db`. Stripped from\n\t * each request's path before routing, so the handler can be mounted\n\t * anywhere without a router of its own.\n\t */\n\tbasePath?: string;\n}\n\n/** A fetch-style handler: a web `Request` in, a JSON `Response` out. */\nexport type IntrospectionHandler = (request: Request) => Promise<Response>;\n\nconst OPERATORS = new Set<string>(Object.values(FilterOperator));\n\n/**\n * A read-only JSON API over a Postgres database's structure and rows.\n *\n * A plain `(Request) => Promise<Response>` rather than a router, so it mounts\n * in anything that speaks web `Request`s — Hono, Bun, Deno, a Node adapter —\n * and `@geekmidas/db` takes on no HTTP framework to provide it.\n *\n * | Route | Returns |\n * | ----------------------------- | ----------------------------------------------- |\n * | `GET /schemas` | `{ schemas, browsable }` |\n * | `GET /tables?schema=&refresh=`| `{ tables }` with columns and foreign keys |\n * | `GET /tables/:name?schema=` | one table |\n * | `GET /tables/:name/rows` | a page of rows: `{ rows, hasMore, nextCursor }` |\n *\n * Rows take `pageSize` (≤ 100), `cursor`, `sort=col:asc,col2:desc` and filters\n * as `filter[column][operator]=value` — `in`/`nin` take a comma-separated\n * list, `is_null`/`is_not_null` take no value.\n *\n * It exposes every row of every browsable table to whoever can reach it. Mount\n * it only where that is the developer: `gkm dev` serves it at `/__gkm/db` and\n * nothing in a production build does.\n *\n * @example\n * ```typescript\n * const handler = createIntrospectionHandler({ db, basePath: '/__gkm/db' });\n * app.all('/__gkm/db/*', (c) => handler(c.req.raw));\n * ```\n */\nexport function createIntrospectionHandler<DB>(\n\toptions: IntrospectionHandlerOptions<DB>,\n): IntrospectionHandler {\n\tconst browser = new DataBrowser(options);\n\tconst basePath = (options.basePath ?? '').replace(/\\/+$/, '');\n\n\treturn async (request) => {\n\t\tif (request.method !== 'GET') {\n\t\t\treturn json(\n\t\t\t\t{\n\t\t\t\t\terror: 'MethodNotAllowed',\n\t\t\t\t\tmessage: 'The database API is read-only; only GET is served.',\n\t\t\t\t},\n\t\t\t\t405,\n\t\t\t\t{ allow: 'GET' },\n\t\t\t);\n\t\t}\n\n\t\tconst url = new URL(request.url);\n\t\tconst path = url.pathname.startsWith(basePath)\n\t\t\t? url.pathname.slice(basePath.length)\n\t\t\t: url.pathname;\n\t\tconst segments = path.split('/').filter(Boolean).map(decodeURIComponent);\n\t\tconst schema = url.searchParams.get('schema') ?? undefined;\n\n\t\ttry {\n\t\t\tif (segments.length === 1 && segments[0] === 'schemas') {\n\t\t\t\treturn json({\n\t\t\t\t\tschemas: await browser.listSchemas(),\n\t\t\t\t\tbrowsable: browser.schemas,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (segments.length === 1 && segments[0] === 'tables') {\n\t\t\t\tconst refresh = url.searchParams.get('refresh') === 'true';\n\t\t\t\tconst { tables, updatedAt } = await browser.getSchema(refresh);\n\t\t\t\treturn json({\n\t\t\t\t\ttables: schema ? tables.filter((t) => t.schema === schema) : tables,\n\t\t\t\t\tupdatedAt,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (segments[0] === 'tables' && segments[1] && segments.length === 2) {\n\t\t\t\tconst table = await browser.getTableInfo(segments[1], schema);\n\t\t\t\tif (!table) {\n\t\t\t\t\tthrow new TableNotFound(\n\t\t\t\t\t\tsegments[1],\n\t\t\t\t\t\tschema ?? browser.schemas[0] ?? 'public',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn json(table);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tsegments[0] === 'tables' &&\n\t\t\t\tsegments[1] &&\n\t\t\t\tsegments[2] === 'rows' &&\n\t\t\t\tsegments.length === 3\n\t\t\t) {\n\t\t\t\tconst pageSize = Number.parseInt(\n\t\t\t\t\turl.searchParams.get('pageSize') ?? '',\n\t\t\t\t\t10,\n\t\t\t\t);\n\t\t\t\tconst filters = parseFilters(url.searchParams);\n\t\t\t\tconst sort = parseSort(url.searchParams.get('sort'));\n\n\t\t\t\treturn json(\n\t\t\t\t\tawait browser.query({\n\t\t\t\t\t\ttable: segments[1],\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\tpageSize: Number.isNaN(pageSize) ? undefined : pageSize,\n\t\t\t\t\t\tcursor: url.searchParams.get('cursor') || undefined,\n\t\t\t\t\t\tfilters: filters.length > 0 ? filters : undefined,\n\t\t\t\t\t\tsort: sort.length > 0 ? sort : undefined,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn json(\n\t\t\t\t{\n\t\t\t\t\terror: 'NotFound',\n\t\t\t\t\tmessage: `No database route at '${path || '/'}'. Routes: /schemas, /tables, /tables/:name, /tables/:name/rows.`,\n\t\t\t\t},\n\t\t\t\t404,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst status = statusFor(error);\n\t\t\tif (status === undefined) throw error;\n\t\t\treturn json(\n\t\t\t\t{ error: (error as Error).name, message: (error as Error).message },\n\t\t\t\tstatus,\n\t\t\t);\n\t\t}\n\t};\n}\n\n/** The caller's mistakes, answered; anything else is the server's and thrown. */\nfunction statusFor(error: unknown): number | undefined {\n\tif (error instanceof TableNotFound) return 404;\n\tif (\n\t\terror instanceof ColumnNotFound ||\n\t\terror instanceof UnsupportedFilterOperator ||\n\t\terror instanceof InvalidCursor\n\t) {\n\t\treturn 400;\n\t}\n\treturn undefined;\n}\n\n/**\n * `filter[column][operator]=value`. An unknown operator is ignored rather than\n * rejected, as a misspelt one in a hand-typed URL is easier to see in the\n * results than to debug from an error.\n */\nfunction parseFilters(params: URLSearchParams): FilterCondition[] {\n\tconst filters: FilterCondition[] = [];\n\n\tfor (const [key, value] of params) {\n\t\tconst match = key.match(/^filter\\[(\\w+)\\]\\[(\\w+)\\]$/);\n\t\tconst column = match?.[1];\n\t\tconst operator = match?.[2];\n\t\tif (!column || !operator || !OPERATORS.has(operator)) continue;\n\n\t\tconst op = operator as FilterOperator;\n\t\tif (op === FilterOperator.In || op === FilterOperator.Nin) {\n\t\t\tfilters.push({ column, operator: op, value: value.split(',') });\n\t\t} else if (\n\t\t\top === FilterOperator.IsNull ||\n\t\t\top === FilterOperator.IsNotNull\n\t\t) {\n\t\t\tfilters.push({ column, operator: op });\n\t\t} else {\n\t\t\tfilters.push({ column, operator: op, value: parseScalar(value) });\n\t\t}\n\t}\n\n\treturn filters;\n}\n\nfunction parseScalar(value: string): unknown {\n\tif (value === 'true') return true;\n\tif (value === 'false') return false;\n\tif (value !== '' && !Number.isNaN(Number(value))) return Number(value);\n\treturn value;\n}\n\n/** `sort=name:asc,created_at:desc`; a column with no direction is ascending. */\nfunction parseSort(param: string | null): SortConfig[] {\n\tif (!param) return [];\n\n\treturn param.split(',').flatMap((part) => {\n\t\tconst [column, direction] = part.split(':');\n\t\tif (!column) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tcolumn,\n\t\t\t\tdirection: direction === 'desc' ? Direction.Desc : Direction.Asc,\n\t\t\t},\n\t\t];\n\t});\n}\n\nfunction json(\n\tbody: unknown,\n\tstatus = 200,\n\theaders: Record<string, string> = {},\n): Response {\n\t// A row can hold a bigint when the pg client is configured to parse int8,\n\t// and JSON.stringify throws on one.\n\tconst text = JSON.stringify(body, (_key, value) =>\n\t\ttypeof value === 'bigint' ? value.toString() : value,\n\t);\n\treturn new Response(text, {\n\t\tstatus,\n\t\theaders: { 'content-type': 'application/json; charset=utf-8', ...headers },\n\t});\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_pagination = require("../pagination.cjs");
3
+ const require_introspect_errors = require("./errors.cjs");
4
+ const require_introspect_types = require("./types.cjs");
5
+ const require_introspect_filtering = require("./filtering.cjs");
6
+ const require_introspect_introspection = require("./introspection.cjs");
7
+ const require_introspect_DataBrowser = require("./DataBrowser.cjs");
8
+ const require_introspect_handler = require("./handler.cjs");
9
+ exports.ColumnNotFound = require_introspect_errors.ColumnNotFound;
10
+ exports.DataBrowser = require_introspect_DataBrowser.DataBrowser;
11
+ exports.Direction = require_pagination.Direction;
12
+ exports.FilterOperator = require_introspect_types.FilterOperator;
13
+ exports.InvalidCursor = require_pagination.InvalidCursor;
14
+ exports.TableNotFound = require_introspect_errors.TableNotFound;
15
+ exports.UnsupportedFilterOperator = require_introspect_errors.UnsupportedFilterOperator;
16
+ exports.applyFilters = require_introspect_filtering.applyFilters;
17
+ exports.applySorting = require_introspect_filtering.applySorting;
18
+ exports.createIntrospectionHandler = require_introspect_handler.createIntrospectionHandler;
19
+ exports.introspectSchema = require_introspect_introspection.introspectSchema;
20
+ exports.introspectTable = require_introspect_introspection.introspectTable;
21
+ exports.listSchemas = require_introspect_introspection.listSchemas;
22
+ exports.validateFilter = require_introspect_filtering.validateFilter;
@@ -0,0 +1,8 @@
1
+ import { n as InvalidCursor, t as Direction } from "../pagination-CrU89125.cjs";
2
+ import { ColumnInfo, ColumnType, CursorConfig, DataBrowserOptions, FilterCondition, FilterOperator, QueryOptions, QueryResult, SchemaInfo, SortConfig, TableInfo } from "./types.cjs";
3
+ import { DataBrowser } from "./DataBrowser.cjs";
4
+ import { ColumnNotFound, TableNotFound, UnsupportedFilterOperator } from "./errors.cjs";
5
+ import { applyFilters, applySorting, validateFilter } from "./filtering.cjs";
6
+ import { IntrospectionHandler, IntrospectionHandlerOptions, createIntrospectionHandler } from "./handler.cjs";
7
+ import { IntrospectSchemaOptions, introspectSchema, introspectTable, listSchemas } from "./introspection.cjs";
8
+ export { type ColumnInfo, ColumnNotFound, type ColumnType, type CursorConfig, DataBrowser, type DataBrowserOptions, Direction, type FilterCondition, FilterOperator, type IntrospectSchemaOptions, type IntrospectionHandler, type IntrospectionHandlerOptions, InvalidCursor, type QueryOptions, type QueryResult, type SchemaInfo, type SortConfig, type TableInfo, TableNotFound, UnsupportedFilterOperator, applyFilters, applySorting, createIntrospectionHandler, introspectSchema, introspectTable, listSchemas, validateFilter };
@@ -0,0 +1,8 @@
1
+ import { n as InvalidCursor, t as Direction } from "../pagination-CrU89125.mjs";
2
+ import { ColumnInfo, ColumnType, CursorConfig, DataBrowserOptions, FilterCondition, FilterOperator, QueryOptions, QueryResult, SchemaInfo, SortConfig, TableInfo } from "./types.mjs";
3
+ import { DataBrowser } from "./DataBrowser.mjs";
4
+ import { ColumnNotFound, TableNotFound, UnsupportedFilterOperator } from "./errors.mjs";
5
+ import { applyFilters, applySorting, validateFilter } from "./filtering.mjs";
6
+ import { IntrospectionHandler, IntrospectionHandlerOptions, createIntrospectionHandler } from "./handler.mjs";
7
+ import { IntrospectSchemaOptions, introspectSchema, introspectTable, listSchemas } from "./introspection.mjs";
8
+ export { type ColumnInfo, ColumnNotFound, type ColumnType, type CursorConfig, DataBrowser, type DataBrowserOptions, Direction, type FilterCondition, FilterOperator, type IntrospectSchemaOptions, type IntrospectionHandler, type IntrospectionHandlerOptions, InvalidCursor, type QueryOptions, type QueryResult, type SchemaInfo, type SortConfig, type TableInfo, TableNotFound, UnsupportedFilterOperator, applyFilters, applySorting, createIntrospectionHandler, introspectSchema, introspectTable, listSchemas, validateFilter };
@@ -0,0 +1,8 @@
1
+ import { Direction, InvalidCursor } from "../pagination.mjs";
2
+ import { ColumnNotFound, TableNotFound, UnsupportedFilterOperator } from "./errors.mjs";
3
+ import { FilterOperator } from "./types.mjs";
4
+ import { applyFilters, applySorting, validateFilter } from "./filtering.mjs";
5
+ import { introspectSchema, introspectTable, listSchemas } from "./introspection.mjs";
6
+ import { DataBrowser } from "./DataBrowser.mjs";
7
+ import { createIntrospectionHandler } from "./handler.mjs";
8
+ export { ColumnNotFound, DataBrowser, Direction, FilterOperator, InvalidCursor, TableNotFound, UnsupportedFilterOperator, applyFilters, applySorting, createIntrospectionHandler, introspectSchema, introspectTable, listSchemas, validateFilter };
@@ -0,0 +1,149 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let kysely = require("kysely");
3
+ //#region src/introspect/introspection.ts
4
+ /**
5
+ * The schemas the database has, without Postgres's own (`pg_catalog`,
6
+ * `pg_toast`, `information_schema`, …).
7
+ */
8
+ async function listSchemas(db) {
9
+ const { rows } = await kysely.sql`
10
+ SELECT nspname AS name
11
+ FROM pg_catalog.pg_namespace
12
+ WHERE nspname <> 'information_schema'
13
+ AND nspname NOT LIKE 'pg\\_%'
14
+ ORDER BY nspname
15
+ `.execute(db);
16
+ return rows.map((row) => row.name);
17
+ }
18
+ /**
19
+ * Every base table in the given schemas, with its columns, primary key and
20
+ * foreign keys. Views are left out: they have no key to page by.
21
+ */
22
+ async function introspectSchema(db, options = {}) {
23
+ const schemas = options.schemas ?? ["public"];
24
+ const excluded = options.excludeTables ?? [];
25
+ const { rows } = await kysely.sql`
26
+ SELECT table_name AS name, table_schema AS schema
27
+ FROM information_schema.tables
28
+ WHERE table_schema = ANY(${schemas})
29
+ AND table_type = 'BASE TABLE'
30
+ AND NOT (table_name = ANY(${excluded}))
31
+ ORDER BY table_schema, table_name
32
+ `.execute(db);
33
+ return {
34
+ tables: await Promise.all(rows.map((row) => introspectTable(db, row.name, row.schema))),
35
+ updatedAt: /* @__PURE__ */ new Date()
36
+ };
37
+ }
38
+ /**
39
+ * One table's columns, primary key, foreign keys and estimated row count.
40
+ */
41
+ async function introspectTable(db, tableName, schema = "public") {
42
+ const [columns, foreignKeys, estimate] = await Promise.all([
43
+ kysely.sql`
44
+ SELECT
45
+ c.column_name AS name,
46
+ c.udt_name AS udt,
47
+ c.is_nullable AS nullable,
48
+ c.column_default AS fallback,
49
+ EXISTS (
50
+ SELECT 1
51
+ FROM information_schema.table_constraints tc
52
+ JOIN information_schema.key_column_usage ku
53
+ ON tc.constraint_name = ku.constraint_name
54
+ AND tc.table_schema = ku.table_schema
55
+ WHERE tc.table_name = c.table_name
56
+ AND tc.table_schema = c.table_schema
57
+ AND tc.constraint_type = 'PRIMARY KEY'
58
+ AND ku.column_name = c.column_name
59
+ ) AS pk
60
+ FROM information_schema.columns c
61
+ WHERE c.table_name = ${tableName}
62
+ AND c.table_schema = ${schema}
63
+ ORDER BY c.ordinal_position
64
+ `.execute(db),
65
+ kysely.sql`
66
+ SELECT
67
+ kcu.column_name AS name,
68
+ ccu.table_name AS target,
69
+ ccu.column_name AS referenced
70
+ FROM information_schema.table_constraints tc
71
+ JOIN information_schema.key_column_usage kcu
72
+ ON tc.constraint_name = kcu.constraint_name
73
+ AND tc.table_schema = kcu.table_schema
74
+ JOIN information_schema.constraint_column_usage ccu
75
+ ON tc.constraint_name = ccu.constraint_name
76
+ AND tc.table_schema = ccu.table_schema
77
+ WHERE tc.table_name = ${tableName}
78
+ AND tc.table_schema = ${schema}
79
+ AND tc.constraint_type = 'FOREIGN KEY'
80
+ `.execute(db),
81
+ kysely.sql`
82
+ SELECT c.reltuples::bigint AS estimate
83
+ FROM pg_catalog.pg_class c
84
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
85
+ WHERE c.relname = ${tableName}
86
+ AND n.nspname = ${schema}
87
+ `.execute(db)
88
+ ]);
89
+ const references = new Map(foreignKeys.rows.map((row) => [row.name, {
90
+ table: row.target,
91
+ column: row.referenced
92
+ }]));
93
+ const columnInfo = columns.rows.map((row) => {
94
+ const reference = references.get(row.name);
95
+ return {
96
+ name: row.name,
97
+ type: mapPostgresType(row.udt),
98
+ rawType: row.udt,
99
+ nullable: row.nullable === "YES",
100
+ isPrimaryKey: row.pk,
101
+ isForeignKey: !!reference,
102
+ foreignKeyTable: reference?.table,
103
+ foreignKeyColumn: reference?.column,
104
+ defaultValue: row.fallback ?? void 0
105
+ };
106
+ });
107
+ const rowEstimate = Number(estimate.rows[0]?.estimate ?? 0);
108
+ return {
109
+ name: tableName,
110
+ schema,
111
+ columns: columnInfo,
112
+ primaryKey: columnInfo.filter((c) => c.isPrimaryKey).map((c) => c.name),
113
+ estimatedRowCount: rowEstimate > 0 ? rowEstimate : void 0
114
+ };
115
+ }
116
+ const POSTGRES_TYPES = {
117
+ varchar: "string",
118
+ char: "string",
119
+ text: "string",
120
+ name: "string",
121
+ bpchar: "string",
122
+ citext: "string",
123
+ int2: "number",
124
+ int4: "number",
125
+ int8: "number",
126
+ float4: "number",
127
+ float8: "number",
128
+ numeric: "number",
129
+ money: "number",
130
+ bool: "boolean",
131
+ date: "date",
132
+ timestamp: "datetime",
133
+ timestamptz: "datetime",
134
+ time: "datetime",
135
+ timetz: "datetime",
136
+ json: "json",
137
+ jsonb: "json",
138
+ bytea: "binary",
139
+ uuid: "uuid"
140
+ };
141
+ function mapPostgresType(udtName) {
142
+ return POSTGRES_TYPES[udtName] ?? "unknown";
143
+ }
144
+ //#endregion
145
+ exports.introspectSchema = introspectSchema;
146
+ exports.introspectTable = introspectTable;
147
+ exports.listSchemas = listSchemas;
148
+
149
+ //# sourceMappingURL=introspection.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.cjs","names":[],"sources":["../../src/introspect/introspection.ts"],"sourcesContent":["import { type Kysely, sql } from 'kysely';\nimport type { ColumnInfo, ColumnType, SchemaInfo, TableInfo } from './types';\n\n// Every query aliases its columns to a single lower-case word. A `Kysely` built\n// with `CamelCasePlugin` rewrites result keys, so `column_name` would come back\n// as `columnName` from one client and `column_name` from another; a one-word\n// alias reads the same through both.\n\n/**\n * The schemas the database has, without Postgres's own (`pg_catalog`,\n * `pg_toast`, `information_schema`, …).\n */\nexport async function listSchemas<DB>(db: Kysely<DB>): Promise<string[]> {\n\tconst { rows } = await sql<{ name: string }>`\n\t\tSELECT nspname AS name\n\t\tFROM pg_catalog.pg_namespace\n\t\tWHERE nspname <> 'information_schema'\n\t\t\tAND nspname NOT LIKE 'pg\\\\_%'\n\t\tORDER BY nspname\n\t`.execute(db);\n\n\treturn rows.map((row) => row.name);\n}\n\nexport interface IntrospectSchemaOptions {\n\t/** The schemas to read tables from (default: `['public']`). */\n\tschemas?: string[];\n\t/** Tables to leave out, by name. */\n\texcludeTables?: string[];\n}\n\n/**\n * Every base table in the given schemas, with its columns, primary key and\n * foreign keys. Views are left out: they have no key to page by.\n */\nexport async function introspectSchema<DB>(\n\tdb: Kysely<DB>,\n\toptions: IntrospectSchemaOptions = {},\n): Promise<SchemaInfo> {\n\tconst schemas = options.schemas ?? ['public'];\n\tconst excluded = options.excludeTables ?? [];\n\n\tconst { rows } = await sql<{ name: string; schema: string }>`\n\t\tSELECT table_name AS name, table_schema AS schema\n\t\tFROM information_schema.tables\n\t\tWHERE table_schema = ANY(${schemas})\n\t\t\tAND table_type = 'BASE TABLE'\n\t\t\tAND NOT (table_name = ANY(${excluded}))\n\t\tORDER BY table_schema, table_name\n\t`.execute(db);\n\n\tconst tables = await Promise.all(\n\t\trows.map((row) => introspectTable(db, row.name, row.schema)),\n\t);\n\n\treturn { tables, updatedAt: new Date() };\n}\n\n/**\n * One table's columns, primary key, foreign keys and estimated row count.\n */\nexport async function introspectTable<DB>(\n\tdb: Kysely<DB>,\n\ttableName: string,\n\tschema = 'public',\n): Promise<TableInfo> {\n\tconst [columns, foreignKeys, estimate] = await Promise.all([\n\t\tsql<{\n\t\t\tname: string;\n\t\t\tudt: string;\n\t\t\tnullable: string;\n\t\t\tfallback: string | null;\n\t\t\tpk: boolean;\n\t\t}>`\n\t\t\tSELECT\n\t\t\t\tc.column_name AS name,\n\t\t\t\tc.udt_name AS udt,\n\t\t\t\tc.is_nullable AS nullable,\n\t\t\t\tc.column_default AS fallback,\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM information_schema.table_constraints tc\n\t\t\t\t\tJOIN information_schema.key_column_usage ku\n\t\t\t\t\t\tON tc.constraint_name = ku.constraint_name\n\t\t\t\t\t\tAND tc.table_schema = ku.table_schema\n\t\t\t\t\tWHERE tc.table_name = c.table_name\n\t\t\t\t\t\tAND tc.table_schema = c.table_schema\n\t\t\t\t\t\tAND tc.constraint_type = 'PRIMARY KEY'\n\t\t\t\t\t\tAND ku.column_name = c.column_name\n\t\t\t\t) AS pk\n\t\t\tFROM information_schema.columns c\n\t\t\tWHERE c.table_name = ${tableName}\n\t\t\t\tAND c.table_schema = ${schema}\n\t\t\tORDER BY c.ordinal_position\n\t\t`.execute(db),\n\t\tsql<{ name: string; target: string; referenced: string }>`\n\t\t\tSELECT\n\t\t\t\tkcu.column_name AS name,\n\t\t\t\tccu.table_name AS target,\n\t\t\t\tccu.column_name AS referenced\n\t\t\tFROM information_schema.table_constraints tc\n\t\t\tJOIN information_schema.key_column_usage kcu\n\t\t\t\tON tc.constraint_name = kcu.constraint_name\n\t\t\t\tAND tc.table_schema = kcu.table_schema\n\t\t\tJOIN information_schema.constraint_column_usage ccu\n\t\t\t\tON tc.constraint_name = ccu.constraint_name\n\t\t\t\tAND tc.table_schema = ccu.table_schema\n\t\t\tWHERE tc.table_name = ${tableName}\n\t\t\t\tAND tc.table_schema = ${schema}\n\t\t\t\tAND tc.constraint_type = 'FOREIGN KEY'\n\t\t`.execute(db),\n\t\t// The planner's estimate rather than `count(*)`, which scans the table.\n\t\t// It is -1 for a table never analysed, which is \"unknown\", not empty.\n\t\tsql<{ estimate: string | number }>`\n\t\t\tSELECT c.reltuples::bigint AS estimate\n\t\t\tFROM pg_catalog.pg_class c\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\t\tWHERE c.relname = ${tableName}\n\t\t\t\tAND n.nspname = ${schema}\n\t\t`.execute(db),\n\t]);\n\n\tconst references = new Map(\n\t\tforeignKeys.rows.map((row) => [\n\t\t\trow.name,\n\t\t\t{ table: row.target, column: row.referenced },\n\t\t]),\n\t);\n\n\tconst columnInfo: ColumnInfo[] = columns.rows.map((row) => {\n\t\tconst reference = references.get(row.name);\n\t\treturn {\n\t\t\tname: row.name,\n\t\t\ttype: mapPostgresType(row.udt),\n\t\t\trawType: row.udt,\n\t\t\tnullable: row.nullable === 'YES',\n\t\t\tisPrimaryKey: row.pk,\n\t\t\tisForeignKey: !!reference,\n\t\t\tforeignKeyTable: reference?.table,\n\t\t\tforeignKeyColumn: reference?.column,\n\t\t\tdefaultValue: row.fallback ?? undefined,\n\t\t};\n\t});\n\n\tconst rowEstimate = Number(estimate.rows[0]?.estimate ?? 0);\n\n\treturn {\n\t\tname: tableName,\n\t\tschema,\n\t\tcolumns: columnInfo,\n\t\tprimaryKey: columnInfo.filter((c) => c.isPrimaryKey).map((c) => c.name),\n\t\testimatedRowCount: rowEstimate > 0 ? rowEstimate : undefined,\n\t};\n}\n\nconst POSTGRES_TYPES: Record<string, ColumnType> = {\n\tvarchar: 'string',\n\tchar: 'string',\n\ttext: 'string',\n\tname: 'string',\n\tbpchar: 'string',\n\tcitext: 'string',\n\n\tint2: 'number',\n\tint4: 'number',\n\tint8: 'number',\n\tfloat4: 'number',\n\tfloat8: 'number',\n\tnumeric: 'number',\n\tmoney: 'number',\n\n\tbool: 'boolean',\n\n\tdate: 'date',\n\ttimestamp: 'datetime',\n\ttimestamptz: 'datetime',\n\ttime: 'datetime',\n\ttimetz: 'datetime',\n\n\tjson: 'json',\n\tjsonb: 'json',\n\n\tbytea: 'binary',\n\n\tuuid: 'uuid',\n};\n\nfunction mapPostgresType(udtName: string): ColumnType {\n\treturn POSTGRES_TYPES[udtName] ?? 'unknown';\n}\n"],"mappings":";;;;;;;AAYA,eAAsB,YAAgB,IAAmC;CACxE,MAAM,EAAE,SAAS,MAAM,OAAA,GAAqB;;;;;;GAM1C,QAAQ,EAAE;CAEZ,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI;AAClC;;;;;AAaA,eAAsB,iBACrB,IACA,UAAmC,CAAC,GACd;CACtB,MAAM,UAAU,QAAQ,WAAW,CAAC,QAAQ;CAC5C,MAAM,WAAW,QAAQ,iBAAiB,CAAC;CAE3C,MAAM,EAAE,SAAS,MAAM,OAAA,GAAqC;;;6BAGhC,QAAQ;;+BAEN,SAAS;;GAErC,QAAQ,EAAE;CAMZ,OAAO;EAAE,QAAA,MAJY,QAAQ,IAC5B,KAAK,KAAK,QAAQ,gBAAgB,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC,CAC5D;EAEiB,2BAAW,IAAI,KAAK;CAAE;AACxC;;;;AAKA,eAAsB,gBACrB,IACA,WACA,SAAS,UACY;CACrB,MAAM,CAAC,SAAS,aAAa,YAAY,MAAM,QAAQ,IAAI;EAC1D,OAAA,GAME;;;;;;;;;;;;;;;;;;0BAkBsB,UAAU;2BACT,OAAO;;IAE9B,QAAQ,EAAE;EACZ,OAAA,GAAyD;;;;;;;;;;;;2BAYhC,UAAU;4BACT,OAAO;;IAE/B,QAAQ,EAAE;EAGZ,OAAA,GAAkC;;;;uBAIb,UAAU;sBACX,OAAO;IACzB,QAAQ,EAAE;CACb,CAAC;CAED,MAAM,aAAa,IAAI,IACtB,YAAY,KAAK,KAAK,QAAQ,CAC7B,IAAI,MACJ;EAAE,OAAO,IAAI;EAAQ,QAAQ,IAAI;CAAW,CAC7C,CAAC,CACF;CAEA,MAAM,aAA2B,QAAQ,KAAK,KAAK,QAAQ;EAC1D,MAAM,YAAY,WAAW,IAAI,IAAI,IAAI;EACzC,OAAO;GACN,MAAM,IAAI;GACV,MAAM,gBAAgB,IAAI,GAAG;GAC7B,SAAS,IAAI;GACb,UAAU,IAAI,aAAa;GAC3B,cAAc,IAAI;GAClB,cAAc,CAAC,CAAC;GAChB,iBAAiB,WAAW;GAC5B,kBAAkB,WAAW;GAC7B,cAAc,IAAI,YAAY,KAAA;EAC/B;CACD,CAAC;CAED,MAAM,cAAc,OAAO,SAAS,KAAK,EAAE,EAAE,YAAY,CAAC;CAE1D,OAAO;EACN,MAAM;EACN;EACA,SAAS;EACT,YAAY,WAAW,QAAQ,MAAM,EAAE,YAAY,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI;EACtE,mBAAmB,cAAc,IAAI,cAAc,KAAA;CACpD;AACD;AAEA,MAAM,iBAA6C;CAClD,SAAS;CACT,MAAM;CACN,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CAER,MAAM;CACN,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,OAAO;CAEP,MAAM;CAEN,MAAM;CACN,WAAW;CACX,aAAa;CACb,MAAM;CACN,QAAQ;CAER,MAAM;CACN,OAAO;CAEP,OAAO;CAEP,MAAM;AACP;AAEA,SAAS,gBAAgB,SAA6B;CACrD,OAAO,eAAe,YAAY;AACnC"}
@@ -0,0 +1,25 @@
1
+ import { SchemaInfo, TableInfo } from "./types.cjs";
2
+ import { Kysely } from "kysely";
3
+ //#region src/introspect/introspection.d.ts
4
+ /**
5
+ * The schemas the database has, without Postgres's own (`pg_catalog`,
6
+ * `pg_toast`, `information_schema`, …).
7
+ */
8
+ export declare function listSchemas<DB>(db: Kysely<DB>): Promise<string[]>;
9
+ export interface IntrospectSchemaOptions {
10
+ /** The schemas to read tables from (default: `['public']`). */
11
+ schemas?: string[];
12
+ /** Tables to leave out, by name. */
13
+ excludeTables?: string[];
14
+ }
15
+ /**
16
+ * Every base table in the given schemas, with its columns, primary key and
17
+ * foreign keys. Views are left out: they have no key to page by.
18
+ */
19
+ export declare function introspectSchema<DB>(db: Kysely<DB>, options?: IntrospectSchemaOptions): Promise<SchemaInfo>;
20
+ /**
21
+ * One table's columns, primary key, foreign keys and estimated row count.
22
+ */
23
+ export declare function introspectTable<DB>(db: Kysely<DB>, tableName: string, schema?: string): Promise<TableInfo>;
24
+ //#endregion
25
+ //# sourceMappingURL=introspection.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.d.cts","names":[],"sources":["../../src/introspect/introspection.ts"],"mappings":";;;;;;;wBAYsB,YAAY,IAAI,IAAI,OAAO,MAAM;iBAYtC;;EAEhB;;EAEA;;;;;;wBAOqB,iBAAiB,IACtC,IAAI,OAAO,KACX,UAAS,0BACP,QAAQ;;;;wBAuBW,gBAAgB,IACrC,IAAI,OAAO,KACX,mBACA,kBACE,QAAQ"}
@@ -0,0 +1,25 @@
1
+ import { SchemaInfo, TableInfo } from "./types.mjs";
2
+ import { Kysely } from "kysely";
3
+ //#region src/introspect/introspection.d.ts
4
+ /**
5
+ * The schemas the database has, without Postgres's own (`pg_catalog`,
6
+ * `pg_toast`, `information_schema`, …).
7
+ */
8
+ export declare function listSchemas<DB>(db: Kysely<DB>): Promise<string[]>;
9
+ export interface IntrospectSchemaOptions {
10
+ /** The schemas to read tables from (default: `['public']`). */
11
+ schemas?: string[];
12
+ /** Tables to leave out, by name. */
13
+ excludeTables?: string[];
14
+ }
15
+ /**
16
+ * Every base table in the given schemas, with its columns, primary key and
17
+ * foreign keys. Views are left out: they have no key to page by.
18
+ */
19
+ export declare function introspectSchema<DB>(db: Kysely<DB>, options?: IntrospectSchemaOptions): Promise<SchemaInfo>;
20
+ /**
21
+ * One table's columns, primary key, foreign keys and estimated row count.
22
+ */
23
+ export declare function introspectTable<DB>(db: Kysely<DB>, tableName: string, schema?: string): Promise<TableInfo>;
24
+ //#endregion
25
+ //# sourceMappingURL=introspection.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.d.mts","names":[],"sources":["../../src/introspect/introspection.ts"],"mappings":";;;;;;;wBAYsB,YAAY,IAAI,IAAI,OAAO,MAAM;iBAYtC;;EAEhB;;EAEA;;;;;;wBAOqB,iBAAiB,IACtC,IAAI,OAAO,KACX,UAAS,0BACP,QAAQ;;;;wBAuBW,gBAAgB,IACrC,IAAI,OAAO,KACX,mBACA,kBACE,QAAQ"}
@@ -0,0 +1,146 @@
1
+ import { sql } from "kysely";
2
+ //#region src/introspect/introspection.ts
3
+ /**
4
+ * The schemas the database has, without Postgres's own (`pg_catalog`,
5
+ * `pg_toast`, `information_schema`, …).
6
+ */
7
+ async function listSchemas(db) {
8
+ const { rows } = await sql`
9
+ SELECT nspname AS name
10
+ FROM pg_catalog.pg_namespace
11
+ WHERE nspname <> 'information_schema'
12
+ AND nspname NOT LIKE 'pg\\_%'
13
+ ORDER BY nspname
14
+ `.execute(db);
15
+ return rows.map((row) => row.name);
16
+ }
17
+ /**
18
+ * Every base table in the given schemas, with its columns, primary key and
19
+ * foreign keys. Views are left out: they have no key to page by.
20
+ */
21
+ async function introspectSchema(db, options = {}) {
22
+ const schemas = options.schemas ?? ["public"];
23
+ const excluded = options.excludeTables ?? [];
24
+ const { rows } = await sql`
25
+ SELECT table_name AS name, table_schema AS schema
26
+ FROM information_schema.tables
27
+ WHERE table_schema = ANY(${schemas})
28
+ AND table_type = 'BASE TABLE'
29
+ AND NOT (table_name = ANY(${excluded}))
30
+ ORDER BY table_schema, table_name
31
+ `.execute(db);
32
+ return {
33
+ tables: await Promise.all(rows.map((row) => introspectTable(db, row.name, row.schema))),
34
+ updatedAt: /* @__PURE__ */ new Date()
35
+ };
36
+ }
37
+ /**
38
+ * One table's columns, primary key, foreign keys and estimated row count.
39
+ */
40
+ async function introspectTable(db, tableName, schema = "public") {
41
+ const [columns, foreignKeys, estimate] = await Promise.all([
42
+ sql`
43
+ SELECT
44
+ c.column_name AS name,
45
+ c.udt_name AS udt,
46
+ c.is_nullable AS nullable,
47
+ c.column_default AS fallback,
48
+ EXISTS (
49
+ SELECT 1
50
+ FROM information_schema.table_constraints tc
51
+ JOIN information_schema.key_column_usage ku
52
+ ON tc.constraint_name = ku.constraint_name
53
+ AND tc.table_schema = ku.table_schema
54
+ WHERE tc.table_name = c.table_name
55
+ AND tc.table_schema = c.table_schema
56
+ AND tc.constraint_type = 'PRIMARY KEY'
57
+ AND ku.column_name = c.column_name
58
+ ) AS pk
59
+ FROM information_schema.columns c
60
+ WHERE c.table_name = ${tableName}
61
+ AND c.table_schema = ${schema}
62
+ ORDER BY c.ordinal_position
63
+ `.execute(db),
64
+ sql`
65
+ SELECT
66
+ kcu.column_name AS name,
67
+ ccu.table_name AS target,
68
+ ccu.column_name AS referenced
69
+ FROM information_schema.table_constraints tc
70
+ JOIN information_schema.key_column_usage kcu
71
+ ON tc.constraint_name = kcu.constraint_name
72
+ AND tc.table_schema = kcu.table_schema
73
+ JOIN information_schema.constraint_column_usage ccu
74
+ ON tc.constraint_name = ccu.constraint_name
75
+ AND tc.table_schema = ccu.table_schema
76
+ WHERE tc.table_name = ${tableName}
77
+ AND tc.table_schema = ${schema}
78
+ AND tc.constraint_type = 'FOREIGN KEY'
79
+ `.execute(db),
80
+ sql`
81
+ SELECT c.reltuples::bigint AS estimate
82
+ FROM pg_catalog.pg_class c
83
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
84
+ WHERE c.relname = ${tableName}
85
+ AND n.nspname = ${schema}
86
+ `.execute(db)
87
+ ]);
88
+ const references = new Map(foreignKeys.rows.map((row) => [row.name, {
89
+ table: row.target,
90
+ column: row.referenced
91
+ }]));
92
+ const columnInfo = columns.rows.map((row) => {
93
+ const reference = references.get(row.name);
94
+ return {
95
+ name: row.name,
96
+ type: mapPostgresType(row.udt),
97
+ rawType: row.udt,
98
+ nullable: row.nullable === "YES",
99
+ isPrimaryKey: row.pk,
100
+ isForeignKey: !!reference,
101
+ foreignKeyTable: reference?.table,
102
+ foreignKeyColumn: reference?.column,
103
+ defaultValue: row.fallback ?? void 0
104
+ };
105
+ });
106
+ const rowEstimate = Number(estimate.rows[0]?.estimate ?? 0);
107
+ return {
108
+ name: tableName,
109
+ schema,
110
+ columns: columnInfo,
111
+ primaryKey: columnInfo.filter((c) => c.isPrimaryKey).map((c) => c.name),
112
+ estimatedRowCount: rowEstimate > 0 ? rowEstimate : void 0
113
+ };
114
+ }
115
+ const POSTGRES_TYPES = {
116
+ varchar: "string",
117
+ char: "string",
118
+ text: "string",
119
+ name: "string",
120
+ bpchar: "string",
121
+ citext: "string",
122
+ int2: "number",
123
+ int4: "number",
124
+ int8: "number",
125
+ float4: "number",
126
+ float8: "number",
127
+ numeric: "number",
128
+ money: "number",
129
+ bool: "boolean",
130
+ date: "date",
131
+ timestamp: "datetime",
132
+ timestamptz: "datetime",
133
+ time: "datetime",
134
+ timetz: "datetime",
135
+ json: "json",
136
+ jsonb: "json",
137
+ bytea: "binary",
138
+ uuid: "uuid"
139
+ };
140
+ function mapPostgresType(udtName) {
141
+ return POSTGRES_TYPES[udtName] ?? "unknown";
142
+ }
143
+ //#endregion
144
+ export { introspectSchema, introspectTable, listSchemas };
145
+
146
+ //# sourceMappingURL=introspection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.mjs","names":[],"sources":["../../src/introspect/introspection.ts"],"sourcesContent":["import { type Kysely, sql } from 'kysely';\nimport type { ColumnInfo, ColumnType, SchemaInfo, TableInfo } from './types';\n\n// Every query aliases its columns to a single lower-case word. A `Kysely` built\n// with `CamelCasePlugin` rewrites result keys, so `column_name` would come back\n// as `columnName` from one client and `column_name` from another; a one-word\n// alias reads the same through both.\n\n/**\n * The schemas the database has, without Postgres's own (`pg_catalog`,\n * `pg_toast`, `information_schema`, …).\n */\nexport async function listSchemas<DB>(db: Kysely<DB>): Promise<string[]> {\n\tconst { rows } = await sql<{ name: string }>`\n\t\tSELECT nspname AS name\n\t\tFROM pg_catalog.pg_namespace\n\t\tWHERE nspname <> 'information_schema'\n\t\t\tAND nspname NOT LIKE 'pg\\\\_%'\n\t\tORDER BY nspname\n\t`.execute(db);\n\n\treturn rows.map((row) => row.name);\n}\n\nexport interface IntrospectSchemaOptions {\n\t/** The schemas to read tables from (default: `['public']`). */\n\tschemas?: string[];\n\t/** Tables to leave out, by name. */\n\texcludeTables?: string[];\n}\n\n/**\n * Every base table in the given schemas, with its columns, primary key and\n * foreign keys. Views are left out: they have no key to page by.\n */\nexport async function introspectSchema<DB>(\n\tdb: Kysely<DB>,\n\toptions: IntrospectSchemaOptions = {},\n): Promise<SchemaInfo> {\n\tconst schemas = options.schemas ?? ['public'];\n\tconst excluded = options.excludeTables ?? [];\n\n\tconst { rows } = await sql<{ name: string; schema: string }>`\n\t\tSELECT table_name AS name, table_schema AS schema\n\t\tFROM information_schema.tables\n\t\tWHERE table_schema = ANY(${schemas})\n\t\t\tAND table_type = 'BASE TABLE'\n\t\t\tAND NOT (table_name = ANY(${excluded}))\n\t\tORDER BY table_schema, table_name\n\t`.execute(db);\n\n\tconst tables = await Promise.all(\n\t\trows.map((row) => introspectTable(db, row.name, row.schema)),\n\t);\n\n\treturn { tables, updatedAt: new Date() };\n}\n\n/**\n * One table's columns, primary key, foreign keys and estimated row count.\n */\nexport async function introspectTable<DB>(\n\tdb: Kysely<DB>,\n\ttableName: string,\n\tschema = 'public',\n): Promise<TableInfo> {\n\tconst [columns, foreignKeys, estimate] = await Promise.all([\n\t\tsql<{\n\t\t\tname: string;\n\t\t\tudt: string;\n\t\t\tnullable: string;\n\t\t\tfallback: string | null;\n\t\t\tpk: boolean;\n\t\t}>`\n\t\t\tSELECT\n\t\t\t\tc.column_name AS name,\n\t\t\t\tc.udt_name AS udt,\n\t\t\t\tc.is_nullable AS nullable,\n\t\t\t\tc.column_default AS fallback,\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM information_schema.table_constraints tc\n\t\t\t\t\tJOIN information_schema.key_column_usage ku\n\t\t\t\t\t\tON tc.constraint_name = ku.constraint_name\n\t\t\t\t\t\tAND tc.table_schema = ku.table_schema\n\t\t\t\t\tWHERE tc.table_name = c.table_name\n\t\t\t\t\t\tAND tc.table_schema = c.table_schema\n\t\t\t\t\t\tAND tc.constraint_type = 'PRIMARY KEY'\n\t\t\t\t\t\tAND ku.column_name = c.column_name\n\t\t\t\t) AS pk\n\t\t\tFROM information_schema.columns c\n\t\t\tWHERE c.table_name = ${tableName}\n\t\t\t\tAND c.table_schema = ${schema}\n\t\t\tORDER BY c.ordinal_position\n\t\t`.execute(db),\n\t\tsql<{ name: string; target: string; referenced: string }>`\n\t\t\tSELECT\n\t\t\t\tkcu.column_name AS name,\n\t\t\t\tccu.table_name AS target,\n\t\t\t\tccu.column_name AS referenced\n\t\t\tFROM information_schema.table_constraints tc\n\t\t\tJOIN information_schema.key_column_usage kcu\n\t\t\t\tON tc.constraint_name = kcu.constraint_name\n\t\t\t\tAND tc.table_schema = kcu.table_schema\n\t\t\tJOIN information_schema.constraint_column_usage ccu\n\t\t\t\tON tc.constraint_name = ccu.constraint_name\n\t\t\t\tAND tc.table_schema = ccu.table_schema\n\t\t\tWHERE tc.table_name = ${tableName}\n\t\t\t\tAND tc.table_schema = ${schema}\n\t\t\t\tAND tc.constraint_type = 'FOREIGN KEY'\n\t\t`.execute(db),\n\t\t// The planner's estimate rather than `count(*)`, which scans the table.\n\t\t// It is -1 for a table never analysed, which is \"unknown\", not empty.\n\t\tsql<{ estimate: string | number }>`\n\t\t\tSELECT c.reltuples::bigint AS estimate\n\t\t\tFROM pg_catalog.pg_class c\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\t\tWHERE c.relname = ${tableName}\n\t\t\t\tAND n.nspname = ${schema}\n\t\t`.execute(db),\n\t]);\n\n\tconst references = new Map(\n\t\tforeignKeys.rows.map((row) => [\n\t\t\trow.name,\n\t\t\t{ table: row.target, column: row.referenced },\n\t\t]),\n\t);\n\n\tconst columnInfo: ColumnInfo[] = columns.rows.map((row) => {\n\t\tconst reference = references.get(row.name);\n\t\treturn {\n\t\t\tname: row.name,\n\t\t\ttype: mapPostgresType(row.udt),\n\t\t\trawType: row.udt,\n\t\t\tnullable: row.nullable === 'YES',\n\t\t\tisPrimaryKey: row.pk,\n\t\t\tisForeignKey: !!reference,\n\t\t\tforeignKeyTable: reference?.table,\n\t\t\tforeignKeyColumn: reference?.column,\n\t\t\tdefaultValue: row.fallback ?? undefined,\n\t\t};\n\t});\n\n\tconst rowEstimate = Number(estimate.rows[0]?.estimate ?? 0);\n\n\treturn {\n\t\tname: tableName,\n\t\tschema,\n\t\tcolumns: columnInfo,\n\t\tprimaryKey: columnInfo.filter((c) => c.isPrimaryKey).map((c) => c.name),\n\t\testimatedRowCount: rowEstimate > 0 ? rowEstimate : undefined,\n\t};\n}\n\nconst POSTGRES_TYPES: Record<string, ColumnType> = {\n\tvarchar: 'string',\n\tchar: 'string',\n\ttext: 'string',\n\tname: 'string',\n\tbpchar: 'string',\n\tcitext: 'string',\n\n\tint2: 'number',\n\tint4: 'number',\n\tint8: 'number',\n\tfloat4: 'number',\n\tfloat8: 'number',\n\tnumeric: 'number',\n\tmoney: 'number',\n\n\tbool: 'boolean',\n\n\tdate: 'date',\n\ttimestamp: 'datetime',\n\ttimestamptz: 'datetime',\n\ttime: 'datetime',\n\ttimetz: 'datetime',\n\n\tjson: 'json',\n\tjsonb: 'json',\n\n\tbytea: 'binary',\n\n\tuuid: 'uuid',\n};\n\nfunction mapPostgresType(udtName: string): ColumnType {\n\treturn POSTGRES_TYPES[udtName] ?? 'unknown';\n}\n"],"mappings":";;;;;;AAYA,eAAsB,YAAgB,IAAmC;CACxE,MAAM,EAAE,SAAS,MAAM,GAAqB;;;;;;GAM1C,QAAQ,EAAE;CAEZ,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI;AAClC;;;;;AAaA,eAAsB,iBACrB,IACA,UAAmC,CAAC,GACd;CACtB,MAAM,UAAU,QAAQ,WAAW,CAAC,QAAQ;CAC5C,MAAM,WAAW,QAAQ,iBAAiB,CAAC;CAE3C,MAAM,EAAE,SAAS,MAAM,GAAqC;;;6BAGhC,QAAQ;;+BAEN,SAAS;;GAErC,QAAQ,EAAE;CAMZ,OAAO;EAAE,QAAA,MAJY,QAAQ,IAC5B,KAAK,KAAK,QAAQ,gBAAgB,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC,CAC5D;EAEiB,2BAAW,IAAI,KAAK;CAAE;AACxC;;;;AAKA,eAAsB,gBACrB,IACA,WACA,SAAS,UACY;CACrB,MAAM,CAAC,SAAS,aAAa,YAAY,MAAM,QAAQ,IAAI;EAC1D,GAME;;;;;;;;;;;;;;;;;;0BAkBsB,UAAU;2BACT,OAAO;;IAE9B,QAAQ,EAAE;EACZ,GAAyD;;;;;;;;;;;;2BAYhC,UAAU;4BACT,OAAO;;IAE/B,QAAQ,EAAE;EAGZ,GAAkC;;;;uBAIb,UAAU;sBACX,OAAO;IACzB,QAAQ,EAAE;CACb,CAAC;CAED,MAAM,aAAa,IAAI,IACtB,YAAY,KAAK,KAAK,QAAQ,CAC7B,IAAI,MACJ;EAAE,OAAO,IAAI;EAAQ,QAAQ,IAAI;CAAW,CAC7C,CAAC,CACF;CAEA,MAAM,aAA2B,QAAQ,KAAK,KAAK,QAAQ;EAC1D,MAAM,YAAY,WAAW,IAAI,IAAI,IAAI;EACzC,OAAO;GACN,MAAM,IAAI;GACV,MAAM,gBAAgB,IAAI,GAAG;GAC7B,SAAS,IAAI;GACb,UAAU,IAAI,aAAa;GAC3B,cAAc,IAAI;GAClB,cAAc,CAAC,CAAC;GAChB,iBAAiB,WAAW;GAC5B,kBAAkB,WAAW;GAC7B,cAAc,IAAI,YAAY,KAAA;EAC/B;CACD,CAAC;CAED,MAAM,cAAc,OAAO,SAAS,KAAK,EAAE,EAAE,YAAY,CAAC;CAE1D,OAAO;EACN,MAAM;EACN;EACA,SAAS;EACT,YAAY,WAAW,QAAQ,MAAM,EAAE,YAAY,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI;EACtE,mBAAmB,cAAc,IAAI,cAAc,KAAA;CACpD;AACD;AAEA,MAAM,iBAA6C;CAClD,SAAS;CACT,MAAM;CACN,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CAER,MAAM;CACN,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,OAAO;CAEP,MAAM;CAEN,MAAM;CACN,WAAW;CACX,aAAa;CACb,MAAM;CACN,QAAQ;CAER,MAAM;CACN,OAAO;CAEP,OAAO;CAEP,MAAM;AACP;AAEA,SAAS,gBAAgB,SAA6B;CACrD,OAAO,eAAe,YAAY;AACnC"}
@@ -0,0 +1,24 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/introspect/types.ts
3
+ /**
4
+ * Filter operators a row query accepts.
5
+ */
6
+ let FilterOperator = /* @__PURE__ */ function(FilterOperator) {
7
+ FilterOperator["Eq"] = "eq";
8
+ FilterOperator["Neq"] = "neq";
9
+ FilterOperator["Gt"] = "gt";
10
+ FilterOperator["Gte"] = "gte";
11
+ FilterOperator["Lt"] = "lt";
12
+ FilterOperator["Lte"] = "lte";
13
+ FilterOperator["Like"] = "like";
14
+ FilterOperator["Ilike"] = "ilike";
15
+ FilterOperator["In"] = "in";
16
+ FilterOperator["Nin"] = "nin";
17
+ FilterOperator["IsNull"] = "is_null";
18
+ FilterOperator["IsNotNull"] = "is_not_null";
19
+ return FilterOperator;
20
+ }({});
21
+ //#endregion
22
+ exports.FilterOperator = FilterOperator;
23
+
24
+ //# sourceMappingURL=types.cjs.map