chore: Do the migrations

This commit is contained in:
BluJ
2023-04-24 15:52:43 -06:00
parent 75ec297be1
commit 50cfc7aa43
19 changed files with 167 additions and 98 deletions

View File

@@ -3,14 +3,17 @@ version = $(shell git tag --sort=committerdate | tail -1)
test: $(TS_FILES) lib/test/output.ts
npm test
make clean:
rm -rf dist
clean:
rm -rf dist/* | true
lib/test/output.ts: lib/test/makeOutput.ts scripts/oldSpecToBuilder.ts
npm run buildOutput
bundle: fmt $(TS_FILES) package.json .FORCE node_modules test
bundle: clean fmt $(TS_FILES) package.json .FORCE node_modules test
npx tsc
cp package.json dist/package.json
cp README.md dist/README.md
cp LICENSE dist/LICENSE
check:
npm run check
@@ -22,9 +25,6 @@ node_modules: package.json
npm install
publish: clean bundle package.json README.md LICENSE
cp package.json dist/package.json
cp README.md dist/README.md
cp LICENSE dist/LICENSE
cd dist && npm publish
link: bundle
cp package.json dist/package.json

View File

@@ -8,13 +8,13 @@ export function setupActions(...createdActions: CreatedAction<any, any>[]) {
actions[action.metaData.id] = action.exportedAction;
}
const manifestActions = async (effects: Effects) => {
const initializeActions = async (effects: Effects) => {
for (const action of createdActions) {
action.exportAction(effects);
}
};
return {
actions,
manifestActions,
initializeActions,
};
}

View File

@@ -1,17 +1,18 @@
import { GenericManifest } from "../manifest/ManifestTypes";
import * as T from "../types";
export type BACKUP = "BACKUP";
export const DEFAULT_OPTIONS: T.BackupOptions = {
delete: true,
force: true,
ignoreExisting: false,
exclude: [],
};
type BackupSet = {
type BackupSet<Volumes extends string> = {
srcPath: string;
srcVolume: string;
srcVolume: Volumes | BACKUP;
dstPath: string;
dstVolume: string;
dstVolume: Volumes | BACKUP;
options?: Partial<T.BackupOptions>;
};
/**
@@ -37,13 +38,13 @@ type BackupSet = {
* ```
*/
export class Backups<M extends GenericManifest> {
static BACKUP = "BACKUP" as const;
static BACKUP: BACKUP = "BACKUP";
constructor(
private options = DEFAULT_OPTIONS,
private backupSet = [] as BackupSet[],
private backupSet = [] as BackupSet<keyof M["volumes"] & string>[],
) {}
static volumes<M extends GenericManifest>(
static volumes<M extends GenericManifest = never>(
...volumeNames: Array<keyof M["volumes"] & string>
) {
return new Backups().addSets(
@@ -55,10 +56,14 @@ export class Backups<M extends GenericManifest> {
})),
);
}
static addSets(...options: BackupSet[]) {
static addSets<M extends GenericManifest = never>(
...options: BackupSet<keyof M["volumes"] & string>[]
) {
return new Backups().addSets(...options);
}
static with_options(options?: Partial<T.BackupOptions>) {
static with_options<M extends GenericManifest = never>(
options?: Partial<T.BackupOptions>,
) {
return new Backups({ ...DEFAULT_OPTIONS, ...options });
}
set_options(options?: Partial<T.BackupOptions>) {
@@ -78,7 +83,7 @@ export class Backups<M extends GenericManifest> {
})),
);
}
addSets(...options: BackupSet[]) {
addSets(...options: BackupSet<keyof M["volumes"] & string>[]) {
options.forEach((x) =>
this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }),
);

View File

@@ -1,7 +1,7 @@
import { string } from "ts-matches";
import { Backups } from ".";
import { GenericManifest } from "../manifest/ManifestTypes";
import { BackupOptions } from "../types";
import { BackupOptions, ExpectedExports } from "../types";
export type SetupBackupsParams<M extends GenericManifest> =
| [Partial<BackupOptions>, ...Array<keyof M["volumes"] & string>]

View File

