Skip to content

Commit

Permalink
Merge pull request #8144 from Agoric/mfig-base-zone
Browse files Browse the repository at this point in the history
feat: add `@agoric/base-zone`
  • Loading branch information
michaelfig authored Aug 5, 2023
2 parents 81776f3 + 7580805 commit c4e4693
Show file tree
Hide file tree
Showing 45 changed files with 560 additions and 243 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test-all-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ jobs:
- name: yarn test (agoric-cli)
if: (success() || failure())
run: cd packages/agoric-cli && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (base-zone)
if: (success() || failure())
run: cd packages/base-zone && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
- name: yarn test (cosmos)
if: (success() || failure())
run: cd golang/cosmos && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT
Expand Down
1 change: 1 addition & 0 deletions packages/agoric-cli/src/sdk-package-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export default [
"@agoric/access-token",
"@agoric/assert",
"@agoric/base-zone",
"@agoric/boot",
"@agoric/cache",
"@agoric/casting",
Expand Down
4 changes: 4 additions & 0 deletions packages/base-zone/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
9 changes: 9 additions & 0 deletions packages/base-zone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Base Zone Library

Each Zone provides an API that allows the allocation of [Exo objects](https://github.com/endojs/endo/tree/master/packages/exo#readme) and [Stores
(object collections)](../store/README.md) which use the same underlying persistence mechanism. This
allows library code to be agnostic to whether its objects are backed purely by
the JS heap (ephemeral), pageable out to disk (virtual) or can be revived after
a vat upgrade (durable).

This library is used internally by [`@agoric/zone`](../zone/README.md); refer to it for more details. Unless you are an author of a new Zone backing store type, or want to use `makeHeapZone` without introducing build dependencies on `@agoric/vat-data`, you should instead use `@agoric/zone`.
3 changes: 3 additions & 0 deletions packages/base-zone/heap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @jessie-check

export * from './src/heap.js';
11 changes: 11 additions & 0 deletions packages/base-zone/jsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": [
"./jsconfig.json",
"../../tsconfig-build-options.json"
],
"exclude": [
"test",
"scripts",
"**/exports.js",
]
}
10 changes: 10 additions & 0 deletions packages/base-zone/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This file can contain .js-specific Typescript compiler config.
{
"extends": "../../tsconfig.json",
"include": [
"*.js",
"scripts",
"src/**/*.js",
"test/**/*.js",
],
}
52 changes: 52 additions & 0 deletions packages/base-zone/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@agoric/base-zone",
"version": "0.1.0",
"description": "Allocation zone abstraction library and heap implementation",
"type": "module",
"repository": "https://github.com/Agoric/agoric-sdk",
"main": "./src/index.js",
"scripts": {
"build": "exit 0",
"prepack": "tsc --build jsconfig.build.json",
"postpack": "git clean -f '*.d.ts*'",
"test": "ava",
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
"test:xs": "exit 0",
"lint-fix": "yarn lint:eslint --fix",
"lint": "run-s --continue-on-error lint:*",
"lint:types": "tsc -p jsconfig.json",
"lint:eslint": "eslint ."
},
"exports": {
".": "./src/index.js",
"./heap.js": "./heap.js",
"./tools/*": "./tools/*"
},
"keywords": [],
"author": "Agoric",
"license": "Apache-2.0",
"dependencies": {
"@agoric/store": "^0.9.2",
"@endo/exo": "^0.2.3",
"@endo/far": "^0.2.19",
"@endo/pass-style": "^0.1.4",
"@endo/patterns": "^0.2.3"
},
"devDependencies": {
"@endo/init": "^0.5.57",
"ava": "^5.3.0"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=14.15.0"
},
"ava": {
"files": [
"test/**/test-*.js"
],
"timeout": "20m",
"workerThreads": false
}
}
9 changes: 9 additions & 0 deletions packages/base-zone/src/exports.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable import/export */

