Skip to content
Closed
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
4 changes: 1 addition & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
"editor.formatOnSave": true,
"files.autoSave": "onFocusChange",
"rust-analyzer.checkOnSave.command": "clippy",
"editor.rulers": [
80
],

"search.exclude": {
"**/.git": true,
"**/node_modules": true,
Expand Down
52 changes: 52 additions & 0 deletions Earthfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
VERSION 0.7
PROJECT applied-knowledge-systems/atomic-server
# You can compile front end separately and copy dist folder
# IMPORT ./browser AS browser
FROM rust:latest
WORKDIR /code

main-pipeline:
PIPELINE --push
TRIGGER push main
TRIGGER pr main
ARG tag=latest
BUILD +build --tag=$tag

deps:
RUN curl -fsSL https://bun.sh/install | bash
RUN /root/.bun/bin/bun install -y pnpm
# COPY . .
COPY --dir server lib cli desktop Cargo.lock Cargo.toml .
# RUN mkdir src
# RUN touch src/main.rs # adding main.rs stub so cargo fetch works to prepare the cache
RUN cargo fetch

test:
FROM +deps
RUN cargo test

build:
FROM +deps
RUN rustup target add x86_64-unknown-linux-musl
RUN apt update && apt install -y musl-tools musl-dev
RUN update-ca-certificates
WORKDIR /app
# FIXME: Joep you need to fix this line and modify Earthfile inside browser
# COPY browser+build/dist ./public
COPY --dir server lib cli desktop Cargo.lock Cargo.toml .
RUN cargo build --release --bin atomic-server --config net.git-fetch-with-cli=true --target x86_64-unknown-linux-musl
RUN strip -s /app/target/x86_64-unknown-linux-musl/release/atomic-server
SAVE ARTIFACT /app/target/x86_64-unknown-linux-musl/release/atomic-server

docker:
# We only need a small runtime for this step, but make sure glibc is installed
FROM scratch
COPY --chmod=0755 +build/atomic-server /atomic-server-bin
# For a complete list of possible ENV vars or available flags, run with `--help`
ENV ATOMIC_STORE_PATH="/atomic-storage/db"
ENV ATOMIC_CONFIG_PATH="/atomic-storage/config.toml"
ENV ATOMIC_PORT="80"
EXPOSE 80
VOLUME /atomic-storage
ENTRYPOINT ["/atomic-server-bin"]
SAVE IMAGE --push ghcr.io/applied-knowledge-systems/atomic-server:edge
3 changes: 2 additions & 1 deletion browser/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ module.exports = {
tsconfigRootDir: __dirname,
project: [
'lib/tsconfig.json',
'cli/tsconfig.json',
'react/tsconfig.json',
'data-browser/tsconfig.json',
'data-browser/tsconfig.json'
],
},
plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks', 'jsx-a11y'],
Expand Down
22 changes: 22 additions & 0 deletions browser/Earthfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
VERSION 0.7
PROJECT applied-knowledge-systems/atomic-server
FROM node:latest
WORKDIR browser

main-pipeline:
PIPELINE --push
TRIGGER push main
TRIGGER pr main
ARG tag=latest
BUILD +build --tag=$tag

deps:
RUN curl -f https://get.pnpm.io/v6.14.js | node - add --global pnpm
COPY . .
RUN pnpm install --no-frozen-lockfile
SAVE ARTIFACT node_modules /node_modules

build:
FROM +deps
RUN pnpm run build
SAVE ARTIFACT dist /dist AS LOCAL dist
Binary file removed browser/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions browser/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/bin
34 changes: 34 additions & 0 deletions browser/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"version": "0.35.2",
"author": "Polle Pas",
"dependencies": {
"@tomic/lib": "^0.35.2",
"chalk": "^5.3.0"
},
"devDependencies": {
"typescript": "^4.8"
},
"description": "",
"license": "MIT",
"name": "@tomic/cli",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"lint": "eslint ./src --ext .js,.ts",
"lint-fix": "eslint ./src --ext .js,.ts --fix",
"prepublishOnly": "pnpm run build && pnpm run lint-fix",
"watch": "tsc --build --watch",
"start": "pnpm watch",
"tsc": "tsc --build",
"typecheck": "tsc --noEmit"
},
"bin": {
"ad-generate": "./bin/src/index.js"
},
"type": "module",
"peerDependencies": {
"@tomic/lib": "^0.35.2"
}
}
161 changes: 161 additions & 0 deletions browser/cli/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# @tomic/cli

@tomic/cli is a cli tool that helps the developer with creating a front-end for their atomic data project by providing typesafety on resources.

