@harlem/core
Version:
Powerfully simple global state management for Vue 3
514 lines (508 loc) • 14.1 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
EVENTS: () => EVENTS,
INTERNAL: () => INTERNAL,
PRODUCERS: () => PRODUCERS,
createInstance: () => createInstance,
createStore: () => createStore,
createVuePlugin: () => createVuePlugin,
off: () => off,
on: () => on,
once: () => once
});
module.exports = __toCommonJS(src_exports);
// src/event-emitter.ts
function createEventBus() {
const listeners = /* @__PURE__ */ new Map();
function on2(event, handler) {
const handlers = listeners.get(event) || /* @__PURE__ */ new Set();
handlers.add(handler);
listeners.set(event, handlers);
return {
dispose: () => off2(event, handler)
};
}
function off2(event, handler) {
const handlers = listeners.get(event);
if (!handlers) {
return;
}
handlers.delete(handler);
if (!handlers.size) {
listeners.delete(event);
}
}
function once2(event, handler) {
const callback = (payload) => {
handler(payload);
off2(event, callback);
};
return on2(event, callback);
}
function emit(event, payload) {
const handlers = listeners.get(event);
if (handlers) {
handlers.forEach((handler) => handler(payload));
}
}
return {
on: on2,
off: off2,
once: once2,
emit
};
}
// src/constants.ts
var import_utilities = require("@harlem/utilities");
var SENDER = "core";
var EVENTS = {
core: {
installed: "core:installed"
},
store: {
created: "store:created",
ready: "store:ready",
destroyed: "store:destroyed"
},
mutation: {
before: "mutation:before",
after: "mutation:after",
success: "mutation:success",
error: "mutation:error"
},
action: {
before: "action:before",
after: "action:after",
success: "action:success",
error: "action:error"
},
ssr: {
initServer: "ssr:init:server",
initClient: "ssr:init:client"
},
devtools: {
update: "devtools:update",
reset: "devtools:reset"
}
};
var MUTATIONS = {
snapshot: "core:snapshot",
reset: "core:reset"
};
var PRODUCERS = {
read: (value) => value,
write: (value) => value,
payload: (value) => (0, import_utilities.objectClone)(value)
};
var INTERNAL = {
prefix: "$harlem:",
pattern: /^\$harlem:/
};
// src/store.ts
var import_vue = require("vue");
var import_utilities2 = require("@harlem/utilities");
function localiseHandler(name, handler) {
return (payload) => {
if (payload && payload.store === name) {
handler(payload);
}
};
}
function createInternalStore(name, initialState, eventBus, options) {
var _a;
const {
allowsOverwrite,
producers
} = {
allowsOverwrite: true,
...options,
producers: {
...PRODUCERS,
...options == null ? void 0 : options.producers
}
};
const registrations = {};
const flags = /* @__PURE__ */ new Map();
const scope = (0, import_vue.effectScope)();
const stack = /* @__PURE__ */ new Set();
let isSuppressing = false;
const writeState = (0, import_vue.reactive)(initialState);
const readState = (0, import_vue.readonly)(writeState);
let resetSnapshot;
function emit(event, sender, data) {
if (!scope.active || isSuppressing) {
return;
}
const payload = {
data,
sender,
store: name
};
eventBus.emit(event, payload);
}
function on2(event, handler) {
return eventBus.on(event, localiseHandler(name, handler));
}
function once2(event, handler) {
return eventBus.once(event, localiseHandler(name, handler));
}
function track(callback) {
return scope.run(callback);
}
function hasRegistration(group, name2) {
var _a2;
return !!((_a2 = registrations[group]) == null ? void 0 : _a2.has(name2));
}
function getRegistration(group, name2) {
var _a2;
return (_a2 = registrations[group]) == null ? void 0 : _a2.get(name2);
}
function register(group, name2, producer, type = "other") {
if (!name2) {
throw new Error("Registration name cannot be empty");
}
if (!(group in registrations)) {
registrations[group] = /* @__PURE__ */ new Map();
}
if (!allowsOverwrite && hasRegistration(group, name2)) {
throw new Error(`A ${group} named ${name2} has already been registered on this store`);
}
registrations[group].set(name2, {
type,
producer
});
}
function unregister(group, name2) {
var _a2;
(_a2 = registrations[group]) == null ? void 0 : _a2.delete(name2);
}
function suppress(callback) {
isSuppressing = true;
try {
return callback();
} finally {
isSuppressing = false;
}
}
function getter(name2, getter2) {
const output = track(() => (0, import_vue.computed)(() => getter2(readState)));
register("getters", name2, () => output.value, "computed");
return output;
}
function mutate(name2, sender, mutator, payload) {
var _a2, _b;
if (!scope.active) {
throw new Error("The current store has been destroyed. Mutations can no longer take place.");
}
if (stack.has(name2)) {
throw new Error("Circular mutation reference detected. Avoid calling mutations inside other mutations to prevent circular references.");
}
stack.add(name2);
let result;
const trigger = (event) => emit(event, sender, {
name: name2,
payload,
result
});
trigger(EVENTS.mutation.before);
try {
const producedState = (_a2 = producers.write(writeState)) != null ? _a2 : writeState;
const producedPayload = (_b = producers.payload(payload)) != null ? _b : payload;
result = mutator(producedState, producedPayload);
trigger(EVENTS.mutation.success);
} catch (error) {
trigger(EVENTS.mutation.error);
throw error;
} finally {
stack.delete(name2);
trigger(EVENTS.mutation.after);
}
return result;
}
function mutation(name2, mutator) {
const mutation2 = (payload) => {
return mutate(name2, SENDER, mutator, payload);
};
register("mutations", name2, () => mutation2);
return mutation2;
}
function action(name2, body) {
const mutate2 = (mutator) => write(name2, SENDER, mutator);
const action2 = async (payload) => {
var _a2;
let result;
const trigger = (event) => emit(event, SENDER, {
name: name2,
payload,
result
});
trigger(EVENTS.action.before);
try {
const producedPayload = (_a2 = producers.payload(payload)) != null ? _a2 : payload;
result = await body(producedPayload, mutate2);
trigger(EVENTS.action.success);
} catch (error) {
trigger(EVENTS.action.error);
throw error;
} finally {
trigger(EVENTS.action.after);
}
return result;
};
register("actions", name2, () => action2);
return action2;
}
function snapshot() {
const snapshot2 = (0, import_utilities2.objectClone)(initialState);
const {
value,
getNodes,
resetNodes
} = (0, import_utilities2.objectTrace)();
const apply = (branchAccessor = import_utilities2.functionIdentity, mutationName = MUTATIONS.snapshot) => {
write(mutationName, SENDER, (state2) => {
if (!snapshot2) {
return console.warn("Couldn't find snapshot for this operation!");
}
resetNodes();
branchAccessor(value);
const nodes = getNodes();
const source = (0, import_utilities2.objectFromPath)(snapshot2, nodes);
(0, import_utilities2.objectSet)(state2, nodes, (0, import_utilities2.objectClone)(source));
});
};
return {
apply,
get state() {
return (0, import_utilities2.objectClone)(snapshot2);
}
};
}
function reset(branchAccessor = import_utilities2.functionIdentity) {
resetSnapshot == null ? void 0 : resetSnapshot.apply(branchAccessor, MUTATIONS.reset);
}
function write(name2, sender, mutator, suppressEvent) {
const mutation2 = () => mutate(name2, sender, mutator, void 0);
return suppressEvent ? suppress(mutation2) : mutation2();
}
function destroy() {
scope.stop();
}
once2(EVENTS.store.ready, () => resetSnapshot = snapshot());
on2(EVENTS.devtools.reset, () => reset());
const state = (_a = producers.read(readState)) != null ? _a : readState;
return {
name,
allowsOverwrite,
flags,
producers,
registrations,
on: on2,
once: once2,
emit,
state,
getter,
mutation,
action,
write,
snapshot,
reset,
register,
unregister,
hasRegistration,
getRegistration,
track,
suppress,
destroy
};
}
// src/index.ts
var import_utilities3 = require("@harlem/utilities");
function createInstance() {
const eventBus = createEventBus();
const stores = /* @__PURE__ */ new Map();
let installed = false;
function validateStoreCreation(name) {
const store = stores.get(name);
if (store && !store.allowsOverwrite) {
throw new Error(`A store named ${name} has already been registered.`);
}
}
function emitCreated(store, state) {
const created = () => {
store.emit(EVENTS.ssr.initClient, SENDER, state);
store.emit(EVENTS.store.created, SENDER, state);
store.emit(EVENTS.ssr.initServer, SENDER, state);
store.emit(EVENTS.store.ready, SENDER, state);
store.emit(EVENTS.devtools.update, SENDER, state);
};
if (installed) {
return created();
}
eventBus.once(EVENTS.core.installed, created);
}
function getExtensionApis(store, extensions) {
return extensions.reduce((output, extension) => {
let result = {};
try {
result = extension(store) || {};
} catch (e) {
result = {};
}
return {
...output,
...result
};
}, {});
}
function installPlugin(plugin, app) {
if (!(0, import_utilities3.typeIsFunction)(plugin)) {
return;
}
const lockedStores = (0, import_utilities3.objectLock)(stores, [
"set",
"delete",
"clear"
]);
try {
plugin(app, eventBus, lockedStores);
} catch (error) {
console.warn("Failed to install Harlem plugin. Skipping.");
}
}
function createStore2(name, state, options) {
const {
allowsOverwrite,
producers: providers,
extensions
} = {
allowsOverwrite: true,
extensions: [],
...options
};
validateStoreCreation(name);
const store = createInternalStore(name, state, eventBus, {
allowsOverwrite,
producers: providers
});
const destroy = () => {
stores.delete(name);
store.destroy();
store.emit(EVENTS.store.destroyed, SENDER, state);
store.emit(EVENTS.devtools.update, SENDER, state);
};
const getTrigger = (eventName) => {
return (matcher, handler) => {
const filter = (0, import_utilities3.matchGetFilter)(
(0, import_utilities3.typeIsMatchable)(matcher) ? matcher : {
include: matcher
}
);
return store.on(eventName, (event) => {
if (event && filter(event.data.name)) {
handler(event.data);
}
});
};
};
const onBeforeMutation = getTrigger(EVENTS.mutation.before);
const onAfterMutation = getTrigger(EVENTS.mutation.after);
const onMutationSuccess = getTrigger(EVENTS.mutation.success);
const onMutationError = getTrigger(EVENTS.mutation.error);
const onBeforeAction = getTrigger(EVENTS.action.before);
const onAfterAction = getTrigger(EVENTS.action.after);
const onActionSuccess = getTrigger(EVENTS.action.success);
const onActionError = getTrigger(EVENTS.action.error);
const extensionApis = getExtensionApis(store, extensions);
stores.set(name, store);
emitCreated(store, state);
return {
destroy,
onBeforeMutation,
onAfterMutation,
onMutationSuccess,
onMutationError,
onBeforeAction,
onAfterAction,
onActionSuccess,
onActionError,
state: store.state,
getter: store.getter.bind(store),
mutation: store.mutation.bind(store),
action: store.action.bind(store),
snapshot: store.snapshot.bind(store),
reset: store.reset.bind(store),
suppress: store.suppress.bind(store),
on: store.on.bind(store),
once: store.once.bind(store),
...extensionApis
};
}
function createVuePlugin2(options) {
return {
install(app) {
const {
plugins
} = {
plugins: [],
...options
};
if (plugins) {
plugins.forEach((plugin) => installPlugin(plugin, app));
}
installed = true;
eventBus.emit(EVENTS.core.installed);
}
};
}
return {
createVuePlugin: createVuePlugin2,
createStore: createStore2,
on: eventBus.on,
once: eventBus.once,
off: eventBus.off
};
}
var {
on,
off,
once,
createVuePlugin,
createStore
} = createInstance();
if (typeof window !== "undefined") {
window.$harlem = {
createInstance
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
EVENTS,
INTERNAL,
PRODUCERS,
createInstance,
createStore,
createVuePlugin,
off,
on,
once
});