@@ -1,7 +1,7 @@
import { Config } from "./builder";
import { DeepPartial, Dependencies, Effects, ExpectedExports } from "../types";
import { InputSpec } from "./configTypes";
import { Utils, nullIfEmpty, utils } from "../util";
import { Utils, nullIfEmpty, once, utils } from "../util";
import { TypeFromProps } from "../util/propertiesMatcher";
declare const dependencyProof: unique symbol;
@@ -30,11 +30,11 @@ export function setupConfig<WD, A extends Config<InputSpec>>(
write: Save<WD, TypeFromProps<A>>,
read: Read<WD, TypeFromProps<A>>,
) {
const validator = spec.validator();
const validator = once(() => spec.validator());
return {
setConfig: (async ({ effects, input }) => {
if (!validator.test(input)) {
await effects.error(String(validator.errorMessage(input)));
if (!validator().test(input)) {
await effects.error(String(validator().errorMessage(input)));
return { error: "Set config type error for config" };
}
await write({

View File

@@ -9,7 +9,9 @@ describe("EmVer", () => {
expect(checker.check("1.2.3.4")).toEqual(true);
});
test("rangeOf('*') invalid", () => {
// @ts-expect-error
expect(() => checker.check("a")).toThrow();
// @ts-expect-error
expect(() => checker.check("")).toThrow();
expect(() => checker.check("1..3")).toThrow();
});
@@ -18,6 +20,7 @@ describe("EmVer", () => {
{
const checker = rangeOf(">1.2.3.4");
test(`rangeOf(">1.2.3.4") valid`, () => {
expect(checker.check("2-beta123")).toEqual(true);
expect(checker.check("2")).toEqual(true);
expect(checker.check("1.2.3.5")).toEqual(true);
expect(checker.check("1.2.3.4.1")).toEqual(true);

View File

@@ -1,6 +1,8 @@
import * as matches from "ts-matches";
const starSub = /((\d+\.)*\d+)\.\*/;
// prettier-ignore
export type ValidEmVer = `${'>' | '<' | '>=' | '<=' | '=' | ''}${number | '*'}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`-${string}` | ""}`;
function incrementLastNumber(list: number[]) {
const newList = [...list];
@@ -61,7 +63,7 @@ export class EmVer {
* Or an already made emver
* IsUnsafe
*/
static from(range: string | EmVer): EmVer {
static from(range: ValidEmVer | EmVer): EmVer {
if (range instanceof EmVer) {
return range;
}
@@ -71,22 +73,26 @@ export class EmVer {
* Convert the range, should be 1.2.* or * into a emver
* IsUnsafe
*/
static parse(range: string): EmVer {
static parse(rangeExtra: string): EmVer {
const [range, extra] = rangeExtra.split("-");
const values = range.split(".").map((x) => parseInt(x));
for (const value of values) {
if (isNaN(value)) {
throw new Error(`Couldn't parse range: ${range}`);
}
}
return new EmVer(values);
return new EmVer(values, extra);
}
private constructor(public readonly values: number[]) {}
private constructor(
public readonly values: number[],
readonly extra: string | null,
) {}
/**
* Used when we need a new emver that has the last number incremented, used in the 1.* like things
*/
public withLastIncremented() {
return new EmVer(incrementLastNumber(this.values));
return new EmVer(incrementLastNumber(this.values), null);
}
public greaterThan(other: EmVer): boolean {
@@ -153,6 +159,10 @@ export class EmVer {
.when("less", () => -1 as const)
.unwrap();
}
toString() {
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}`;
}
}
/**
@@ -248,7 +258,7 @@ export class Checker {
* Check is the function that will be given a emver or unparsed emver and should give if it follows
* a pattern
*/
public readonly check: (value: string | EmVer) => boolean,
public readonly check: (value: ValidEmVer | EmVer) => boolean,
) {}
/**

View File

@@ -1,53 +0,0 @@
import { ExpectedExports } from "../types";
declare const ActionProof: unique symbol;
export type ActionReceipt = {
[ActionProof]: never;
};
export function noActions(): ActionReceipt {
return {} as ActionReceipt;
}
declare const MigrationProof: unique symbol;
export type MigrationReceipt = {
[MigrationProof]: never;
};
export function noMigrationsUp(): MigrationReceipt {
return {} as MigrationReceipt;
}
export function migrationUp(fn: () => Promise<unknown>): MigrationReceipt {
fn();
return {} as MigrationReceipt;
}
declare const MigrationDownProof: unique symbol;
export type MigrationDownReceipt = {
[MigrationDownProof]: never;
};
export function noMigrationsDown(): MigrationDownReceipt {
return {} as MigrationDownReceipt;
}
export function migrationDown(
fn: () => Promise<unknown>,
): MigrationDownReceipt {
fn();
return {} as MigrationDownReceipt;
}
export function setupInit(
fn: (
...args: Parameters<ExpectedExports.init>
) => Promise<[MigrationReceipt, ActionReceipt]>,
) {
const initFn: ExpectedExports.init = (...args) => fn(...args);
return initFn;
}
export function setupUninit(
fn: (
...args: Parameters<ExpectedExports.uninit>
) => Promise<[MigrationDownReceipt]>,
) {
const uninitFn: ExpectedExports.uninit = (...args) => fn(...args);
return uninitFn;
}

View File

@@ -1,4 +1,5 @@
import { Effects, ExpectedExports } from "../types";
import { Utils, utils } from "../util";
import { Daemons } from "./Daemons";
export * as network from "./exportInterfaces";
export { LocalBinding } from "./LocalBinding";
@@ -21,14 +22,18 @@ export { Daemons } from "./Daemons";
* @param fn
* @returns
*/
export const runningMain: (
export const setupMain = <WrapperData>(
fn: (o: {
effects: Effects;
started(onTerm: () => void): null;
utils: Utils<WrapperData>;
}) => Promise<Daemons<any>>,
) => ExpectedExports.main = (fn) => {
): ExpectedExports.main => {
return async (options) => {
const result = await fn(options);
const result = await fn({
...options,
utils: utils<WrapperData>(options.effects),
});
await result.build().then((x) => x.wait());
};
};

View File

@@ -1,3 +1,5 @@
import { ValidEmVer } from "../emverLite/mod";
export interface Container {
image: string;
mounts: Record<string, string>;
@@ -5,10 +7,12 @@ export interface Container {
sigtermTimeout?: string; // if more than 30s to shutdown
}
export type ManifestVersion = ValidEmVer;
export interface GenericManifest {
id: string;
title: string;
version: string;
version: ManifestVersion;
releaseNotes: string;
license: string; // name of license
replaces: string[];

View File

@@ -1,8 +1,9 @@
import { GenericManifest } from "./ManifestTypes";
import { GenericManifest, ManifestVersion } from "./ManifestTypes";
export function setupManifest<
M extends GenericManifest & { id: Id },
M extends GenericManifest & { id: Id; version: Version },
Id extends string,
Version extends ManifestVersion,
>(manifest: M): M {
return manifest;
}

View File

@@ -0,0 +1,28 @@
import { ManifestVersion } from "../manifest/ManifestTypes";
import { Effects } from "../types";
import { Utils } from "../util";
export class Migration<Version extends ManifestVersion> {
constructor(
readonly options: {
version: Version;
up: (opts: { effects: Effects }) => Promise<void>;
down: (opts: { effects: Effects }) => Promise<void>;
},
) {}
static of<Version extends ManifestVersion>(options: {
version: Version;
up: (opts: { effects: Effects }) => Promise<void>;
down: (opts: { effects: Effects }) => Promise<void>;
}) {
return new Migration(options);
}
async up(opts: { effects: Effects }) {
this.up(opts);
}
async down(opts: { effects: Effects }) {
this.down(opts);
}
}

View File

@@ -0,0 +1,52 @@
import { setupActions } from "../actions/setupActions";
import { EmVer } from "../emverLite/mod";
import { GenericManifest } from "../manifest/ManifestTypes";
import { ExpectedExports } from "../types";
import { once } from "../util/once";
import { Migration } from "./Migration";
export function setupMigrations<Migrations extends Array<Migration<any>>>(
manifest: GenericManifest,
initializeActions: ReturnType<typeof setupActions>["initializeActions"],
...migrations: EnsureUniqueId<Migrations>
) {
const sortedMigrations = once(() => {
const migrationsAsVersions = (migrations as Array<Migration<any>>).map(
(x) => [EmVer.parse(x.options.version), x] as const,
);
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]));
return migrationsAsVersions;
});
const currentVersion = once(() => EmVer.parse(manifest.version));
const init: ExpectedExports.init = async ({ effects, previousVersion }) => {
await initializeActions(effects);
if (!!previousVersion) {
const previousVersionEmVer = EmVer.parse(previousVersion);
for (const [_, migration] of sortedMigrations()
.filter((x) => x[0].greaterThan(previousVersionEmVer))
.filter((x) => x[0].lessThanOrEqual(currentVersion()))) {
await migration.up({ effects });
}
}
};
const uninit: ExpectedExports.uninit = async ({ effects, nextVersion }) => {
if (!!nextVersion) {
const nextVersionEmVer = EmVer.parse(nextVersion);
const reversed = [...sortedMigrations()].reverse();
for (const [_, migration] of reversed
.filter((x) => x[0].greaterThan(nextVersionEmVer))
.filter((x) => x[0].lessThanOrEqual(currentVersion()))) {
await migration.down({ effects });
}
}
};
return { init, uninit };
}
// prettier-ignore
export type EnsureUniqueId<A, B = A, ids = never> =
B extends [] ? A :
B extends [Migration<infer id>, ...infer Rest] ? (
id extends ids ? "One of the ids are not unique"[] :
EnsureUniqueId<A, Rest, id | ids>
) : "There exists a migration that is not a Migration"[]

View File

@@ -1,4 +1,5 @@
import { ExpectedExports, Properties } from "../types";
import { Utils, utils } from "../util";
import "../util/extensions";
import { PropertyGroup } from "./PropertyGroup";
import { PropertyString } from "./PropertyString";
@@ -18,13 +19,17 @@ export type UnionToIntersection<T> = ((x: T) => any) extends (x: infer R) => any
* @param fn
* @returns
*/
export function setupPropertiesExport(
fn: (
...args: Parameters<ExpectedExports.properties>
) => void | Promise<void> | Promise<(PropertyGroup | PropertyString)[]>,
export function setupProperties<WrapperData>(
fn: (args: {
wrapperData: WrapperData;
}) => void | Promise<void> | Promise<(PropertyGroup | PropertyString)[]>,
): ExpectedExports.properties {
return (async (...args) => {
const result = await fn(...args);
return (async (options) => {
const result = await fn(
options as {
wrapperData: WrapperData & typeof options.wrapperData;
},
);
if (result) {
const answer: Properties = result.map((x) => x.data);
return answer;

View File

@@ -1,7 +1,6 @@
export * as configTypes from "./config/configTypes";
import { InputSpec } from "./config/configTypes";
import { DependenciesReceipt } from "./config/setupConfig";
import { ActionReceipt } from "./init";
export type ExportedAction = (options: {
effects: Effects;
@@ -350,7 +349,7 @@ export type Effects = {
*
* @param options
*/
exportAction(options: ActionMetaData): Promise<void & ActionReceipt>;
exportAction(options: ActionMetaData): Promise<void>;
/**
* Remove an action that was exported. Used problably during main or during setConfig.
*/

View File

@@ -17,6 +17,7 @@ export { FileHelper } from "./fileHelper";
export { getWrapperData } from "./getWrapperData";
export { deepEqual } from "./deepEqual";
export { deepMerge } from "./deepMerge";
export { once } from "./once";
/** Used to check if the file exists before hand */
export const exists = (

9
lib/util/once.ts Normal file
View File

@@ -0,0 +1,9 @@
export function once<B>(fn: () => B): () => B {
let result: [B] | [] = [];
return () => {
if (!result.length) {
result = [fn()];
}
return result[0];
};
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "start-sdk",
"version": "0.4.0-lib0.charlie33",
"version": "0.4.0-lib0.charlie34",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "start-sdk",
"version": "0.4.0-lib0.charlie33",
"version": "0.4.0-lib0.charlie34",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",

View File

@@ -1,6 +1,6 @@
{
"name": "start-sdk",
"version": "0.4.0-lib0.charlie33",
"version": "0.4.0-lib0.charlie34",
"description": "For making the patterns that are wanted in making services for the startOS.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",