Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency Updates #1

Merged
merged 3 commits into from
Nov 12, 2023
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
21 changes: 9 additions & 12 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
name: Test

on: push

on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
node-version:
- lts/* # LTS version
- "17" # latest version
node:
- lts/*
- current
os: [ubuntu-latest, macOS-latest, windows-latest]

steps:
- uses: actions/checkout@v2
- name: Node ${{ matrix.node-version }}
uses: actions/setup-node@v2
- uses: actions/checkout@v3
- name: Node ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- run: npm i
node-version: ${{ matrix.node }}
- run: npm install
- run: npm test
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# pico-framework

[![Build Status](https://github.com/Picolab/pico-framework/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/Picolab/pico-framework/actions/workflows/test.yml)

A framework for building actor-based, people-centric systems. (pico = PersIstent Compute Objects)

## Why Picos?
Expand All @@ -19,7 +21,7 @@ It handles the building blocks of a Pico based system.
- Rulesets
- What the ruleset code is allowed to do to a pico i.e. sandboxing

The pico-framework also handles persistence of the pico objects. You can provided the persistance layer via an implementation of [abstract-leveldown](https://github.com/Level/abstract-leveldown)
The pico-framework also handles persistence of the pico objects. You simply provide the persistence layer via an implementation of [abstract-level](https://github.com/Level/abstract-level).

## Contributing

Expand Down
23 changes: 10 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,25 @@
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"prepublish": "npm run build",
"prepare": "npm run build",
"build": "rm -rf dist && tsc",
"test": "nyc ava"
"test": "ava reset-cache && nyc ava"
},
"dependencies": {
"charwise": "^3.0.1",
"abstract-level": "^1.0.3",
"cuid": "^2.1.8",
"encoding-down": "^6.3.0",
"level-json-coerce-null": "^1.0.1",
"levelup": "^4.4.0",
"lodash": "^4.17.19",
"memdown": "^5.1.0",
"select-when": "^0.1.4"
"select-when": "^0.1.9"
},
"devDependencies": {
"@types/abstract-leveldown": "^5.0.1",
"@types/levelup": "^4.3.0",
"@types/lodash": "^4.14.157",
"ava": "^4.0.0",
"@types/lodash": "^4.14.201",
"ava": "^5.3.1",
"charwise": "^3.0.1",
"level-json-coerce-null": "^1.0.1",
"memory-level": "^1.0.0",
"nyc": "^15.1.0",
"ts-node": "^10.4.0",
"typescript": "^4.5.4"
"typescript": "^5.2.2"
},
"ava": {
"extensions": [
Expand Down
25 changes: 14 additions & 11 deletions src/Pico.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ export class Pico {

private queue: PicoQueue;

constructor(private pf: PicoFramework, id: string) {
constructor(
private pf: PicoFramework,
id: string,
) {
this.id = id;
this.channels[id] = new Channel(this, id, { tags: ["system", "self"] }, id);

this.queue = new PicoQueue(
this.id,
this.doTxn.bind(this),
(ev: PicoFrameworkEvent) => this.pf.emit(ev)
(ev: PicoFrameworkEvent) => this.pf.emit(ev),
);
}

Expand Down Expand Up @@ -152,13 +155,13 @@ export class Pico {
const child = new Pico(this.pf, this.pf.genID());
const parentChannel = this.newChannelBase(
{ tags: ["system", "parent"] },
child.id
child.id,
);
child.parent = parentChannel.id;

const childChannel = child.newChannelBase(
{ tags: ["system", "child"] },
this.id
this.id,
);
child.channels[childChannel.id] = childChannel;
this.children.push(childChannel.id);
Expand Down Expand Up @@ -304,7 +307,7 @@ export class Pico {

async newChannel(
conf?: ChannelConfig,
familyChannelPicoID?: string
familyChannelPicoID?: string,
): Promise<Channel> {
const chann = this.newChannelBase(conf, familyChannelPicoID);
await this.pf.db.batch([chann.toDbPut()]);
Expand All @@ -314,7 +317,7 @@ export class Pico {

private newChannelBase(
conf?: ChannelConfig,
familyChannelPicoID?: string
familyChannelPicoID?: string,
): Channel {
const chann = new Channel(this, this.pf.genID(), conf, familyChannelPicoID);
return chann;
Expand Down Expand Up @@ -364,7 +367,7 @@ export class Pico {

private async installBase(
rs: Ruleset,
config: RulesetConfig = {}
config: RulesetConfig = {},
): Promise<{ instance: RulesetInstance; dbPut: LevelBatch }> {
// even if we already have that rid installed, we need to init again
// b/c the configuration may have changed
Expand Down Expand Up @@ -403,7 +406,7 @@ export class Pico {
key: data.key,
};
return delEnt;
}
},
);
ops.push({
type: "del",
Expand Down Expand Up @@ -454,7 +457,7 @@ export class Pico {
domain: string,
name: string,
attrs: PicoEventPayload["attrs"],
forRid?: string
forRid?: string,
) {
const event = cleanEvent({
eci: (this.current && this.current.event.eci) || "[raise]",
Expand Down Expand Up @@ -515,7 +518,7 @@ export class Pico {
// must process one event at a time to maintain the pico's single-threaded guarantee
const response = await rs.instance.event(
this.current.event,
eid
eid,
);
responses.push(response);
}
Expand All @@ -532,7 +535,7 @@ export class Pico {
const qfn = rs?.instance?.query && rs.instance.query[txn.query.name];
if (!qfn) {
throw new Error(
`Ruleset ${txn.query.rid} does not have query function "${txn.query.name}"`
`Ruleset ${txn.query.rid} does not have query function "${txn.query.name}"`,
);
}
const data = await qfn(txn.query, txn.id);
Expand Down
26 changes: 7 additions & 19 deletions src/PicoFramework.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { AbstractLevelDOWN } from "abstract-leveldown";
import * as cuid from "cuid";
import { default as level, LevelUp } from "levelup";
import { PicoDb } from "./utils";
import { Channel } from "./Channel";
import { dbRange } from "./dbRange";
import { Pico } from "./Pico";
import { cleanEvent, PicoEvent } from "./PicoEvent";
import { PicoFrameworkEvent } from "./PicoFrameworkEvent";
import { cleanQuery, PicoQuery } from "./PicoQuery";
import { Ruleset, RulesetConfig } from "./Ruleset";
const charwise = require("charwise");
const encode = require("encoding-down");
const safeJsonCodec = require("level-json-coerce-null");
const memdown = require("memdown");

export type RulesetLoader = (
picoId: string,
rid: string,
config: RulesetConfig
config: RulesetConfig,
) => Ruleset | Promise<Ruleset>;

type OnFrameworkEvent = (event: PicoFrameworkEvent) => void;
Expand All @@ -26,10 +21,8 @@ export interface PicoFrameworkConf {

/**
* Specify how data should be persisted.
*
* By default it uses memdown
*/
leveldown?: AbstractLevelDOWN;
db: PicoDb;

