@askdb/introspect 0.3.0-beta.17 → 0.3.0-beta.18

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 (46) hide show
  1. package/README.md +54 -1
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/kit/driver.d.ts +71 -0
  7. package/dist/kit/driver.d.ts.map +1 -0
  8. package/dist/kit/driver.js +127 -0
  9. package/dist/kit/driver.js.map +1 -0
  10. package/dist/kit/filters.d.ts +29 -0
  11. package/dist/kit/filters.d.ts.map +1 -0
  12. package/dist/kit/filters.js +58 -0
  13. package/dist/kit/filters.js.map +1 -0
  14. package/dist/kit/ids.d.ts +16 -0
  15. package/dist/kit/ids.d.ts.map +1 -0
  16. package/dist/kit/ids.js +20 -0
  17. package/dist/kit/ids.js.map +1 -0
  18. package/dist/kit/index.d.ts +18 -0
  19. package/dist/kit/index.d.ts.map +1 -0
  20. package/dist/kit/index.js +18 -0
  21. package/dist/kit/index.js.map +1 -0
  22. package/dist/kit/label.d.ts +44 -0
  23. package/dist/kit/label.d.ts.map +1 -0
  24. package/dist/kit/label.js +110 -0
  25. package/dist/kit/label.js.map +1 -0
  26. package/dist/kit/provider.d.ts +57 -0
  27. package/dist/kit/provider.d.ts.map +1 -0
  28. package/dist/kit/provider.js +50 -0
  29. package/dist/kit/provider.js.map +1 -0
  30. package/dist/kit/rows.d.ts +42 -0
  31. package/dist/kit/rows.d.ts.map +1 -0
  32. package/dist/kit/rows.js +86 -0
  33. package/dist/kit/rows.js.map +1 -0
  34. package/dist/registry.d.ts +144 -0
  35. package/dist/registry.d.ts.map +1 -0
  36. package/dist/registry.js +91 -0
  37. package/dist/registry.js.map +1 -0
  38. package/dist/render/render.d.ts +23 -1
  39. package/dist/render/render.d.ts.map +1 -1
  40. package/dist/render/render.js +45 -7
  41. package/dist/render/render.js.map +1 -1
  42. package/dist/render/types.d.ts +10 -0
  43. package/dist/render/types.d.ts.map +1 -1
  44. package/dist/types.d.ts +12 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +9 -4
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Connection labels: the credential-free text shown for a configured database
3
+ * connection (Studio's `GET /api/introspect/status` `sourceLabel`).
4
+ *
5
+ * A label is built only from parts an engine's parser extracted cleanly — host,
6
+ * port, database, or a SQLite file path — and only when each part passes the
7
+ * allowlist below. No other substring of the input is ever copied. Input that
8
+ * does not parse cleanly, or a part that fails its allowlist, becomes
9
+ * `configured <engine> connection`. Nothing here masks: see
10
+ * docs/adrs/0011-connection-labels-from-parsed-parts.md for why.
11
+ */
12
+ const HOST = /^(?:[A-Za-z0-9_](?:[A-Za-z0-9_.-]*[A-Za-z0-9_])?|\[[0-9A-Fa-f:.]+\])$/;
13
+ const PORT = /^[0-9]{1,5}$/;
14
+ const DATABASE = /^[A-Za-z0-9_$.-]+$/;
15
+ // A plain filesystem path: no control characters, and none of the characters
16
+ // that would make it a URI query/fragment, a `key=value` list, or userinfo.
17
+ const FILE = /^[^\u0000-\u001f\u007f?#;=@]+$/;
18
+ /**
19
+ * Build a connection label from parsed parts: `<engine>://host[:port][/database]`
20
+ * for network engines, the path itself for a file. Returns
21
+ * `configured <engine> connection` when `parts` is `undefined` (the input did
22
+ * not parse) or any part fails its allowlist. The shape is checked at runtime
23
+ * too, because a plain-JS adapter can return anything: a value that isn't a
24
+ * plain object (a string, an array, a `URL`), or a part that isn't a string,
25
+ * also gets the fallback instead of throwing.
26
+ */
27
+ export function formatConnectionLabel(engine, parts) {
28
+ const fallback = `configured ${engine} connection`;
29
+ if (!isPlainObject(parts))
30
+ return fallback;
31
+ if ("file" in parts) {
32
+ const { file } = parts;
33
+ return typeof file === "string" && FILE.test(file) && !file.includes("://") ? file : fallback;
34
+ }
35
+ const raw = parts;
36
+ if (![raw.host, raw.port, raw.database].every((part) => part === undefined || typeof part === "string")) {
37
+ return fallback;
38
+ }
39
+ const { host, port, database } = raw;
40
+ if (host === undefined && database === undefined)
41
+ return fallback;
42
+ if (host !== undefined && !HOST.test(host))
43
+ return fallback;
44
+ if (port !== undefined && (host === undefined || !PORT.test(port)))
45
+ return fallback;
46
+ if (database !== undefined && !DATABASE.test(database))
47
+ return fallback;
48
+ return `${engine}://${host ?? ""}${port === undefined ? "" : `:${port}`}${database === undefined ? "" : `/${database}`}`;
49
+ }
50
+ function isPlainObject(value) {
51
+ if (typeof value !== "object" || value === null)
52
+ return false;
53
+ const proto = Object.getPrototypeOf(value);
54
+ return proto === Object.prototype || proto === null;
55
+ }
56
+ // scheme://authority[/path][?query] — no fragment, and the whole input must match.
57
+ const URL_FORM = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^/?#]*)(\/[^?#]*)?(?:\?[^#]*)?$/;
58
+ /**
59
+ * Parse a standard `scheme://[user[:password]@]host[:port][/database][?query]`
60
+ * connection URL (Postgres, MySQL, SQL Server `mssql://`) into label parts.
61
+ * Userinfo and the query string are never returned. Returns `undefined` —
62
+ * so the label falls back — when:
63
+ *
64
+ * - the scheme is not one of `schemes` (so `jdbc:postgresql://…`, a quoted
65
+ * URL, and `postgres:/…` all fall back);
66
+ * - the input contains whitespace, a control character, or a `#`;
67
+ * - an `@` appears after the authority (a password containing `/`, `?` or `#`
68
+ * cut the authority short, so where it ends is unknown);
69
+ * - the host is not a single `host[:port]`, or the path has more than one segment.
70
+ */
71
+ export function parseConnectionUrl(input, schemes) {
72
+ if (/[\s\u0000-\u001f\u007f]/.test(input))
73
+ return undefined;
74
+ const match = URL_FORM.exec(input);
75
+ if (!match)
76
+ return undefined;
77
+ const [, scheme = "", authority = "", path = ""] = match;
78
+ if (!schemes.includes(scheme.toLowerCase()))
79
+ return undefined;
80
+ const authorityEnd = scheme.length + "://".length + authority.length;
81
+ if (input.includes("@", authorityEnd))
82
+ return undefined;
83
+ const hostPort = splitHostPort(authority.slice(authority.lastIndexOf("@") + 1));
84
+ if (!hostPort)
85
+ return undefined;
86
+ const database = path.length > 1 ? path.slice(1) : undefined;
87
+ if (database?.includes("/"))
88
+ return undefined;
89
+ return {
90
+ ...(hostPort.host === "" ? {} : { host: hostPort.host }),
91
+ ...(hostPort.port === undefined ? {} : { port: hostPort.port }),
92
+ ...(database === undefined ? {} : { database }),
93
+ };
94
+ }
95
+ function splitHostPort(value) {
96
+ if (value.startsWith("[")) {
97
+ const close = value.indexOf("]");
98
+ if (close === -1)
99
+ return undefined;
100
+ const rest = value.slice(close + 1);
101
+ if (rest === "")
102
+ return { host: value.slice(0, close + 1) };
103
+ return rest.startsWith(":") ? { host: value.slice(0, close + 1), port: rest.slice(1) } : undefined;
104
+ }
105
+ const pieces = value.split(":");
106
+ if (pieces.length > 2)
107
+ return undefined;
108
+ return pieces.length === 2 ? { host: pieces[0], port: pieces[1] } : { host: value };
109
+ }
110
+ //# sourceMappingURL=label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.js","sourceRoot":"","sources":["../../src/kit/label.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { ConnectorProviderAdapter, ConnectorProviderId, ConnectorRuntimeConfig } from "../registry.js";
2
+ import type { CatalogQueryRunner, Connector, IntrospectionFilters } from "../types.js";
3
+ import type { ConnectionLabelParts } from "./label.js";
4
+ /** Input shape of a live-only catalog connector: `{ mode: "live", runner, filters? }`. */
5
+ export type LiveCatalogInput = {
6
+ mode: "live";
7
+ runner: CatalogQueryRunner;
8
+ filters?: IntrospectionFilters;
9
+ };
10
+ export type LiveConnectorProviderSpec = {
11
+ provider: ConnectorProviderId;
12
+ /** Engine name used in error messages, e.g. `"MySQL"`. */
13
+ displayName: string;
14
+ /**
15
+ * Key in `runtime.introspection` (from `@askdb/config`) holding the configured
16
+ * connection, e.g. `"mysqlDatabaseUrl"`. An explicit `url` always wins.
17
+ */
18
+ runtimeKey: string;
19
+ /** What `config.url` holds, for the missing-url error: `"a connection URL"`, `"a file path"`. */
20
+ connectionNoun: string;
21
+ /**
22
+ * Error when the caller passes an export bundle (`--from-export`), which a
23
+ * live-only engine can't read. Defaults to
24
+ * `--from-export is not supported for --engine <provider>.`
25
+ */
26
+ fromExportUnsupported?: string;
27
+ /**
28
+ * Error when neither an explicit URL nor a configured one exists: `cli` names
29
+ * `askdb introspect` flags, `config` names `askdb.config.ts` keys.
30
+ */
31
+ missingConnection: {
32
+ cli: string;
33
+ config: string;
34
+ };
35
+ createConnector(): Connector<LiveCatalogInput>;
36
+ createRunner(url: string): CatalogQueryRunner;
37
+ /**
38
+ * The display-safe parts of a connection string (`{ host?, port?, database? }`
39
+ * or `{ file }`), parsed by the engine's own rules, or `undefined` when it
40
+ * doesn't parse cleanly. Becomes the adapter's `connectionLabelParts`; the
41
+ * registry builds the label from it (ADR 0011).
42
+ */
43
+ connectionLabelParts(url: string): ConnectionLabelParts | undefined;
44
+ };
45
+ /**
46
+ * Build the `ConnectorProviderAdapter` for an engine that introspects a live
47
+ * database through a `CatalogQueryRunner` only (no export bundle, no schema
48
+ * file) — the shape shared by `@askdb/mysql`, `@askdb/sqlite`, and
49
+ * `@askdb/sqlserver`.
50
+ */
51
+ export declare function defineLiveConnectorProvider(spec: LiveConnectorProviderSpec): ConnectorProviderAdapter;
52
+ /**
53
+ * Read a non-empty string from `runtime.introspection[key]` — the shape
54
+ * `@askdb/config` resolves per-engine connection values into.
55
+ */
56
+ export declare function runtimeIntrospectionString(runtime: ConnectorRuntimeConfig, key: string): string | undefined;
57
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/kit/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC5G,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,iBAAiB,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,eAAe,IAAI,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC/C,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAC9C;;;;;OAKG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAC;CACrE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,GAAG,wBAAwB,CAkCrG;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3G"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Build the `ConnectorProviderAdapter` for an engine that introspects a live
3
+ * database through a `CatalogQueryRunner` only (no export bundle, no schema
4
+ * file) — the shape shared by `@askdb/mysql`, `@askdb/sqlite`, and
5
+ * `@askdb/sqlserver`.
6
+ */
7
+ export function defineLiveConnectorProvider(spec) {
8
+ return {
9
+ provider: spec.provider,
10
+ createConnector(config) {
11
+ if (!config.url) {
12
+ throw new Error(`${spec.displayName} connector requires ${spec.connectionNoun} (config.url).`);
13
+ }
14
+ const input = {
15
+ mode: "live",
16
+ runner: spec.createRunner(config.url),
17
+ filters: config.filters,
18
+ };
19
+ return { mode: "live", input, connector: spec.createConnector() };
20
+ },
21
+ resolveConnection({ explicit = {}, runtime, surface }) {
22
+ const url = explicit.url ?? runtimeIntrospectionString(runtime, spec.runtimeKey);
23
+ if (!url) {
24
+ return {
25
+ ok: false,
26
+ error: surface === "cli" ? spec.missingConnection.cli : spec.missingConnection.config,
27
+ };
28
+ }
29
+ if (explicit.fromExport) {
30
+ return {
31
+ ok: false,
32
+ error: spec.fromExportUnsupported ?? `--from-export is not supported for --engine ${spec.provider}.`,
33
+ };
34
+ }
35
+ return { ok: true, connection: { url } };
36
+ },
37
+ connectionLabelParts({ url }) {
38
+ return url === undefined ? undefined : spec.connectionLabelParts(url);
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * Read a non-empty string from `runtime.introspection[key]` — the shape
44
+ * `@askdb/config` resolves per-engine connection values into.
45
+ */
46
+ export function runtimeIntrospectionString(runtime, key) {
47
+ const value = runtime.introspection[key];
48
+ return typeof value === "string" && value !== "" ? value : undefined;
49
+ }
50
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/kit/provider.ts"],"names":[],"mappings":"AA4CA;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAA+B;IACzE,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,eAAe,CAAC,MAAM;YACpB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,WAAW,uBAAuB,IAAI,CAAC,cAAc,gBAAgB,CAAC,CAAC;YACjG,CAAC;YACD,MAAM,KAAK,GAAqB;gBAC9B,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC;gBACrC,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;YACF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,eAAe,EAAwB,EAAE,CAAC;QAC1F,CAAC;QACD,iBAAiB,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;YACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,0BAA0B,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YACjF,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM;iBACtF,CAAC;YACJ,CAAC;YACD,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;gBACxB,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,IAAI,CAAC,qBAAqB,IAAI,+CAA+C,IAAI,CAAC,QAAQ,GAAG;iBACrG,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;QAC3C,CAAC;QACD,oBAAoB,CAAC,EAAE,GAAG,EAAE;YAC1B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAA+B,EAAE,GAAW;IACrF,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { CatalogQueryResult, SqlForeignKeyAction } from "../types.js";
2
+ export type RowsToRecordsOptions = {
3
+ /**
4
+ * Columns the caller's SQL promises to return. When set, only these columns
5
+ * are copied into each record, and a result that lacks one of them throws —
6
+ * that signals a runner or bundle that does not match the query's contract.
7
+ * When omitted, every returned column is copied.
8
+ */
9
+ expectedColumns?: readonly string[];
10
+ /** Prefix for the missing-column error, e.g. `"@askdb/postgres"`. */
11
+ source?: string;
12
+ };
13
+ /**
14
+ * Turn a positional `CatalogQueryResult` into one record per row keyed by
15
+ * column name. A result with zero rows yields `[]` even when the runner
16
+ * omitted the column headers (some drivers / fake runners don't populate them
17
+ * on empty result sets).
18
+ */
19
+ export declare function rowsToRecords<T>(result: CatalogQueryResult, options?: RowsToRecordsOptions): T[];
20
+ /** Group rows by key, preserving first-seen key order and row order within a group. */
21
+ export declare function groupBy<T, K>(rows: Iterable<T>, key: (row: T) => K): Map<K, T[]>;
22
+ /**
23
+ * Group rows by key, order each group by `position` (e.g. a constraint's
24
+ * column ordinal), and build one named object per group; `build` may return
25
+ * `undefined` to drop a group. The result is sorted by name — the shape every
26
+ * multi-column constraint/index builder needs for deterministic output.
27
+ */
28
+ export declare function buildOrderedGroups<T, K, R extends {
29
+ name: string;
30
+ }>(rows: Iterable<T>, key: (row: T) => K, position: (row: T) => number, build: (key: K, ordered: T[]) => R | undefined): R[];
31
+ /** Comparator: `localeCompare` on `.name`. */
32
+ export declare function byName<T extends {
33
+ name: string;
34
+ }>(a: T, b: T): number;
35
+ /** Deduplicate and `localeCompare`-sort. */
36
+ export declare function sortedUnique(values: Iterable<string>): string[];
37
+ /**
38
+ * Map an SQL-standard referential action name (`CASCADE`, `SET NULL`, …;
39
+ * case-insensitive) to the Schema v2 action. Unknown or empty → `undefined`.
40
+ */
41
+ export declare function mapFkAction(rule: string | null | undefined): SqlForeignKeyAction | undefined;
42
+ //# sourceMappingURL=rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/kit/rows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE3E,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAE,oBAAyB,GAAG,CAAC,EAAE,CAsBpG;AAED,uFAAuF;AACvF,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAShF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EACjE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,EAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,SAAS,GAC7C,CAAC,EAAE,CAOL;AAED,8CAA8C;AAC9C,wBAAgB,MAAM,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAErE;AAED,4CAA4C;AAC5C,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAE/D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,mBAAmB,GAAG,SAAS,CAS5F"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Turn a positional `CatalogQueryResult` into one record per row keyed by
3
+ * column name. A result with zero rows yields `[]` even when the runner
4
+ * omitted the column headers (some drivers / fake runners don't populate them
5
+ * on empty result sets).
6
+ */
7
+ export function rowsToRecords(result, options = {}) {
8
+ if (result.rows.length === 0)
9
+ return [];
10
+ const indexByName = new Map();
11
+ if (options.expectedColumns) {
12
+ for (const name of options.expectedColumns) {
13
+ const idx = result.columns.indexOf(name);
14
+ if (idx === -1) {
15
+ const prefix = options.source ? `${options.source}: ` : "";
16
+ throw new Error(`${prefix}result is missing column '${name}' (got [${result.columns.join(", ")}])`);
17
+ }
18
+ indexByName.set(name, idx);
19
+ }
20
+ }
21
+ else {
22
+ for (let i = 0; i < result.columns.length; i++)
23
+ indexByName.set(result.columns[i], i);
24
+ }
25
+ return result.rows.map((row) => {
26
+ const record = {};
27
+ for (const [name, idx] of indexByName)
28
+ record[name] = row[idx];
29
+ return record;
30
+ });
31
+ }
32
+ /** Group rows by key, preserving first-seen key order and row order within a group. */
33
+ export function groupBy(rows, key) {
34
+ const out = new Map();
35
+ for (const row of rows) {
36
+ const k = key(row);
37
+ const list = out.get(k) ?? [];
38
+ list.push(row);
39
+ out.set(k, list);
40
+ }
41
+ return out;
42
+ }
43
+ /**
44
+ * Group rows by key, order each group by `position` (e.g. a constraint's
45
+ * column ordinal), and build one named object per group; `build` may return
46
+ * `undefined` to drop a group. The result is sorted by name — the shape every
47
+ * multi-column constraint/index builder needs for deterministic output.
48
+ */
49
+ export function buildOrderedGroups(rows, key, position, build) {
50
+ const out = [];
51
+ for (const [k, list] of groupBy(rows, key)) {
52
+ const built = build(k, list.slice().sort((a, b) => position(a) - position(b)));
53
+ if (built !== undefined)
54
+ out.push(built);
55
+ }
56
+ return out.sort(byName);
57
+ }
58
+ /** Comparator: `localeCompare` on `.name`. */
59
+ export function byName(a, b) {
60
+ return a.name.localeCompare(b.name);
61
+ }
62
+ /** Deduplicate and `localeCompare`-sort. */
63
+ export function sortedUnique(values) {
64
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
65
+ }
66
+ /**
67
+ * Map an SQL-standard referential action name (`CASCADE`, `SET NULL`, …;
68
+ * case-insensitive) to the Schema v2 action. Unknown or empty → `undefined`.
69
+ */
70
+ export function mapFkAction(rule) {
71
+ if (!rule)
72
+ return undefined;
73
+ const r = rule.toLowerCase();
74
+ if (r === "cascade")
75
+ return "cascade";
76
+ if (r === "restrict")
77
+ return "restrict";
78
+ if (r === "set null")
79
+ return "set null";
80
+ if (r === "set default")
81
+ return "set default";
82
+ if (r === "no action")
83
+ return "no action";
84
+ return undefined;
85
+ }
86
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/kit/rows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { type ConnectionLabelParts } from "./kit/label.js";
2
+ import type { Connector, IntrospectionFilters, SqlTemplateBundle } from "./types.js";
3
+ /** Provider ids of the first-party engine packages (`@askdb/<id>`). */
4
+ export declare const BUILT_IN_CONNECTOR_PROVIDERS: readonly ["postgres", "prisma", "mysql", "sqlite", "sqlserver"];
5
+ export type BuiltInConnectorProvider = (typeof BUILT_IN_CONNECTOR_PROVIDERS)[number];
6
+ /**
7
+ * A connector provider id. Open: any string is valid, so a third-party engine
8
+ * package registers its own id without editing AskDB. The built-in ids stay in
9
+ * the type for editor autocompletion.
10
+ */
11
+ export type ConnectorProviderId = BuiltInConnectorProvider | (string & {});
12
+ /**
13
+ * Unified config passed to a connector provider adapter. The registry converts
14
+ * this into the engine-specific connector + input pair consumed by `introspect()`.
15
+ */
16
+ export type ConnectorConfig = {
17
+ provider: ConnectorProviderId;
18
+ /** Connection URL for live introspection (postgres, mysql, sqlserver) or file path (sqlite). */
19
+ url?: string;
20
+ /** Bundle directory path for from-export mode (postgres only). */
21
+ fromExport?: string;
22
+ /** Prisma schema file path or directory containing `.prisma` files (prisma only). */
23
+ schemaPath?: string;
24
+ filters?: IntrospectionFilters;
25
+ /** Schema ID embedded in the resulting `SqlSchema`. Defaults to `"introspected"`. */
26
+ schemaId?: string;
27
+ };
28
+ /** Connector + typed input pair returned by a provider adapter. */
29
+ export type ConnectorResult = {
30
+ connector: Connector<unknown>;
31
+ input: unknown;
32
+ /** Informational mode string (e.g. `"live"`, `"from-export"`, `"prisma-schema"`). */
33
+ mode: string;
34
+ };
35
+ /** Where a connection comes from: the three `ConnectorConfig` source fields. */
36
+ export type ConnectorConnection = Pick<ConnectorConfig, "url" | "fromExport" | "schemaPath">;
37
+ /**
38
+ * The AskDB runtime configuration an adapter resolves its connection from.
39
+ * Structurally typed so engine packages need not depend on `@askdb/config`;
40
+ * pass `getAskDbRuntimeConfig()` from `@askdb/config` as-is.
41
+ */
42
+ export type ConnectorRuntimeConfig = {
43
+ /**
44
+ * Resolved introspection values (`getAskDbRuntimeConfig().introspection`),
45
+ * e.g. `postgresDatabaseUrl`, `sqliteFile`, `prismaSchemaPath` — config file
46
+ * first, then the engine's env var, as resolved by `@askdb/config`.
47
+ */
48
+ readonly introspection: Readonly<Record<string, unknown>>;
49
+ /**
50
+ * The authoring-time config (`getAskDbRuntimeConfig().structured`). Engines
51
+ * without a first-class runtime field read their own block from
52
+ * `structured.introspection.providerConfig.<id>` here: `@askdb/config` passes
53
+ * it through unchanged, though its typed config doesn't declare third-party
54
+ * ids yet.
55
+ */
56
+ readonly structured?: unknown;
57
+ };
58
+ export type ConnectorConnectionRequest = {
59
+ /** Values the caller passed explicitly (e.g. CLI flags). They take precedence over config. */
60
+ explicit?: ConnectorConnection;
61
+ /** AskDB runtime configuration to fall back to. */
62
+ runtime: ConnectorRuntimeConfig;
63
+ /**
64
+ * The surface asking, so errors can point at the right fix: `"cli"` names
65
+ * `askdb introspect` flags; anything else (e.g. `"studio"`) names config keys.
66
+ */
67
+ surface?: "cli" | "studio" | (string & {});
68
+ };
69
+ /**
70
+ * What an adapter's `resolveConnection` returns: the connection, or an error.
71
+ * The error is shown to users as-is, so it must never echo the connection
72
+ * string. There is no label here: the registry builds it (ADR 0011).
73
+ */
74
+ export type ConnectorConnectionResult = {
75
+ ok: true;
76
+ connection: ConnectorConnection;
77
+ } | {
78
+ ok: false;
79
+ error: string;
80
+ };
81
+ /** What `registry.resolveConnection` returns: the adapter's result plus the registry-built label. */
82
+ export type ConnectorConnectionResolution = {
83
+ ok: true;
84
+ connection: ConnectorConnection;
85
+ /**
86
+ * Credential-free description of the source, safe to show in a UI or log:
87
+ * `registry.connectionLabel(provider, connection)`.
88
+ */
89
+ sourceLabel: string;
90
+ } | {
91
+ ok: false;
92
+ error: string;
93
+ };
94
+ export type ConnectorProviderAdapter = {
95
+ provider: ConnectorProviderId;
96
+ createConnector(config: ConnectorConfig): ConnectorResult;
97
+ /** Returns the engine's catalog SQL template bundle, if the engine supports it. */
98
+ getTemplates?(): SqlTemplateBundle;
99
+ /**
100
+ * Turn explicit values + AskDB runtime config into this engine's connection
101
+ * (URL, export bundle, or schema path), applying the engine's precedence and
102
+ * validation. Hosts (CLI, Studio) call this instead of switching on the engine.
103
+ * When omitted, the registry passes `explicit` through unchanged.
104
+ */
105
+ resolveConnection?(request: ConnectorConnectionRequest): ConnectorConnectionResult;
106
+ /**
107
+ * The parts of `connection` that are safe to display (host, port, database,
108
+ * or a file path), parsed by the engine's own rules; `undefined` when the
109
+ * connection doesn't parse cleanly. The registry turns them into the label
110
+ * with `formatConnectionLabel`, so an adapter never supplies label text
111
+ * itself (ADR 0011).
112
+ */
113
+ connectionLabelParts?(connection: ConnectorConnection): ConnectionLabelParts | undefined;
114
+ };
115
+ export type ConnectorProviderAdapters = readonly ConnectorProviderAdapter[] | Readonly<Record<string, ConnectorProviderAdapter | undefined>>;
116
+ export type ConnectorRegistry = {
117
+ hasProvider(provider: string): boolean;
118
+ /** Registered provider ids, in registration order. */
119
+ providers(): ConnectorProviderId[];
120
+ createConnector(config: ConnectorConfig): ConnectorResult;
121
+ /**
122
+ * Returns the catalog SQL template bundle for the given provider, or `undefined`
123
+ * if the provider is not registered or does not support templates.
124
+ */
125
+ getTemplates(provider: string): SqlTemplateBundle | undefined;
126
+ /**
127
+ * Resolve a provider's connection via its adapter's `resolveConnection` hook
128
+ * (or pass `explicit` through when the adapter has none), with `sourceLabel`
129
+ * from `connectionLabel`. A blank explicit value (empty or whitespace-only)
130
+ * counts as absent, so the configured connection applies. Throws when the
131
+ * provider is not registered.
132
+ */
133
+ resolveConnection(provider: string, request: ConnectorConnectionRequest): ConnectorConnectionResolution;
134
+ /**
135
+ * A credential-free label for a connection, safe to show in a UI or log:
136
+ * `formatConnectionLabel(provider, parts)` over the adapter's
137
+ * `connectionLabelParts`. A provider that is not registered, has no hook, or
138
+ * returns `undefined` gets `configured <provider> connection`.
139
+ */
140
+ connectionLabel(provider: string, connection: ConnectorConnection): string;
141
+ };
142
+ export declare function createConnectorRegistry(adapters: ConnectorProviderAdapters): ConnectorRegistry;
143
+ export declare function connectorProviderMissingMessage(provider: string): string;
144
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { formatConnectionLabel } from "./kit/label.js";
2
+ /** Provider ids of the first-party engine packages (`@askdb/<id>`). */
3
+ export const BUILT_IN_CONNECTOR_PROVIDERS = [
4
+ "postgres",
5
+ "prisma",
6
+ "mysql",
7
+ "sqlite",
8
+ "sqlserver",
9
+ ];
10
+ export function createConnectorRegistry(adapters) {
11
+ const byProvider = normalizeAdapters(adapters);
12
+ function adapterFor(provider) {
13
+ const adapter = byProvider.get(provider);
14
+ if (!adapter)
15
+ throw new Error(connectorProviderMissingMessage(provider));
16
+ return adapter;
17
+ }
18
+ return {
19
+ hasProvider(provider) {
20
+ return byProvider.has(provider);
21
+ },
22
+ providers() {
23
+ return [...byProvider.keys()];
24
+ },
25
+ createConnector(config) {
26
+ return adapterFor(config.provider).createConnector(config);
27
+ },
28
+ getTemplates(provider) {
29
+ return byProvider.get(provider)?.getTemplates?.();
30
+ },
31
+ resolveConnection(provider, request) {
32
+ const adapter = adapterFor(provider);
33
+ // A blank explicit value (an empty form field or env var a host forwards)
34
+ // is absent, so the configured connection still applies.
35
+ const normalized = { ...request, explicit: withoutBlankValues(request.explicit) };
36
+ const result = adapter.resolveConnection
37
+ ? adapter.resolveConnection(normalized)
38
+ : { ok: true, connection: { ...normalized.explicit } };
39
+ if (!result.ok)
40
+ return result;
41
+ return { ...result, sourceLabel: labelFor(adapter, provider, result.connection) };
42
+ },
43
+ connectionLabel(provider, connection) {
44
+ return labelFor(byProvider.get(provider), provider, connection);
45
+ },
46
+ };
47
+ }
48
+ function labelFor(adapter, provider, connection) {
49
+ try {
50
+ return formatConnectionLabel(provider, adapter?.connectionLabelParts?.(connection));
51
+ }
52
+ catch {
53
+ // A throwing parser, or parts whose getter or Proxy trap throws while
54
+ // being read, get the fallback. The message may quote the connection
55
+ // string, so it must never become user-visible text.
56
+ return formatConnectionLabel(provider, undefined);
57
+ }
58
+ }
59
+ export function connectorProviderMissingMessage(provider) {
60
+ const install = BUILT_IN_CONNECTOR_PROVIDERS.includes(provider)
61
+ ? `Install @askdb/${provider}`
62
+ : "Install the package that provides it";
63
+ return (`Connector provider "${provider}" is not registered. ` +
64
+ `${install} and pass its connector provider adapter to createConnectorRegistry().`);
65
+ }
66
+ function withoutBlankValues(explicit) {
67
+ if (explicit === undefined)
68
+ return undefined;
69
+ return Object.fromEntries(Object.entries(explicit).filter(([, value]) => typeof value !== "string" || value.trim() !== ""));
70
+ }
71
+ function normalizeAdapters(adapters) {
72
+ const entries = Array.isArray(adapters)
73
+ ? adapters.map((a) => [a.provider, a])
74
+ : Object.entries(adapters).filter(isAdapterEntry);
75
+ const byProvider = new Map();
76
+ for (const [provider, adapter] of entries) {
77
+ if (adapter.provider !== provider) {
78
+ throw new Error(`Connector provider adapter mismatch: registry key "${provider}" points to adapter "${adapter.provider}".`);
79
+ }
80
+ // A second adapter for the same id would silently replace the first one.
81
+ if (byProvider.has(provider)) {
82
+ throw new Error(`Connector provider "${provider}" is registered twice. Pass one adapter per provider id to createConnectorRegistry().`);
83
+ }
84
+ byProvider.set(provider, adapter);
85
+ }
86
+ return byProvider;
87
+ }
88
+ function isAdapterEntry(entry) {
89
+ return entry[1] !== undefined;
90
+ }
91
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}