@midsummer-ai/sdk 0.0.0-stage → 0.1.0-next.1723
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 +177 -0
- package/README.md +4 -2
- package/agents/contract.d.mts +9 -0
- package/agents/contract.mjs +221 -0
- package/agents/index.d.mts +128 -0
- package/agents/index.mjs +203 -0
- package/agents/stand-in.d.mts +35 -0
- package/agents/stand-in.mjs +149 -0
- package/ai/contract.d.mts +10 -0
- package/ai/contract.mjs +77 -0
- package/ai/index.d.mts +94 -0
- package/ai/index.mjs +193 -0
- package/ai/stand-in.d.mts +7 -0
- package/ai/stand-in.mjs +41 -0
- package/build-SQ44jwSa.d.mts +41 -0
- package/client.d.mts +45 -0
- package/client.mjs +106 -0
- package/connection-BHjDaLYA.mjs +2009 -0
- package/contract.d.mts +10 -0
- package/contract.mjs +469 -0
- package/database/build.d.mts +2 -0
- package/database/build.mjs +579 -0
- package/database/client.d.mts +60 -0
- package/database/client.mjs +133 -0
- package/database/contract.d.mts +11 -0
- package/database/contract.mjs +164 -0
- package/database/errors.d.mts +23 -0
- package/database/errors.mjs +13 -0
- package/database/server.d.mts +3 -0
- package/database/server.mjs +320 -0
- package/database/stand-in.d.mts +99 -0
- package/database/stand-in.mjs +430 -0
- package/email/client.d.mts +34 -0
- package/email/client.mjs +131 -0
- package/email/contract.d.mts +11 -0
- package/email/contract.mjs +99 -0
- package/email/index.d.mts +74 -0
- package/email/index.mjs +59 -0
- package/email/smtp.d.mts +22 -0
- package/email/smtp.mjs +296 -0
- package/email/stand-in.d.mts +13 -0
- package/email/stand-in.mjs +51 -0
- package/index-DdoK0y7D.d.mts +192 -0
- package/index-sgdyoA2P.d.mts +212 -0
- package/index.d.mts +2 -0
- package/index.mjs +137 -0
- package/names-DfCBKupJ.mjs +115 -0
- package/package.json +152 -4
- package/predict-Drfm_8pO.mjs +179 -0
- package/queues/contract.d.mts +13 -0
- package/queues/contract.mjs +278 -0
- package/queues/index.d.mts +184 -0
- package/queues/index.mjs +126 -0
- package/queues/stand-in.d.mts +7 -0
- package/queues/stand-in.mjs +110 -0
- package/queues/wire.d.mts +9 -0
- package/queues/wire.mjs +199 -0
- package/s3.d.mts +43 -0
- package/s3.mjs +1003 -0
- package/schema-BF-Y1EVu.mjs +142 -0
- package/schema-DS8x1Dn8.mjs +115 -0
- package/schema-f7E9uzUn.d.mts +74 -0
- package/server-C7O7CICU.d.mts +182 -0
- package/socket.d.mts +67 -0
- package/socket.mjs +263 -0
- package/stand-in.d.mts +7 -0
- package/stand-in.mjs +185 -0
- package/store-BA8K-bAR.mjs +237 -0
- package/tabs-DEeMee7M.mjs +457 -0
- package/vector/contract.d.mts +10 -0
- package/vector/contract.mjs +635 -0
- package/vector/index.d.mts +2 -0
- package/vector/index.mjs +108 -0
- package/vector/stand-in.d.mts +11 -0
- package/vector/stand-in.mjs +169 -0
- package/vector/wire.d.mts +25 -0
- package/vector/wire.mjs +175 -0
- package/wire.d.mts +54 -0
- package/wire.mjs +731 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { i as indexFields, n as defineSchema, o as v, r as defineTable } from "../schema-BF-Y1EVu.mjs";
|
|
2
|
+
import { parse } from "acorn";
|
|
3
|
+
//#region src/database/build.ts
|
|
4
|
+
/**
|
|
5
|
+
* A Project's `db/` folder, built: its TypeScript stripped of types, checked as the Database checks
|
|
6
|
+
* it, and its generated files written. `midsummer db dev` builds with it on every save, so a refusal
|
|
7
|
+
* shows where it was written long before a deploy; the Database checks a deploy again, the same way.
|
|
8
|
+
*
|
|
9
|
+
* const built = buildDatabase({ "schema.ts": schema, "messages.ts": messages }, strip)
|
|
10
|
+
* built.files // { "schema.mjs": js, "messages.mjs": js }: what the Stand-in runs
|
|
11
|
+
* built.functions // [{ name: "messages.send", kind: "mutation", internal: false, ... }]
|
|
12
|
+
* built.generated // the files of db/_generated/: api.mjs and the types
|
|
13
|
+
*
|
|
14
|
+
* `strip` takes TypeScript to JavaScript, keeping every position, so a refusal points at the author's
|
|
15
|
+
* line: `transformSync(ts, { mode: "strip-only" }).code` of `amaro`. Only types are taken out; an
|
|
16
|
+
* enum or a namespace is code, and is refused. Nothing here runs the Project's code.
|
|
17
|
+
*
|
|
18
|
+
* What is admitted: imports of `@midsummer-ai/sdk/server`, `./_generated/server` and
|
|
19
|
+
* `./_generated/api` by name (no renaming), and of the folder's own modules; functions as top-level
|
|
20
|
+
* exports (`export const send = mutation({ ... })`); actions in files of their own; the schema as
|
|
21
|
+
* literals (`export default defineSchema({ ... })` in `schema.ts`). Never `eval`, a dynamic
|
|
22
|
+
* `import()`, `import.meta` or import attributes.
|
|
23
|
+
*/
|
|
24
|
+
/** A file the Database would refuse; the message gives the file, and the line when there is one. */
|
|
25
|
+
var Refused = class extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "Refused";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const SOURCES = [
|
|
32
|
+
".ts",
|
|
33
|
+
".mts",
|
|
34
|
+
".mjs",
|
|
35
|
+
".js"
|
|
36
|
+
];
|
|
37
|
+
const extensionOf = (name) => /\.[^.]*$/u.exec(name)?.[0] ?? "";
|
|
38
|
+
/** Whether a file directly in `db/` is a source: types (`.d.ts`) and dotfiles are none. */
|
|
39
|
+
const isSource = (name) => SOURCES.includes(extensionOf(name)) && !name.endsWith(".d.ts") && !name.startsWith(".") && !name.includes("/");
|
|
40
|
+
const SDK = "@midsummer-ai/sdk/server";
|
|
41
|
+
const DEFINERS = [
|
|
42
|
+
"query",
|
|
43
|
+
"mutation",
|
|
44
|
+
"internalQuery",
|
|
45
|
+
"internalMutation",
|
|
46
|
+
"action",
|
|
47
|
+
"internalAction"
|
|
48
|
+
];
|
|
49
|
+
const ACTIONS = /* @__PURE__ */ new Set(["action", "internalAction"]);
|
|
50
|
+
const ADMITTED = /* @__PURE__ */ new Set([
|
|
51
|
+
...DEFINERS,
|
|
52
|
+
"v",
|
|
53
|
+
"increment",
|
|
54
|
+
"take",
|
|
55
|
+
"give",
|
|
56
|
+
"InsufficientError",
|
|
57
|
+
"NotFoundError",
|
|
58
|
+
"defineSchema",
|
|
59
|
+
"defineTable"
|
|
60
|
+
]);
|
|
61
|
+
const API_NAMES = /* @__PURE__ */ new Set(["api", "internal"]);
|
|
62
|
+
const SURFACES = new Map([[SDK, ADMITTED], ...[
|
|
63
|
+
"",
|
|
64
|
+
".js",
|
|
65
|
+
".mjs"
|
|
66
|
+
].flatMap((x) => [[`./_generated/server${x}`, ADMITTED], [`./_generated/api${x}`, API_NAMES]])]);
|
|
67
|
+
const SCHEMA_FILE = "schema.mjs";
|
|
68
|
+
const FUNCTION_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/u;
|
|
69
|
+
const FORBIDDEN = /* @__PURE__ */ new Set([
|
|
70
|
+
"eval",
|
|
71
|
+
"__realm",
|
|
72
|
+
"__step"
|
|
73
|
+
]);
|
|
74
|
+
function* nodes(node) {
|
|
75
|
+
yield node;
|
|
76
|
+
for (const value of Object.values(node)) if (Array.isArray(value)) {
|
|
77
|
+
for (const child of value) if (typeof child?.type === "string") yield* nodes(child);
|
|
78
|
+
} else if (typeof value?.type === "string") yield* nodes(value);
|
|
79
|
+
}
|
|
80
|
+
/** Strips each source and points its siblings at their built files. Throws `Refused` with the author's file. */
|
|
81
|
+
function stripSources(sources, strip) {
|
|
82
|
+
const entries = Object.keys(sources).filter(isSource);
|
|
83
|
+
const names = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const file of entries) {
|
|
85
|
+
const name = file.slice(0, -extensionOf(file).length);
|
|
86
|
+
const first = names.get(name);
|
|
87
|
+
if (first !== void 0) throw new Refused(`${first} and ${file} compile to ${name}.mjs. Rename one file.`);
|
|
88
|
+
names.set(name, file);
|
|
89
|
+
}
|
|
90
|
+
const files = {}, from = {};
|
|
91
|
+
for (const [name, file] of names) {
|
|
92
|
+
let js;
|
|
93
|
+
try {
|
|
94
|
+
js = /\.m?ts$/u.test(file) ? strip(sources[file]) : sources[file];
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const { startLine: line, code } = error ?? {};
|
|
97
|
+
const why = `${file}${Number.isInteger(line) ? `:${line}` : ""}: ${String(error?.message ?? error).split("\n")[0]}`;
|
|
98
|
+
throw new Refused(code === "UnsupportedSyntax" ? `${why}. Write it as JavaScript instead: an object of constants for an enum, plain exports for a namespace.` : why);
|
|
99
|
+
}
|
|
100
|
+
files[`${name}.mjs`] = siblingsNamed(file, js, names);
|
|
101
|
+
from[`${name}.mjs`] = file;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
files,
|
|
105
|
+
from
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** `js` with each import of the folder (`./helper`, `./helper.ts`, `./helper.js`) naming its built file. */
|
|
109
|
+
function siblingsNamed(file, js, names) {
|
|
110
|
+
let tree;
|
|
111
|
+
try {
|
|
112
|
+
tree = parse(js, {
|
|
113
|
+
ecmaVersion: "latest",
|
|
114
|
+
sourceType: "module"
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw new Refused(`${file}: does not parse as a module: ${error.message}`);
|
|
118
|
+
}
|
|
119
|
+
const edits = [];
|
|
120
|
+
for (const node of tree.body) {
|
|
121
|
+
if (!node.source || !/^(Import|ExportNamed|ExportAll)Declaration$/u.test(node.type)) continue;
|
|
122
|
+
const match = /^\.\/([A-Za-z0-9_-]+)(?:\.(?:ts|mts|js|mjs))?$/u.exec(node.source.value);
|
|
123
|
+
if (match && names.has(match[1])) edits.push({
|
|
124
|
+
at: node.source.start,
|
|
125
|
+
end: node.source.end,
|
|
126
|
+
text: JSON.stringify(`./${match[1]}.mjs`)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
let out = js;
|
|
130
|
+
for (const edit of edits.sort((a, b) => b.at - a.at)) out = `${out.slice(0, edit.at)}${edit.text}${out.slice(edit.end)}`;
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/** Parses one built module and checks every rule of admission. Throws `Refused` naming the author's file and line. */
|
|
134
|
+
function inspect(file, text, siblings) {
|
|
135
|
+
const refuse = (why, node) => {
|
|
136
|
+
throw new Refused(`${file}${node?.loc ? `:${node.loc.start.line}` : ""}: ${why}`);
|
|
137
|
+
};
|
|
138
|
+
let tree;
|
|
139
|
+
try {
|
|
140
|
+
tree = parse(text, {
|
|
141
|
+
ecmaVersion: "latest",
|
|
142
|
+
sourceType: "module",
|
|
143
|
+
locations: true
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return refuse(`does not parse as a module: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
for (const node of nodes(tree)) {
|
|
149
|
+
if (node.type === "ImportExpression") refuse("a dynamic import() (a function loads nothing at runtime)", node);
|
|
150
|
+
if (node.type === "MetaProperty" && node.meta.name === "import") refuse("import.meta", node);
|
|
151
|
+
if ((node.type === "Identifier" || node.type === "PrivateIdentifier") && FORBIDDEN.has(node.name)) refuse(`the name ${node.name}`, node);
|
|
152
|
+
if (!node.source || !/^(Import|ExportNamed|ExportAll)Declaration$/u.test(node.type)) continue;
|
|
153
|
+
if (node.attributes?.length) refuse("import attributes", node);
|
|
154
|
+
const spec = node.source.value;
|
|
155
|
+
const allowed = SURFACES.get(spec);
|
|
156
|
+
if (allowed) {
|
|
157
|
+
const names = (node.specifiers ?? []).map((s) => s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === s.local.name ? s.local.name : null);
|
|
158
|
+
if (names.includes("defineFunction")) refuse("defineFunction: define with query, mutation or action", node);
|
|
159
|
+
if (node.type !== "ImportDeclaration" || !names.length || !names.every((name) => name !== null && allowed.has(name))) refuse(`only \`import { ${[...allowed].join(", ")} } from "${spec}"\` (no renaming)`, node);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const sibling = /^\.\/([A-Za-z0-9_-]+\.mjs)$/u.exec(spec)?.[1];
|
|
163
|
+
if (!sibling || !siblings.has(sibling)) refuse(`import of "${spec}" (only "${SDK}", "./_generated/server", "./_generated/api" and modules of this folder are admitted)`, node);
|
|
164
|
+
}
|
|
165
|
+
return tree;
|
|
166
|
+
}
|
|
167
|
+
/** A file's functions, as top-level exports, and the count of definer calls anywhere (each must be one of them). */
|
|
168
|
+
function definersOf(tree, base) {
|
|
169
|
+
const isDefiner = (n) => n?.type === "CallExpression" && n.callee.type === "Identifier" && DEFINERS.includes(n.callee.name);
|
|
170
|
+
const literal = (n) => n?.type === "Literal" && typeof n.value === "string" ? n.value : null;
|
|
171
|
+
const calls = [...nodes(tree)].filter(isDefiner).length;
|
|
172
|
+
const defs = [];
|
|
173
|
+
for (const top of tree.body) {
|
|
174
|
+
if (top.type === "ExportDefaultDeclaration" && isDefiner(top.declaration)) {
|
|
175
|
+
const first = top.declaration.arguments[0];
|
|
176
|
+
defs.push({
|
|
177
|
+
kind: top.declaration.callee.name,
|
|
178
|
+
name: literal(first) ?? (first?.type === "ObjectExpression" ? `${base}.default` : null),
|
|
179
|
+
export: "default",
|
|
180
|
+
call: top.declaration
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (top.type !== "ExportNamedDeclaration" || top.declaration?.type !== "VariableDeclaration") continue;
|
|
184
|
+
for (const one of top.declaration.declarations) {
|
|
185
|
+
if (!isDefiner(one.init) || one.id.type !== "Identifier") continue;
|
|
186
|
+
defs.push({
|
|
187
|
+
kind: one.init.callee.name,
|
|
188
|
+
name: `${base}.${one.id.name}`,
|
|
189
|
+
export: one.id.name,
|
|
190
|
+
named: one.init.arguments[0]?.type !== "ObjectExpression",
|
|
191
|
+
call: one.init
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
defs,
|
|
197
|
+
calls
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A reader of literal values in a module's parse (the schema's, and a function's `args`): literals,
|
|
202
|
+
* arrays, objects, `v.<shape>(...)`, `defineTable(...).index(...)` and the module's top-level
|
|
203
|
+
* `const`s of these. Anything else calls `refuse`.
|
|
204
|
+
*/
|
|
205
|
+
function literalReader(tree, refuse, lenient = false) {
|
|
206
|
+
const consts = /* @__PURE__ */ new Map();
|
|
207
|
+
const vShapes = v;
|
|
208
|
+
const value = (node) => {
|
|
209
|
+
if (!node) return refuse(null);
|
|
210
|
+
if (node.type === "Identifier" && consts.has(node.name)) return consts.get(node.name);
|
|
211
|
+
if (node.type === "Literal" && !node.regex && typeof node.value !== "bigint") return node.value;
|
|
212
|
+
if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
|
|
213
|
+
if (node.type === "ArrayExpression" && node.elements.every((e) => e && e.type !== "SpreadElement")) return node.elements.map(value);
|
|
214
|
+
if (node.type === "ObjectExpression") {
|
|
215
|
+
const out = {};
|
|
216
|
+
for (const p of node.properties) {
|
|
217
|
+
if (p.type !== "Property" || p.computed || p.kind !== "init" || p.method) refuse(p);
|
|
218
|
+
out[p.key.type === "Identifier" ? p.key.name : String(p.key.value)] = value(p.value);
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
if (node.type === "CallExpression" && !node.optional && node.arguments.every((a) => a.type !== "SpreadElement")) {
|
|
223
|
+
const callee = node.callee;
|
|
224
|
+
const args = () => node.arguments.map(value);
|
|
225
|
+
if (callee.type === "Identifier" && callee.name === "defineTable") return defineTable(args()[0]);
|
|
226
|
+
if (callee.type === "MemberExpression" && !callee.computed && !callee.optional && callee.object.type === "Identifier" && callee.object.name === "v" && Object.hasOwn(v, callee.property.name)) return vShapes[callee.property.name](...args());
|
|
227
|
+
if (callee.type === "MemberExpression" && !callee.computed && !callee.optional && [
|
|
228
|
+
"index",
|
|
229
|
+
"searchIndex",
|
|
230
|
+
"vectorIndex"
|
|
231
|
+
].includes(callee.property.name)) {
|
|
232
|
+
const table = value(callee.object);
|
|
233
|
+
if (typeof table?.[callee.property.name] === "function") return table[callee.property.name](...args());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return refuse(node);
|
|
237
|
+
};
|
|
238
|
+
for (const top of tree.body) {
|
|
239
|
+
const declaration = top.type === "ExportNamedDeclaration" ? top.declaration : top;
|
|
240
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") continue;
|
|
241
|
+
for (const one of declaration.declarations) {
|
|
242
|
+
if (one.id.type !== "Identifier" || !one.init) continue;
|
|
243
|
+
if (!lenient) {
|
|
244
|
+
consts.set(one.id.name, value(one.init));
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
consts.set(one.id.name, value(one.init));
|
|
249
|
+
} catch {}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { value };
|
|
253
|
+
}
|
|
254
|
+
/** A function's `args` fields and `returns` shape as written, for its types; null where they are not literals. */
|
|
255
|
+
function signatureOf(tree, call) {
|
|
256
|
+
const UNKNOWN = Symbol("unknown");
|
|
257
|
+
let value;
|
|
258
|
+
try {
|
|
259
|
+
({value} = literalReader(tree, () => {
|
|
260
|
+
throw UNKNOWN;
|
|
261
|
+
}, true));
|
|
262
|
+
} catch {
|
|
263
|
+
return {
|
|
264
|
+
args: null,
|
|
265
|
+
returns: null
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const spec = (call?.arguments)?.find((a) => a.type === "ObjectExpression");
|
|
269
|
+
const prop = (name) => (spec?.properties)?.find((p) => p.type === "Property" && !p.computed && (p.key.name ?? p.key.value) === name)?.value;
|
|
270
|
+
const read = (node) => {
|
|
271
|
+
if (!node) return null;
|
|
272
|
+
try {
|
|
273
|
+
return value(node);
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const args = prop("args");
|
|
279
|
+
const fields = args?.type === "ObjectExpression" ? Object.fromEntries(args.properties.filter((p) => p.type === "Property" && !p.computed).map((p) => [p.key.name ?? String(p.key.value), read(p.value)])) : read(args);
|
|
280
|
+
return {
|
|
281
|
+
args: fields && typeof fields === "object" && !("object" in fields) ? fields : fields?.object ?? null,
|
|
282
|
+
returns: read(prop("returns"))
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function schemaOf(file, tree) {
|
|
286
|
+
const refuse = (why, node) => {
|
|
287
|
+
throw new Refused(`${file}${node?.loc ? `:${node.loc.start.line}` : ""}: ${why}`);
|
|
288
|
+
};
|
|
289
|
+
const literal = "the schema is written as literals: defineSchema({ <table>: defineTable({ <field>: v.<shape>(...) }) })";
|
|
290
|
+
let value;
|
|
291
|
+
try {
|
|
292
|
+
({value} = literalReader(tree, (node) => refuse(literal, node)));
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (error instanceof Refused) throw error;
|
|
295
|
+
return refuse(error.message, null);
|
|
296
|
+
}
|
|
297
|
+
const declaration = tree.body.find((n) => n.type === "ExportDefaultDeclaration")?.declaration;
|
|
298
|
+
if (declaration?.type !== "CallExpression" || declaration.callee.type !== "Identifier" || declaration.callee.name !== "defineSchema" || declaration.arguments.length !== 1) refuse("the schema is `export default defineSchema({ ... })`", declaration);
|
|
299
|
+
try {
|
|
300
|
+
const built = defineSchema(value(declaration.arguments[0]));
|
|
301
|
+
return {
|
|
302
|
+
tables: built.tables,
|
|
303
|
+
indexes: built.indexes
|
|
304
|
+
};
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (error instanceof Refused) throw error;
|
|
307
|
+
return refuse(error.message, declaration);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** Builds a `db/` folder's sources: `{ <file>: text }`, its top level. Throws `Refused`. */
|
|
311
|
+
function buildDatabase(sources, strip) {
|
|
312
|
+
const { files, from } = stripSources(sources, strip);
|
|
313
|
+
const names = Object.keys(files).sort();
|
|
314
|
+
const siblings = new Set(names);
|
|
315
|
+
const functions = [];
|
|
316
|
+
const defined = /* @__PURE__ */ new Map();
|
|
317
|
+
let tables = null, indexes = null;
|
|
318
|
+
for (const file of names) {
|
|
319
|
+
const author = from[file];
|
|
320
|
+
const tree = inspect(author, files[file], siblings);
|
|
321
|
+
const { defs, calls } = definersOf(tree, file.replace(/\.mjs$/u, ""));
|
|
322
|
+
if (calls !== defs.length) throw new Refused(`${author}: a function defined outside a top-level export (\`export const send = mutation({ ... })\`)`);
|
|
323
|
+
for (const def of defs) {
|
|
324
|
+
if (def.named) throw new Refused(`${author}: export const ${def.export} = ${def.kind}({ ... }): its name is the export's (${def.name}), so the definer takes none`);
|
|
325
|
+
if (!def.name || !def.name.split(".").every((part) => FUNCTION_NAME.test(part))) throw new Refused(`${author}: a function's name: [A-Za-z][A-Za-z0-9_]{0,63}, or <file>.<export> with both so (${JSON.stringify(def.name)})`);
|
|
326
|
+
if (defined.has(def.name)) throw new Refused(`${author}: the name "${def.name}" is also defined by ${defined.get(def.name)}`);
|
|
327
|
+
defined.set(def.name, author);
|
|
328
|
+
}
|
|
329
|
+
const acting = defs.filter((def) => ACTIONS.has(def.kind)).length;
|
|
330
|
+
if (acting && acting !== defs.length) throw new Refused(`${author}: actions and queries or mutations in one file (an action waits on the network, the others run as one step): put the actions in a file of their own`);
|
|
331
|
+
if (file === SCHEMA_FILE) ({tables, indexes} = schemaOf(author, tree));
|
|
332
|
+
for (const def of defs) functions.push({
|
|
333
|
+
name: def.name,
|
|
334
|
+
kind: def.kind,
|
|
335
|
+
file,
|
|
336
|
+
export: def.export,
|
|
337
|
+
...signatureOf(tree, def.call)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
files,
|
|
342
|
+
from,
|
|
343
|
+
functions,
|
|
344
|
+
tables,
|
|
345
|
+
indexes,
|
|
346
|
+
generated: generatedOf(functions, tables, indexes)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const HEAD = "// Generated by `midsummer db dev` from this folder. Do not edit.";
|
|
350
|
+
const kindOf = (kind) => kind.replace(/^internal/u, "").toLowerCase();
|
|
351
|
+
/** The function references, nested by file: `api.messages.send`; `internal` has the internal functions alike. */
|
|
352
|
+
function referencesOf(functions) {
|
|
353
|
+
const tree = (list) => {
|
|
354
|
+
const top = /* @__PURE__ */ new Map();
|
|
355
|
+
for (const fn of list) {
|
|
356
|
+
const [a, b] = fn.name.split(".");
|
|
357
|
+
const ref = {
|
|
358
|
+
name: fn.name,
|
|
359
|
+
kind: kindOf(fn.kind)
|
|
360
|
+
};
|
|
361
|
+
const had = top.get(a);
|
|
362
|
+
if (b === void 0 ? had : had?.ref) throw new Refused(`${fn.file}: "${a}" names both a function and a file of functions`);
|
|
363
|
+
if (b === void 0) top.set(a, { ref });
|
|
364
|
+
else top.set(a, { group: new Map([...had?.group ?? [], [b, ref]]) });
|
|
365
|
+
}
|
|
366
|
+
return top;
|
|
367
|
+
};
|
|
368
|
+
tree(functions);
|
|
369
|
+
const js = (top) => `Object.freeze({ ${[...top].map(([key, x]) => `${key}: ${x.ref ? `Object.freeze(${JSON.stringify(x.ref)})` : `Object.freeze({ ${[...x.group].map(([b, r]) => `${b}: Object.freeze(${JSON.stringify(r)})`).join(", ")} })`}`).join(", ")} })`;
|
|
370
|
+
return [`export const api = ${js(tree(functions.filter((fn) => !fn.kind.startsWith("internal"))))};`, `export const internal = ${js(tree(functions.filter((fn) => fn.kind.startsWith("internal"))))};`];
|
|
371
|
+
}
|
|
372
|
+
/** A shape as a TypeScript type. */
|
|
373
|
+
function typeOf(shape) {
|
|
374
|
+
if (shape === void 0 || shape === null) return "unknown";
|
|
375
|
+
if (typeof shape === "string") return {
|
|
376
|
+
string: "string",
|
|
377
|
+
number: "number",
|
|
378
|
+
boolean: "boolean",
|
|
379
|
+
document: "unknown",
|
|
380
|
+
int64: "number | string",
|
|
381
|
+
bytes: "string",
|
|
382
|
+
timestamp: "number",
|
|
383
|
+
decimal: "string"
|
|
384
|
+
}[shape] ?? "unknown";
|
|
385
|
+
if ("optional" in shape) return `${typeOf(shape.optional)} | null | undefined`;
|
|
386
|
+
if ("array" in shape) return `Array<${typeOf(shape.array)}>`;
|
|
387
|
+
if ("object" in shape) return `{ ${Object.entries(shape.object).map(([key, inner]) => `${JSON.stringify(key)}${inner && typeof inner === "object" && "optional" in inner ? "?" : ""}: ${typeOf(inner)}`).join("; ")} }`;
|
|
388
|
+
if ("literal" in shape) return JSON.stringify(shape.literal);
|
|
389
|
+
if ("union" in shape) return shape.union.map(typeOf).join(" | ") || "never";
|
|
390
|
+
if ("record" in shape) return `Record<string, ${typeOf(shape.record)}>`;
|
|
391
|
+
if ("id" in shape) return `Id<${JSON.stringify(shape.id)}>`;
|
|
392
|
+
return "unknown";
|
|
393
|
+
}
|
|
394
|
+
const fieldsType = (fields) => typeOf({ object: fields ?? {} });
|
|
395
|
+
function dataModelOf(tables, indexes) {
|
|
396
|
+
const names = Object.keys(tables ?? {});
|
|
397
|
+
if (!names.length) return [
|
|
398
|
+
HEAD,
|
|
399
|
+
"export type TableNames = string;",
|
|
400
|
+
"export type Id<T extends TableNames = TableNames> = string & { readonly __table?: T };",
|
|
401
|
+
"export interface DataModel { [table: string]: { document: { _id: Id; _version: number; [field: string]: any }; indexes: Record<string, string[]> } }",
|
|
402
|
+
"export type Doc<T extends TableNames> = DataModel[T][\"document\"];",
|
|
403
|
+
""
|
|
404
|
+
].join("\n");
|
|
405
|
+
return [
|
|
406
|
+
HEAD,
|
|
407
|
+
`export type TableNames = ${names.map((name) => JSON.stringify(name)).join(" | ")};`,
|
|
408
|
+
"/** A document's id: a string that identifies its table. */",
|
|
409
|
+
"export type Id<T extends TableNames> = string & { readonly __table: T };",
|
|
410
|
+
"export interface DataModel {",
|
|
411
|
+
...names.map((name) => ` ${JSON.stringify(name)}: { document: { _id: Id<${JSON.stringify(name)}>; _version: number } & ${typeOf({ object: tables[name] })}; indexes: { ${Object.entries(indexes?.[name] ?? {}).map(([index, fields]) => `${JSON.stringify(index)}: ${JSON.stringify(indexFields(fields))}`).join("; ")} } };`),
|
|
412
|
+
"}",
|
|
413
|
+
"/** A document of a table, as a read returns it. */",
|
|
414
|
+
"export type Doc<T extends TableNames> = DataModel[T][\"document\"];",
|
|
415
|
+
""
|
|
416
|
+
].join("\n");
|
|
417
|
+
}
|
|
418
|
+
function apiTypesOf(functions) {
|
|
419
|
+
const tree = (list) => {
|
|
420
|
+
const top = /* @__PURE__ */ new Map();
|
|
421
|
+
for (const fn of list) {
|
|
422
|
+
const [a, b] = fn.name.split(".");
|
|
423
|
+
const ref = `FunctionReference<${JSON.stringify(kindOf(fn.kind))}, ${fn.args ? fieldsType(fn.args) : "any"}, ${fn.returns ? typeOf(fn.returns) : "any"}>`;
|
|
424
|
+
if (b === void 0) top.set(a, ref);
|
|
425
|
+
else top.set(a, new Map([...top.get(a) ?? [], [b, ref]]));
|
|
426
|
+
}
|
|
427
|
+
return `{ ${[...top].map(([key, x]) => `readonly ${key}: ${typeof x === "string" ? x : `{ ${[...x].map(([b, r]) => `readonly ${b}: ${r};`).join(" ")} }`};`).join(" ")} }`;
|
|
428
|
+
};
|
|
429
|
+
return [
|
|
430
|
+
HEAD,
|
|
431
|
+
"import type { FunctionReference } from \"./server\";",
|
|
432
|
+
"export type { FunctionReference } from \"./server\";",
|
|
433
|
+
`export declare const api: ${tree(functions.filter((fn) => !fn.kind.startsWith("internal")))};`,
|
|
434
|
+
`export declare const internal: ${tree(functions.filter((fn) => fn.kind.startsWith("internal")))};`,
|
|
435
|
+
""
|
|
436
|
+
].join("\n");
|
|
437
|
+
}
|
|
438
|
+
function generatedOf(functions, tables, indexes) {
|
|
439
|
+
return {
|
|
440
|
+
"api.mjs": [
|
|
441
|
+
`${HEAD} A function's reference for the client: \`db.mutation(api.messages.send, args)\`.`,
|
|
442
|
+
...referencesOf(functions),
|
|
443
|
+
""
|
|
444
|
+
].join("\n"),
|
|
445
|
+
"api.d.ts": apiTypesOf(functions),
|
|
446
|
+
"dataModel.d.ts": dataModelOf(tables, indexes),
|
|
447
|
+
"server.d.ts": SERVER_TYPES
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/** `@midsummer-ai/sdk/server`, typed by this Project's schema (`./dataModel`). */
|
|
451
|
+
const SERVER_TYPES = `${HEAD} What "@midsummer-ai/sdk/server" gives, typed by this
|
|
452
|
+
// Database's schema: its tables, documents and function references.
|
|
453
|
+
import type { DataModel, Doc, Id, TableNames } from "./dataModel";
|
|
454
|
+
export type { DataModel, Doc, Id, TableNames } from "./dataModel";
|
|
455
|
+
|
|
456
|
+
/** A field type. \`T\` is the value's type; \`O\` whether the field may be left out. */
|
|
457
|
+
export interface Validator<T, O extends boolean = false> {
|
|
458
|
+
readonly type?: string;
|
|
459
|
+
readonly optional?: O;
|
|
460
|
+
/** For the types only: never present at run time. */
|
|
461
|
+
readonly __value?: T;
|
|
462
|
+
}
|
|
463
|
+
type Required<T> = Validator<T, false>;
|
|
464
|
+
export type Infer<V> = V extends Validator<infer T, boolean> ? T : never;
|
|
465
|
+
export type Fields = Record<string, Validator<any, boolean>>;
|
|
466
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
467
|
+
type OptionalKeys<F extends Fields> = { [K in keyof F]: F[K] extends Validator<any, true> ? K : never }[keyof F];
|
|
468
|
+
type RequiredKeys<F extends Fields> = Exclude<keyof F, OptionalKeys<F>>;
|
|
469
|
+
/** The document a table's fields describe. */
|
|
470
|
+
export type DocOf<F extends Fields> = Simplify<{ [K in RequiredKeys<F>]: Infer<F[K]> } & { [K in OptionalKeys<F>]?: Infer<F[K]> }>;
|
|
471
|
+
export declare const v: {
|
|
472
|
+
string(): Required<string>;
|
|
473
|
+
number(): Required<number>;
|
|
474
|
+
boolean(): Required<boolean>;
|
|
475
|
+
/** Any JSON value, at any depth. */
|
|
476
|
+
document(): Required<unknown>;
|
|
477
|
+
id<T extends TableNames>(table: T): Required<Id<T>>;
|
|
478
|
+
array<V extends Validator<any, boolean>>(of: V): Required<Infer<V>[]>;
|
|
479
|
+
object<F extends Fields>(fields: F): Required<DocOf<F>>;
|
|
480
|
+
/** May be left out or null. */
|
|
481
|
+
optional<V extends Validator<any, boolean>>(inner: V): Validator<Infer<V> | null, true>;
|
|
482
|
+
int64(): Validator<number | string>;
|
|
483
|
+
bytes(): Validator<string>;
|
|
484
|
+
timestamp(): Validator<number>;
|
|
485
|
+
decimal(): Validator<string>;
|
|
486
|
+
literal<L extends string | number | boolean>(value: L): Validator<L>;
|
|
487
|
+
union<V extends Validator<any, boolean>[]>(...of: V): Validator<Infer<V[number]>>;
|
|
488
|
+
record<V extends Validator<any, boolean>>(of: V): Validator<Record<string, Infer<V>>>;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// ---- Operators: a patch's values ----
|
|
492
|
+
|
|
493
|
+
export interface Operator { readonly __operator?: "increment" | "take" | "give"; readonly n: number }
|
|
494
|
+
/** The field goes up by \`n\` (default 1). */
|
|
495
|
+
export declare function increment(n?: number): Operator;
|
|
496
|
+
/** Takes \`n\` from a field; below zero the whole mutation fails with InsufficientError and writes nothing. */
|
|
497
|
+
export declare function take(n?: number): Operator;
|
|
498
|
+
/** Gives \`n\` back: the inverse of \`take\`. */
|
|
499
|
+
export declare function give(n?: number): Operator;
|
|
500
|
+
export type Patch<D> = { [K in keyof D]?: D[K] | (D[K] extends number ? Operator : never) };
|
|
501
|
+
|
|
502
|
+
// ---- The tables a function reads and writes: \`ctx.db.<table>\` ----
|
|
503
|
+
|
|
504
|
+
type Value<T extends TableNames> = Omit<Doc<T>, "_id" | "_version">;
|
|
505
|
+
/** A range's bounds on the index's first field (\`eq\`), or on it (\`gte\`, \`gt\`, \`lte\`, \`lt\`); \`order\`; at most \`limit\`. */
|
|
506
|
+
export interface RangeOptions { eq?: unknown; gte?: unknown; gt?: unknown; lte?: unknown; lt?: unknown; order?: "asc" | "desc"; limit?: number }
|
|
507
|
+
export interface Reader<T extends TableNames> {
|
|
508
|
+
get(id: Id<T>): Doc<T> | null;
|
|
509
|
+
/** The rows of an index (\`""\`: by id) within the bounds, in its order. */
|
|
510
|
+
range(index: keyof DataModel[T]["indexes"] | "", field: string, options?: RangeOptions): { ok: true; rows: Doc<T>[] };
|
|
511
|
+
}
|
|
512
|
+
export interface Writer<T extends TableNames> extends Reader<T> {
|
|
513
|
+
/** Its id at once, usable anywhere in the same mutation. */
|
|
514
|
+
insert(doc: Value<T>): Id<T>;
|
|
515
|
+
patch(id: Id<T>, fields: Patch<Value<T>>): void;
|
|
516
|
+
replace(id: Id<T>, doc: Value<T>): void;
|
|
517
|
+
delete(id: Id<T>): void;
|
|
518
|
+
}
|
|
519
|
+
type Tables<R> = { readonly [T in TableNames]: R extends "write" ? Writer<T> : Reader<T> } & { table<T extends TableNames>(name: T): R extends "write" ? Writer<T> : Reader<T> };
|
|
520
|
+
type AsyncReader<T extends TableNames> = { get(id: Id<T>): Promise<Doc<T> | null>; range(index: keyof DataModel[T]["indexes"] | "", field: string, options?: RangeOptions): Promise<{ ok: true; rows: Doc<T>[] }> };
|
|
521
|
+
|
|
522
|
+
// ---- Functions ----
|
|
523
|
+
|
|
524
|
+
/** A function's reference (\`api.messages.send\`): its name, kind, arguments and answer. */
|
|
525
|
+
export interface FunctionReference<Kind extends "query" | "mutation" | "action", Args = any, Returns = any> { readonly name: string; readonly kind: Kind; readonly __args?: Args; readonly __returns?: Returns }
|
|
526
|
+
type ArgsOf<F> = F extends FunctionReference<any, infer A, any> ? A : never;
|
|
527
|
+
export interface Identity { readonly user: string | null; readonly server: boolean }
|
|
528
|
+
/** The caller as the Project's sign-in identifies it (its \`sub\`), and the issuer when the Database trusts one; null: no user. */
|
|
529
|
+
export type Auth = { readonly subject: string; readonly issuer: string | null } | null;
|
|
530
|
+
export interface Scheduler {
|
|
531
|
+
runAfter<F extends FunctionReference<"mutation" | "action">>(ms: number, fn: F, args: ArgsOf<F>): string;
|
|
532
|
+
runAt<F extends FunctionReference<"mutation" | "action">>(at: number, fn: F, args: ArgsOf<F>): string;
|
|
533
|
+
status(id: string): { name: string; args: unknown; at: number; state: string; error?: string } | null;
|
|
534
|
+
cancel(id: string): boolean;
|
|
535
|
+
}
|
|
536
|
+
/** Reads only; run again when anything it read changes. */
|
|
537
|
+
export interface QueryCtx { readonly db: Tables<"read">; readonly identity: Identity; readonly auth: Auth; readonly now: number }
|
|
538
|
+
/** One serializable transaction; run again when a row it read changed. */
|
|
539
|
+
export interface MutationCtx { readonly db: Tables<"write">; readonly identity: Identity; readonly auth: Auth; readonly now: number; readonly scheduler: Scheduler }
|
|
540
|
+
/** May wait on the network; its writes are the mutations it runs. */
|
|
541
|
+
export interface ActionCtx {
|
|
542
|
+
readonly db: { readonly [T in TableNames]: AsyncReader<T> };
|
|
543
|
+
readonly identity: Identity;
|
|
544
|
+
readonly auth: Auth;
|
|
545
|
+
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
546
|
+
now(): number; random(bytes?: number): Uint8Array;
|
|
547
|
+
run<F extends FunctionReference<any>>(fn: F, args: ArgsOf<F>): Promise<F extends FunctionReference<any, any, infer R> ? R : never>;
|
|
548
|
+
readonly scheduler: { runAfter<F extends FunctionReference<"mutation" | "action">>(ms: number, fn: F, args: ArgsOf<F>): Promise<string>; runAt<F extends FunctionReference<"mutation" | "action">>(at: number, fn: F, args: ArgsOf<F>): Promise<string>; status(id: string): Promise<unknown>; cancel(id: string): Promise<boolean> };
|
|
549
|
+
}
|
|
550
|
+
type ArgsFrom<A> = A extends Fields ? DocOf<A> : Record<string, never>;
|
|
551
|
+
interface Definition<Ctx, A, R> { args?: A; returns?: Validator<R, boolean>; handler: (ctx: Ctx, args: ArgsFrom<A>) => R | Promise<R> }
|
|
552
|
+
/** A mutation or an action may run on a schedule: \`cron: "0 6 * * *"\` (minute hour day month weekday, UTC). */
|
|
553
|
+
interface Scheduled { cron?: string }
|
|
554
|
+
export interface RegisteredFunction<Kind extends string, Args, Returns> { readonly __kind?: Kind; readonly __args?: Args; readonly __returns?: Returns }
|
|
555
|
+
export declare function query<A extends Fields = {}, R = any>(definition: Definition<QueryCtx, A, R>): RegisteredFunction<"query", ArgsFrom<A>, R>;
|
|
556
|
+
export declare function internalQuery<A extends Fields = {}, R = any>(definition: Definition<QueryCtx, A, R>): RegisteredFunction<"query", ArgsFrom<A>, R>;
|
|
557
|
+
export declare function mutation<A extends Fields = {}, R = any>(definition: Definition<MutationCtx, A, R> & Scheduled): RegisteredFunction<"mutation", ArgsFrom<A>, R>;
|
|
558
|
+
export declare function internalMutation<A extends Fields = {}, R = any>(definition: Definition<MutationCtx, A, R> & Scheduled): RegisteredFunction<"mutation", ArgsFrom<A>, R>;
|
|
559
|
+
export declare function action<A extends Fields = {}, R = any>(definition: Definition<ActionCtx, A, R> & Scheduled): RegisteredFunction<"action", ArgsFrom<A>, R>;
|
|
560
|
+
export declare function internalAction<A extends Fields = {}, R = any>(definition: Definition<ActionCtx, A, R> & Scheduled): RegisteredFunction<"action", ArgsFrom<A>, R>;
|
|
561
|
+
|
|
562
|
+
// ---- Errors ----
|
|
563
|
+
|
|
564
|
+
/** A \`take\` asked for more than the field holds: the mutation wrote nothing. */
|
|
565
|
+
export declare class InsufficientError extends Error { readonly detail: { table: string; field: string; has: number; asked: number } }
|
|
566
|
+
/** A mutation patched or replaced a row that is not there. */
|
|
567
|
+
export declare class NotFoundError extends Error { readonly detail: { table: string; id: string } }
|
|
568
|
+
|
|
569
|
+
// ---- The schema ----
|
|
570
|
+
|
|
571
|
+
export interface TableDefinition<F extends Fields> {
|
|
572
|
+
/** One field, or a list for a compound index; \`{ unique: true }\`: no two rows may share its key. */
|
|
573
|
+
index(name: string, field: (keyof F & string) | ReadonlyArray<keyof F & string>, options?: { unique?: boolean }): TableDefinition<F>;
|
|
574
|
+
}
|
|
575
|
+
export declare function defineTable<F extends Fields>(fields: F): TableDefinition<F>;
|
|
576
|
+
export declare function defineSchema<T extends Record<string, TableDefinition<any>>>(tables: T): { tables: T };
|
|
577
|
+
`;
|
|
578
|
+
//#endregion
|
|
579
|
+
export { Refused, buildDatabase, isSource, typeOf };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DatabaseError, DatabaseErrorCode } from "./errors.mjs";
|
|
2
|
+
//#region src/database/client.d.ts
|
|
3
|
+
/** A function's reference, as `api.<file>.<export>` holds it, typed by its arguments and its answer. */
|
|
4
|
+
interface Reference<Kind extends "query" | "mutation" | "action", Args = any, Returns = any> {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly kind: Kind;
|
|
7
|
+
readonly __args?: Args;
|
|
8
|
+
readonly __returns?: Returns;
|
|
9
|
+
}
|
|
10
|
+
type ArgsOf<F> = F extends Reference<any, infer A, any> ? A : never;
|
|
11
|
+
type ReturnsOf<F> = F extends Reference<any, any, infer R> ? R : never;
|
|
12
|
+
interface Database {
|
|
13
|
+
query<F extends Reference<"query">>(fn: F, args: ArgsOf<F>): Promise<ReturnsOf<F>>;
|
|
14
|
+
/**
|
|
15
|
+
* `tag` identifies this mutation for retries: sent again under it, it applies once while the Database
|
|
16
|
+
* still keeps the tag, which is for the client's last 64 writes until it has been idle 24 hours. A
|
|
17
|
+
* tag is made when not given. Give each mutation a tag of its own, and never rely on one past that.
|
|
18
|
+
*/
|
|
19
|
+
mutation<F extends Reference<"mutation">>(fn: F, args: ArgsOf<F>, options?: {
|
|
20
|
+
readonly tag?: string;
|
|
21
|
+
}): Promise<ReturnsOf<F>>;
|
|
22
|
+
action<F extends Reference<"action">>(fn: F, args: ArgsOf<F>): Promise<ReturnsOf<F>>;
|
|
23
|
+
/** Gives `onValue` the query's value now and after every change; returns a stop. */
|
|
24
|
+
watch<F extends Reference<"query">>(fn: F, args: ArgsOf<F>, onValue: (value: ReturnsOf<F>) => void): () => void;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
type ConnectOptions = {
|
|
28
|
+
/** Where the Database serves, as `midsummer db show` gives it, or the development server's address. */
|
|
29
|
+
readonly url: string;
|
|
30
|
+
/** A token for this Database. */
|
|
31
|
+
readonly token?: string;
|
|
32
|
+
/** A new token, asked for before the one held runs out and when it is refused. */
|
|
33
|
+
readonly refresh?: () => Promise<string> | string;
|
|
34
|
+
} | {
|
|
35
|
+
/** A Database Key, `mdbk_…`: exchanged for a token and the Database's address. */
|
|
36
|
+
readonly key: string;
|
|
37
|
+
/** The platform's API, where a key is exchanged. */
|
|
38
|
+
readonly api?: string;
|
|
39
|
+
} | {
|
|
40
|
+
/** The name the run's Environment binds the Database under: in CI, an Agent's run or `midsummer dev`. */
|
|
41
|
+
readonly binding: string;
|
|
42
|
+
/** The platform's API; the one the run's Workload token came from when left out. */
|
|
43
|
+
readonly api?: string;
|
|
44
|
+
} | {
|
|
45
|
+
/** `context.database`, the Databases a Deployment's Environment binds. */
|
|
46
|
+
readonly database: BoundDatabases;
|
|
47
|
+
/** The name the Environment binds the Database under, such as `main`. */
|
|
48
|
+
readonly binding: string;
|
|
49
|
+
};
|
|
50
|
+
/** What Midsummer hands a Deployment as `context.database`: a token and address for a Database its Environment binds. */
|
|
51
|
+
interface BoundDatabases {
|
|
52
|
+
token(binding: string): Promise<{
|
|
53
|
+
readonly token: string;
|
|
54
|
+
readonly url: string;
|
|
55
|
+
readonly expiresAt: number;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
declare function connect(options: ConnectOptions): Promise<Database>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { BoundDatabases, ConnectOptions, Database, DatabaseError, type DatabaseErrorCode, Reference, connect };
|