create-harper 0.0.4 → 0.0.6

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.
Files changed (62) hide show
  1. package/index.js +29 -20
  2. package/lib/constants/defaultEnv.js +3 -0
  3. package/lib/constants/exampleFiles.js +22 -0
  4. package/lib/constants/frameworks.js +1 -13
  5. package/lib/constants/helpMessage.js +1 -0
  6. package/lib/constants/templates.js +2 -2
  7. package/lib/fs/crawlTemplateDir.js +9 -3
  8. package/lib/pkg/getLatestVersion.js +14 -0
  9. package/lib/pkg/isVersionNewer.js +17 -0
  10. package/lib/steps/checkForUpdate.js +43 -0
  11. package/lib/steps/getEnvVars.js +57 -6
  12. package/lib/steps/getExamples.js +48 -0
  13. package/lib/steps/getTemplate.js +4 -4
  14. package/lib/steps/helpAgents.js +15 -0
  15. package/lib/steps/scaffoldProject.js +3 -2
  16. package/package.json +1 -2
  17. package/template-react/config.yaml +2 -2
  18. package/template-react/resources/exampleSocket.js +34 -0
  19. package/template-react/resources/exampleTable.js +14 -0
  20. package/template-react/resources/greeting.js +10 -0
  21. package/{template-studio/schema.graphql → template-react/schemas/exampleTable.graphql} +1 -1
  22. package/template-react-ts/config.yaml +2 -2
  23. package/template-react-ts/resources/exampleSocket.ts +41 -0
  24. package/template-react-ts/resources/exampleTable.ts +20 -0
  25. package/template-react-ts/{resources.ts → resources/greeting.ts} +0 -23
  26. package/{template-vanilla/schema.graphql → template-react-ts/schemas/exampleTable.graphql} +1 -1
  27. package/template-studio/config.yaml +2 -2
  28. package/template-studio/resources/exampleSocket.js +34 -0
  29. package/template-studio/resources/exampleTable.js +14 -0
  30. package/template-studio/resources/greeting.js +10 -0
  31. package/{template-vanilla-ts/schema.graphql → template-studio/schemas/exampleTable.graphql} +1 -1
  32. package/template-studio-ts/config.yaml +2 -2
  33. package/template-studio-ts/resources/exampleSocket.ts +41 -0
  34. package/template-studio-ts/resources/exampleTable.ts +20 -0
  35. package/{template-vanilla-ts/resources.ts → template-studio-ts/resources/greeting.ts} +0 -23
  36. package/template-studio-ts/{schema.graphql → schemas/exampleTable.graphql} +1 -1
  37. package/template-vanilla/README.md +1 -1
  38. package/template-vanilla/config.yaml +2 -2
  39. package/template-vanilla/resources/exampleSocket.js +34 -0
  40. package/template-vanilla/resources/exampleTable.js +14 -0
  41. package/template-vanilla/resources/greeting.js +10 -0
  42. package/template-vanilla/schemas/exampleTable.graphql +7 -0
  43. package/template-vanilla-ts/README.md +2 -2
  44. package/template-vanilla-ts/config.yaml +2 -2
  45. package/template-vanilla-ts/resources/exampleSocket.ts +41 -0
  46. package/template-vanilla-ts/resources/exampleTable.ts +20 -0
  47. package/{template-studio-ts/resources.ts → template-vanilla-ts/resources/greeting.ts} +0 -23
  48. package/template-vanilla-ts/schemas/exampleTable.graphql +7 -0
  49. package/template-barebones/README.md +0 -7
  50. package/template-barebones/_aiignore +0 -1
  51. package/template-barebones/_env +0 -3
  52. package/template-barebones/_env.example +0 -3
  53. package/template-barebones/_gitignore +0 -147
  54. package/template-barebones/config.yaml +0 -7
  55. package/template-barebones/graphql.config.yml +0 -3
  56. package/template-barebones/package.json +0 -14
  57. package/template-barebones/schema.graphql +0 -1
  58. package/template-react/resources.js +0 -27
  59. package/template-react/schema.graphql +0 -5
  60. package/template-react-ts/schema.graphql +0 -5
  61. package/template-studio/resources.js +0 -27
  62. package/template-vanilla/resources.js +0 -27
package/index.js CHANGED
@@ -1,16 +1,18 @@
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';
7
+ import { checkForUpdate } from './lib/steps/checkForUpdate.js';
8
8
  import { getEnvVars } from './lib/steps/getEnvVars.js';
