DBNova registers some type information with the TypeScript language service of the Monaco Editor to provide better code completion and type checking.
The Monaco Editor supports only a single global TypeScript language service instance. Since DBNova needs to support SQL, MongoDB, and Redis, interaction logic is implemented in separate entry functions.
mongoshell<IMongoDBTypes>((ctx, db) => {
db.status().export();
db.collection("users").find().limit(10).export();
});
redisshell((ctx, cli) => {
cli.scan(0).stream().export();
});
sqlshell<ISQLTypes>((ctx, db) => {
sql`select ${params.int("a")} + ${params.int("b")}`.export(ctx);
// mysql
db.manycols.select({}).export(ctx);
// postgresql
db.public.datatypetest.select({}).export(ctx);
});Since all commands for MongoDB and Redis are directly spawned by db or cli, they inherently capture the execution tracking, eliminating the need to pass ctx during .export().
In contrast, the SQL Shell requires ctx to be passed explicitly. While attempting to leverage Zone.js to automate this, async/await boundaries consistently broke the context chain. Thus, explicit passing remains necessary until AsyncContext natively arrives in browsers (eagerly awaiting the Async Context Proposal).
TIP
Entry functions are automatically populated when opening the editor and do not need to be manually entered.
You can also customize the initial code in the database settings.
TIP
Functions may also be asynchronous.
Defining Parameters
In TypeScript, you can define parameters through methods of the params object.
WARNING
Corresponding type values are not actually returned, so parameters cannot be used for calculations.
// This is incorrect; parameters cannot be directly used in calculations
const incorrect_pa = params.int("a") * 2;
// Use `valmap` to implement calculations
const pa = params.int("a", { valmap: (v) => v * 2 });For more parameter options, refer to Feats-Params.
MongoDB
declare class MongoDB<T extends Record<string, any>> {
mkcmd(cmd: Record<string, any>, opts?: IMakeCmdOpts): ICommand;
aggregate(pipeline: IDoc[], opts?: IAggregateOptions): ICommand;
collection<K extends keyof T & string>(name: K): MongoCollection<T[K]>;
...
stats(opts?: IDBStatusOptions): ICommand;
}
declare class MongoCollection<T> {
insertOne(doc: T): ICommand;
insertMany(docs: T[], opts?: ICommonOptions): ICommand;
...
dropIndex(name: string): ICommand;
stats(): ICommand;
}Users can also call the db.mkcmd method to register arbitrary commands.
Redis
declare class RedisClient {
mkcmd(cmd: string, ...args: (string | number)[]): ICommand;
bitcount(key: string, opts?: BitCountOptions): ICommand;
...
ping(message?: string): ICommand;
}Users can also call the cli.mkcmd method to register arbitrary commands.
SQL
DBNova has open-sourced its built-in SQL Generator aghsorm, which you can use to generate SQL statements.
sqlshell<IDBTypes, typeof SqliteDialect>((ctx, db, dialect) => {
// raw sql
sql`select ${params.int("a")} + ${params.int("b")}`.export(ctx);
// --------- DMLs:
// select
db.article.select(
{ id: params.int("id") },
{ exclude: ["content"] }
).export(ctx);
// delete
db.article.delete({ id: params.int("id") }).export(ctx);
// insert
db.article
.insert({
id: params.int("id"),
title: params.string("title"),
content: params.string("content"),
})
.export(ctx);
// update
db.article
.update(
{
id: params.int("id"),
name: ThisCol.like(params.string("name"))
},
{ read_count: ThisCol.plus(1) },
)
.export(ctx);
// ------ DDLs:
ctx.addtable("user", [
{
name: "id",
sqltype: dialect.types.integer(),
opts: dialect.colopts({
primary: true,
autoincr: true,
}),
},
...
]).export(ctx);
db.article.ddl.addcol(...).export(ctx);
db.article.ddl.addindex(...).export(ctx);
db.article.ddl.dropcol(...).export(ctx);
db.article.ddl.dropindex(...).export(ctx);
db.article.ddl.modcoltype(...).export(ctx);
db.article.ddl.renamecol(...).export(ctx);
});For column rendering options, refer to Feats-ColRender.
Database Type Inference
To prevent database lag during index data processing from affecting normal business operations, DBNova's indexing is manual. Currently, this feature supports SQL and MongoDB.
All field names that do not comply with JavaScript variable name rules will be converted to valid names.
MongoDB
Use collection.aggregate([{$sample: {size: 50}}]) to obtain sample documents; you can change the sample size in the settings.
For heterogeneous fields, a union based on first-level fields is generated. For example:
[
{ a: 1, b: { c: 122 } },
{ a: 2, b: { d: false } },
];
// The following types will be generated:
interface ICollectionAB {
c: number;
}
interface ICollectionAB_1 {
d: boolean;
}
interface ICollectionA {
a: number;
b: ICollectionAB | ICollectionAB_1;
}For method calls such as find and updateOne, corresponding field prompts and checks are provided.
SQL
Special type conversions:
- 64-bit and larger integer types are inferred as
bigint. - decimal types are inferred as
Decimal. - Time-related types are inferred as
Date. - Binary types are inferred as
Binary.
Schema concepts in databases such as PostgreSQL are also supported, with each schema being a separate namespace.
Batch
batch is designed to group and streamline a series of sequential statements or database commands.
redisshell(async (ctx, cli) => {
batch("fill fake name", function* () {
for (const i of range(100)) {
yield cli.set(`nameof:${i}`, faker().person.fullName());
}
}).export(ctx);
batch("fill fake address", function* () {
for (const i of range(100)) {
yield cli.set(`addressof:${i}`, faker().location.streetAddress());
}
}).export(ctx);
});
sqlshell<IDBSqlite, typeof SqliteDialect>((ctx, db, dialect) => {
db.article.select({}).export(ctx);
batch("fill fake", function* () {
for (const i of range(100)) {
yield db.article.insert({
id: i,
userid: faker().number.int({ min: 16 }),
title: faker().book.title(),
content: faker().string.alpha(512),
});
}
}).export(ctx);
});Statements or commands yielded inside a batch generator function do not need to call .export() individually.
Additional TypeScript Types and APIs
declare class Base64 {
static encode(input: Uint8Array): string;
static decode(input: string): Uint8Array;
}
declare class FileSelection {
readonly text: string;
readonly bytes: Uint8Array;
constructor(opts: { defaultdir?: string; patterns?: string[] });
}
// To protect your sensitive production data from malicious
// or compromised scripts, this function is strictly sandboxed.
// Refer to: https://dbnova.ruiransoft.com/docs/static-http-api-hosts.html
declare function http(
method: string,
url: string,
opts?: {
query?: {
[k: string]: string[] | string;
};
headers?: {
[k: string]: string[] | string;
};
body?: FileSelection | Uint8Array | string | any;
timeout?: number;
proxy?: "app" | string;
responseBodySizeLimit?: number;
follow_redirect?: number;
},
): Promise<{
body: Uint8Array;
text(): string;
json<T>(): T;
code: number;
message: string;
headers: Record<string, Array<string>>;
}>;
// This function never grants direct, unprompted disk access.
// Calling this function will always trigger a native system file picker.
declare function fsread(title: string, select?: FileSelection): Promise<Uint8Array>;
declare function range(
c: number,
opts?: {
begin?: number;
step?: number;
cond?: (cur: number, end: number) => boolean;
},
): Generator<number>;
declare function range<K, V>(ary: Map<K, V>): Generator<[K, V]>;
declare function range<T>(ary: Iterable<T>): Generator<[number, T]>;
declare function range<T extends Record<string, any>>(
obj: T,
): Generator<[keyof T & string, T[keyof T]]>;
// Refer to: https://github.com/mongodb/js-bson
declare namespace bson {}
declare const Binary: typeof bson.Binary;
// Refer to: https://github.com/MikeMcl/decimal.js/
declare namespace Decimal {}
// @param loc - The locale to use for the faker instance,
// such as "AF_ZA" or "AR", default is "EN_US".
// See faker locales: https://fakerjs.dev/guide/localization.html#available-locales
// @returns A faker instance
declare function faker(loc?: string): fakerlib.Faker;