@milkdown/ctx
Advanced tools
Comparing version 6.5.4 to 7.0.0-next.0
@@ -1,8 +0,9 @@ | ||
import type { Slice, SliceValue } from './slice'; | ||
export interface Container { | ||
readonly sliceMap: Map<symbol, SliceValue>; | ||
readonly getSlice: <T, N extends string = string>(slice: Slice<T, N> | N) => SliceValue<T, N>; | ||
readonly removeSlice: <T, N extends string = string>(slice: Slice<T, N> | N) => void; | ||
import type { Slice, SliceType } from './slice'; | ||
export type SliceMap = Map<symbol, Slice>; | ||
export declare class Container { | ||
sliceMap: SliceMap; | ||
get: <T, N extends string = string>(slice: N | SliceType<T, N>) => Slice<T, N>; | ||
remove: <T, N extends string = string>(slice: N | SliceType<T, N>) => void; | ||
has: <T, N extends string = string>(slice: N | SliceType<T, N>) => boolean; | ||
} | ||
export declare const createContainer: () => Container; | ||
//# sourceMappingURL=container.d.ts.map |
@@ -1,16 +0,19 @@ | ||
export interface SliceValue<T = unknown, N extends string = string> { | ||
import type { SliceMap } from './container'; | ||
export declare class Slice<T = any, N extends string = string> { | ||
#private; | ||
readonly type: SliceType<T, N>; | ||
constructor(container: SliceMap, value: T, type: SliceType<T, N>); | ||
set: (value: T) => void; | ||
get: () => T; | ||
update: (updater: (prev: T) => T) => void; | ||
} | ||
export declare class SliceType<T = any, N extends string = string> { | ||
readonly id: symbol; | ||
readonly name: N; | ||
readonly set: (value: T) => void; | ||
readonly get: () => T; | ||
readonly update: (updater: (prev: T) => T) => void; | ||
} | ||
export type SliceMap = Map<symbol, SliceValue>; | ||
export interface Slice<T, N extends string = string> { | ||
readonly id: symbol; | ||
readonly sliceName: N; | ||
readonly _typeInfo: () => T; | ||
(container: SliceMap, resetValue?: T): SliceValue<T>; | ||
readonly _defaultValue: T; | ||
constructor(value: T, name: N); | ||
create(container: SliceMap, value?: T): Slice<T, N>; | ||
} | ||
export declare const createSlice: <T, N extends string = string>(value: T, name: N) => Slice<T, N>; | ||
export declare const createSlice: <T = any, N extends string = string>(value: T, name: N) => SliceType<T, N>; | ||
//# sourceMappingURL=slice.d.ts.map |
export * from './context'; | ||
export * from './plugin'; | ||
export * from './timing'; | ||
export * from './timer'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,151 +0,156 @@ | ||
var P = Object.defineProperty; | ||
var k = (e, t, i) => t in e ? P(e, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : e[t] = i; | ||
var r = (e, t, i) => (k(e, typeof t != "symbol" ? t + "" : t, i), i), T = (e, t, i) => { | ||
if (!t.has(e)) | ||
throw TypeError("Cannot " + i); | ||
var g = Object.defineProperty; | ||
var v = (i, t, e) => t in i ? g(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e; | ||
var s = (i, t, e) => (v(i, typeof t != "symbol" ? t + "" : t, e), e), y = (i, t, e) => { | ||
if (!t.has(i)) | ||
throw TypeError("Cannot " + e); | ||
}; | ||
var n = (e, t, i) => (T(e, t, "read from private field"), i ? i.call(e) : t.get(e)), u = (e, t, i) => { | ||
if (t.has(e)) | ||
var r = (i, t, e) => (y(i, t, "read from private field"), e ? e.call(i) : t.get(i)), o = (i, t, e) => { | ||
if (t.has(i)) | ||
throw TypeError("Cannot add the same private member more than once"); | ||
t instanceof WeakSet ? t.add(e) : t.set(e, i); | ||
}, h = (e, t, i, s) => (T(e, t, "write to private field"), s ? s.call(e, i) : t.set(e, i), i); | ||
import { contextNotFound as M, ctxCallOutOfScope as b, timerNotFound as x } from "@milkdown/exception"; | ||
const F = () => { | ||
const e = /* @__PURE__ */ new Map(); | ||
return { sliceMap: e, getSlice: (s) => { | ||
const o = typeof s == "string" ? [...e.values()].find((c) => c.name === s) : e.get(s.id); | ||
if (!o) { | ||
const c = typeof s == "string" ? s : s.sliceName; | ||
throw M(c); | ||
} | ||
return o; | ||
}, removeSlice: (s) => { | ||
const o = typeof s == "string" ? [...e.values()].find((c) => c.name === s) : e.get(s.id); | ||
!o || e.delete(o.id); | ||
} }; | ||
}, j = (e) => Array.isArray(e) ? [...e] : typeof e == "object" ? { ...e } : e, I = (e, t) => { | ||
const i = Symbol(`Context-${t}`), s = (o, c = j(e)) => { | ||
let a = c; | ||
const g = { | ||
name: t, | ||
id: i, | ||
set: (m) => { | ||
a = m; | ||
}, | ||
get: () => a, | ||
update: (m) => { | ||
a = m(a); | ||
t instanceof WeakSet ? t.add(i) : t.set(i, e); | ||
}, a = (i, t, e, n) => (y(i, t, "write to private field"), n ? n.call(i, e) : t.set(i, e), e); | ||
import { contextNotFound as w, ctxCallOutOfScope as M, timerNotFound as T } from "@milkdown/exception"; | ||
class b { | ||
constructor() { | ||
s(this, "sliceMap", /* @__PURE__ */ new Map()); | ||
s(this, "get", (t) => { | ||
const e = typeof t == "string" ? [...this.sliceMap.values()].find((n) => n.type.name === t) : this.sliceMap.get(t.id); | ||
if (!e) { | ||
const n = typeof t == "string" ? t : t.name; | ||
throw w(n); | ||
} | ||
return e; | ||
}); | ||
s(this, "remove", (t) => { | ||
const e = typeof t == "string" ? [...this.sliceMap.values()].find((n) => n.type.name === t) : this.sliceMap.get(t.id); | ||
!e || this.sliceMap.delete(e.type.id); | ||
}); | ||
s(this, "has", (t) => typeof t == "string" ? [...this.sliceMap.values()].some((e) => e.type.name === t) : this.sliceMap.has(t.id)); | ||
} | ||
} | ||
var h; | ||
class x { | ||
constructor(t, e, n) { | ||
s(this, "type"); | ||
o(this, h, void 0); | ||
s(this, "set", (t) => { | ||
a(this, h, t); | ||
}); | ||
s(this, "get", () => r(this, h)); | ||
s(this, "update", (t) => { | ||
a(this, h, t(r(this, h))); | ||
}); | ||
this.type = n, a(this, h, e), t.set(n.id, this); | ||
} | ||
} | ||
h = new WeakMap(); | ||
class C { | ||
constructor(t, e) { | ||
s(this, "id"); | ||
s(this, "name"); | ||
s(this, "_typeInfo"); | ||
s(this, "_defaultValue"); | ||
this.id = Symbol(`Context-${e}`), this.name = e, this._defaultValue = t, this._typeInfo = () => { | ||
throw M(); | ||
}; | ||
return o.set(i, g), g; | ||
}; | ||
return s.sliceName = t, s.id = i, s._typeInfo = () => { | ||
throw b(); | ||
}, s; | ||
}; | ||
var p, v; | ||
class O { | ||
constructor(t, i) { | ||
u(this, p, void 0); | ||
u(this, v, void 0); | ||
r(this, "use", (t) => n(this, p).getSlice(t)); | ||
r(this, "get", (t) => this.use(t).get()); | ||
r(this, "set", (t, i) => this.use(t).set(i)); | ||
r(this, "update", (t, i) => this.use(t).update(i)); | ||
r(this, "timing", (t) => n(this, v).get(t)); | ||
r(this, "done", (t) => this.timing(t).done()); | ||
r(this, "wait", (t) => this.timing(t)()); | ||
r(this, "waitTimers", async (t) => { | ||
await Promise.all(this.get(t).map((i) => this.wait(i))); | ||
} | ||
create(t, e = this._defaultValue) { | ||
return new x(t, e, this); | ||
} | ||
} | ||
const L = (i, t) => new C(i, t); | ||
var c, m; | ||
class P { | ||
constructor(t, e) { | ||
o(this, c, void 0); | ||
o(this, m, void 0); | ||
s(this, "inject", (t, e) => { | ||
const n = t.create(r(this, c).sliceMap); | ||
return e != null && n.set(e), this; | ||
}); | ||
h(this, p, t), h(this, v, i); | ||
s(this, "remove", (t) => (r(this, c).remove(t), this)); | ||
s(this, "record", (t) => (t.create(r(this, m).store), this)); | ||
s(this, "clearTimer", (t) => (r(this, m).remove(t), this)); | ||
s(this, "isInjected", (t) => r(this, c).has(t)); | ||
s(this, "isRecorded", (t) => r(this, m).has(t)); | ||
s(this, "use", (t) => r(this, c).get(t)); | ||
s(this, "get", (t) => this.use(t).get()); | ||
s(this, "set", (t, e) => this.use(t).set(e)); | ||
s(this, "update", (t, e) => this.use(t).update(e)); | ||
s(this, "timer", (t) => r(this, m).get(t)); | ||
s(this, "done", (t) => this.timer(t).done()); | ||
s(this, "wait", (t) => this.timer(t).start()); | ||
s(this, "waitTimers", async (t) => { | ||
await Promise.all(this.get(t).map((e) => this.wait(e))); | ||
}); | ||
a(this, c, t), a(this, m, e); | ||
} | ||
} | ||
p = new WeakMap(), v = new WeakMap(); | ||
var l, d; | ||
class $ { | ||
constructor(t, i) { | ||
u(this, l, void 0); | ||
u(this, d, void 0); | ||
r(this, "inject", (t, i) => (t(n(this, l).sliceMap, i), this)); | ||
r(this, "remove", (t) => (n(this, l).removeSlice(t), this)); | ||
r(this, "record", (t) => (t(n(this, d).store), this)); | ||
r(this, "clearTimer", (t) => (n(this, d).remove(t), this)); | ||
r(this, "use", (t) => n(this, l).getSlice(t)); | ||
r(this, "get", (t) => this.use(t).get()); | ||
r(this, "set", (t, i) => this.use(t).set(i)); | ||
r(this, "update", (t, i) => this.use(t).update(i)); | ||
r(this, "timing", (t) => n(this, d).get(t)); | ||
r(this, "wait", (t) => this.timing(t)()); | ||
r(this, "done", (t) => this.timing(t).done()); | ||
r(this, "waitTimers", async (t) => { | ||
await Promise.all(this.get(t).map((i) => this.wait(i))); | ||
c = new WeakMap(), m = new WeakMap(); | ||
class V { | ||
constructor() { | ||
s(this, "store", /* @__PURE__ */ new Map()); | ||
s(this, "get", (t) => { | ||
const e = this.store.get(t.id); | ||
if (!e) | ||
throw T(t.name); | ||
return e; | ||
}); | ||
h(this, l, t), h(this, d, i); | ||
s(this, "remove", (t) => { | ||
this.store.delete(t.id); | ||
}); | ||
s(this, "has", (t) => this.store.has(t.id)); | ||
} | ||
} | ||
l = new WeakMap(), d = new WeakMap(); | ||
var f, w; | ||
class _ { | ||
constructor(t, i) { | ||
u(this, f, void 0); | ||
u(this, w, void 0); | ||
r(this, "clearTimer", (t) => (n(this, w).remove(t), this)); | ||
r(this, "remove", (t) => (n(this, f).removeSlice(t), this)); | ||
h(this, f, t), h(this, w, i); | ||
var l, u, d, p, f; | ||
class S { | ||
constructor(t, e) { | ||
s(this, "type"); | ||
o(this, l, null); | ||
o(this, u, null); | ||
o(this, d, void 0); | ||
s(this, "start", () => (r(this, l) ?? a(this, l, new Promise((t, e) => { | ||
a(this, u, (n) => { | ||
n instanceof CustomEvent && n.detail.id === r(this, d) && (r(this, p).call(this), n.stopImmediatePropagation(), t()); | ||
}), r(this, f).call(this, () => { | ||
r(this, p).call(this), e(new Error(`Timing ${this.type.name} timeout.`)); | ||
}), addEventListener(this.type.name, r(this, u)); | ||
})), r(this, l))); | ||
s(this, "done", () => { | ||
const t = new CustomEvent(this.type.name, { detail: { id: r(this, d) } }); | ||
dispatchEvent(t); | ||
}); | ||
o(this, p, () => { | ||
r(this, u) && removeEventListener(this.type.name, r(this, u)); | ||
}); | ||
o(this, f, (t) => { | ||
setTimeout(() => { | ||
t(); | ||
}, this.type.timeout); | ||
}); | ||
a(this, d, Symbol(e.name)), this.type = e, t.set(e.id, this); | ||
} | ||
} | ||
f = new WeakMap(), w = new WeakMap(); | ||
var y, S; | ||
class q { | ||
constructor(t, i) { | ||
u(this, y, void 0); | ||
u(this, S, void 0); | ||
r(this, "inject", (t, i) => (t(n(this, y).sliceMap, i), this)); | ||
r(this, "record", (t) => (t(n(this, S).store), this)); | ||
h(this, y, t), h(this, S, i); | ||
l = new WeakMap(), u = new WeakMap(), d = new WeakMap(), p = new WeakMap(), f = new WeakMap(); | ||
class E { | ||
constructor(t, e = 3e3) { | ||
s(this, "id"); | ||
s(this, "name"); | ||
s(this, "timeout"); | ||
s(this, "create", (t) => new S(t, this)); | ||
this.id = Symbol(`Timer-${t}`), this.name = t, this.timeout = e; | ||
} | ||
} | ||
y = new WeakMap(), S = new WeakMap(); | ||
const z = () => { | ||
const e = /* @__PURE__ */ new Map(); | ||
return { | ||
store: e, | ||
get: (s) => { | ||
const o = e.get(s.id); | ||
if (!o) | ||
throw x(s.timerName); | ||
return o; | ||
}, | ||
remove: (s) => { | ||
e.delete(s.id); | ||
} | ||
}; | ||
}, B = (e, t = 3e3) => { | ||
const i = Symbol("Timer"), s = (o) => { | ||
let c = null, a; | ||
const g = Symbol(e), m = () => c != null ? c : c = new Promise((C, N) => { | ||
a = (E) => { | ||
E instanceof CustomEvent && E.detail.id === g && (removeEventListener(e, a), E.stopImmediatePropagation(), C()); | ||
}, setTimeout(() => { | ||
N(new Error(`Timing ${e} timeout.`)), removeEventListener(e, a); | ||
}, t), addEventListener(e, a); | ||
}); | ||
return m.done = () => { | ||
const C = new CustomEvent(e, { detail: { id: g } }); | ||
dispatchEvent(C); | ||
}, o.set(i, m), m; | ||
}; | ||
return s.id = i, s.timerName = e, s; | ||
}; | ||
const $ = (i, t = 3e3) => new E(i, t); | ||
export { | ||
O as Ctx, | ||
$ as Env, | ||
_ as Post, | ||
q as Pre, | ||
z as createClock, | ||
F as createContainer, | ||
I as createSlice, | ||
B as createTimer | ||
V as Clock, | ||
b as Container, | ||
P as Ctx, | ||
x as Slice, | ||
C as SliceType, | ||
S as Timer, | ||
E as TimerType, | ||
L as createSlice, | ||
$ as createTimer | ||
}; | ||
//# sourceMappingURL=index.es.js.map |
@@ -1,73 +0,21 @@ | ||
import type { Container, Slice, SliceValue } from '../context'; | ||
import type { Clock, Timer } from '../timing'; | ||
/** | ||
* The ctx object that can be accessed in plugin and action. | ||
*/ | ||
import type { Container, Slice, SliceType } from '../context'; | ||
import type { Clock, TimerType } from '../timer'; | ||
export declare class Ctx { | ||
#private; | ||
constructor(container: Container, clock: Clock); | ||
/** | ||
* Get the slice instance. | ||
* | ||
* @param slice - The slice or slice name that needs to be used. | ||
* @returns The slice instance. | ||
*/ | ||
readonly use: <T, N extends string = string>(slice: N | Slice<T, N>) => SliceValue<T, N>; | ||
/** | ||
* Get the slice value. | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @returns The slice value. | ||
*/ | ||
readonly get: <T, N extends string>(slice: Slice<T, N>) => T; | ||
/** | ||
* Set the slice value. | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @param value - The default value. | ||
* @returns | ||
*/ | ||
readonly set: <T, N extends string>(slice: Slice<T, N>, value: T) => void; | ||
/** | ||
* Update the slice by its current value. | ||
* | ||
* @example | ||
* ``` | ||
* update(NumberSlice, x => x + 1); | ||
* ``` | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @param updater - The update function, gets current value as parameter and returns new value. | ||
* @returns | ||
*/ | ||
readonly update: <T, N extends string>(slice: Slice<T, N>, updater: (prev: T) => T) => void; | ||
/** | ||
* Get the timer instance. | ||
* | ||
* @param timer - The timer needs to be used. | ||
* @returns The timer instance. | ||
*/ | ||
readonly timing: (timer: Timer) => import("../timing").Timing; | ||
/** | ||
* Finish a timer | ||
* | ||
* @param timer - The timer needs to be finished. | ||
* @returns | ||
*/ | ||
readonly done: (timer: Timer) => void; | ||
/** | ||
* Wait for a timer to finish. | ||
* | ||
* @param timer - The timer needs to be used. | ||
* @returns A promise that will be resolved when timer finish. | ||
*/ | ||
readonly wait: (timer: Timer) => Promise<void>; | ||
/** | ||
* Wait for a list of timers in target slice to be all finished. | ||
* | ||
* @param slice - The slice that holds a list of timer. | ||
* @returns A promise that will be resolved when all timers finish. | ||
*/ | ||
readonly waitTimers: (slice: Slice<Timer[]>) => Promise<void>; | ||
readonly inject: <T>(sliceType: SliceType<T, string>, value?: T | undefined) => this; | ||
readonly remove: <T, N extends string = string>(sliceType: N | SliceType<T, N>) => this; | ||
readonly record: (timerType: TimerType) => this; | ||
readonly clearTimer: (timerType: TimerType) => this; | ||
readonly isInjected: <T, N extends string = string>(sliceType: N | SliceType<T, N>) => boolean; | ||
readonly isRecorded: (timerType: TimerType) => boolean; | ||
readonly use: <T, N extends string = string>(sliceType: N | SliceType<T, N>) => Slice<T, N>; | ||
readonly get: <T, N extends string>(sliceType: SliceType<T, N>) => T; | ||
readonly set: <T, N extends string>(sliceType: SliceType<T, N>, value: T) => void; | ||
readonly update: <T, N extends string>(sliceType: SliceType<T, N>, updater: (prev: T) => T) => void; | ||
readonly timer: (timer: TimerType) => import("../timer").Timer; | ||
readonly done: (timer: TimerType) => void; | ||
readonly wait: (timer: TimerType) => Promise<void>; | ||
readonly waitTimers: (slice: SliceType<TimerType[]>) => Promise<void>; | ||
} | ||
//# sourceMappingURL=ctx.d.ts.map |
export * from './ctx'; | ||
export * from './env'; | ||
export * from './post'; | ||
export * from './pre'; | ||
export * from './types'; | ||
//# sourceMappingURL=index.d.ts.map |
import type { Ctx } from './ctx'; | ||
import type { Post } from './post'; | ||
import type { Pre } from './pre'; | ||
export type Cleanup = (post: Post) => void | Promise<void>; | ||
export type HandlerReturnType = void | Promise<void> | Cleanup | Promise<Cleanup>; | ||
export type CtxHandler = (ctx: Ctx) => HandlerReturnType; | ||
export type MilkdownPlugin = (pre: Pre) => CtxHandler; | ||
export type Cleanup = () => void | Promise<void>; | ||
export type RunnerReturnType = void | Promise<void> | Cleanup | Promise<Cleanup>; | ||
export type CtxRunner = () => RunnerReturnType; | ||
export type MilkdownPlugin = (ctx: Ctx) => CtxRunner; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@milkdown/ctx", | ||
"type": "module", | ||
"version": "6.5.4", | ||
"version": "7.0.0-next.0", | ||
"license": "MIT", | ||
@@ -29,3 +29,3 @@ "repository": { | ||
"tslib": "^2.4.0", | ||
"@milkdown/exception": "6.5.4" | ||
"@milkdown/exception": "7.0.0-next.0" | ||
}, | ||
@@ -32,0 +32,0 @@ "nx": { |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import { describe, expect, it } from 'vitest' | ||
import { createContainer, createSlice } from '.' | ||
import { Container, SliceType } from '.' | ||
describe('context/container', () => { | ||
it('sliceMap', () => { | ||
const container = createContainer() | ||
const container = new Container() | ||
@@ -14,30 +14,43 @@ expect(container.sliceMap).toEqual(new Map()) | ||
it('getSlice', () => { | ||
const container = createContainer() | ||
const ctx = createSlice(0, 'num') | ||
const container = new Container() | ||
const ctx = new SliceType(0, 'num') | ||
ctx(container.sliceMap) | ||
ctx.create(container.sliceMap) | ||
expect(container.getSlice(ctx).id).toBe(ctx.id) | ||
expect(container.getSlice(ctx).get()).toBe(0) | ||
expect(container.get(ctx).type.id).toBe(ctx.id) | ||
expect(container.get(ctx).get()).toBe(0) | ||
container.getSlice(ctx).set(10) | ||
container.get(ctx).set(10) | ||
expect(container.getSlice(ctx).get()).toBe(10) | ||
expect(container.get(ctx).get()).toBe(10) | ||
expect(container.getSlice<number>('num').get()).toBe(10) | ||
expect(container.get<number>('num').get()).toBe(10) | ||
}) | ||
it('removeSlice', () => { | ||
const container = createContainer() | ||
const ctx = createSlice(0, 'num') | ||
const container = new Container() | ||
const ctx = new SliceType(0, 'num') | ||
ctx(container.sliceMap) | ||
ctx.create(container.sliceMap) | ||
expect(container.getSlice(ctx).id).toBe(ctx.id) | ||
expect(container.getSlice(ctx).get()).toBe(0) | ||
expect(container.get(ctx).type.id).toBe(ctx.id) | ||
expect(container.get(ctx).get()).toBe(0) | ||
container.removeSlice(ctx) | ||
container.remove(ctx) | ||
expect(() => container.getSlice(ctx)).toThrow() | ||
expect(() => container.get(ctx)).toThrow() | ||
}) | ||
it('hasSlice', () => { | ||
const container = new Container() | ||
const ctx = new SliceType(0, 'num') | ||
ctx.create(container.sliceMap) | ||
expect(container.has(ctx)).toBeTruthy() | ||
container.remove(ctx) | ||
expect(container.has(ctx)).toBeFalsy() | ||
}) | ||
}) |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import { contextNotFound } from '@milkdown/exception' | ||
import type { Slice, SliceValue } from './slice' | ||
import type { Slice, SliceType } from './slice' | ||
export interface Container { | ||
readonly sliceMap: Map<symbol, SliceValue> | ||
readonly getSlice: <T, N extends string = string>(slice: Slice<T, N> | N) => SliceValue<T, N> | ||
readonly removeSlice: <T, N extends string = string>(slice: Slice<T, N> | N) => void | ||
} | ||
/// @internal | ||
export type SliceMap = Map<symbol, Slice> | ||
export const createContainer = (): Container => { | ||
const sliceMap: Map<symbol, SliceValue> = new Map() | ||
/// Container is a map of slices. | ||
export class Container { | ||
/// @internal | ||
sliceMap: SliceMap = new Map() | ||
const getSlice = <T, N extends string = string>(slice: Slice<T, N> | N): SliceValue<T, N> => { | ||
const context | ||
= typeof slice === 'string' ? [...sliceMap.values()].find(x => x.name === slice) : sliceMap.get(slice.id) | ||
/// Get a slice from the container by slice type or slice name. | ||
get = <T, N extends string = string>(slice: SliceType<T, N> | N): Slice<T, N> => { | ||
const context = typeof slice === 'string' | ||
? [...this.sliceMap.values()].find(x => x.type.name === slice) | ||
: this.sliceMap.get(slice.id) | ||
if (!context) { | ||
const name = typeof slice === 'string' ? slice : slice.sliceName | ||
const name = typeof slice === 'string' ? slice : slice.name | ||
throw contextNotFound(name) | ||
} | ||
return context as SliceValue<T, N> | ||
return context as Slice<T, N> | ||
} | ||
const removeSlice = <T, N extends string = string>(slice: Slice<T, N> | N): void => { | ||
const context | ||
= typeof slice === 'string' ? [...sliceMap.values()].find(x => x.name === slice) : sliceMap.get(slice.id) | ||
/// Remove a slice from the container by slice type or slice name. | ||
remove = <T, N extends string = string>(slice: SliceType<T, N> | N): void => { | ||
const context = typeof slice === 'string' | ||
? [...this.sliceMap.values()].find(x => x.type.name === slice) | ||
: this.sliceMap.get(slice.id) | ||
@@ -33,6 +36,12 @@ if (!context) | ||
sliceMap.delete(context.id) | ||
this.sliceMap.delete(context.type.id) | ||
} | ||
return { sliceMap, getSlice, removeSlice } | ||
/// Check if the container has a slice by slice type or slice name. | ||
has = <T, N extends string = string>(slice: SliceType<T, N> | N): boolean => { | ||
if (typeof slice === 'string') | ||
return [...this.sliceMap.values()].some(x => x.type.name === slice) | ||
return this.sliceMap.has(slice.id) | ||
} | ||
} |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import { describe, expect, it } from 'vitest' | ||
import { createSlice } from './slice' | ||
import { SliceType } from './slice' | ||
describe('context/slice', () => { | ||
it('primitive slice', () => { | ||
const factory = createSlice(0, 'primitive') | ||
const sliceType = new SliceType(0, 'primitive') | ||
const map = new Map() | ||
const ctx = factory(map) | ||
const ctx = sliceType.create(map) | ||
expect(factory.sliceName).toBe('primitive') | ||
expect(factory.id).toBeTypeOf('symbol') | ||
expect(ctx.name).toBe('primitive') | ||
expect(ctx.id).toBe(factory.id) | ||
expect(sliceType.name).toBe('primitive') | ||
expect(sliceType.id).toBeTypeOf('symbol') | ||
expect(ctx.type.name).toBe('primitive') | ||
expect(ctx.type.id).toBe(sliceType.id) | ||
@@ -27,28 +27,28 @@ expect(ctx.get()).toBe(0) | ||
it('structure slice', () => { | ||
const factory = createSlice<number[]>([], 'structure') | ||
expect(factory.sliceName).toBe('structure') | ||
const sliceType = new SliceType<number[], 'structure'>([], 'structure') | ||
expect(sliceType.name).toBe('structure') | ||
const map1 = new Map() | ||
const ctx1 = factory(map1) | ||
expect(ctx1.id).toBe(factory.id) | ||
const slice1 = sliceType.create(map1) | ||
expect(slice1.type.id).toBe(sliceType.id) | ||
expect(ctx1.name).toBe('structure') | ||
expect(slice1.type.name).toBe('structure') | ||
const map2 = new Map() | ||
const ctx2 = factory(map2) | ||
const slice2 = sliceType.create(map2) | ||
expect(ctx2.name).toBe('structure') | ||
expect(ctx2.id).toBe(factory.id) | ||
expect(slice2.type.name).toBe('structure') | ||
expect(slice2.type.id).toBe(sliceType.id) | ||
expect(ctx1.get()).toEqual([]) | ||
ctx1.set([1]) | ||
expect(ctx1.get()).toEqual([1]) | ||
expect(slice1.get()).toEqual([]) | ||
slice1.set([1]) | ||
expect(slice1.get()).toEqual([1]) | ||
expect(ctx2.get()).toEqual([]) | ||
expect(slice2.get()).toEqual([]) | ||
ctx1.update(x => x.concat(3)) | ||
expect(ctx1.get()).toEqual([1, 3]) | ||
slice1.update(x => x.concat(3)) | ||
expect(slice1.get()).toEqual([1, 3]) | ||
expect(ctx2.get()).toEqual([]) | ||
expect(slice2.get()).toEqual([]) | ||
}) | ||
}) |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import { ctxCallOutOfScope } from '@milkdown/exception' | ||
import type { SliceMap } from './container' | ||
import { shallowClone } from './shallow-clone' | ||
/// Slice is a value of slice type. | ||
export class Slice<T = any, N extends string = string> { | ||
/// The type of the slice. | ||
readonly type: SliceType<T, N> | ||
export interface SliceValue<T = unknown, N extends string = string> { | ||
readonly id: symbol | ||
readonly name: N | ||
readonly set: (value: T) => void | ||
readonly get: () => T | ||
readonly update: (updater: (prev: T) => T) => void | ||
/// @internal | ||
#value: T | ||
/// @internal | ||
constructor(container: SliceMap, value: T, type: SliceType<T, N>) { | ||
this.type = type | ||
this.#value = value | ||
container.set(type.id, this) | ||
} | ||
/// Set the value of the slice. | ||
set = (value: T) => { | ||
this.#value = value | ||
} | ||
/// Get the value of the slice. | ||
get = () => this.#value | ||
/// Update the value of the slice with a callback. | ||
update = (updater: (prev: T) => T) => { | ||
this.#value = updater(this.#value) | ||
} | ||
} | ||
export type SliceMap = Map<symbol, SliceValue> | ||
export interface Slice<T, N extends string = string> { | ||
/// Slice type can be used to create slices in different containers. | ||
export class SliceType<T = any, N extends string = string> { | ||
/// The unique id of the slice type. | ||
readonly id: symbol | ||
readonly sliceName: N | ||
/// The name of the slice type. | ||
readonly name: N | ||
/// @internal | ||
readonly _typeInfo: () => T | ||
(container: SliceMap, resetValue?: T): SliceValue<T> | ||
} | ||
/// @internal | ||
readonly _defaultValue: T | ||
export const createSlice = <T, N extends string = string>(value: T, name: N): Slice<T, N> => { | ||
const id = Symbol(`Context-${name}`) | ||
const factory = (container: SliceMap, resetValue = shallowClone(value)) => { | ||
let inner = resetValue | ||
const context: SliceValue<T> = { | ||
name, | ||
id, | ||
set: (next) => { | ||
inner = next | ||
}, | ||
get: () => inner, | ||
update: (updater) => { | ||
inner = updater(inner) | ||
}, | ||
/// Create a slice type with a default value and a name. | ||
/// The name should be unique in the container. | ||
constructor(value: T, name: N) { | ||
this.id = Symbol(`Context-${name}`) | ||
this.name = name | ||
this._defaultValue = value | ||
this._typeInfo = (): T => { | ||
throw ctxCallOutOfScope() | ||
} | ||
container.set(id, context as SliceValue) | ||
return context | ||
} | ||
factory.sliceName = name | ||
factory.id = id | ||
factory._typeInfo = (): T => { | ||
throw ctxCallOutOfScope() | ||
/// Create a slice with a container. | ||
/// You can also pass a value to override the default value. | ||
create(container: SliceMap, value: T = this._defaultValue): Slice<T, N> { | ||
return new Slice(container, value, this) | ||
} | ||
} | ||
return factory | ||
} | ||
/// Create a slice type with a default value and a name. | ||
/// This is equivalent to `new SliceType(value, name)`. | ||
export const createSlice = <T = any, N extends string = string>(value: T, name: N) => new SliceType(value, name) |
@@ -5,2 +5,2 @@ /* Copyright 2021, Milkdown by Mirone. */ | ||
export * from './plugin' | ||
export * from './timing' | ||
export * from './timer' |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import type { Container, Slice, SliceValue } from '../context' | ||
import type { Clock, Timer } from '../timing' | ||
import type { Container, Slice, SliceType } from '../context' | ||
import type { Clock, TimerType } from '../timer' | ||
/** | ||
* The ctx object that can be accessed in plugin and action. | ||
*/ | ||
/// The ctx object that can be accessed in plugin and action. | ||
export class Ctx { | ||
/// @internal | ||
#container: Container | ||
/// @internal | ||
#clock: Clock | ||
/// Create a ctx object with container and clock. | ||
constructor(container: Container, clock: Clock) { | ||
@@ -17,76 +18,67 @@ this.#container = container | ||
/** | ||
* Get the slice instance. | ||
* | ||
* @param slice - The slice or slice name that needs to be used. | ||
* @returns The slice instance. | ||
*/ | ||
readonly use = <T, N extends string = string>(slice: Slice<T, N> | N): SliceValue<T, N> => | ||
this.#container.getSlice(slice) | ||
/// Add a slice into the ctx. | ||
readonly inject = <T>(sliceType: SliceType<T>, value?: T) => { | ||
const slice = sliceType.create(this.#container.sliceMap) | ||
if (value != null) | ||
slice.set(value) | ||
/** | ||
* Get the slice value. | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @returns The slice value. | ||
*/ | ||
readonly get = <T, N extends string>(slice: Slice<T, N>) => this.use(slice).get() | ||
return this | ||
} | ||
/** | ||
* Set the slice value. | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @param value - The default value. | ||
* @returns | ||
*/ | ||
readonly set = <T, N extends string>(slice: Slice<T, N>, value: T) => this.use(slice).set(value) | ||
/// Remove a slice from the ctx. | ||
readonly remove = <T, N extends string = string>(sliceType: SliceType<T, N> | N) => { | ||
this.#container.remove(sliceType) | ||
return this | ||
} | ||
/** | ||
* Update the slice by its current value. | ||
* | ||
* @example | ||
* ``` | ||
* update(NumberSlice, x => x + 1); | ||
* ``` | ||
* | ||
* @param slice - The slice needs to be used. | ||
* @param updater - The update function, gets current value as parameter and returns new value. | ||
* @returns | ||
*/ | ||
readonly update = <T, N extends string>(slice: Slice<T, N>, updater: (prev: T) => T) => | ||
this.use(slice).update(updater) | ||
/// Add a timer into the ctx. | ||
readonly record = (timerType: TimerType) => { | ||
timerType.create(this.#clock.store) | ||
return this | ||
} | ||
/** | ||
* Get the timer instance. | ||
* | ||
* @param timer - The timer needs to be used. | ||
* @returns The timer instance. | ||
*/ | ||
readonly timing = (timer: Timer) => this.#clock.get(timer) | ||
/// Remove a timer from the ctx. | ||
readonly clearTimer = (timerType: TimerType) => { | ||
this.#clock.remove(timerType) | ||
return this | ||
} | ||
/** | ||
* Finish a timer | ||
* | ||
* @param timer - The timer needs to be finished. | ||
* @returns | ||
*/ | ||
readonly done = (timer: Timer) => this.timing(timer).done() | ||
/// Check if the ctx has a slice. | ||
readonly isInjected = <T, N extends string = string>(sliceType: SliceType<T, N> | N) => this.#container.has(sliceType) | ||
/** | ||
* Wait for a timer to finish. | ||
* | ||
* @param timer - The timer needs to be used. | ||
* @returns A promise that will be resolved when timer finish. | ||
*/ | ||
readonly wait = (timer: Timer) => this.timing(timer)() | ||
/// Check if the ctx has a timer. | ||
readonly isRecorded = (timerType: TimerType) => this.#clock.has(timerType) | ||
/** | ||
* Wait for a list of timers in target slice to be all finished. | ||
* | ||
* @param slice - The slice that holds a list of timer. | ||
* @returns A promise that will be resolved when all timers finish. | ||
*/ | ||
readonly waitTimers = async (slice: Slice<Timer[]>) => { | ||
/// Get a slice from the ctx. | ||
readonly use = <T, N extends string = string>(sliceType: SliceType<T, N> | N): Slice<T, N> => | ||
this.#container.get(sliceType) | ||
/// Get a slice value from the ctx. | ||
readonly get = <T, N extends string>(sliceType: SliceType<T, N>) => this.use(sliceType).get() | ||
/// Get a slice value from the ctx. | ||
readonly set = <T, N extends string>(sliceType: SliceType<T, N>, value: T) => this.use(sliceType).set(value) | ||
/// Update a slice value from the ctx by a callback. | ||
readonly update = <T, N extends string>(sliceType: SliceType<T, N>, updater: (prev: T) => T) => | ||
this.use(sliceType).update(updater) | ||
/// Get a timer from the ctx. | ||
readonly timer = (timer: TimerType) => this.#clock.get(timer) | ||
/// Resolve a timer from the ctx. | ||
readonly done = (timer: TimerType) => this.timer(timer).done() | ||
/// Start a timer from the ctx. | ||
readonly wait = (timer: TimerType) => this.timer(timer).start() | ||
/// Start a list of timers from the ctx, the list is stored in a slice in the ctx. | ||
/// This is equivalent to | ||
/// | ||
/// ```typescript | ||
/// Promise.all(ctx.get(slice).map(x => ctx.wait(x))). | ||
/// ``` | ||
readonly waitTimers = async (slice: SliceType<TimerType[]>) => { | ||
await Promise.all(this.get(slice).map(x => this.wait(x))) | ||
} | ||
} |
/* Copyright 2021, Milkdown by Mirone. */ | ||
export * from './ctx' | ||
export * from './env' | ||
export * from './post' | ||
export * from './pre' | ||
export * from './types' |
/* Copyright 2021, Milkdown by Mirone. */ | ||
import type { Ctx } from './ctx' | ||
import type { Post } from './post' | ||
import type { Pre } from './pre' | ||
export type Cleanup = (post: Post) => void | Promise<void> | ||
/// @internal | ||
export type Cleanup = () => void | Promise<void> | ||
export type HandlerReturnType = void | Promise<void> | Cleanup | Promise<Cleanup> | ||
/// @internal | ||
export type RunnerReturnType = void | Promise<void> | Cleanup | Promise<Cleanup> | ||
export type CtxHandler = (ctx: Ctx) => HandlerReturnType | ||
/// @internal | ||
export type CtxRunner = () => RunnerReturnType | ||
export type MilkdownPlugin = (pre: Pre) => CtxHandler | ||
/// The type of the plugin. | ||
/// | ||
/// ```typescript | ||
/// // A full plugin example | ||
/// const plugin1 = (ctx: Ctx) => { | ||
/// // setup | ||
/// return async () => { | ||
/// // run | ||
/// return async () => { | ||
/// // cleanup | ||
/// } | ||
/// } | ||
/// } | ||
/// | ||
/// // A plugin doesn't need to return a cleanup function | ||
/// const plugin2 = (ctx: Ctx) => { | ||
/// // setup | ||
/// return async () => { | ||
/// // run | ||
/// } | ||
/// } | ||
/// | ||
/// // A plugin doesn't need to be async | ||
/// const plugin3 = (ctx: Ctx) => { | ||
/// // setup | ||
/// return () => { | ||
/// // run | ||
/// } | ||
/// } | ||
/// ``` | ||
export type MilkdownPlugin = (ctx: Ctx) => CtxRunner |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51728
41
862
2
1
+ Added@milkdown/exception@7.0.0-next.0(transitive)
- Removed@milkdown/exception@6.5.4(transitive)