9
+ import { getExamples } from './lib/steps/getExamples.js';
9
10
  import { getImmediate } from './lib/steps/getImmediate.js';
10
11
  import { getPackageName } from './lib/steps/getPackageName.js';
11
12
  import { getProjectName } from './lib/steps/getProjectName.js';
12
13
  import { getTemplate } from './lib/steps/getTemplate.js';
13
14
  import { handleExistingDir } from './lib/steps/handleExistingDir.js';
15
+ import { helpAgents } from './lib/steps/helpAgents.js';
14
16
  import { scaffoldProject } from './lib/steps/scaffoldProject.js';
15
17
  import { showOutro } from './lib/steps/showOutro.js';
16
18
 
@@ -37,52 +39,59 @@ async function init() {
37
39
  return;
38
40
  }
39
41
 
42
+ const currentVersion = await checkForUpdate();
43
+ const version = argv.version;
44
+ if (version) {
45
+ console.log(`Current version: ${currentVersion}`);
46
+ return;
47
+ }
48
+
40
49
  const interactive = argInteractive ?? process.stdin.isTTY;
41
50
 
42
51
  // Detect AI agent environment for better agent experience (AX)
43
- const { isAgent } = await determineAgent();
44
- if (isAgent && interactive) {
45
- console.log(
46
- '\nTo create in one go, run: create-harper <DIRECTORY> --no-interactive --template <TEMPLATE>\n',
47
- );
48
- }
52
+ await helpAgents(interactive);
49
53
 
50
54
  const cancel = () => prompts.cancel('Operation cancelled');
51
55
 
52
- // 1. Get the project name and target directory
56
+ // Get the project name and target directory
53
57
  const projectNameResult = await getProjectName(argTargetDir, interactive);
54
58
  if (projectNameResult.cancelled) { return cancel(); }
55
59
  const { projectName, targetDir } = projectNameResult;
56
60
 
57
- // 2. Handle if the directory exists and isn't empty
61
+ // Handle if the directory exists and isn't empty
58
62
  const handleExistingDirResult = await handleExistingDir(targetDir, argOverwrite, interactive);
59
63
  if (handleExistingDirResult.cancelled) { return cancel(); }
60
64
 
61
- // 3. Get the package name
65
+ // Get the package name
62
66
  const packageNameResult = await getPackageName(targetDir, interactive);
63
67
  if (packageNameResult.cancelled) { return cancel(); }
64
68
  const { packageName } = packageNameResult;
65
69
 
66
- // 4. Choose a framework and variant
70
+ // Choose a framework and variant
67
71
  const templateResult = await getTemplate(argTemplate, interactive);
68
72
  if (templateResult.cancelled) { return cancel(); }
69
73
  const { template } = templateResult;
70
74
 
71
- // 5. Should we do a package manager installation?
75
+ // Choose which examples to include
76
+ const examplesResult = await getExamples(template, interactive);
77
+ if (examplesResult.cancelled) { return cancel(); }
78
+ const { excludedFiles } = examplesResult;
79
+
80
+ // Get environment variables for .env file
81
+ const envVarsResult = await getEnvVars(argv, interactive, template);
82
+ if (envVarsResult.cancelled) { return cancel(); }
83
+ const { envVars } = envVarsResult;
84
+
85
+ // Should we do a package manager installation?
72
86
  const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
73
87
  const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
74
88
  const immediateResult = await getImmediate(argImmediate, interactive, pkgManager);
75
89
  if (immediateResult.cancelled) { return cancel(); }
76
90
  const { immediate } = immediateResult;
77
91
 
78
- // 6. Get environment variables for .env file
79
- const envVarsResult = await getEnvVars(argv, interactive, template);
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);
92
+ // Write out the contents based on all prior steps.
93
+ const root = scaffoldProject(targetDir, projectName, packageName, template, envVars, excludedFiles);
85
94
 
86
- // 8. Log out the next steps.
95
+ // Log out the next steps.
87
96
  showOutro(root, pkgManager, immediate);
88
97
  }
@@ -0,0 +1,3 @@
1
+ export const defaultUsername = 'YOUR_CLUSTER_USERNAME';
2
+ export const defaultPassword = 'YOUR_CLUSTER_PASSWORD';
3
+ export const defaultTarget = 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE';
@@ -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 FRAMEWORKS = [
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
  ];
@@ -16,6 +16,7 @@ Options:
16
16
  -t, --template NAME use a specific template
