create-harper 0.0.3 → 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.
Files changed (64) hide show
  1. package/index.js +21 -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/templates.js +2 -2
  6. package/lib/fs/crawlTemplateDir.js +9 -3
  7. package/lib/steps/getEnvVars.js +75 -16
  8. package/lib/steps/getExamples.js +48 -0
  9. package/lib/steps/getTemplate.js +4 -4
  10. package/lib/steps/helpAgents.js +15 -0
  11. package/lib/steps/scaffoldProject.js +3 -2
  12. package/package.json +1 -2
  13. package/template-react/_github/workflow/deploy.yaml +2 -9
  14. package/template-react/_gitignore +2 -0
  15. package/template-react/config.yaml +2 -2
  16. package/template-react/package.json +1 -1
  17. package/template-react/resources/exampleSocket.js +34 -0
  18. package/template-react/resources/exampleTable.js +14 -0
  19. package/template-react/resources/greeting.js +10 -0
  20. package/{template-studio/schema.graphql → template-react/schemas/exampleTable.graphql} +1 -1
  21. package/template-react-ts/_github/workflow/deploy.yaml +2 -9
  22. package/template-react-ts/_gitignore +2 -0
  23. package/template-react-ts/config.yaml +2 -2
  24. package/template-react-ts/package.json +1 -1
  25. package/template-react-ts/resources/exampleSocket.ts +41 -0
  26. package/template-react-ts/resources/exampleTable.ts +20 -0
  27. package/template-react-ts/{resources.ts → resources/greeting.ts} +0 -23
  28. package/{template-vanilla/schema.graphql → template-react-ts/schemas/exampleTable.graphql} +1 -1
  29. package/template-studio/config.yaml +2 -2
  30. package/template-studio/resources/exampleSocket.js +34 -0
  31. package/template-studio/resources/exampleTable.js +14 -0
  32. package/template-studio/resources/greeting.js +10 -0
  33. package/{template-vanilla-ts/schema.graphql → template-studio/schemas/exampleTable.graphql} +1 -1
  34. package/template-studio-ts/config.yaml +2 -2
  35. package/template-studio-ts/resources/exampleSocket.ts +41 -0
  36. package/template-studio-ts/resources/exampleTable.ts +20 -0
  37. package/{template-vanilla-ts/resources.ts → template-studio-ts/resources/greeting.ts} +0 -23
  38. package/template-studio-ts/{schema.graphql → schemas/exampleTable.graphql} +1 -1
  39. package/template-vanilla/README.md +1 -1
  40. package/template-vanilla/config.yaml +2 -2
  41. package/template-vanilla/resources/exampleSocket.js +34 -0
  42. package/template-vanilla/resources/exampleTable.js +14 -0
  43. package/template-vanilla/resources/greeting.js +10 -0
  44. package/template-vanilla/schemas/exampleTable.graphql +7 -0
  45. package/template-vanilla-ts/README.md +2 -2
  46. package/template-vanilla-ts/config.yaml +2 -2
  47. package/template-vanilla-ts/resources/exampleSocket.ts +41 -0
  48. package/template-vanilla-ts/resources/exampleTable.ts +20 -0
  49. package/{template-studio-ts/resources.ts → template-vanilla-ts/resources/greeting.ts} +0 -23
  50. package/template-vanilla-ts/schemas/exampleTable.graphql +7 -0
  51. package/template-barebones/README.md +0 -7
  52. package/template-barebones/_aiignore +0 -1
  53. package/template-barebones/_env +0 -3
  54. package/template-barebones/_env.example +0 -3
  55. package/template-barebones/_gitignore +0 -147
  56. package/template-barebones/config.yaml +0 -7
  57. package/template-barebones/graphql.config.yml +0 -3
  58. package/template-barebones/package.json +0 -14
  59. package/template-barebones/schema.graphql +0 -1
  60. package/template-react/resources.js +0 -27
  61. package/template-react/schema.graphql +0 -5
  62. package/template-react-ts/schema.graphql +0 -5
  63. package/template-studio/resources.js +0 -27
  64. 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
