@tablecraft-app/plugin-api 0.0.0-stage → 1.1.0
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.
- package/LICENSE +21 -0
- package/README.md +46 -2
- package/dist/catalog.d.ts +112 -0
- package/dist/catalog.js +5 -0
- package/dist/completion.d.ts +109 -0
- package/dist/completion.js +12 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.js +106 -0
- package/dist/explain.d.ts +58 -0
- package/dist/explain.js +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/inspection.d.ts +38 -0
- package/dist/inspection.js +21 -0
- package/dist/manifest.d.ts +186 -0
- package/dist/manifest.js +176 -0
- package/dist/objects.d.ts +140 -0
- package/dist/objects.js +2 -0
- package/dist/plugin.d.ts +460 -0
- package/dist/plugin.js +49 -0
- package/dist/sessions.d.ts +59 -0
- package/dist/sessions.js +2 -0
- package/dist/sql/classify.d.ts +19 -0
- package/dist/sql/classify.js +95 -0
- package/dist/sql/complete.d.ts +23 -0
- package/dist/sql/complete.js +480 -0
- package/dist/sql/explain.d.ts +42 -0
- package/dist/sql/explain.js +108 -0
- package/dist/sql/format.d.ts +26 -0
- package/dist/sql/format.js +117 -0
- package/dist/sql/functions.d.ts +47 -0
- package/dist/sql/functions.js +159 -0
- package/dist/sql/generate.d.ts +22 -0
- package/dist/sql/generate.js +64 -0
- package/dist/sql/index.d.ts +16 -0
- package/dist/sql/index.js +16 -0
- package/dist/sql/inspect.d.ts +10 -0
- package/dist/sql/inspect.js +134 -0
- package/dist/sql/inspectContext.d.ts +78 -0
- package/dist/sql/inspectContext.js +251 -0
- package/dist/sql/inspectResolve.d.ts +6 -0
- package/dist/sql/inspectResolve.js +391 -0
- package/dist/sql/inspectStyle.d.ts +3 -0
- package/dist/sql/inspectStyle.js +384 -0
- package/dist/sql/inspectSyntax.d.ts +10 -0
- package/dist/sql/inspectSyntax.js +179 -0
- package/dist/sql/layout.d.ts +8 -0
- package/dist/sql/layout.js +114 -0
- package/dist/sql/layoutNodes.d.ts +46 -0
- package/dist/sql/layoutNodes.js +317 -0
- package/dist/sql/layoutPrint.d.ts +60 -0
- package/dist/sql/layoutPrint.js +806 -0
- package/dist/sql/layoutWriter.d.ts +21 -0
- package/dist/sql/layoutWriter.js +77 -0
- package/dist/sql/literals.d.ts +28 -0
- package/dist/sql/literals.js +78 -0
- package/dist/sql/namespace.d.ts +41 -0
- package/dist/sql/namespace.js +220 -0
- package/dist/sql/objects.d.ts +42 -0
- package/dist/sql/objects.js +222 -0
- package/dist/sql/parameters.d.ts +20 -0
- package/dist/sql/parameters.js +88 -0
- package/dist/sql/resolution.d.ts +73 -0
- package/dist/sql/resolution.js +192 -0
- package/dist/sql/routines.d.ts +27 -0
- package/dist/sql/routines.js +42 -0
- package/dist/sql/scope.d.ts +117 -0
- package/dist/sql/scope.js +540 -0
- package/dist/sql/select.d.ts +41 -0
- package/dist/sql/select.js +287 -0
- package/dist/sql/signature.d.ts +18 -0
- package/dist/sql/signature.js +137 -0
- package/dist/sql/splitter.d.ts +15 -0
- package/dist/sql/splitter.js +217 -0
- package/dist/sql/suggest.d.ts +6 -0
- package/dist/sql/suggest.js +36 -0
- package/dist/sql/tokens.d.ts +62 -0
- package/dist/sql/tokens.js +227 -0
- package/dist/sql/words.d.ts +23 -0
- package/dist/sql/words.js +72 -0
- package/dist/testing/index.d.ts +177 -0
- package/dist/testing/index.js +2070 -0
- package/dist/tls.d.ts +33 -0
- package/dist/tls.js +118 -0
- package/dist/url.d.ts +52 -0
- package/dist/url.js +219 -0
- package/dist/values.d.ts +44 -0
- package/dist/values.js +44 -0
- package/dist/version.d.ts +22 -0
- package/dist/version.js +29 -0
- package/package.json +42 -4
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The file that marks a directory as a plugin (decision 005). */
|
|
3
|
+
export declare const MANIFEST_FILE = "table-craft-plugin.json";
|
|
4
|
+
/**
|
|
5
|
+
* What the loader reads before executing any plugin code, so an unavailable plugin can still be
|
|
6
|
+
* named in the UI.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CONNECTION_FIELD_KINDS: readonly ["text", "number", "password", "file", "select", "checkbox"];
|
|
9
|
+
export type ConnectionFieldKind = (typeof CONNECTION_FIELD_KINDS)[number];
|
|
10
|
+
export declare const CONNECTION_FIELD_GROUPS: readonly ["general", "advanced"];
|
|
11
|
+
export type ConnectionFieldGroup = (typeof CONNECTION_FIELD_GROUPS)[number];
|
|
12
|
+
/**
|
|
13
|
+
* One control of the connection form (decision 019). `key` names the config property it fills;
|
|
14
|
+
* `secret` sends the value to the secret store instead of `datasources.json` (decision 020).
|
|
15
|
+
*/
|
|
16
|
+
export declare const connectionFieldSchema: z.ZodObject<{
|
|
17
|
+
key: z.ZodString;
|
|
18
|
+
label: z.ZodString;
|
|
19
|
+
kind: z.ZodEnum<{
|
|
20
|
+
number: "number";
|
|
21
|
+
text: "text";
|
|
22
|
+
select: "select";
|
|
23
|
+
password: "password";
|
|
24
|
+
file: "file";
|
|
25
|
+
checkbox: "checkbox";
|
|
26
|
+
}>;
|
|
27
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
28
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
30
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
31
|
+
value: z.ZodString;
|
|
32
|
+
label: z.ZodString;
|
|
33
|
+
}, z.core.$strict>>>;
|
|
34
|
+
group: z.ZodOptional<z.ZodEnum<{
|
|
35
|
+
general: "general";
|
|
36
|
+
advanced: "advanced";
|
|
37
|
+
}>>;
|
|
38
|
+
secret: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export type ConnectionField = z.infer<typeof connectionFieldSchema>;
|
|
41
|
+
/** `server`: `scheme://user:password@host:port/database?params`; `file`: a path, bare or as a URL. */
|
|
42
|
+
export declare const CONNECTION_URL_FORMS: readonly ["server", "file"];
|
|
43
|
+
export type ConnectionUrlForm = (typeof CONNECTION_URL_FORMS)[number];
|
|
44
|
+
/** The pieces of a connection URL a field can take; `path` belongs to the `file` form. */
|
|
45
|
+
export declare const CONNECTION_URL_PARTS: readonly ["host", "port", "database", "user", "password", "path"];
|
|
46
|
+
export type ConnectionUrlPart = (typeof CONNECTION_URL_PARTS)[number];
|
|
47
|
+
/**
|
|
48
|
+
* How a connection URL maps onto the form's fields (decision 055), so the host parses and writes
|
|
49
|
+
* one without running plugin code. `parts` names the field each piece of the URL fills; a query
|
|
50
|
+
* parameter fills `key`, its value translated through `values` (compared case-insensitively) when
|
|
51
|
+
* given, so a value outside them is reported as ignored.
|
|
52
|
+
*/
|
|
53
|
+
export declare const connectionUrlSchema: z.ZodObject<{
|
|
54
|
+
schemes: z.ZodArray<z.ZodString>;
|
|
55
|
+
form: z.ZodEnum<{
|
|
56
|
+
file: "file";
|
|
57
|
+
server: "server";
|
|
58
|
+
}>;
|
|
59
|
+
parts: z.ZodRecord<z.ZodEnum<{
|
|
60
|
+
database: "database";
|
|
61
|
+
path: "path";
|
|
62
|
+
password: "password";
|
|
63
|
+
host: "host";
|
|
64
|
+
port: "port";
|
|
65
|
+
user: "user";
|
|
66
|
+
}> & z.core.$partial, z.ZodString>;
|
|
67
|
+
params: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
key: z.ZodString;
|
|
70
|
+
values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
71
|
+
}, z.core.$strict>>>;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
export type ConnectionUrl = z.infer<typeof connectionUrlSchema>;
|
|
74
|
+
/**
|
|
75
|
+
* Which fields hold the server's address (decision 070), so the host can open an SSH tunnel and
|
|
76
|
+
* hand the plugin `127.0.0.1` and the forwarded port instead. `connectTimeout`, in seconds, bounds
|
|
77
|
+
* the SSH hop too. Absent: the data source is not reached over the network and offers no tunnel.
|
|
78
|
+
*/
|
|
79
|
+
export declare const networkSpecSchema: z.ZodObject<{
|
|
80
|
+
host: z.ZodString;
|
|
81
|
+
port: z.ZodString;
|
|
82
|
+
connectTimeout: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
export type NetworkSpec = z.infer<typeof networkSpecSchema>;
|
|
85
|
+
/**
|
|
86
|
+
* Which fields configure TLS (decision 072), so the dialog groups them under SSL, hints when a CA
|
|
87
|
+
* file goes unchecked, and main asks for the key passphrase only when the key file is encrypted.
|
|
88
|
+
* `mode` is the `select` of SSL modes; `verifyModes` are its values that check the server
|
|
89
|
+
* certificate. Absent: the plugin speaks no TLS the host knows about.
|
|
90
|
+
*/
|
|
91
|
+
export declare const tlsSpecSchema: z.ZodObject<{
|
|
92
|
+
mode: z.ZodString;
|
|
93
|
+
verifyModes: z.ZodArray<z.ZodString>;
|
|
94
|
+
caFile: z.ZodOptional<z.ZodString>;
|
|
95
|
+
certFile: z.ZodOptional<z.ZodString>;
|
|
96
|
+
keyFile: z.ZodOptional<z.ZodString>;
|
|
97
|
+
keyPassphrase: z.ZodOptional<z.ZodString>;
|
|
98
|
+
}, z.core.$strict>;
|
|
99
|
+
export type TlsSpec = z.infer<typeof tlsSpecSchema>;
|
|
100
|
+
/** The roles of `TlsSpec` that name a field, in the order the dialog draws them. */
|
|
101
|
+
export declare const TLS_FIELD_ROLES: readonly ["mode", "caFile", "certFile", "keyFile", "keyPassphrase"];
|
|
102
|
+
export type TlsFieldRole = (typeof TLS_FIELD_ROLES)[number];
|
|
103
|
+
export declare const pluginManifestSchema: z.ZodObject<{
|
|
104
|
+
id: z.ZodString;
|
|
105
|
+
name: z.ZodString;
|
|
106
|
+
version: z.ZodString;
|
|
107
|
+
apiVersion: z.ZodString;
|
|
108
|
+
entry: z.ZodString;
|
|
109
|
+
capabilities: z.ZodArray<z.ZodEnum<{
|
|
110
|
+
sql: "sql";
|
|
111
|
+
cancel: "cancel";
|
|
112
|
+
schemaTree: "schemaTree";
|
|
113
|
+
transactions: "transactions";
|
|
114
|
+
ddl: "ddl";
|
|
115
|
+
explain: "explain";
|
|
116
|
+
explainAnalyze: "explainAnalyze";
|
|
117
|
+
currentNamespace: "currentNamespace";
|
|
118
|
+
keepAlive: "keepAlive";
|
|
119
|
+
bulkInsert: "bulkInsert";
|
|
120
|
+
objects: "objects";
|
|
121
|
+
serverMessages: "serverMessages";
|
|
122
|
+
sessions: "sessions";
|
|
123
|
+
routineCall: "routineCall";
|
|
124
|
+
}>>;
|
|
125
|
+
highlight: z.ZodString;
|
|
126
|
+
connection: z.ZodObject<{
|
|
127
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
128
|
+
key: z.ZodString;
|
|
129
|
+
label: z.ZodString;
|
|
130
|
+
kind: z.ZodEnum<{
|
|
131
|
+
number: "number";
|
|
132
|
+
text: "text";
|
|
133
|
+
select: "select";
|
|
134
|
+
password: "password";
|
|
135
|
+
file: "file";
|
|
136
|
+
checkbox: "checkbox";
|
|
137
|
+
}>;
|
|
138
|
+
default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
139
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
140
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
141
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
142
|
+
value: z.ZodString;
|
|
143
|
+
label: z.ZodString;
|
|
144
|
+
}, z.core.$strict>>>;
|
|
145
|
+
group: z.ZodOptional<z.ZodEnum<{
|
|
146
|
+
general: "general";
|
|
147
|
+
advanced: "advanced";
|
|
148
|
+
}>>;
|
|
149
|
+
secret: z.ZodOptional<z.ZodBoolean>;
|
|
150
|
+
}, z.core.$strict>>;
|
|
151
|
+
url: z.ZodOptional<z.ZodObject<{
|
|
152
|
+
schemes: z.ZodArray<z.ZodString>;
|
|
153
|
+
form: z.ZodEnum<{
|
|
154
|
+
file: "file";
|
|
155
|
+
server: "server";
|
|
156
|
+
}>;
|
|
157
|
+
parts: z.ZodRecord<z.ZodEnum<{
|
|
158
|
+
database: "database";
|
|
159
|
+
path: "path";
|
|
160
|
+
password: "password";
|
|
161
|
+
host: "host";
|
|
162
|
+
port: "port";
|
|
163
|
+
user: "user";
|
|
164
|
+
}> & z.core.$partial, z.ZodString>;
|
|
165
|
+
params: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
166
|
+
name: z.ZodString;
|
|
167
|
+
key: z.ZodString;
|
|
168
|
+
values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
169
|
+
}, z.core.$strict>>>;
|
|
170
|
+
}, z.core.$strict>>;
|
|
171
|
+
network: z.ZodOptional<z.ZodObject<{
|
|
172
|
+
host: z.ZodString;
|
|
173
|
+
port: z.ZodString;
|
|
174
|
+
connectTimeout: z.ZodOptional<z.ZodString>;
|
|
175
|
+
}, z.core.$strict>>;
|
|
176
|
+
tls: z.ZodOptional<z.ZodObject<{
|
|
177
|
+
mode: z.ZodString;
|
|
178
|
+
verifyModes: z.ZodArray<z.ZodString>;
|
|
179
|
+
caFile: z.ZodOptional<z.ZodString>;
|
|
180
|
+
certFile: z.ZodOptional<z.ZodString>;
|
|
181
|
+
keyFile: z.ZodOptional<z.ZodString>;
|
|
182
|
+
keyPassphrase: z.ZodOptional<z.ZodString>;
|
|
183
|
+
}, z.core.$strict>>;
|
|
184
|
+
}, z.core.$strict>;
|
|
185
|
+
}, z.core.$strict>;
|
|
186
|
+
export type PluginManifest = z.infer<typeof pluginManifestSchema>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CAPABILITIES } from "./plugin.js";
|
|
3
|
+
import { parseVersion } from "./version.js";
|
|
4
|
+
/** The file that marks a directory as a plugin (decision 005). */
|
|
5
|
+
export const MANIFEST_FILE = "table-craft-plugin.json";
|
|
6
|
+
const PLUGIN_ID = /^[a-z][a-z0-9-]*$/;
|
|
7
|
+
const semver = z.string().refine((text) => parseVersion(text) !== null, { message: "expected major.minor.patch" });
|
|
8
|
+
/**
|
|
9
|
+
* What the loader reads before executing any plugin code, so an unavailable plugin can still be
|
|
10
|
+
* named in the UI.
|
|
11
|
+
*/
|
|
12
|
+
export const CONNECTION_FIELD_KINDS = ["text", "number", "password", "file", "select", "checkbox"];
|
|
13
|
+
export const CONNECTION_FIELD_GROUPS = ["general", "advanced"];
|
|
14
|
+
const FIELD_KEY = /^[a-zA-Z][a-zA-Z0-9]*$/;
|
|
15
|
+
/**
|
|
16
|
+
* One control of the connection form (decision 019). `key` names the config property it fills;
|
|
17
|
+
* `secret` sends the value to the secret store instead of `datasources.json` (decision 020).
|
|
18
|
+
*/
|
|
19
|
+
export const connectionFieldSchema = z.strictObject({
|
|
20
|
+
key: z.string().regex(FIELD_KEY, { message: "expected a camelCase config key" }),
|
|
21
|
+
label: z.string().min(1),
|
|
22
|
+
kind: z.enum(CONNECTION_FIELD_KINDS),
|
|
23
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
24
|
+
required: z.boolean().optional(),
|
|
25
|
+
placeholder: z.string().optional(),
|
|
26
|
+
/** The choices of a `select`, in display order. */
|
|
27
|
+
options: z
|
|
28
|
+
.array(z.strictObject({ value: z.string(), label: z.string().min(1) }))
|
|
29
|
+
.min(1)
|
|
30
|
+
.optional(),
|
|
31
|
+
group: z.enum(CONNECTION_FIELD_GROUPS).optional(),
|
|
32
|
+
secret: z.boolean().optional(),
|
|
33
|
+
});
|
|
34
|
+
/** `server`: `scheme://user:password@host:port/database?params`; `file`: a path, bare or as a URL. */
|
|
35
|
+
export const CONNECTION_URL_FORMS = ["server", "file"];
|
|
36
|
+
/** The pieces of a connection URL a field can take; `path` belongs to the `file` form. */
|
|
37
|
+
export const CONNECTION_URL_PARTS = ["host", "port", "database", "user", "password", "path"];
|
|
38
|
+
const URL_SCHEME = /^[a-z][a-z0-9+.-]*$/;
|
|
39
|
+
/**
|
|
40
|
+
* How a connection URL maps onto the form's fields (decision 055), so the host parses and writes
|
|
41
|
+
* one without running plugin code. `parts` names the field each piece of the URL fills; a query
|
|
42
|
+
* parameter fills `key`, its value translated through `values` (compared case-insensitively) when
|
|
43
|
+
* given, so a value outside them is reported as ignored.
|
|
44
|
+
*/
|
|
45
|
+
export const connectionUrlSchema = z.strictObject({
|
|
46
|
+
/** Without `://`; the first is the one the host writes. */
|
|
47
|
+
schemes: z.array(z.string().regex(URL_SCHEME, { message: "expected a lower-case URL scheme" })).min(1),
|
|
48
|
+
form: z.enum(CONNECTION_URL_FORMS),
|
|
49
|
+
parts: z.partialRecord(z.enum(CONNECTION_URL_PARTS), z.string().regex(FIELD_KEY)),
|
|
50
|
+
params: z
|
|
51
|
+
.array(z.strictObject({
|
|
52
|
+
name: z.string().min(1),
|
|
53
|
+
key: z.string().regex(FIELD_KEY),
|
|
54
|
+
values: z.record(z.string(), z.string()).optional(),
|
|
55
|
+
}))
|
|
56
|
+
.optional(),
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Which fields hold the server's address (decision 070), so the host can open an SSH tunnel and
|
|
60
|
+
* hand the plugin `127.0.0.1` and the forwarded port instead. `connectTimeout`, in seconds, bounds
|
|
61
|
+
* the SSH hop too. Absent: the data source is not reached over the network and offers no tunnel.
|
|
62
|
+
*/
|
|
63
|
+
export const networkSpecSchema = z.strictObject({
|
|
64
|
+
host: z.string().regex(FIELD_KEY),
|
|
65
|
+
port: z.string().regex(FIELD_KEY),
|
|
66
|
+
connectTimeout: z.string().regex(FIELD_KEY).optional(),
|
|
67
|
+
});
|
|
68
|
+
const NETWORK_FIELD_KINDS = { host: "text", port: "number", connectTimeout: "number" };
|
|
69
|
+
function networkFieldsDeclared(connection) {
|
|
70
|
+
const network = connection.network;
|
|
71
|
+
if (network === undefined)
|
|
72
|
+
return true;
|
|
73
|
+
return Object.keys(NETWORK_FIELD_KINDS).every((role) => {
|
|
74
|
+
const key = network[role];
|
|
75
|
+
if (key === undefined)
|
|
76
|
+
return true;
|
|
77
|
+
return connection.fields.some((field) => field.key === key && field.kind === NETWORK_FIELD_KINDS[role]);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Which fields configure TLS (decision 072), so the dialog groups them under SSL, hints when a CA
|
|
82
|
+
* file goes unchecked, and main asks for the key passphrase only when the key file is encrypted.
|
|
83
|
+
* `mode` is the `select` of SSL modes; `verifyModes` are its values that check the server
|
|
84
|
+
* certificate. Absent: the plugin speaks no TLS the host knows about.
|
|
85
|
+
*/
|
|
86
|
+
export const tlsSpecSchema = z.strictObject({
|
|
87
|
+
mode: z.string().regex(FIELD_KEY),
|
|
88
|
+
verifyModes: z.array(z.string().min(1)).min(1),
|
|
89
|
+
caFile: z.string().regex(FIELD_KEY).optional(),
|
|
90
|
+
certFile: z.string().regex(FIELD_KEY).optional(),
|
|
91
|
+
keyFile: z.string().regex(FIELD_KEY).optional(),
|
|
92
|
+
keyPassphrase: z.string().regex(FIELD_KEY).optional(),
|
|
93
|
+
});
|
|
94
|
+
/** The roles of `TlsSpec` that name a field, in the order the dialog draws them. */
|
|
95
|
+
export const TLS_FIELD_ROLES = ["mode", "caFile", "certFile", "keyFile", "keyPassphrase"];
|
|
96
|
+
function tlsFieldFits(role, field, tls) {
|
|
97
|
+
switch (role) {
|
|
98
|
+
case "mode": {
|
|
99
|
+
const values = new Set((field.options ?? []).map((option) => option.value));
|
|
100
|
+
return field.kind === "select" && tls.verifyModes.every((mode) => values.has(mode));
|
|
101
|
+
}
|
|
102
|
+
case "keyPassphrase":
|
|
103
|
+
return field.kind === "password" && field.secret === true;
|
|
104
|
+
default:
|
|
105
|
+
return field.kind === "file";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function tlsFieldsDeclared(connection) {
|
|
109
|
+
const tls = connection.tls;
|
|
110
|
+
if (tls === undefined)
|
|
111
|
+
return true;
|
|
112
|
+
return TLS_FIELD_ROLES.every((role) => {
|
|
113
|
+
const key = tls[role];
|
|
114
|
+
if (key === undefined)
|
|
115
|
+
return true;
|
|
116
|
+
const field = connection.fields.find((candidate) => candidate.key === key);
|
|
117
|
+
return field !== undefined && tlsFieldFits(role, field, tls);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export const pluginManifestSchema = z
|
|
121
|
+
.strictObject({
|
|
122
|
+
id: z.string().regex(PLUGIN_ID, { message: "expected lower-case letters, digits and dashes" }),
|
|
123
|
+
name: z.string().min(1),
|
|
124
|
+
version: semver,
|
|
125
|
+
apiVersion: semver,
|
|
126
|
+
/** An ES module, relative to the manifest, whose default export is the `DatasourcePlugin`. */
|
|
127
|
+
entry: z.string().min(1),
|
|
128
|
+
capabilities: z.array(z.enum(CAPABILITIES)),
|
|
129
|
+
/** The highlighter the console uses for this plugin's dialect (decision 008). */
|
|
130
|
+
highlight: z.string().min(1),
|
|
131
|
+
/** The connection form, readable without running plugin code (decision 019). */
|
|
132
|
+
connection: z
|
|
133
|
+
.strictObject({
|
|
134
|
+
fields: z.array(connectionFieldSchema).min(1),
|
|
135
|
+
/** Absent: the form offers no URL to paste and the data source no Copy as URL. */
|
|
136
|
+
url: connectionUrlSchema.optional(),
|
|
137
|
+
network: networkSpecSchema.optional(),
|
|
138
|
+
tls: tlsSpecSchema.optional(),
|
|
139
|
+
})
|
|
140
|
+
.refine(networkFieldsDeclared, {
|
|
141
|
+
message: "network names a field the form does not declare, or one of the wrong kind",
|
|
142
|
+
path: ["network"],
|
|
143
|
+
})
|
|
144
|
+
.refine(tlsFieldsDeclared, {
|
|
145
|
+
message: "tls names a field the form does not declare or one of the wrong kind: mode a select offering every verify mode, files of kind file, the passphrase a secret password",
|
|
146
|
+
path: ["tls"],
|
|
147
|
+
}),
|
|
148
|
+
})
|
|
149
|
+
// A definition is read through the introspector, which only `schemaTree` promises (decision 029).
|
|
150
|
+
.refine((manifest) => !manifest.capabilities.includes("ddl") || manifest.capabilities.includes("schemaTree"), {
|
|
151
|
+
message: "the ddl capability requires schemaTree",
|
|
152
|
+
path: ["capabilities"],
|
|
153
|
+
})
|
|
154
|
+
// A plan is of a statement, and Analyse is Explain that also runs it (decision 031).
|
|
155
|
+
.refine((manifest) => !manifest.capabilities.includes("explain") || manifest.capabilities.includes("sql"), {
|
|
156
|
+
message: "the explain capability requires sql",
|
|
157
|
+
path: ["capabilities"],
|
|
158
|
+
})
|
|
159
|
+
.refine((manifest) => !manifest.capabilities.includes("explainAnalyze") || manifest.capabilities.includes("explain"), { message: "the explainAnalyze capability requires explain", path: ["capabilities"] })
|
|
160
|
+
// A namespace is switched by a statement, and the switch is told apart by the language (decision 048).
|
|
161
|
+
.refine((manifest) => !manifest.capabilities.includes("currentNamespace") || manifest.capabilities.includes("sql"), {
|
|
162
|
+
message: "the currentNamespace capability requires sql",
|
|
163
|
+
path: ["capabilities"],
|
|
164
|
+
})
|
|
165
|
+
// An import commits at boundaries it chooses and rolls back the rest (decision 046).
|
|
166
|
+
.refine((manifest) => !manifest.capabilities.includes("bulkInsert") || manifest.capabilities.includes("transactions"), { message: "the bulkInsert capability requires transactions", path: ["capabilities"] })
|
|
167
|
+
// Object DDL is planned against described tables and reads its names from the introspector (decision 052).
|
|
168
|
+
.refine((manifest) => !manifest.capabilities.includes("objects") ||
|
|
169
|
+
(manifest.capabilities.includes("sql") && manifest.capabilities.includes("schemaTree")), { message: "the objects capability requires sql and schemaTree", path: ["capabilities"] })
|
|
170
|
+
// A listed session's query opens in a console, and the engine lists sessions on its introspection
|
|
171
|
+
// session, which only a schemaTree plugin opens (decision 088).
|
|
172
|
+
.refine((manifest) => !manifest.capabilities.includes("sessions") ||
|
|
173
|
+
(manifest.capabilities.includes("sql") && manifest.capabilities.includes("schemaTree")), { message: "the sessions capability requires sql and schemaTree", path: ["capabilities"] })
|
|
174
|
+
// A call runs in a console and is built from the routine the introspector describes (decision 090).
|
|
175
|
+
.refine((manifest) => !manifest.capabilities.includes("routineCall") ||
|
|
176
|
+
(manifest.capabilities.includes("sql") && manifest.capabilities.includes("schemaTree")), { message: "the routineCall capability requires sql and schemaTree", path: ["capabilities"] });
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Relation } from "./catalog.js";
|
|
2
|
+
/** What the database tree can rename or drop (decision 052). */
|
|
3
|
+
export declare const OBJECT_KINDS: readonly ["table", "view", "materializedView", "sequence", "function", "procedure"];
|
|
4
|
+
export type ObjectKind = (typeof OBJECT_KINDS)[number];
|
|
5
|
+
/** One object in a namespace, named as the catalog names it. */
|
|
6
|
+
export interface CatalogObject {
|
|
7
|
+
readonly kind: ObjectKind;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A column of a table design. `original` is the name the column has in the database, null for a
|
|
12
|
+
* column the design adds, so a rename is told apart from a drop and an add.
|
|
13
|
+
*/
|
|
14
|
+
export interface ColumnDesign {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly type: string;
|
|
17
|
+
readonly nullable: boolean;
|
|
18
|
+
/** A SQL expression, written as the column's `DEFAULT` clause takes it; null for none. */
|
|
19
|
+
readonly default: string | null;
|
|
20
|
+
readonly autoIncrement: boolean;
|
|
21
|
+
readonly comment: string | null;
|
|
22
|
+
readonly original: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface IndexDesign {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly columns: readonly string[];
|
|
27
|
+
readonly unique: boolean;
|
|
28
|
+
/** The index's name in the database, null for one the design adds. */
|
|
29
|
+
readonly original: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface ForeignKeyDesign {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly columns: readonly string[];
|
|
34
|
+
readonly target: {
|
|
35
|
+
readonly path: readonly string[];
|
|
36
|
+
readonly relation: string;
|
|
37
|
+
readonly columns: readonly string[];
|
|
38
|
+
};
|
|
39
|
+
/** The referential action as the dialect spells it, such as `CASCADE`; null for the default. */
|
|
40
|
+
readonly onDelete: string | null;
|
|
41
|
+
readonly onUpdate: string | null;
|
|
42
|
+
readonly original: string | null;
|
|
43
|
+
}
|
|
44
|
+
export interface CheckDesign {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly expression: string;
|
|
47
|
+
readonly original: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** A table as the Create and Modify Table dialog edits it (decision 052). */
|
|
50
|
+
export interface TableDesign {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly comment: string | null;
|
|
53
|
+
readonly columns: readonly ColumnDesign[];
|
|
54
|
+
/** Column names in key order; empty for no primary key. */
|
|
55
|
+
readonly primaryKey: readonly string[];
|
|
56
|
+
/** The primary key constraint's name in the database, null when the table has none yet. */
|
|
57
|
+
readonly primaryKeyName: string | null;
|
|
58
|
+
readonly indexes: readonly IndexDesign[];
|
|
59
|
+
readonly foreignKeys: readonly ForeignKeyDesign[];
|
|
60
|
+
readonly checks: readonly CheckDesign[];
|
|
61
|
+
}
|
|
62
|
+
/** One change to the catalog of `path`, which the plugin turns into a `DdlScript`. */
|
|
63
|
+
export type ObjectChange = {
|
|
64
|
+
readonly kind: "createTable";
|
|
65
|
+
readonly path: readonly string[];
|
|
66
|
+
readonly design: TableDesign;
|
|
67
|
+
} | {
|
|
68
|
+
readonly kind: "alterTable";
|
|
69
|
+
readonly path: readonly string[];
|
|
70
|
+
readonly relation: string;
|
|
71
|
+
readonly design: TableDesign;
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: "rename";
|
|
74
|
+
readonly path: readonly string[];
|
|
75
|
+
readonly object: CatalogObject;
|
|
76
|
+
readonly name: string;
|
|
77
|
+
} | {
|
|
78
|
+
readonly kind: "drop";
|
|
79
|
+
readonly path: readonly string[];
|
|
80
|
+
readonly object: CatalogObject;
|
|
81
|
+
readonly cascade: boolean;
|
|
82
|
+
} | {
|
|
83
|
+
readonly kind: "truncate";
|
|
84
|
+
readonly path: readonly string[];
|
|
85
|
+
readonly relation: string;
|
|
86
|
+
readonly cascade: boolean;
|
|
87
|
+
}
|
|
88
|
+
/** A schema or database inside `path`; `path` is empty for a top-level one. */
|
|
89
|
+
| {
|
|
90
|
+
readonly kind: "createNamespace";
|
|
91
|
+
readonly path: readonly string[];
|
|
92
|
+
readonly name: string;
|
|
93
|
+
};
|
|
94
|
+
export type ObjectChangeKind = ObjectChange["kind"];
|
|
95
|
+
/**
|
|
96
|
+
* The statements for one change. The host runs `prepare` and `finish` outside any transaction on
|
|
97
|
+
* the session it opens for the change, `statements` in one transaction when `transactional`, then
|
|
98
|
+
* each of `verify`, which must return no row, before it commits.
|
|
99
|
+
*/
|
|
100
|
+
export interface DdlScript {
|
|
101
|
+
readonly prepare: readonly string[];
|
|
102
|
+
readonly statements: readonly string[];
|
|
103
|
+
readonly verify: readonly string[];
|
|
104
|
+
readonly finish: readonly string[];
|
|
105
|
+
readonly transactional: boolean;
|
|
106
|
+
/** What the preview tells the user before they run it, such as an implicit commit. */
|
|
107
|
+
readonly notes: readonly string[];
|
|
108
|
+
}
|
|
109
|
+
/** What a dialect lets the tree and the table dialog offer; the UI reads these, never a plugin id. */
|
|
110
|
+
export interface ObjectFeatures {
|
|
111
|
+
/** Column types the dialog suggests, most common first. */
|
|
112
|
+
readonly types: readonly string[];
|
|
113
|
+
readonly checks: boolean;
|
|
114
|
+
readonly comments: boolean;
|
|
115
|
+
/** Whether Drop and Truncate take `CASCADE`. */
|
|
116
|
+
readonly cascade: boolean;
|
|
117
|
+
readonly truncate: boolean;
|
|
118
|
+
/** What New ▸ creates inside a namespace, or null when the dialect creates none. */
|
|
119
|
+
readonly namespaceKind: "schema" | "database" | null;
|
|
120
|
+
readonly renamable: readonly ObjectKind[];
|
|
121
|
+
readonly droppable: readonly ObjectKind[];
|
|
122
|
+
}
|
|
123
|
+
/** What the host knows that a script needs: names as the introspector qualifies them, and the table as it is. */
|
|
124
|
+
export interface DdlContext {
|
|
125
|
+
qualifiedName(path: readonly string[], name: string): string;
|
|
126
|
+
/** For `alterTable`, the design of the table as the catalog describes it now; otherwise null. */
|
|
127
|
+
readonly current: TableDesign | null;
|
|
128
|
+
}
|
|
129
|
+
/** Present exactly when the plugin declares `objects` (decision 052). Pure: it never runs SQL. */
|
|
130
|
+
export interface ObjectEditor {
|
|
131
|
+
readonly features: ObjectFeatures;
|
|
132
|
+
/** The design of a described table, every name its `original`. */
|
|
133
|
+
design(relation: Relation): TableDesign;
|
|
134
|
+
/**
|
|
135
|
+
* The statements for `change`; an `alterTable` gets only the ones its difference from
|
|
136
|
+
* `context.current` needs. Throws a `PluginError` coded `statementFailed` saying what to change
|
|
137
|
+
* when the dialect cannot express it.
|
|
138
|
+
*/
|
|
139
|
+
script(change: ObjectChange, context: DdlContext): DdlScript;
|
|
140
|
+
}
|
package/dist/objects.js
ADDED