// Module Types //////////////////////////////////////////////////////
//
// Types exposed from modules.
//

// @ts-expect-error TS1383: Only named exports may use 'export type'.
export type * from './types.js';
2 changes: 2 additions & 0 deletions packages/base-zone/src/exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Just a dummy to use exports.d.ts and satisfy runtime imports.
export {};
7 changes: 4 additions & 3 deletions packages/zone/src/heap.js → packages/base-zone/src/heap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
// @jessie-check
/// <reference path="./store-types.d.ts" />

import { Far } from '@endo/far';
import { makeExo, defineExoClass, defineExoClassKit } from '@endo/exo';
Expand All @@ -8,14 +9,14 @@ import {
makeScalarSetStore,
makeScalarWeakMapStore,
makeScalarWeakSetStore,
} from '@agoric/vat-data';
} from '@agoric/store';

import { makeOnceKit } from './make-once.js';
import { agoricVatDataKeys as keys } from './keys.js';
import { isPassable } from './is-passable.js';

/**
* @type {import('.').Stores}
* @type {import('./types.js').Stores}
*/
const detachedHeapStores = Far('heapStores', {
detached: () => detachedHeapStores,
Expand All @@ -31,7 +32,7 @@ const detachedHeapStores = Far('heapStores', {
* Create a heap (in-memory) zone that uses the default exo and store implementations.
*
* @param {string} [baseLabel]
* @returns {import('.').Zone}
* @returns {import('./types.js').Zone}
*/
export const makeHeapZone = (baseLabel = 'heapZone') => {
const { makeOnce, wrapProvider } = makeOnceKit(baseLabel, detachedHeapStores);
Expand Down
9 changes: 9 additions & 0 deletions packages/base-zone/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @jessie-check

// eslint-disable-next-line import/export
export * from './exports.js';

// Utilities for creating zones.
export * from './make-once.js';
export * from './keys.js';
export * from './is-passable.js';
File renamed without changes.
9 changes: 8 additions & 1 deletion packages/zone/src/keys.js → packages/base-zone/src/keys.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// @ts-check

/** @param {string} label */
const kind = label => `${label}_kindHandle`;

/** @param {string} label */
const singleton = label => `${label}_singleton`;

/** @type {Record<'exoClass' | 'exoClassKit' | 'exo' | 'store' | 'zone', (label: string) => string[]>} */
/**
* KeyMakers compatible with `@agoric/vat-data`.
*
* @type {import('./types.js').KeyMakers}
*/
export const agoricVatDataKeys = {
exoClass: label => harden([kind(label)]),
exoClassKit: label => harden([kind(label)]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ harden(defaultLabelToKeys);

/**
* @param {string} debugName Only used internally for diagnostics, not available to user code
* @param {import('.').Stores} stores
* @param {import('./types.js').Stores} stores
* @param {import('@agoric/swingset-liveslots').MapStore<string, any>} [backingStore]
*/
export const makeOnceKit = (debugName, stores, backingStore = undefined) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/base-zone/src/store-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Prevent the compiler from complaining about the @agoric/store type not being
// defined if `"noImplicitAny": true` is set in tsconfig.json.
declare module '@agoric/store';
33 changes: 33 additions & 0 deletions packages/base-zone/src/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// eslint-disable-next-line no-unused-vars
import { makeExo, defineExoClass, defineExoClassKit } from '@endo/exo';

/** @typedef {'exoClass' | 'exoClassKit' | 'exo' | 'store' | 'zone'} KeyCategories */
/** @typedef {Record<KeyCategories, (label: string) => string[]>} KeyMakers */

/**
* @typedef {ExoZone & Stores} Zone A bag of methods for creating defensible objects and
* collections with the same allocation semantics (ephemeral, persistent, etc)
*/

/**
* @typedef {object} ExoZone
* @property {typeof makeExo} exo create a singleton exo-object instance bound to this zone
* @property {typeof defineExoClass} exoClass create a maker function that can be used to create exo-objects bound to this zone
* @property {typeof defineExoClassKit} exoClassKit create a "kit" maker function that can be used to create a record of exo-objects sharing the same state
* @property {<T>(key: string, maker: (key: string) => T) => T} makeOnce create or retrieve a singleton object bound to this zone
* @property {(label: string, options?: StoreOptions) => Zone} subZone create a new Zone that can be passed to untrusted consumers without exposing the storage of the parent zone
*/

/**
* @typedef {object} Stores
* @property {() => Stores} detached obtain store providers which are detached (the stores are anonymous rather than bound to `label` in the zone)
* @property {(specimen: unknown) => boolean} isStorable return true if the specimen can be stored in the zone, whether as exo-object state or in a store
* @property {<K,V>(label: string, options?: StoreOptions) => MapStore<K, V>} mapStore provide a Map-like store named `label` in the zone
* @property {<K>(label: string, options?: StoreOptions) => SetStore<K>} setStore provide a Set-like store named `label` in the zone
* @property {<K,V>(
* label: string, options?: StoreOptions) => WeakMapStore<K, V>
* } weakMapStore provide a WeakMap-like store named `label` in the zone
* @property {<K>(
* label: string, options?: StoreOptions) => WeakSetStore<K>
* } weakSetStore provide a WeakSet-like store named `label` in the zone
*/
6 changes: 6 additions & 0 deletions packages/base-zone/test/prepare-test-env-ava.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import '@endo/init/debug.js';

import test from 'ava';

export { test };
10 changes: 10 additions & 0 deletions packages/base-zone/test/test-exos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// eslint-disable-next-line import/order
import { test } from './prepare-test-env-ava.js';

import { makeHeapZone } from '../heap.js';
import { testFirstZoneIncarnation } from '../tools/testers.js';

test('heapZone', t => {
const zone = makeHeapZone();
testFirstZoneIncarnation(t, zone);
});
8 changes: 8 additions & 0 deletions packages/base-zone/test/test-make-once.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test } from './prepare-test-env-ava.js';

import { makeHeapZone } from '../heap.js';
import { testMakeOnce } from '../tools/testers.js';

test('heapZone', t => {
testMakeOnce(t, makeHeapZone());
});
54 changes: 54 additions & 0 deletions packages/base-zone/tools/greeter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { M } from '@endo/patterns';

export const bindAllMethodsTo = (obj, that = obj) =>
Object.fromEntries(
Object.entries(obj).map(([name, fn]) => [name, fn.bind(that)]),
);

export const greetGuard = M.interface('Greeter', {
greet: M.call().optional(M.string()).returns(M.string()),
});
export const greetFacet = {
greet(greeting = 'Hello') {
return `${greeting}, ${this.state.nick}`;
},
};

export const adminGuard = M.interface('GreeterAdmin', {
setNick: M.call(M.string()).returns(),
});
export const adminFacet = {
setNick(nick) {
this.state.nick = nick;
},
};

export const combinedGuard = M.interface('GreeterWithAdmin', {
...greetGuard.methodGuards,
...adminGuard.methodGuards,
});

export const prepareGreeterSingleton = (zone, label, nick) => {
const myThis = Object.freeze({ state: { nick } });
return zone.exo(label, combinedGuard, {
...bindAllMethodsTo(greetFacet, myThis),
...bindAllMethodsTo(adminFacet, myThis),
});
};

export const prepareGreeter = zone =>
zone.exoClass('Greeter', combinedGuard, nick => ({ nick }), {
...greetFacet,
...adminFacet,
});

export const prepareGreeterKit = zone =>
zone.exoClassKit(
'GreeterKit',
{ greeter: greetGuard, admin: adminGuard },
nick => ({ nick }),
{
greeter: greetFacet,
admin: adminFacet,
},
);
Loading

0 comments on commit c4e4693

Please sign in to comment.