- 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
- }
44
+ await helpAgents(interactive);
49
45
 
50
46
  const cancel = () => prompts.cancel('Operation cancelled');
51
47
 
52
- // 1. Get the project name and target directory
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
- // 2. Handle if the directory exists and isn't empty
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
- // 3. Get the package name
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
- // 4. Choose a framework and variant
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
- // 5. Should we do a package manager installation?
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
- // 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);
84
+ // Write out the contents based on all prior steps.
85
+ const root = scaffoldProject(targetDir, projectName, packageName, template, envVars, excludedFiles);
85
86
 
86
- // 8. Log out the next steps.
87
+ // Log out the next steps.
87
88
  showOutro(root, pkgManager, immediate);
88
89
  }
@@ -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
  ];
@@ -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
  }
@@ -2,6 +2,13 @@ import * as prompts from '@clack/prompts';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
+ import colors from 'picocolors';
6
+ import { defaultPassword, defaultTarget, defaultUsername } from '../constants/defaultEnv.js';
7
+
8
+ const {
9
+ gray,
10
+ magenta,
11
+ } = colors;
5
12
 
6
13
  /**
7
14
  * Step 5: Get environment variables for .env file
@@ -31,11 +38,13 @@ export async function getEnvVars(argv, interactive, template) {
31
38
  let target = argv['cli-target'];
32
39
  let password = '';
33
40
 
41
+ const prefix = gray('https://') + magenta('fabric.harper.fast') + gray('/') + ' Cluster ';
42
+
34
43
  if (interactive) {
35
44
  if (!username) {
36
45
  const usernameResult = await prompts.text({
37
- message: 'CLI Target Username:',
38
- placeholder: 'YOUR_CLUSTER_USERNAME',
46
+ message: prefix + 'Username:',
47
+ placeholder: defaultUsername,
39
48
  });
40
49
 
41
50
  if (prompts.isCancel(usernameResult)) {
@@ -44,10 +53,19 @@ export async function getEnvVars(argv, interactive, template) {
44
53
  username = usernameResult;
45
54
  }
46
55
 
47
- if (!target) {
56
+ const passwordResult = username && await prompts.password({
57
+ message: prefix + 'Password:',
58
+ });
59
+
60
+ if (prompts.isCancel(passwordResult)) {
61
+ return { envVars: {}, cancelled: true };
62
+ }
63
+ password = passwordResult;
64
+
65
+ if (!target && username) {
48
66
  const targetResult = await prompts.text({
49
- message: 'CLI Target URL:',
50
- placeholder: 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE',
67
+ message: prefix + 'URL:',
68
+ placeholder: 'YOUR_CLUSTER_URL_HERE',
51
69
  });
52
70
 
53
71
  if (prompts.isCancel(targetResult)) {
@@ -55,24 +73,65 @@ export async function getEnvVars(argv, interactive, template) {
55
73
  }
56
74
  target = targetResult;
57
75
  }
76
+ } else {
77
+ prompts.log.warn('Non-interactive mode: Please update your .env to add your CLI_TARGET_PASSWORD on your own.');
78
+ }
58
79
 
59
- const passwordResult = await prompts.password({
60
- message: 'CLI Target Password:',
61
- });
80
+ if (target) {
81
+ if (!target.startsWith('http://') && !target.startsWith('https://')) {
82
+ target = 'https://' + target;
83
+ }
84
+ if (!target.endsWith('/')) {
85
+ target = target + '/';
86
+ }
62
87
 
63
- if (prompts.isCancel(passwordResult)) {
64
- return { envVars: {}, cancelled: true };
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
+ }
65
127
  }
66
- password = passwordResult;
67
- } else {
68
- prompts.log.warn('Non-interactive mode: Please update your .env to add your CLI_TARGET_PASSWORD on your own.');
69
128
  }
70
129
 
71
130
  return {
72
131
  envVars: {
73
- username: username || 'YOUR_CLUSTER_USERNAME',
74
- target: target || 'YOUR_FABRIC.HARPER.FAST_CLUSTER_URL_HERE',
75
- password: password || 'YOUR_CLUSTER_PASSWORD',
132
+ username: username || defaultUsername,
133
+ target: target || defaultTarget,
134
+ password: password || defaultPassword,
76
135
  },
77
136
  cancelled: false,
78
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.3",
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/",
@@ -29,12 +29,5 @@ jobs:
29
29
  run: npm test
30
30
  - name: Run lint
31
31
  run: npm run lint
32
- - name: Build
33
- run: npm run build
34
- - name: Deploy
35
- run: |
36
- mkdir deploy
37
- mv web deploy/
38
- cp -R deploy-template/* deploy/
39
- cd deploy
40
- CLI_TARGET_USERNAME=${{ secrets.CLI_TARGET_USERNAME }} CLI_TARGET_PASSWORD='${{ secrets.CLI_TARGET_PASSWORD }}' harperdb deploy_component project='${{ secrets.CLI_DEPLOY_TARGET_NAME }}' target='${{ secrets.CLI_DEPLOY_TARGET_URL }}' restart=false replicated=true
32
+ - name: Build & deploy
33
+ run: npm run build-and-deploy
@@ -1,4 +1,6 @@
1
1
  .DS_Store
2
+ web
3
+ deploy
2
4
 
3
5
  #
4
6
  # https://raw.githubusercontent.com/github/gitignore/refs/heads/main/Node.gitignore
@@ -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'
@@ -9,7 +9,7 @@
9
9
  "lint": "echo 'No lint implemented yet'",
10
10
  "build": "vite build",
11
11
  "preview": "vite preview",
12
- "deploy": "npx -y dotenv-cli -- harperdb deploy . restart=rolling replicated=true"
12
+ "build-and-deploy": "npm run build && mkdir deploy && mv web deploy/ && cp -R deploy-template/* deploy/ && (cd deploy && npx -y dotenv-cli -- harperdb deploy . restart=rolling replicated=true)"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@harperfast/vite-plugin": "^0.0.1",
@@ -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
@@ -29,12 +29,5 @@ jobs:
29
29
  run: npm test
30
30
  - name: Run lint
31
31
  run: npm run lint
32
- - name: Build
33
- run: npm run build
34
- - name: Deploy
35
- run: |
36
- mkdir deploy
37
- mv web deploy/
38
- cp -R deploy-template/* deploy/
39
- cd deploy
40
- CLI_TARGET_USERNAME=${{ secrets.CLI_TARGET_USERNAME }} CLI_TARGET_PASSWORD='${{ secrets.CLI_TARGET_PASSWORD }}' harperdb deploy_component project='${{ secrets.CLI_DEPLOY_TARGET_NAME }}' target='${{ secrets.CLI_DEPLOY_TARGET_URL }}' restart=false replicated=true
32
+ - name: Build & deploy
33
+ run: npm run build-and-deploy
@@ -1,4 +1,6 @@
1
1
  .DS_Store
2
+ web
3
+ deploy
2
4
 
3
5
  #
4
6
  # https://raw.githubusercontent.com/github/gitignore/refs/heads/main/Node.gitignore
@@ -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'
@@ -9,7 +9,7 @@
9
9
  "lint": "echo 'No lint implemented yet'",
10
10
  "build": "tsc -b && vite build",
11
11
  "preview": "vite preview",
12
- "deploy": "npx -y dotenv-cli -- harperdb deploy . restart=rolling replicated=true"
12
+ "build-and-deploy": "npm run build && mkdir deploy && mv web deploy/ && cp -R deploy-template/* deploy/ && (cd deploy && npx -y dotenv-cli -- harperdb deploy . restart=rolling replicated=true)"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@harperfast/vite-plugin": "^0.0.1",
@@ -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
+ }