Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @HarperFast/developers
55 changes: 55 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Release

on:
merge_group:
branches:
- main
push:
branches:
- main
workflow_dispatch:

# Needed for semantic-release to create GitHub releases and publish to npm via OIDC
permissions:
contents: write
issues: write
pull-requests: write
id-token: write

concurrency:
group: Release
cancel-in-progress: false

jobs:
release:
name: Release
environment: Release
# Ensure releases run only when code reaches main via GitHub Merge Queue, or when manually dispatched
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
cache: npm
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Check lint
run: npm run lint:check
- name: Check format
run: npm run format:check
- name: Run unit tests
run: npm test
- name: Semantic Release
if: ${{ github.event_name == 'push' }}
uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true
- name: Publish to NPM
run: npm publish --provenance
16 changes: 16 additions & 0 deletions .github/workflows/verify-commits.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Verify Commits
on:
pull_request:
types: [opened, synchronize, edited, reopened, ready_for_review]
push:
branches: [main]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Commitlint
uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6.2.1
with:
configFile: commitlint.config.cjs
60 changes: 60 additions & 0 deletions .github/workflows/verify-pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Verify PR
on:
workflow_dispatch:
pull_request:
branches: [main]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
cache: npm
- name: Install dependencies
run: npm ci
- name: Check lint
run: npm run lint:check

format:
name: Format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
cache: npm
- name: Install dependencies
run: npm ci
- name: Check format
run: npm run format:check

test:
name: Test (${{ matrix.os }}, Node ${{ matrix.node-version }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [20, 22, 24]
defaults:
run:
shell: bash
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/jsLibraryMappings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you're curious, here's rocksdb-js' oxfmt config: https://github.com/HarperFast/rocksdb-js/blob/main/.oxfmtrc.json. Most of the settings are the defaults and could have been omitted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've got it down to the minimum in this repo -- are there other ones that you think should be the norm for Harper? Single quotes and tabs.

{
	"$schema": "./node_modules/oxfmt/configuration_schema.json",
	"singleQuote": true,
	"useTabs": true
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, I'll start applying this consistently across other repos I touch like Studio, Agent and create harper.

"singleQuote": true,
"useTabs": true
}
6 changes: 6 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"ignorePatterns": [],
"rules": {},
"overrides": []
}
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# @HarperFast/Schema-Codegen

Schema Codegen will generate TypeScript types for your GraphQL schemas, making it easier to work with your data in TypeScript applications.
Schema Codegen will generate TypeScript types for your GraphQL schemas, making it easier to work with your data in TypeScript and JavaScript applications.

## Installation

Install this with your favorite package manager!

**Warning**: I haven't actually published this yet. :)

```bash
npm install --save-dev @harperfast/schema-codegen
npm install --save @harperfast/schema-codegen
```

Drop this in your Harper application's config.yaml:
Expand All @@ -21,11 +19,20 @@ Drop this in your Harper application's config.yaml:
schemaTypes: 'schemas/types.ts'
```

When you `harper dev`, it will watch any file ending in .graphql.
Alternatively, if you are using pure JavaScript, you can generate JSDoc instead:

```yaml
'@harperfast/schema-codegen':
package: '@harperfast/schema-codegen'
jsdoc: 'schemas/jsdocTypes.js'
```

When you `harper dev`, it will generate types based on the schema that's actually in your Harper database. If you change the schema, we will automatically regenerate the types for you.

## Example

For example, here's a tracks.graphql schema:

```graphql
type Tracks @table @sealed {
id: ID @primaryKey
Expand All @@ -34,7 +41,7 @@ type Tracks @table @sealed {
}
```

Next to it, a management.graphql.ts file will get generated with this:
Next to it, a schemas/types.ts file will get generated with this:

```typescript
/**
Expand All @@ -55,7 +62,8 @@ export type TrackRecords = Track[];
export type NewTrackRecord = Omit<Track, 'id'>;
```

An ambient declaration will also be generated in a top level globalTypes.d.ts to enhance the global `tables` and `databases` from Harper:
An ambient declaration will also be generated in globalTypes.d.ts to enhance the global `tables` and `databases` from Harper:

```typescript
/**
Generated from your schema files
Expand All @@ -67,20 +75,18 @@ import type { Track } from './types.ts';

declare module 'harperdb' {
export const tables: {
Tracks: { new(...args: any[]): Table<Track> };
Tracks: { new (...args: any[]): Table<Track> };
};
export const databases: {
data: {
Tracks: { new(...args: any[]): Table<Track> };
Tracks: { new (...args: any[]): Table<Track> };
};
};
}
```

## Development

This code uses type stripping, so as long as you use a compatible version of Node, nothing needs to be compiled.

To use this in an application, first link it:

```bash
Expand All @@ -90,6 +96,7 @@ npm link
```

Then cd to your awesome application you want to test this with:

```bash
cd ~/my-awesome-app
npm link @harperfast/schema-codegen
Expand Down
8 changes: 8 additions & 0 deletions commitlint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'subject-case': [0, 'never'],
'body-max-line-length': [0, 'never'],
'footer-max-line-length': [0, 'never'],
},
};
2 changes: 1 addition & 1 deletion config.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
extensionModule: ./extensionModule.ts
extensionModule: ./extensionModule.js
62 changes: 62 additions & 0 deletions extensionModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/** @typedef {import('harperdb').Scope} Scope */
import { setTimeout as delay } from 'node:timers/promises';
import { setLogger } from './utils/logger.js';
import { regenerateAll } from './utils/regenerateAll.js';

export const suppressHandleApplicationWarning = true;

/**
* @param {Scope} scope
*/
export async function handleApplication(scope) {
setLogger(scope.logger);

if (!process.env.DEV_MODE) {
scope.logger.trace?.('@harperfast/schema-codegen skipping execution outside of dev mode');
return;
}

const watchConfig = scope.options.get(['watch']);
const shouldWatch = watchConfig === true || watchConfig === undefined;
const globalTypes = /** @type {string} */ (scope.options.get(['globalTypes'])) ||
'./schema.globalTypes.d.ts';
const schemaTypes = /** @type {string} */ (scope.options.get(['schemaTypes'])) ||
'./schema.types.ts';
const jsdoc = /** @type {string | undefined} */ (scope.options.get(['jsdoc']));

if (shouldWatch) {
scope.on('close', scopeClosed);
}

// Do not await this.
delay(500).then(() => {
// Initial generation
regenerateAll(globalTypes, schemaTypes, jsdoc);

if (shouldWatch) {
// Watch for schema/database changes via events
scope.databaseEvents.on('updateTable', updateTable);
scope.databaseEvents.on('dropTable', dropTable);
scope.databaseEvents.on('dropDatabase', dropDatabase);
}
});

function updateTable() {
regenerateAll(globalTypes, schemaTypes, jsdoc);
}

function dropTable() {
regenerateAll(globalTypes, schemaTypes, jsdoc);
}

function dropDatabase() {
regenerateAll(globalTypes, schemaTypes, jsdoc);
}

function scopeClosed() {
scope.databaseEvents.off('updateTable', updateTable);
scope.databaseEvents.off('dropTable', dropTable);
scope.databaseEvents.off('dropDatabase', dropDatabase);
scope.off('close', scopeClosed);
}
}
Loading