/**
* Function that is called on a pico framework event
Expand All @@ -55,7 +48,7 @@ export interface PicoFrameworkConf {
}

export class PicoFramework {
db: LevelUp;
db: PicoDb;

private rootPico_?: Pico;
public get rootPico(): Pico {
Expand All @@ -82,12 +75,7 @@ export class PicoFramework {
private onFrameworkEvent?: OnFrameworkEvent;

constructor(conf: PicoFrameworkConf) {
this.db = level(
encode((conf && conf.leveldown) || memdown(), {
keyEncoding: charwise,
valueEncoding: safeJsonCodec,
})
);
this.db = conf.db;
this.rulesetLoader = conf && conf.rulesetLoader;
this.genID = (conf && conf.genID) || cuid;
this.environment = conf && conf.environment;
Expand Down Expand Up @@ -219,7 +207,7 @@ export class PicoFramework {
*/
async eventWait(
event: PicoEvent,
fromPicoId?: string
fromPicoId?: string,
): Promise<{ eid: string; responses: any[] }> {
event = this.cleanEvent(event);

Expand All @@ -239,7 +227,7 @@ export class PicoFramework {
async eventQuery(
event: PicoEvent,
query: PicoQuery,
fromPicoId?: string
fromPicoId?: string,
): Promise<any> {
event = this.cleanEvent(event);
query = cleanQuery(query);
Expand Down
57 changes: 17 additions & 40 deletions src/dbRange.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
import { LevelUp } from "levelup";
import * as _ from "lodash";
import { AbstractIteratorOptions } from "abstract-level";
import { PicoDb, PicoDbKey } from "./utils";

export function dbRange<T = any>(
ldb: LevelUp,
opts: any,
onData: (data: any, stop: () => void) => Promise<T> | T
export async function dbRange<T = any>(
db: PicoDb,
opts: { prefix?: PicoDbKey },
onData: (data: any) => Promise<T> | T,
): Promise<T[]> {
return new Promise(function(resolve, reject) {
const promises: Promise<T>[] = [];
const promises: Promise<T>[] = [];

let hasCalledback = false;
function callback(err?: any) {
if (hasCalledback) return;
hasCalledback = true;
if (err) {
return reject(err);
}
Promise.all(promises)
.then(resolve)
.catch(reject);
}

if (_.has(opts, "prefix")) {
opts = _.assign({}, opts, {
gte: opts.prefix,
lte: opts.prefix.concat([undefined]) // bytewise sorts with null at the bottom and undefined at the top
});
delete opts.prefix;
}
const s = ldb.createReadStream(opts);
function stopRange() {
(s as any).destroy();
callback();
}
s.on("error", function(err) {
callback(err);
});
s.on("end", callback);
s.on("data", function(data) {
promises.push(Promise.resolve(onData(data, stopRange)));
});
});
let streamOpts: AbstractIteratorOptions<PicoDbKey, any> = {};
if (opts && Array.isArray(opts.prefix)) {
streamOpts.gte = opts.prefix;
streamOpts.lte = opts.prefix.concat([undefined] as any); // bytewise sorts with null at the bottom and undefined at the top
}
const iter = db.iterator(streamOpts);
for await (const [key, value] of iter) {
promises.push(Promise.resolve(onData({ key, value })));
}
return await Promise.all(promises);
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ import {
import { PicoQuery } from "./PicoQuery";
import { Ruleset, RulesetConfig, RulesetInstance } from "./Ruleset";
import { createRulesetContext, RulesetContext } from "./RulesetContext";
import { PicoDb, PicoDbKey } from "./utils";

export {
Pico,
PicoDb,
PicoDbKey,
PicoEvent,
PicoQuery,
PicoReadOnly,
Expand Down
20 changes: 5 additions & 15 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { AbstractLevel, AbstractBatchOperation } from "abstract-level";

export function isNotStringOrBlank(val: any) {
return typeof val !== "string" || val.trim().length === 0;
}

/**
* Copied AbstractBatch from "abstract-leveldown"
* b/c typescript module resolution doesn't work very well with lerna (used in pico-engine)
*/
export type LevelBatch = PutBatch | DelBatch;
type LevelKey = (string | number | null | boolean)[];
interface PutBatch {
readonly type: "put";
readonly key: LevelKey;
readonly value: any;
}
interface DelBatch {
readonly type: "del";
readonly key: LevelKey;
}
export type PicoDbKey = (string | number | null | boolean)[];
export type PicoDb = AbstractLevel<any, PicoDbKey, any>;
export type LevelBatch = AbstractBatchOperation<PicoDb, PicoDbKey, any>;
Loading