Skip to content

Commit

Permalink
Introduce Labs API (#1)
Browse files Browse the repository at this point in the history
* wip

* Update labs.ts

* Revert "Update labs.ts"

This reverts commit 244ba7d.

* Update labs.ts

* Update labs.ts

* Update labs.ts

* Update labs.ts

* wip

* Update labs.ts

* wip

* remove unused function overloads

* wip

* consolidate procedurelab into lab

* generalize link procedure to link n linkables

* rename import to extend

* wip

* satisfies: type check spec compliancy

* update docs, add check workflow

* update does

* add use cases section to readme
  • Loading branch information
EthanThatOneKid authored Apr 18, 2024
1 parent 4a0bb25 commit a97aca0
Show file tree
Hide file tree
Showing 9 changed files with 692 additions and 2 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Check
"on":
push:
branches:
- main
pull_request:
branches:
- main
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v1
- name: Lint
run: deno lint
19 changes: 19 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"deno.enable": true,
"deno.unstable": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[markdown]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[jsonc]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"files.eol": "\n"
}
145 changes: 145 additions & 0 deletions GETTING_STARTED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Getting Started

## Your first lab

The Notes Lab is a simple example that demonstrates the basic concepts of Labs.
Follow the steps below to create a new Lab that manages notes.

1\. Import the `Lab` class in a new TypeScript file, `main.ts`.

```ts
import { Lab } from "./labs.ts";
```

2\. Define the `Note` interface.

```ts
export interface Note {
title?: string;
content: string;
}
```

3\. Create a new Lab instance.

```ts
export const notesLab = new Lab();
```

4\. Define the `notes` variable in the Lab.

```ts
export const notesLab = new Lab()
.variable("notes", new Map<string, Note>());
```

5\. Define the `notes.add` procedure in the Lab.

```ts
export const notesLab = new Lab()
// ...
.procedure(
"notes.add",
(note: Note, { notes }) => {
const id = crypto.randomUUID();
notes.set(id, note);
return { id };
},
["notes"],
);
```

6\. Define the `notes.get` procedure in the Lab.

```ts
export const notesLab = new Lab()
// ...
.procedure(
"notes.get",
({ id }: { id: string }, { notes }) => {
return notes.get(id);
},
["notes"],
);
```

7\. Define the `notes.list` procedure in the Lab.

```ts
export const notesLab = new Lab()
// ...
.procedure(
"notes.list",
(_, { notes }) => {
return Array.from(notes.values());
},
["notes"],
);
```

At this point, the `notesLab` Lab is ready to be used.

8\. Execute the `notes.add` procedure.

```ts
notesLab.execute("notes.add", { title: "Hello", content: "World" });
```

9\. Execute the `notes.list` procedure.

```ts
const notes = notesLab.execute("notes.list", {});
console.log(notes);
```

10\. Test it out!

```ts
import { Lab } from "./labs.ts";

export interface Note {
title?: string;
content: string;
}

export const notesLab = new Lab()
.variable("notes", new Map<string, Note>())
.procedure(
"notes.add",
(note: Note, { notes }) => {
const id = crypto.randomUUID();
notes.set(id, note);
return { id };
},
["notes"],
)
.procedure(
"notes.get",
({ id }: { id: string }, { notes }) => {
return notes.get(id);
},
["notes"],
)
.procedure(
"notes.list",
(_, { notes }) => {
return Array.from(notes.values());
},
["notes"],
);

notesLab.execute("notes.add", { title: "Hello", content: "World" });

const notes = notesLab.execute("notes.list", {});
console.log(notes);
```

Run the program and observe the output.

```sh
deno run main.ts
```

---

