kubun 0.2.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.md +57 -0
- package/README.md +563 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +6 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/account.js +5 -0
- package/dist/client.js +5 -0
- package/dist/commands/account/generate.js +10 -0
- package/dist/commands/account/id.js +18 -0
- package/dist/commands/graph/deploy.js +32 -0
- package/dist/commands/graph/mutate.js +32 -0
- package/dist/commands/graph/query.js +32 -0
- package/dist/commands/graphql/schema.js +46 -0
- package/dist/commands/hello/index.js +19 -0
- package/dist/commands/hello/world.js +12 -0
- package/dist/commands/model/cluster.js +35 -0
- package/dist/commands/model/create.js +69 -0
- package/dist/commands/serve.js +78 -0
- package/dist/fs.js +14 -0
- package/dist/index.js +1 -0
- package/oclif.manifest.json +502 -0
- package/package.json +73 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createClient } from '../../client.js';
|
|
4
|
+
import { readJSON } from '../../fs.js';
|
|
5
|
+
export default class DeployGraph extends Command {
|
|
6
|
+
static description = 'Deploy a documents model graph';
|
|
7
|
+
static flags = {
|
|
8
|
+
cluster: Flags.string({
|
|
9
|
+
description: 'path of the JSON file of the cluster',
|
|
10
|
+
char: 'c',
|
|
11
|
+
required: true,
|
|
12
|
+
multiple: true,
|
|
13
|
+
}),
|
|
14
|
+
id: Flags.string({ description: 'graph ID' }),
|
|
15
|
+
name: Flags.string({ description: 'graph name', char: 'n' }),
|
|
16
|
+
privateKey: Flags.string({
|
|
17
|
+
description: 'base64-encoded private key',
|
|
18
|
+
char: 'k',
|
|
19
|
+
env: 'KUBUN_PRIVATE_KEY',
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
url: Flags.string({ description: 'Kubun server URL' }),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const spinner = ora('Deploying graph clusters...').start();
|
|
26
|
+
const { flags } = await this.parse(DeployGraph);
|
|
27
|
+
const client = createClient(flags.privateKey, flags.url);
|
|
28
|
+
const clusters = await Promise.all(flags.cluster.map((path) => readJSON(path)));
|
|
29
|
+
const graph = await client.deployGraph({ clusters, id: flags.id, name: flags.name });
|
|
30
|
+
spinner.succeed(`Deployed graph with ID: ${graph.id}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createClient } from '../../client.js';
|
|
4
|
+
export default class MutateGraph extends Command {
|
|
5
|
+
static description = 'Execute a GraphQL mutation on a graph';
|
|
6
|
+
static args = {
|
|
7
|
+
id: Args.string({ description: 'graph ID', required: true }),
|
|
8
|
+
text: Args.string({ description: 'mutation text', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static flags = {
|
|
11
|
+
privateKey: Flags.string({
|
|
12
|
+
description: 'base64-encoded private key',
|
|
13
|
+
char: 'k',
|
|
14
|
+
env: 'KUBUN_PRIVATE_KEY',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
url: Flags.string({ description: 'Kubun server URL' }),
|
|
18
|
+
variables: Flags.string({ description: 'query variables as JSON string' }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const spinner = ora('Executing graph mutation...').start();
|
|
22
|
+
const { args, flags } = await this.parse(MutateGraph);
|
|
23
|
+
const client = createClient(flags.privateKey, flags.url);
|
|
24
|
+
const result = await client.mutateGraph({
|
|
25
|
+
id: args.id,
|
|
26
|
+
text: args.text,
|
|
27
|
+
variables: flags.variables ? JSON.parse(flags.variables) : {},
|
|
28
|
+
});
|
|
29
|
+
spinner.succeed('Graph mutation executed');
|
|
30
|
+
this.log(JSON.stringify(result, null, 2));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createClient } from '../../client.js';
|
|
4
|
+
export default class QueryGraph extends Command {
|
|
5
|
+
static description = 'Execute a GraphQL query on a graph';
|
|
6
|
+
static args = {
|
|
7
|
+
id: Args.string({ description: 'graph ID', required: true }),
|
|
8
|
+
text: Args.string({ description: 'query text', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static flags = {
|
|
11
|
+
privateKey: Flags.string({
|
|
12
|
+
description: 'base64-encoded private key',
|
|
13
|
+
char: 'k',
|
|
14
|
+
env: 'KUBUN_PRIVATE_KEY',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
url: Flags.string({ description: 'Kubun server URL' }),
|
|
18
|
+
variables: Flags.string({ description: 'query variables as JSON string' }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const spinner = ora('Querying graph...').start();
|
|
22
|
+
const { args, flags } = await this.parse(QueryGraph);
|
|
23
|
+
const client = createClient(flags.privateKey, flags.url);
|
|
24
|
+
const result = await client.queryGraph({
|
|
25
|
+
id: args.id,
|
|
26
|
+
text: args.text,
|
|
27
|
+
variables: flags.variables ? JSON.parse(flags.variables) : {},
|
|
28
|
+
});
|
|
29
|
+
spinner.succeed('Graph query executed');
|
|
30
|
+
this.log(JSON.stringify(result, null, 2));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ClientSchemaBuilder } from '@kubun/client';
|
|
2
|
+
import { GraphModel } from '@kubun/protocol';
|
|
3
|
+
import { Command, Flags } from '@oclif/core';
|
|
4
|
+
import { printSchema } from 'graphql';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { readJSON, writeFile } from '../../fs.js';
|
|
7
|
+
export default class CreateGraphQLSchema extends Command {
|
|
8
|
+
static description = 'Create a GraphQL schema from clusters of document models';
|
|
9
|
+
static flags = {
|
|
10
|
+
cluster: Flags.string({
|
|
11
|
+
description: 'path of the JSON file of the cluster',
|
|
12
|
+
char: 'c',
|
|
13
|
+
required: true,
|
|
14
|
+
multiple: true,
|
|
15
|
+
}),
|
|
16
|
+
output: Flags.file({
|
|
17
|
+
description: 'output file for the GraphQL schema',
|
|
18
|
+
char: 'o',
|
|
19
|
+
}),
|
|
20
|
+
// id: Flags.string({ description: 'graph ID' }),
|
|
21
|
+
// privateKey: Flags.string({
|
|
22
|
+
// description: 'base64-encoded private key',
|
|
23
|
+
// char: 'k',
|
|
24
|
+
// env: 'KUBUN_PRIVATE_KEY',
|
|
25
|
+
// required: true,
|
|
26
|
+
// }),
|
|
27
|
+
// url: Flags.string({ description: 'Kubun server URL' }),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const spinner = ora('Loading clusters...').start();
|
|
31
|
+
const { flags } = await this.parse(CreateGraphQLSchema);
|
|
32
|
+
const clusters = await Promise.all(flags.cluster.map((path) => readJSON(path)));
|
|
33
|
+
const model = await GraphModel.fromClusters({ clusters });
|
|
34
|
+
const builder = new ClientSchemaBuilder({ record: model.record });
|
|
35
|
+
const schema = printSchema(builder.build());
|
|
36
|
+
if (flags.output == null) {
|
|
37
|
+
spinner.succeed('GraphQL schema generated');
|
|
38
|
+
this.log(schema);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
await writeFile(flags.output, schema);
|
|
42
|
+
spinner.succeed(`GraphQL schema written to ${flags.output}`);
|
|
43
|
+
}
|
|
44
|
+
return schema;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
export default class Hello extends Command {
|
|
3
|
+
static args = {
|
|
4
|
+
person: Args.string({ description: 'Person to say hello to', required: true }),
|
|
5
|
+
};
|
|
6
|
+
static description = 'Say hello';
|
|
7
|
+
static examples = [
|
|
8
|
+
`<%= config.bin %> <%= command.id %> friend --from oclif
|
|
9
|
+
hello friend from oclif! (./src/commands/hello/index.ts)
|
|
10
|
+
`,
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
from: Flags.string({ char: 'f', description: 'Who is saying hello', required: true }),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args, flags } = await this.parse(Hello);
|
|
17
|
+
this.log(`hello ${args.person} from ${flags.from}! (./src/commands/hello/index.ts)`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class World extends Command {
|
|
3
|
+
static description = 'Say hello world';
|
|
4
|
+
static examples = [
|
|
5
|
+
`<%= config.bin %> <%= command.id %>
|
|
6
|
+
hello world! (./src/commands/hello/world.ts)
|
|
7
|
+
`,
|
|
8
|
+
];
|
|
9
|
+
async run() {
|
|
10
|
+
this.log('hello world! (./src/commands/hello/world.ts)');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ClusterBuilder } from '@kubun/protocol';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { writeJSON } from '../../fs.js';
|
|
5
|
+
export default class CreateClusterModel extends Command {
|
|
6
|
+
static description = 'Create a documents cluster model';
|
|
7
|
+
static flags = {
|
|
8
|
+
model: Flags.string({
|
|
9
|
+
description: 'document model as JSON string',
|
|
10
|
+
char: 'm',
|
|
11
|
+
multiple: true,
|
|
12
|
+
required: true,
|
|
13
|
+
}),
|
|
14
|
+
output: Flags.file({
|
|
15
|
+
description: 'output file for the cluster',
|
|
16
|
+
char: 'o',
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const spinner = ora('Creating cluster model...').start();
|
|
21
|
+
const { flags } = await this.parse(CreateClusterModel);
|
|
22
|
+
const builder = new ClusterBuilder();
|
|
23
|
+
builder.addAll(flags.model.map((model) => JSON.parse(model)));
|
|
24
|
+
const cluster = builder.build();
|
|
25
|
+
if (flags.output == null) {
|
|
26
|
+
spinner.succeed('Cluster model created');
|
|
27
|
+
this.log(JSON.stringify(cluster));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
await writeJSON(flags.output, cluster);
|
|
31
|
+
spinner.succeed(`Cluster model written to ${flags.output}`);
|
|
32
|
+
}
|
|
33
|
+
return cluster;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ClusterBuilder, } from '@kubun/protocol';
|
|
2
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { writeJSON } from '../../fs.js';
|
|
5
|
+
export default class CreateDocumentModel extends Command {
|
|
6
|
+
static description = 'Create a document model';
|
|
7
|
+
static args = {
|
|
8
|
+
name: Args.string({ description: 'document model name', required: true }),
|
|
9
|
+
schema: Args.string({ description: 'document schema as JSON string', required: true }),
|
|
10
|
+
};
|
|
11
|
+
static flags = {
|
|
12
|
+
behavior: Flags.string({
|
|
13
|
+
description: 'behavior of the document',
|
|
14
|
+
char: 'b',
|
|
15
|
+
options: ['default', 'interface', 'unique'],
|
|
16
|
+
default: 'default',
|
|
17
|
+
}),
|
|
18
|
+
cluster: Flags.boolean({
|
|
19
|
+
description: 'create a cluster model instead of a document model',
|
|
20
|
+
char: 'c',
|
|
21
|
+
}),
|
|
22
|
+
output: Flags.file({
|
|
23
|
+
description: 'output file',
|
|
24
|
+
char: 'o',
|
|
25
|
+
}),
|
|
26
|
+
uniqueField: Flags.string({
|
|
27
|
+
description: 'unique field of the document when behavior is set to "unique"',
|
|
28
|
+
char: 'u',
|
|
29
|
+
multiple: true,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const spinner = ora('Creating document model...').start();
|
|
34
|
+
const { args, flags } = await this.parse(CreateDocumentModel);
|
|
35
|
+
const behavior = flags.behavior;
|
|
36
|
+
const model = {
|
|
37
|
+
name: args.name,
|
|
38
|
+
behavior,
|
|
39
|
+
schema: JSON.parse(args.schema),
|
|
40
|
+
};
|
|
41
|
+
if (behavior === 'unique') {
|
|
42
|
+
model.uniqueFields = flags.uniqueField;
|
|
43
|
+
}
|
|
44
|
+
const builder = new ClusterBuilder();
|
|
45
|
+
builder.add(model);
|
|
46
|
+
if (flags.cluster) {
|
|
47
|
+
const cluster = builder.build();
|
|
48
|
+
if (flags.output == null) {
|
|
49
|
+
spinner.succeed('Cluster model created');
|
|
50
|
+
this.log(JSON.stringify(cluster));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await writeJSON(flags.output, cluster);
|
|
54
|
+
spinner.succeed(`Cluster model written to ${flags.output}`);
|
|
55
|
+
}
|
|
56
|
+
return cluster;
|
|
57
|
+
}
|
|
58
|
+
const document = builder.cluster[0];
|
|
59
|
+
if (flags.output == null) {
|
|
60
|
+
spinner.succeed('Document model created');
|
|
61
|
+
this.log(JSON.stringify(document));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await writeJSON(flags.output, document);
|
|
65
|
+
spinner.succeed(`Document model written to ${flags.output}`);
|
|
66
|
+
}
|
|
67
|
+
return document;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defer } from '@enkaku/async';
|
|
2
|
+
import { toB64 } from '@enkaku/codec';
|
|
3
|
+
import { ServerTransport } from '@enkaku/http-server-transport';
|
|
4
|
+
import { serve } from '@enkaku/server';
|
|
5
|
+
import { randomSigner, toTokenSigner } from '@enkaku/token';
|
|
6
|
+
import { serve as serveHTTP } from '@hono/node-server';
|
|
7
|
+
import { KubunDB } from '@kubun/db';
|
|
8
|
+
import { PostgresAdapter } from '@kubun/db-postgres';
|
|
9
|
+
import { SQLiteAdapter } from '@kubun/db-sqlite';
|
|
10
|
+
import { createHandlers } from '@kubun/server';
|
|
11
|
+
import { Command, Flags } from '@oclif/core';
|
|
12
|
+
import getPort from 'get-port';
|
|
13
|
+
import ora from 'ora';
|
|
14
|
+
import { getSigner } from '../account.js';
|
|
15
|
+
import { resolvePath } from '../fs.js';
|
|
16
|
+
export default class Serve extends Command {
|
|
17
|
+
static description = 'Start a local Kubun server';
|
|
18
|
+
static flags = {
|
|
19
|
+
db: Flags.file({ description: 'path to the local SQLite database' }),
|
|
20
|
+
id: Flags.string({ description: 'server ID' }),
|
|
21
|
+
port: Flags.integer({ char: 'p', description: 'port to listen on' }),
|
|
22
|
+
privateKey: Flags.string({
|
|
23
|
+
char: 'k',
|
|
24
|
+
description: 'base64-encoded private key',
|
|
25
|
+
env: 'KUBUN_PRIVATE_KEY',
|
|
26
|
+
exclusive: ['id'],
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
let signer;
|
|
31
|
+
const spinner = ora();
|
|
32
|
+
const { flags } = await this.parse(Serve);
|
|
33
|
+
let id = flags.id;
|
|
34
|
+
if (id == null) {
|
|
35
|
+
if (flags.privateKey == null) {
|
|
36
|
+
spinner.info('Generating a random private key for the server').start();
|
|
37
|
+
const ownSigner = randomSigner();
|
|
38
|
+
spinner.warn(`Generated private key: ${toB64(ownSigner.privateKey)}`);
|
|
39
|
+
signer = toTokenSigner(ownSigner);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
spinner.info('Using the provided private key for the server').start();
|
|
43
|
+
signer = getSigner(flags.privateKey);
|
|
44
|
+
}
|
|
45
|
+
id = signer.id;
|
|
46
|
+
}
|
|
47
|
+
spinner.info(`Server DID: ${id}`);
|
|
48
|
+
let db;
|
|
49
|
+
if (flags.db == null) {
|
|
50
|
+
db = new KubunDB({ adapter: new SQLiteAdapter({ database: ':memory:' }) });
|
|
51
|
+
spinner.info('Using ephemeral (in memory) SQLite database');
|
|
52
|
+
}
|
|
53
|
+
else if (flags.db.startsWith('postgres://')) {
|
|
54
|
+
db = new KubunDB({ adapter: new PostgresAdapter({ url: flags.db }) });
|
|
55
|
+
spinner.info(`Using PostgreSQL database: ${flags.db}`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const database = resolvePath(flags.db);
|
|
59
|
+
db = new KubunDB({ adapter: new SQLiteAdapter({ database }) });
|
|
60
|
+
spinner.info(`Using local SQLite database: ${database}`);
|
|
61
|
+
}
|
|
62
|
+
const transport = new ServerTransport();
|
|
63
|
+
serve({ handlers: createHandlers({ db }), id, transport });
|
|
64
|
+
spinner.start('Starting the server');
|
|
65
|
+
const port = flags.port ?? (await getPort({ port: 4321 }));
|
|
66
|
+
const httpServer = serveHTTP({ fetch: transport.fetch, port }, (info) => {
|
|
67
|
+
spinner.succeed(`HTTP server listening on port ${info.port}`);
|
|
68
|
+
});
|
|
69
|
+
const stopped = defer();
|
|
70
|
+
async function stop() {
|
|
71
|
+
httpServer.close();
|
|
72
|
+
stopped.resolve();
|
|
73
|
+
}
|
|
74
|
+
process.on('SIGINT', stop);
|
|
75
|
+
process.on('SIGTERM', stop);
|
|
76
|
+
return stopped.promise;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/fs.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFile as read, writeFile as write } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
export function resolvePath(value) {
|
|
4
|
+
return isAbsolute(value) ? value : resolve(process.cwd(), value);
|
|
5
|
+
}
|
|
6
|
+
export async function readJSON(path) {
|
|
7
|
+
return JSON.parse(await read(resolvePath(path), { encoding: 'utf8' }));
|
|
8
|
+
}
|
|
9
|
+
export async function writeFile(path, value) {
|
|
10
|
+
await write(resolvePath(path), value, { encoding: 'utf8' });
|
|
11
|
+
}
|
|
12
|
+
export async function writeJSON(path, value, format = false) {
|
|
13
|
+
await writeFile(path, format ? JSON.stringify(value, null, 2) : JSON.stringify(value));
|
|
14
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|