In atomic data you can create [ontologies](https://atomicdata.dev/class/ontology) that describe your business model. Then you use this tool to generate Typscript types for these ontologies in your front-end.

```typescript
import { Post } from './ontolgies/blog'; // <--- generated

const myBlogpost = await store.getResourceAsync<Post>(
'https://myblog.com/atomic-is-awesome',
);

const comments = myBlogpost.props.comments; // string[] automatically infered!
```

## Getting started

### Installation

You can install the package globally or as a dev dependancy of your project.

**Globally**:

```
npm install -g @tomic/cli
```

**Dev Dependancy:**

```
npm install -D @tomic/cli
```

If you've installed it globally you can now run the `ad-generate` command in your command line.
When installing as a dependancy your PATH won't know about the command and so you will have to make a script in your `package.json` and run it via `npm <script_name>` instead.

```json
"scripts": {
"generate": "ad-generate"
}
```

### Generating the files

To start generating your ontologies you first need to configure the cli. Start by creating the config file by running:

```
ad-generate init
```

There should now be a file called `atomic.config.json` in the folder where you ran this command. The contents will look like this:

```json
{
"outputFolder": "./src/ontologies",
"moduleAlias": "@tomic/lib",
"ontologies": []
}
```

> If you want to change the location where the files are generated you can change the `outputFolder` field.

Next add the subjects of your atomic ontologies to the `ontologies` array in the config.

Now we will generate the ontology files. We do this by running the `ad-generate ontologies` command. If your ontologies don't have public read rights you will have to add an agent secret to the command that has access to these resources.

```
ad-generate ontologies --agent <AGENT_SECRET>
```

> Agent secret can also be preconfigured in the config **but be careful** when using version control as you can easily leak your secret this way.

After running the command the files will have been generated in the specified output folder along with an `index.ts` file. The only thing left to do is to register our ontologies with @tomic/lib. This should be done as soon in your apps runtime lifecycle as possible, for example in your App.tsx when using React or root index.ts in most cases.

```typescript
import { initOntologies } from './ontologies';

initOntologies();
```

### Using the types

If everything went well the generated files should now be in the output folder.
In order to gain the benefit of the typings we will need to annotate our resource with its respective class like follows:

```typescript
import { Book, creativeWorks } from './ontologies/creativeWorks.js';

const book = await store.getResourceAsync<Book>(
'https://mybookstore.com/books/1',
);
```

Now we know what properties are required and recommend on this resource so we can safely infer the types

Because we know `written-by` is a required property on book we can safely infer type string;

```typescript
const authorSubject = book.get(creativeWorks.properties.writtenBy); // string
```

`description` has datatype Markdown and is inferred as string but it is a recommended property and might therefore be undefined

```typescript
const description = book.get(core.properties.description); // string | undefined
```

If the property is not in any ontology we can not infer the type so it will be of type `JSONValue`
(this type includes `undefined`)

```typescript
const unknownProp = book.get('https://unknownprop.site/prop/42'); // JSONValue
```

### Props shorthand

Because you have initialised your ontologies before lib is aware of what properties exist and what their name and type is. Because of this it is possible to use the props field on a resource and get full intellisense and typing on it.

```typescript
const book = await store.getResourceAsync<Book>(
'https://mybookstore.com/books/1',
);

const name = book.props.name; // string
const description = book.props.description; // string | undefined
```

> The props field is a computed property and is readonly.
>
> If you have to read very large number of properties at a time it is more efficient to use the `resource.get()` method instead of the props field because the props field iterates over the resources propval map.

## Configuration

@tomic/cli loads the config file from the root of your project. This file should be called `atomic.config.json` and needs to conform to the following interface.

```typescript
interface AtomicConfig {
/**
* Path relative to this file where the generated files should be written to.
*/
outputFolder: string;

/**
* [OPTIONAL] The @tomic/lib module identifier.
* The default should be sufficient in most but if you have given the module an alias you should change this value
*/
moduleAlias?: string;

/**
* [OPTIONAL] The secret of the agent that is used to access your atomic data server. This can also be provided as a command line argument if you don't want to store it in the config file.
* If left empty the public agent is used.
*/
agentSecret?: string;

/** The list of subjects of your ontologies */
ontologies: string[];
}
```

Running `ad-generate init` will create this file for you that you can then tweak to your own preferences.
33 changes: 33 additions & 0 deletions browser/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable no-console */
import chalk from 'chalk';
import * as fs from 'fs';
import * as path from 'path';

const TEMPLATE_CONFIG_FILE = {
outputFolder: './src/ontologies',
moduleAlias: '@tomic/lib',
ontologies: [],
};

export const initCommand = async (args: string[]) => {
const forced = args.includes('--force') || args.includes('-f');
const filePath = path.join(process.cwd(), 'atomic.config.json');
const stat = fs.statSync(filePath);

if (stat.isFile() && !forced) {
return console.error(
chalk.red(
`ERROR: File already exists. If you meant to override the existing file, use the command with the ${chalk.cyan(
'--force',
)} flag.`,
),
);
}

console.log(chalk.cyan('Creating atomic.config.json'));

const template = JSON.stringify(TEMPLATE_CONFIG_FILE, null, 2);
fs.writeFileSync(filePath, template);

console.log(chalk.green('Done!'));
};
49 changes: 49 additions & 0 deletions browser/cli/src/commands/ontologies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable no-console */

import * as fs from 'fs';
import chalk from 'chalk';

import * as path from 'path';
import { generateOntology } from '../generateOntology.js';
import { atomicConfig } from '../config.js';
import { generateIndex } from '../generateIndex.js';

export const ontologiesCommand = async (_args: string[]) => {
console.log(
chalk.blue(
`Found ${chalk.red(
Object.keys(atomicConfig.ontologies).length,
)} ontologies`,
),
);

for (const subject of Object.values(atomicConfig.ontologies)) {
write(await generateOntology(subject));
}

console.log(chalk.blue('Generating index...'));

write(generateIndex(atomicConfig.ontologies));

console.log(chalk.green('Done!'));
};

const write = ({
filename,
content,
}: {
filename: string;
content: string;
}) => {
console.log(chalk.blue(`Writing ${chalk.red(filename)}...`));

const filePath = path.join(
process.cwd(),
atomicConfig.outputFolder,
filename,
);

fs.writeFileSync(filePath, content);

console.log(chalk.blue('Wrote to'), chalk.cyan(filePath));
};
Loading