17
17
  -i, --immediate install dependencies and start dev
18
18
  --interactive / --no-interactive force interactive / non-interactive mode
19
+ --version print out the version of the create-harper templates
19
20
 
20
21
  Available templates:
21
22
  ${yellow('vanilla-ts vanilla')}
@@ -1,6 +1,6 @@
1
- import { FRAMEWORKS } from './frameworks.js';
1
+ import { frameworks } from './frameworks.js';
2
2
 
3
- export const TEMPLATES = FRAMEWORKS.map((f) => f.variants.map((v) => v.name)).reduce(
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
  }
@@ -0,0 +1,14 @@
1
+ export async function getLatestVersion(packageName) {
2
+ try {
3
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
4
+ signal: AbortSignal.timeout(1000), // 1 second timeout
5
+ });
6
+ if (!response.ok) {
7
+ return null;
8
+ }
9
+ const data = await response.json();
10
+ return data.version;
11
+ } catch (error) {
12
+ return null;
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Simple semver comparison for x.y.z
3
+ * @param {string} latest
4
+ * @param {string} current
5
+ * @returns {boolean}
6
+ */
7
+ export function isVersionNewer(latest, current) {
8
+ const l = latest.split('.').map(x => parseInt(x, 10));
9
+ const c = current.split('.').map(x => parseInt(x, 10));
10
+
11
+ for (let i = 0; i < 3; i++) {
12
+ if (isNaN(l[i]) || isNaN(c[i])) { break; }
13
+ if (l[i] > c[i]) { return true; }
14
+ if (l[i] < c[i]) { return false; }
15
+ }
16
+ return false;
17
+ }
@@ -0,0 +1,43 @@
1
+ import spawn from 'cross-spawn';
2
+ import fs from 'node:fs';
3
+ import pc from 'picocolors';
4
+ import { getLatestVersion } from '../pkg/getLatestVersion.js';
5
+ import { isVersionNewer } from '../pkg/isVersionNewer.js';
6
+
7
+ /**
8
+ * Checks if the current version of the package is the latest version on npm.
9
+ * If not, it tries to re-run the command using npx with the @latest tag.
10
+ */
11
+ export async function checkForUpdate() {
12
+ const pkg = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
13
+ const currentVersion = pkg.version;
14
+
15
+ if (process.env.CREATE_HARPER_SKIP_UPDATE) {
16
+ return currentVersion;
17
+ }
18
+
19
+ try {
20
+ const latestVersion = await getLatestVersion(pkg.name);
21
+
22
+ if (latestVersion && isVersionNewer(latestVersion, currentVersion)) {
23
+ console.log(
24
+ pc.yellow(
25
+ `\n A new version of ${pc.bold(pkg.name)} is available! (${pc.dim(currentVersion)} -> ${
26
+ pc.green(latestVersion)
27
+ })`,
28
+ ),
29
+ );
30
+ console.log(` Automatically updating to the latest version...\n`);
31
+
32
+ const result = spawn.sync('npx', [`${pkg.name}@latest`, ...process.argv.slice(2)], {
33
+ stdio: 'inherit',
34
+ });
35
+
36
+ process.exit(result.status ?? 0);
37
+ }
38
+ } catch {
39
+ // Ignore errors, we don't want to block the user if the check fails
40
+ }
41
+
42
+ return currentVersion;
43
+ }
@@ -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: 'YOUR_CLUSTER_USERNAME',
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 || 'YOUR_CLUSTER_USERNAME',
82
- target: target || 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE',
83
- password: password || 'YOUR_CLUSTER_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
+ }
@@ -1,6 +1,6 @@
1
1
  import * as prompts from '@clack/prompts';
2
- import { FRAMEWORKS } from '../constants/frameworks.js';
3
- import { TEMPLATES } from '../constants/templates.js';
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 && !TEMPLATES.includes(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: FRAMEWORKS
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.4",
3
+ "version": "0.0.6",
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: 'schema.graphql'
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.js'
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
+ }
@@ -0,0 +1,10 @@
1
+ import { Resource } from 'harperdb';
2
+
3
+ export class Greeting extends Resource {
4
+ // a "Hello, world!" handler
5
+ static loadAsInstance = false; // use the updated/newer Resource API
6
+
7
+ get() {
8
+ return { greeting: 'Hello, world!' };
9
+ }
10
+ }
@@ -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 TableName @table @export {
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: 'schema.graphql'
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.ts'
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
+ }