Developed with ❤️ [**@FartLabs**](https://github.com/FartLabs)
88 changes: 86 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,86 @@
# labs
🧪 Labs by FartLabs.
# Labs

[![GitHub Actions](https://github.com/FartLabs/labs/actions/workflows/check.yaml/badge.svg)](https://github.com/FartLabs/labs/actions/workflows/check.yaml)

🧪 Labs by [**@FartLabs**](https://github.com/FartLabs)

## Overview

A Lab is a data structure designed for composable and type-safe management of
variables and procedures.

Labs excel in promoting modularity and reusability. Small, well-defined Labs can
be composed to create complex data structures. This approach simplifies code
composition and improves code quality by reducing _redundancy_ and enhancing
_readability_. Additionally, this approach avoids the need for intricate
inheritance hierarchies with keywords like `super`, `this`, and `extends`.

### Concepts

- **Lab**: A container that holds a collection of named variables and functions
(procedures).
- **Variable**: A named storage location within a Lab that holds a value.
- **Procedure**: A function defined within a Lab that performs a specific task
and has type-safe access to variables and procedures within the same Lab.

### Benefits

By adopting Labs, developers can build applications with cleaner, more
maintainable, and reusable code.

- **Simplified composition**: Labs are easier to compose and work with compared
to native classes. Accessing variables and procedures within a composed Lab is
straightforward.
- **Improved code quality**: By encouraging the breakdown of complex data
structures into smaller, modular Labs, code becomes:
- **Maintainable**: Changes to a Lab's structure are localized and isolated,
reducing the risk of unintended side effects.
- **Less duplicated**: Shared functionalities can be encapsulated in reusable
Labs, minimizing redundancy.
- **More readable**: Clear separation of concerns improves code comprehension.
- **Easier to reason about**: Smaller, well-defined Labs simplify code
analysis and reasoning.

### Use cases

> [!NOTE]
>
> Example Labs demonstrating the following use cases will be added in the
> future.
- **State management**: Labs can be used to manage application state by
encapsulating state variables and state-changing procedures within a Lab.
- **Data processing**: Labs can be used to encapsulate data processing logic,
making it easier to manage and reuse.
- **API clients**: Labs can be used to encapsulate API clients, making it easier
to manage API calls and responses.
- **Configuration management**: Labs can be used to manage application
configuration by encapsulating configuration variables and procedures within a
Lab.
- **Second brain**: Labs can be used to encapsulate knowledge and procedures
that are useful for a specific domain or task.
- **Testing**: Labs can be used to encapsulate test data and test procedures,
making it easier to manage and reuse test cases.
- **Miscellaneous**: Labs can be used for any task that requires encapsulation,
modularity, and reusability.

## Usage

- The [GETTING_STARTED.md](./GETTING_STARTED.md) guide is a Notes Lab example
that demonstrates the basic concepts of Labs.
- The [example.ts](./example.ts) file contains a more advanced example that
showcases the composability of Labs.

## Contribute

We appreciate your help!

### Style

Run `deno fmt` to format the code.

Run `deno lint` to lint the code.

---

Developed with ❤️ [**@FartLabs**](https://github.com/FartLabs)
42 changes: 42 additions & 0 deletions example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Lab } from "./labs.ts";
import { notesLab } from "./notes.ts";
import { linksLab } from "./links.ts";

const myLab = new Lab()
.extend(notesLab)
.extend(linksLab);

// deno run -A example.ts
//
// Proof-of-concept Lab.
//
if (import.meta.main) {
const note1 = myLab.execute(
"notes.add",
{ content: "Hello, world!" },
);
const note2 = myLab.execute(
"notes.add",
{ content: "Goodbye, world!" },
);
myLab.execute("links.link", { ids: [note1.id, note2.id] });

printLinkedNotes(note1.id);
printLinkedNotes(note2.id);
}

function printLinkedNotes(id: string) {
const noteA = myLab.execute("notes.get", { id });
console.log(`Note "${noteA?.content ?? "No content."}" is linked with:`);

const linkedNotes = myLab.execute("links.get", { id });
if (linkedNotes === undefined || linkedNotes.links.length === 0) {
console.log("- No linked notes.");
return;
}

for (const link of linkedNotes.links) {
const noteB = notesLab.execute("notes.get", { id: link.id });
console.log(`- Note "${noteB?.content ?? "No content."}"`);
}
}
Loading

0 comments on commit a97aca0

Please sign in to comment.