create-harper 0.0.4 → 0.0.5
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/index.js +21 -20
- package/lib/constants/defaultEnv.js +3 -0
- package/lib/constants/exampleFiles.js +22 -0
- package/lib/constants/frameworks.js +1 -13
- package/lib/constants/templates.js +2 -2
- package/lib/fs/crawlTemplateDir.js +9 -3
- package/lib/steps/getEnvVars.js +57 -6
- package/lib/steps/getExamples.js +48 -0
- package/lib/steps/getTemplate.js +4 -4
- package/lib/steps/helpAgents.js +15 -0
- package/lib/steps/scaffoldProject.js +3 -2
- package/package.json +1 -2
- package/template-react/config.yaml +2 -2
- package/template-react/resources/exampleSocket.js +34 -0
- package/template-react/resources/exampleTable.js +14 -0
- package/template-react/resources/greeting.js +10 -0
- package/{template-studio/schema.graphql → template-react/schemas/exampleTable.graphql} +1 -1
- package/template-react-ts/config.yaml +2 -2
- package/template-react-ts/resources/exampleSocket.ts +41 -0
- package/template-react-ts/resources/exampleTable.ts +20 -0
- package/template-react-ts/{resources.ts → resources/greeting.ts} +0 -23
- package/{template-vanilla/schema.graphql → template-react-ts/schemas/exampleTable.graphql} +1 -1
- package/template-studio/config.yaml +2 -2
- package/template-studio/resources/exampleSocket.js +34 -0
- package/template-studio/resources/exampleTable.js +14 -0
- package/template-studio/resources/greeting.js +10 -0
- package/{template-vanilla-ts/schema.graphql → template-studio/schemas/exampleTable.graphql} +1 -1
- package/template-studio-ts/config.yaml +2 -2
- package/template-studio-ts/resources/exampleSocket.ts +41 -0
- package/template-studio-ts/resources/exampleTable.ts +20 -0
- package/{template-vanilla-ts/resources.ts → template-studio-ts/resources/greeting.ts} +0 -23
- package/template-studio-ts/{schema.graphql → schemas/exampleTable.graphql} +1 -1
- package/template-vanilla/README.md +1 -1
- package/template-vanilla/config.yaml +2 -2
- package/template-vanilla/resources/exampleSocket.js +34 -0
- package/template-vanilla/resources/exampleTable.js +14 -0
- package/template-vanilla/resources/greeting.js +10 -0
- package/template-vanilla/schemas/exampleTable.graphql +7 -0
- package/template-vanilla-ts/README.md +2 -2
- package/template-vanilla-ts/config.yaml +2 -2
- package/template-vanilla-ts/resources/exampleSocket.ts +41 -0
- package/template-vanilla-ts/resources/exampleTable.ts +20 -0
- package/{template-studio-ts/resources.ts → template-vanilla-ts/resources/greeting.ts} +0 -23
- package/template-vanilla-ts/schemas/exampleTable.graphql +7 -0
- package/template-barebones/README.md +0 -7
- package/template-barebones/_aiignore +0 -1
- package/template-barebones/_env +0 -3
- package/template-barebones/_env.example +0 -3
- package/template-barebones/_gitignore +0 -147
- package/template-barebones/config.yaml +0 -7
- package/template-barebones/graphql.config.yml +0 -3
- package/template-barebones/package.json +0 -14
- package/template-barebones/schema.graphql +0 -1
- package/template-react/resources.js +0 -27
- package/template-react/schema.graphql +0 -5
- package/template-react-ts/schema.graphql +0 -5
- package/template-studio/resources.js +0 -27
- package/template-vanilla/resources.js +0 -27
package/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as prompts from '@clack/prompts';
|
|
3
|
-
import { determineAgent } from '@vercel/detect-agent';
|
|
4
3
|
import mri from 'mri';
|
|
5
4
|
import { helpMessage } from './lib/constants/helpMessage.js';
|
|
6
5
|
import { formatTargetDir } from './lib/fs/formatTargetDir.js';
|
|
7
6
|
import { pkgFromUserAgent } from './lib/pkg/pkgFromUserAgent.js';
|
|
8
7
|
import { getEnvVars } from './lib/steps/getEnvVars.js';
|
|
8
|
+
import { getExamples } from './lib/steps/getExamples.js';
|
|
9
9
|
import { getImmediate } from './lib/steps/getImmediate.js';
|
|
10
10
|
import { getPackageName } from './lib/steps/getPackageName.js';
|
|
11
11
|
import { getProjectName } from './lib/steps/getProjectName.js';
|
|
12
12
|
import { getTemplate } from './lib/steps/getTemplate.js';
|
|
13
13
|
import { handleExistingDir } from './lib/steps/handleExistingDir.js';
|
|
14
|
+
import { helpAgents } from './lib/steps/helpAgents.js';
|
|
14
15
|
import { scaffoldProject } from './lib/steps/scaffoldProject.js';
|
|
15
16
|
import { showOutro } from './lib/steps/showOutro.js';
|
|
16
17
|
|
|
@@ -40,49 +41,49 @@ async function init() {
|
|
|
40
41
|
const interactive = argInteractive ?? process.stdin.isTTY;
|
|
41
42
|
|
|
42
43
|
// Detect AI agent environment for better agent experience (AX)
|
|
43
|
-
|
|
44
|
-
if (isAgent && interactive) {
|
|
45
|
-
console.log(
|
|
46
|
-
'\nTo create in one go, run: create-harper <DIRECTORY> --no-interactive --template <TEMPLATE>\n',
|
|
47
|
-
);
|
|
48
|
-
}
|
|
44
|
+
await helpAgents(interactive);
|
|
49
45
|
|
|
50
46
|
const cancel = () => prompts.cancel('Operation cancelled');
|
|
51
47
|
|
|
52
|
-
//
|
|
48
|
+
// Get the project name and target directory
|
|
53
49
|
const projectNameResult = await getProjectName(argTargetDir, interactive);
|
|
54
50
|
if (projectNameResult.cancelled) { return cancel(); }
|
|
55
51
|
const { projectName, targetDir } = projectNameResult;
|
|
56
52
|
|
|
57
|
-
//
|
|
53
|
+
// Handle if the directory exists and isn't empty
|
|
58
54
|
const handleExistingDirResult = await handleExistingDir(targetDir, argOverwrite, interactive);
|
|
59
55
|
if (handleExistingDirResult.cancelled) { return cancel(); }
|
|
60
56
|
|
|
61
|
-
//
|
|
57
|
+
// Get the package name
|
|
62
58
|
const packageNameResult = await getPackageName(targetDir, interactive);
|
|
63
59
|
if (packageNameResult.cancelled) { return cancel(); }
|
|
64
60
|
const { packageName } = packageNameResult;
|
|
65
61
|
|
|
66
|
-
//
|
|
62
|
+
// Choose a framework and variant
|
|
67
63
|
const templateResult = await getTemplate(argTemplate, interactive);
|
|
68
64
|
if (templateResult.cancelled) { return cancel(); }
|
|
69
65
|
const { template } = templateResult;
|
|
70
66
|
|
|
71
|
-
//
|
|
67
|
+
// Choose which examples to include
|
|
68
|
+
const examplesResult = await getExamples(template, interactive);
|
|
69
|
+
if (examplesResult.cancelled) { return cancel(); }
|
|
70
|
+
const { excludedFiles } = examplesResult;
|
|
71
|
+
|
|
72
|
+
// Get environment variables for .env file
|
|
73
|
+
const envVarsResult = await getEnvVars(argv, interactive, template);
|
|
74
|
+
if (envVarsResult.cancelled) { return cancel(); }
|
|
75
|
+
const { envVars } = envVarsResult;
|
|
76
|
+
|
|
77
|
+
// Should we do a package manager installation?
|
|
72
78
|
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
|
|
73
79
|
const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
|
|
74
80
|
const immediateResult = await getImmediate(argImmediate, interactive, pkgManager);
|
|
75
81
|
if (immediateResult.cancelled) { return cancel(); }
|
|
76
82
|
const { immediate } = immediateResult;
|
|
77
83
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
if (envVarsResult.cancelled) { return cancel(); }
|
|
81
|
-
const { envVars } = envVarsResult;
|
|
82
|
-
|
|
83
|
-
// 7. Write out the contents based on all prior steps.
|
|
84
|
-
const root = scaffoldProject(targetDir, projectName, packageName, template, envVars);
|
|
84
|
+
// Write out the contents based on all prior steps.
|
|
85
|
+
const root = scaffoldProject(targetDir, projectName, packageName, template, envVars, excludedFiles);
|
|
85
86
|
|
|
86
|
-
//
|
|
87
|
+
// Log out the next steps.
|
|
87
88
|
showOutro(root, pkgManager, immediate);
|
|
88
89
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const exampleFiles = [
|
|
2
|
+
{
|
|
3
|
+
label: 'Table schema',
|
|
4
|
+
value: 'tableSchema',
|
|
5
|
+
files: ['schemas/exampleTable.graphql'],
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
label: 'Table handling',
|
|
9
|
+
value: 'table',
|
|
10
|
+
files: ['resources/exampleTable.ts', 'resources/exampleTable.js'],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Resource',
|
|
14
|
+
value: 'resource',
|
|
15
|
+
files: ['resources/greeting.ts', 'resources/greeting.js'],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: 'Sockets',
|
|
19
|
+
value: 'socket',
|
|
20
|
+
files: ['resources/exampleSocket.ts', 'resources/exampleSocket.js'],
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
yellow,
|
|
8
8
|
} = colors;
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const frameworks = [
|
|
11
11
|
{
|
|
12
12
|
name: 'vanilla',
|
|
13
13
|
display: 'Vanilla',
|
|
@@ -60,16 +60,4 @@ export const FRAMEWORKS = [
|
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
},
|
|
63
|
-
{
|
|
64
|
-
name: 'barebones',
|
|
65
|
-
display: 'Barebones',
|
|
66
|
-
color: gray,
|
|
67
|
-
variants: [
|
|
68
|
-
{
|
|
69
|
-
name: 'barebones',
|
|
70
|
-
display: 'Barebones',
|
|
71
|
-
color: gray,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
63
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { frameworks } from './frameworks.js';
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const templates = frameworks.map((f) => f.variants.map((v) => v.name)).reduce(
|
|
4
4
|
(a, b) => a.concat(b),
|
|
5
5
|
[],
|
|
6
6
|
);
|
|
@@ -3,14 +3,20 @@ import path from 'node:path';
|
|
|
3
3
|
import { renameFiles } from '../constants/renameFiles.js';
|
|
4
4
|
import { applyAndWriteTemplateFile } from './applyAndWriteTemplateFile.js';
|
|
5
5
|
|
|
6
|
-
export function crawlTemplateDir(root, dir, substitutions) {
|
|
6
|
+
export function crawlTemplateDir(root, dir, substitutions, excludedFiles = [], templateRootDir = dir) {
|
|
7
7
|
const files = fs.readdirSync(dir);
|
|
8
8
|
for (const file of files) {
|
|
9
|
-
const targetPath = path.join(root, renameFiles[file] ?? file);
|
|
10
9
|
const templatePath = path.join(dir, file);
|
|
10
|
+
const relativePath = path.relative(templateRootDir, templatePath).split(path.sep).join('/');
|
|
11
|
+
|
|
12
|
+
if (excludedFiles.includes(relativePath)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const targetPath = path.join(root, renameFiles[file] ?? file);
|
|
11
17
|
if (fs.lstatSync(templatePath).isDirectory()) {
|
|
12
18
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
13
|
-
crawlTemplateDir(targetPath, templatePath, substitutions);
|
|
19
|
+
crawlTemplateDir(targetPath, templatePath, substitutions, excludedFiles, templateRootDir);
|
|
14
20
|
} else {
|
|
15
21
|
applyAndWriteTemplateFile(targetPath, templatePath, substitutions);
|
|
16
22
|
}
|
package/lib/steps/getEnvVars.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import colors from 'picocolors';
|
|
6
|
+
import { defaultPassword, defaultTarget, defaultUsername } from '../constants/defaultEnv.js';
|
|
6
7
|
|
|
7
8
|
const {
|
|
8
9
|
gray,
|
|
@@ -43,7 +44,7 @@ export async function getEnvVars(argv, interactive, template) {
|
|
|
43
44
|
if (!username) {
|
|
44
45
|
const usernameResult = await prompts.text({
|
|
45
46
|
message: prefix + 'Username:',
|
|
46
|
-
placeholder:
|
|
47
|
+
placeholder: defaultUsername,
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
if (prompts.isCancel(usernameResult)) {
|
|
@@ -52,7 +53,7 @@ export async function getEnvVars(argv, interactive, template) {
|
|
|
52
53
|
username = usernameResult;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const passwordResult = await prompts.password({
|
|
56
|
+
const passwordResult = username && await prompts.password({
|
|
56
57
|
message: prefix + 'Password:',
|
|
57
58
|
});
|
|
58
59
|
|
|
@@ -61,7 +62,7 @@ export async function getEnvVars(argv, interactive, template) {
|
|
|
61
62
|
}
|
|
62
63
|
password = passwordResult;
|
|
63
64
|
|
|
64
|
-
if (!target) {
|
|
65
|
+
if (!target && username) {
|
|
65
66
|
const targetResult = await prompts.text({
|
|
66
67
|
message: prefix + 'URL:',
|
|
67
68
|
placeholder: 'YOUR_CLUSTER_URL_HERE',
|
|
@@ -76,11 +77,61 @@ export async function getEnvVars(argv, interactive, template) {
|
|
|
76
77
|
prompts.log.warn('Non-interactive mode: Please update your .env to add your CLI_TARGET_PASSWORD on your own.');
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
if (target) {
|
|
81
|
+
if (!target.startsWith('http://') && !target.startsWith('https://')) {
|
|
82
|
+
target = 'https://' + target;
|
|
83
|
+
}
|
|
84
|
+
if (!target.endsWith('/')) {
|
|
85
|
+
target = target + '/';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!process.env._HARPER_TEST_CLI && target !== defaultTarget) {
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(target);
|
|
91
|
+
const base = `${url.protocol}//${url.hostname}:${(url.port || '9925')}`;
|
|
92
|
+
|
|
93
|
+
const healthResponse = await fetch(`${base}/health`);
|
|
94
|
+
if (healthResponse.ok) {
|
|
95
|
+
if (
|
|
96
|
+
username && username !== defaultUsername && password && password !== defaultPassword
|
|
97
|
+
) {
|
|
98
|
+
const authResponse = await fetch(`${base}/`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
operation: 'user_info',
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (authResponse.ok) {
|
|
110
|
+
prompts.log.success('Successfully authenticated with your cluster!');
|
|
111
|
+
} else {
|
|
112
|
+
prompts.log.error(
|
|
113
|
+
'Failed to authenticate with your cluster. Please check your credentials in the .env file.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
prompts.log.error(
|
|
119
|
+
'Could not reach your cluster health endpoint. Please check your credentials in the .env file.',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
prompts.log.error(
|
|
124
|
+
'An error occurred while connecting to your cluster. Please check your credentials in the .env file.',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
79
130
|
return {
|
|
80
131
|
envVars: {
|
|
81
|
-
username: username ||
|
|
82
|
-
target: target ||
|
|
83
|
-
password: password ||
|
|
132
|
+
username: username || defaultUsername,
|
|
133
|
+
target: target || defaultTarget,
|
|
134
|
+
password: password || defaultPassword,
|
|
84
135
|
},
|
|
85
136
|
cancelled: false,
|
|
86
137
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as prompts from '@clack/prompts';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { exampleFiles } from '../constants/exampleFiles.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Step 5: Choose which examples to include
|
|
9
|
+
* @param {string} template
|
|
10
|
+
* @param {boolean} interactive
|
|
11
|
+
* @returns {Promise<{excludedFiles: string[], cancelled: boolean}>}
|
|
12
|
+
*/
|
|
13
|
+
export async function getExamples(template, interactive) {
|
|
14
|
+
const templateDir = path.resolve(
|
|
15
|
+
fileURLToPath(import.meta.url),
|
|
16
|
+
'..',
|
|
17
|
+
'..',
|
|
18
|
+
'..',
|
|
19
|
+
`template-${template}`,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const availableExamples = exampleFiles.filter((example) =>
|
|
23
|
+
example.files.some((file) => fs.existsSync(path.join(templateDir, file)))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (availableExamples.length === 0 || !interactive) {
|
|
27
|
+
return { excludedFiles: [], cancelled: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const selectedExamples = await prompts.multiselect({
|
|
31
|
+
message: 'Select examples to include:',
|
|
32
|
+
options: availableExamples.map((example) => ({
|
|
33
|
+
label: example.label,
|
|
34
|
+
value: example.value,
|
|
35
|
+
})),
|
|
36
|
+
initialValues: availableExamples.map((example) => example.value),
|
|
37
|
+
required: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (prompts.isCancel(selectedExamples)) {
|
|
41
|
+
return { excludedFiles: [], cancelled: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const unselectedExamples = availableExamples.filter((example) => !selectedExamples.includes(example.value));
|
|
45
|
+
const excludedFiles = unselectedExamples.flatMap((example) => example.files);
|
|
46
|
+
|
|
47
|
+
return { excludedFiles, cancelled: false };
|
|
48
|
+
}
|
package/lib/steps/getTemplate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as prompts from '@clack/prompts';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { frameworks } from '../constants/frameworks.js';
|
|
3
|
+
import { templates } from '../constants/templates.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Step 4: Choose a framework and variant
|
|
@@ -12,7 +12,7 @@ export async function getTemplate(argTemplate, interactive) {
|
|
|
12
12
|
let template = argTemplate;
|
|
13
13
|
let hasInvalidArgTemplate = false;
|
|
14
14
|
|
|
15
|
-
if (argTemplate && !
|
|
15
|
+
if (argTemplate && !templates.includes(argTemplate)) {
|
|
16
16
|
template = undefined;
|
|
17
17
|
hasInvalidArgTemplate = true;
|
|
18
18
|
}
|
|
@@ -23,7 +23,7 @@ export async function getTemplate(argTemplate, interactive) {
|
|
|
23
23
|
message: hasInvalidArgTemplate
|
|
24
24
|
? `"${argTemplate}" isn't a valid template. Please choose from below: `
|
|
25
25
|
: 'Select a framework:',
|
|
26
|
-
options:
|
|
26
|
+
options: frameworks
|
|
27
27
|
.filter(framework => !framework.hidden)
|
|
28
28
|
.map((framework) => {
|
|
29
29
|
const frameworkColor = framework.color;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { determineAgent } from '@vercel/detect-agent';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logs out a helpful message if running in an interactive environment for agents to be more likely to use the
|
|
5
|
+
* parameters correctly.
|
|
6
|
+
* @param {boolean} interactive
|
|
7
|
+
*/
|
|
8
|
+
export async function helpAgents(interactive) {
|
|
9
|
+
const { isAgent } = await determineAgent();
|
|
10
|
+
if (isAgent && interactive) {
|
|
11
|
+
console.log(
|
|
12
|
+
'\nTo create in one go, run: create-harper <DIRECTORY> --no-interactive --template <TEMPLATE>\n',
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -11,9 +11,10 @@ import { crawlTemplateDir } from '../fs/crawlTemplateDir.js';
|
|
|
11
11
|
* @param {string} packageName
|
|
12
12
|
* @param {string} template
|
|
13
13
|
* @param {{username: string, target: string, password?: string}} envVars
|
|
14
|
+
* @param {string[]} excludedFiles
|
|
14
15
|
* @returns {string} The root directory of the project
|
|
15
16
|
*/
|
|
16
|
-
export function scaffoldProject(targetDir, projectName, packageName, template, envVars) {
|
|
17
|
+
export function scaffoldProject(targetDir, projectName, packageName, template, envVars, excludedFiles = []) {
|
|
17
18
|
const cwd = process.cwd();
|
|
18
19
|
const root = path.join(cwd, targetDir);
|
|
19
20
|
fs.mkdirSync(root, { recursive: true });
|
|
@@ -34,7 +35,7 @@ export function scaffoldProject(targetDir, projectName, packageName, template, e
|
|
|
34
35
|
'..',
|
|
35
36
|
`template-${template}`,
|
|
36
37
|
);
|
|
37
|
-
crawlTemplateDir(root, templateDir, substitutions);
|
|
38
|
+
crawlTemplateDir(root, templateDir, substitutions, excludedFiles);
|
|
38
39
|
|
|
39
40
|
return root;
|
|
40
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-harper",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HarperDB",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"index.js",
|
|
15
15
|
"lib/",
|
|
16
16
|
"!lib/**/*.test.js",
|
|
17
|
-
"template-barebones/",
|
|
18
17
|
"template-react/",
|
|
19
18
|
"template-react-ts/",
|
|
20
19
|
"template-studio/",
|
|
@@ -13,11 +13,11 @@ rest: true
|
|
|
13
13
|
|
|
14
14
|
# These reads GraphQL schemas to define the schema of database/tables/attributes.
|
|
15
15
|
graphqlSchema:
|
|
16
|
-
files: '
|
|
16
|
+
files: 'schemas/*.graphql'
|
|
17
17
|
|
|
18
18
|
# Loads JavaScript modules such that their exports are exported as resources
|
|
19
19
|
jsResource:
|
|
20
|
-
files: 'resources
|
|
20
|
+
files: 'resources/*.js'
|
|
21
21
|
|
|
22
22
|
'@harperfast/vite-plugin':
|
|
23
23
|
package: '@harperfast/vite-plugin'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Resource, tables } from 'harperdb';
|
|
2
|
+
|
|
3
|
+
export class ExampleSocket extends Resource {
|
|
4
|
+
static loadAsInstance = false;
|
|
5
|
+
|
|
6
|
+
// This customizes handling the socket connections; tables can have this method too!
|
|
7
|
+
async *connect(
|
|
8
|
+
target,
|
|
9
|
+
incomingMessages,
|
|
10
|
+
) {
|
|
11
|
+
const subscription = await tables.ExampleTable.subscribe(target);
|
|
12
|
+
if (!incomingMessages) {
|
|
13
|
+
// Server sent events, no incoming messages!
|
|
14
|
+
// Subscribe to changes to the table.
|
|
15
|
+
return subscription;
|
|
16
|
+
}
|
|
17
|
+
for await (let message of incomingMessages) {
|
|
18
|
+
const { type, id, name, tag } = message;
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'get':
|
|
21
|
+
const loaded = await tables.ExampleTable.get(id);
|
|
22
|
+
yield {
|
|
23
|
+
type: 'get',
|
|
24
|
+
id,
|
|
25
|
+
...(loaded ? loaded : {}),
|
|
26
|
+
};
|
|
27
|
+
break;
|
|
28
|
+
case 'put':
|
|
29
|
+
await tables.ExampleTable.put(id, { name, tag });
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { tables } from 'harperdb';
|
|
2
|
+
|
|
3
|
+
export class ExampleTable extends tables.ExampleTable {
|
|
4
|
+
// we can define our own custom POST handler
|
|
5
|
+
post(content) {
|
|
6
|
+
// do something with the incoming content;
|
|
7
|
+
return super.post(content);
|
|
8
|
+
}
|
|
9
|
+
// or custom GET handler
|
|
10
|
+
get() {
|
|
11
|
+
// we can modify this resource before returning
|
|
12
|
+
return super.get();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Here we can define any tables in our database. This example shows how we define a type as a table using
|
|
2
2
|
## the type name as the table name and specifying it is an "export" available in the REST and other external protocols.
|
|
3
|
-
type
|
|
3
|
+
type ExampleTable @table @export {
|
|
4
4
|
id: ID @primaryKey # Here we define primary key (must be one)
|
|
5
5
|
name: String # we can define any other attributes here
|
|
6
6
|
tag: String @indexed # we can specify any attributes that should be indexed
|
|
@@ -13,11 +13,11 @@ rest: true
|
|
|
13
13
|
|
|
14
14
|
# These reads GraphQL schemas to define the schema of database/tables/attributes.
|
|
15
15
|
graphqlSchema:
|
|
16
|
-
files: '
|
|
16
|
+
files: 'schemas/*.graphql'
|
|
17
17
|
|
|
18
18
|
# Loads JavaScript modules such that their exports are exported as resources
|
|
19
19
|
jsResource:
|
|
20
|
-
files: 'resources
|
|
20
|
+
files: 'resources/*.ts'
|
|
21
21
|
|
|
22
22
|
'@harperfast/vite-plugin':
|
|
23
23
|
package: '@harperfast/vite-plugin'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type IterableEventQueue, RequestTarget, Resource, tables, type User } from 'harperdb';
|
|
2
|
+
|
|
3
|
+
interface ExampleSocketRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
type?: 'get' | 'put';
|
|
6
|
+
name: string;
|
|
7
|
+
tag: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ExampleSocket extends Resource<ExampleSocketRecord> {
|
|
11
|
+
static loadAsInstance = false;
|
|
12
|
+
|
|
13
|
+
// This customizes handling the socket connections; tables can have this method too!
|
|
14
|
+
async *connect(
|
|
15
|
+
target: RequestTarget,
|
|
16
|
+
incomingMessages: IterableEventQueue<ExampleSocketRecord>,
|
|
17
|
+
): AsyncIterable<ExampleSocketRecord> {
|
|
18
|
+
const subscription = await tables.ExampleTable.subscribe(target);
|
|
19
|
+
if (!incomingMessages) {
|
|
20
|
+
// Server sent events, no incoming messages!
|
|
21
|
+
// Subscribe to changes to the table.
|
|
22
|
+
return subscription;
|
|
23
|
+
}
|
|
24
|
+
for await (let message of incomingMessages) {
|
|
25
|
+
const { type, id, name, tag } = message;
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'get':
|
|
28
|
+
const loaded = await tables.ExampleTable.get(id);
|
|
29
|
+
yield {
|
|
30
|
+
type: 'get',
|
|
31
|
+
id,
|
|
32
|
+
...(loaded ? loaded : {}),
|
|
33
|
+
};
|
|
34
|
+
break;
|
|
35
|
+
case 'put':
|
|
36
|
+
await tables.ExampleTable.put(id, { name, tag });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RequestTargetOrId, tables } from 'harperdb';
|
|
2
|
+
|
|
3
|
+
export interface TableNameRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
tag: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ExampleTable extends tables.ExampleTable<TableNameRecord> {
|
|
10
|
+
// we can define our own custom POST handler
|
|
11
|
+
async post(target: RequestTargetOrId, newRecord: Omit<TableNameRecord, 'id'>) {
|
|
12
|
+
// do something with the incoming content;
|
|
13
|
+
return super.post(target, newRecord);
|
|
14
|
+
}
|
|
15
|
+
// or custom GET handler
|
|
16
|
+
async get(target: RequestTargetOrId): Promise<TableNameRecord> {
|
|
17
|
+
// we can modify this resource before returning
|
|
18
|
+
return super.get(target);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
import { type RecordObject, type RequestTargetOrId, Resource } from 'harperdb';
|
|
2
2
|
|
|
3
|
-
/** Here we can define any JavaScript-based resources and extensions to tables
|
|
4
|
-
import { tables, type RequestTarget } from 'harperdb';
|
|
5
|
-
|
|
6
|
-
interface TableNameRecord {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
tag: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class TableName extends tables.TableName<TableNameRecord> {
|
|
13
|
-
// we can define our own custom POST handler
|
|
14
|
-
async post(target: RequestTargetOrId, newRecord: Omit<TableNameRecord, 'id'>) {
|
|
15
|
-
// do something with the incoming content;
|
|
16
|
-
return super.post(target, newRecord);
|
|
17
|
-
}
|
|
18
|
-
// or custom GET handler
|
|
19
|
-
async get(target: RequestTarget): Promise<TableNameRecord> {
|
|
20
|
-
// we can modify this resource before returning
|
|
21
|
-
return super.get(target);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
3
|
interface GreetingRecord {
|
|
27
4
|
greeting: string;
|
|
28
5
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Here we can define any tables in our database. This example shows how we define a type as a table using
|
|
2
2
|
## the type name as the table name and specifying it is an "export" available in the REST and other external protocols.
|
|
3
|
-
type
|
|
3
|
+
type ExampleTable @table @export {
|
|
4
4
|
id: ID @primaryKey # Here we define primary key (must be one)
|
|
5
5
|
name: String # we can define any other attributes here
|
|
6
6
|
tag: String @indexed # we can specify any attributes that should be indexed
|
|
@@ -13,11 +13,11 @@ rest: true
|
|
|
13
13
|
|
|
14
14
|
# These reads GraphQL schemas to define the schema of database/tables/attributes.
|
|
15
15
|
graphqlSchema:
|
|
16
|
-
files: '
|
|
16
|
+
files: 'schemas/*.graphql'
|
|
17
17
|
|
|
18
18
|
# Loads JavaScript modules such that their exports are exported as resources
|
|
19
19
|
jsResource:
|
|
20
|
-
files: 'resources
|
|
20
|
+
files: 'resources/*.js'
|
|
21
21
|
|
|
22
22
|
# Serve the static files from the web directory as a web application
|
|
23
23
|
static:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Resource, tables } from 'harperdb';
|
|
2
|
+
|
|
3
|
+
export class ExampleSocket extends Resource {
|
|
4
|
+
static loadAsInstance = false;
|
|
5
|
+
|
|
6
|
+
// This customizes handling the socket connections; tables can have this method too!
|
|
7
|
+
async *connect(
|
|
8
|
+
target,
|
|
9
|
+
incomingMessages,
|
|
10
|
+
) {
|
|
11
|
+
const subscription = await tables.ExampleTable.subscribe(target);
|
|
12
|
+
if (!incomingMessages) {
|
|
13
|
+
// Server sent events, no incoming messages!
|
|
14
|
+
// Subscribe to changes to the table.
|
|
15
|
+
return subscription;
|
|
16
|
+
}
|
|
17
|
+
for await (let message of incomingMessages) {
|
|
18
|
+
const { type, id, name, tag } = message;
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'get':
|
|
21
|
+
const loaded = await tables.ExampleTable.get(id);
|
|
22
|
+
yield {
|
|
23
|
+
type: 'get',
|
|
24
|
+
id,
|
|
25
|
+
...(loaded ? loaded : {}),
|
|
26
|
+
};
|
|
27
|
+
break;
|
|
28
|
+
case 'put':
|
|
29
|
+
await tables.ExampleTable.put(id, { name, tag });
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|