UNPKG

@empirica/core

Version:
1,455 lines (1,435 loc) 39.4 kB
"use strict"; 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/admin/user.ts var user_exports = {}; __export(user_exports, { AdminConnection: () => AdminConnection, Attribute: () => Attribute, Attributes: () => Attributes2, EventContext: () => EventContext, ListenersCollector: () => ListenersCollector, Scope: () => Scope2, Scopes: () => Scopes2, Subscriptions: () => Subscriptions, TajribaConnection: () => TajribaConnection, TajribaEvent: () => TajribaEvent, participantsSub: () => participantsSub, transitionsSub: () => transitionsSub }); module.exports = __toCommonJS(user_exports); // src/shared/attributes.ts var import_rxjs = require("rxjs"); // src/utils/console.ts var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; var logsMock; var colorHex = { [1 /* Bold */]: "font-weight: bold", [30 /* Black */]: "color: #000000", [31 /* Red */]: "color: #cc0000", [32 /* Green */]: "color: #4e9a06", [33 /* Yellow */]: "color: #c4a000", [34 /* Blue */]: "color: #729fcf", [35 /* Magenta */]: "color: #75507b", [36 /* Cyan */]: "color: #06989a", [37 /* White */]: "color: #d3d7cf", [90 /* DarkGray */]: "color: #555753" }; var levels = { trace: 0, debug: 1, log: 2, info: 2, warn: 3, error: 4 }; var reversLevels = {}; for (const key in levels) { reversLevels[levels[key]] = key; } var currentLevel = 2; function formatConsoleDate(date, level) { var hour = date.getHours(); var minutes = date.getMinutes(); var seconds = date.getSeconds(); var milliseconds = date.getMilliseconds(); const str = (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + ("00" + milliseconds).slice(-3); if (isBrowser) { const ts = colorize(str, 90 /* DarkGray */).concat(level); return [ts[0] + " " + level[0], ts[1], level[1]]; } return colorize(str, 90 /* DarkGray */).concat(level); } var createLogger = (lvl, level) => { return (...args) => { if (lvl < currentLevel) { return; } if (logsMock) { logsMock.log({ level: reversLevels[lvl], args }); return; } if (args.length === 1) { switch (typeof args[0]) { case "string": for (const line of args[0].split("\n")) { console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line)); } return; case "object": if (args[0] instanceof Error) { const error2 = args[0]; const prettyErr = error2.name + ": " + error2.message.replace(new RegExp(`^${error2.name}[: ]*`), "") + "\n" + (error2.stack || "").split("\n").map((line) => line.trim()).map((line) => { if (line.startsWith(error2.name + ": " + error2.message)) return null; if (line.startsWith("at")) { return " " + line; } return line; }).filter(Boolean).join("\n"); for (const line of prettyErr.split("\n")) { console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line)); } return; } } } console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(args)); }; }; function colorize(s, ...cc) { if (isBrowser) { const attr = []; for (const c of cc) { attr.push(colorHex[c]); } return [`%c${s}`, attr.join("; ")]; } let out = ""; for (const c of cc) { out += `\x1B[${c}m`; } out += `${s}\x1B[0m`; return [out]; } var trace = createLogger(0, colorize("TRC", 35 /* Magenta */)); var debug = createLogger(1, colorize("DBG", 33 /* Yellow */)); var log = createLogger(2, colorize("LOG", 33 /* Yellow */)); var info = createLogger(2, colorize("INF", 32 /* Green */)); var warn = createLogger(3, colorize("WRN", 36 /* Cyan */)); var error = createLogger(4, colorize("ERR", 31 /* Red */, 1 /* Bold */)); // src/shared/attributes.ts var Attributes = class { constructor(attributesObs, donesObs, setAttributes) { this.setAttributes = setAttributes; this.attrs = /* @__PURE__ */ new Map(); this.updates = /* @__PURE__ */ new Map(); attributesObs.subscribe({ next: ({ attribute, removed }) => { this.update(attribute, removed); } }); donesObs.subscribe({ next: (scopeIDs) => { this.next(scopeIDs); } }); } attribute(scopeID, key) { let scopeMap = this.attrs.get(scopeID); if (!scopeMap) { scopeMap = /* @__PURE__ */ new Map(); this.attrs.set(scopeID, scopeMap); } let attr = scopeMap.get(key); if (!attr) { attr = new Attribute(this.setAttributes, scopeID, key); scopeMap.set(key, attr); } return attr; } attributes(scopeID) { let scopeMap = this.attrs.get(scopeID); if (!scopeMap) { scopeMap = /* @__PURE__ */ new Map(); this.attrs.set(scopeID, scopeMap); } return Array.from(scopeMap.values()); } attributePeek(scopeID, key) { let scopeUpdateMap = this.updates.get(scopeID); if (scopeUpdateMap) { const updated = scopeUpdateMap.get(key); if (updated) { if (typeof updated === "boolean") { return; } else { if (!updated.val) { return; } else { const attr2 = new Attribute(this.setAttributes, scopeID, key); attr2._update(updated); return attr2; } } } } let scopeMap = this.attrs.get(scopeID); if (!scopeMap) { return; } let attr = scopeMap.get(key); if (!attr) { return; } if (attr.value === void 0) { return; } return attr; } nextAttributeValue(scopeID, key) { const attr = this.attributePeek(scopeID, key); if (!attr) { return; } return attr.value; } update(attr, removed) { let nodeID = attr.nodeID; if (!nodeID) { if (!attr.node?.id) { error(`new attribute without node ID`); return; } nodeID = attr.node.id; } let scopeMap = this.updates.get(nodeID); if (!scopeMap) { scopeMap = /* @__PURE__ */ new Map(); this.updates.set(nodeID, scopeMap); } if (removed) { scopeMap.set(attr.key, true); } else { let key = attr.key; if (attr.index !== void 0 && attr.index !== null) { key = `${key}[${attr.index}]`; } scopeMap.set(key, attr); } } scopeWasUpdated(scopeID) { if (!scopeID) { return false; } return this.updates.has(scopeID); } next(scopeIDs) { for (const [scopeID, attrs] of this.updates) { if (!scopeIDs.includes(scopeID)) { continue; } let scopeMap = this.attrs.get(scopeID); if (!scopeMap) { scopeMap = /* @__PURE__ */ new Map(); this.attrs.set(scopeID, scopeMap); } for (const [key, attrOrDel] of attrs) { if (typeof attrOrDel === "boolean") { let attr = scopeMap.get(key); if (attr) { attr._update(void 0); } } else { let attr = scopeMap.get(attrOrDel.key); if (!attr) { attr = new Attribute(this.setAttributes, scopeID, attrOrDel.key); scopeMap.set(attrOrDel.key, attr); } attr._update(attrOrDel); } } } for (const scopeID of scopeIDs) { this.updates.delete(scopeID); } } }; var Attribute = class { constructor(setAttributes, scopeID, key) { this.setAttributes = setAttributes; this.scopeID = scopeID; this.key = key; this.val = new import_rxjs.BehaviorSubject(void 0); } get id() { return this.attr?.id; } get createdAt() { return this.attr ? new Date(this.attr.createdAt) : null; } get obs() { return this.val; } get value() { return this.val.getValue(); } get nodeID() { return this.scopeID; } // items returns the attribute changes for the current attribute, if it is a // vector. Otherwise it returns null; get items() { if (!this.attrs) { return null; } return this.attrs; } set(value, ao) { const attrProps = this._prepSet(value, ao); if (!attrProps) { return; } this.setAttributes([attrProps]); trace(`SET ${this.key} = ${value} (${this.scopeID})`); } _prepSet(value, ao, item) { if (ao?.append !== void 0 && ao.index !== void 0) { error(`cannot set both append and index`); throw new Error(`cannot set both append and index`); } const serVal = JSON.stringify(value); if (!item && (ao?.index !== void 0 || ao?.append)) { let index = ao.index || 0; if (ao?.append) { index = this.attrs?.length || 0; } if (!this.attrs) { this.attrs = []; } if (!this.attrs[index]) { this.attrs[index] = new Attribute( this.setAttributes, this.scopeID, this.key ); } else { const existing = this.attrs[index]; if (existing && existing.serVal === serVal) { return; } } this.attrs[index]._prepSet(value, ao, true); const v = this._recalcVectorVal(); this.val.next(v); } else { if (this.serVal === serVal) { return; } this.val.next(value); } this.serVal = serVal; const attrProps = { key: this.key, nodeID: this.scopeID, val: serVal }; if (ao) { attrProps.private = ao.private; attrProps.protected = ao.protected; attrProps.immutable = ao.immutable; attrProps.ephemeral = ao.ephemeral; attrProps.append = ao.append; attrProps.index = ao.index; } return attrProps; } _recalcVectorVal() { return this.attrs.map( (a) => !a || a.val == void 0 ? null : a.value || null ); } // internal only _update(attr, item) { if (attr && this.attr && this.attr.id === attr.id) { return; } if (attr && attr.vector && !item) { if (attr.index === void 0) { error(`vector attribute missing index`); return; } if (this.attrs == void 0) { this.attrs = []; } while (this.attrs.length < attr.index + 1) { const newAttr2 = new Attribute( this.setAttributes, this.scopeID, this.key ); this.attrs.push(newAttr2); } const newAttr = new Attribute(this.setAttributes, this.scopeID, this.key); newAttr._update(attr, true); this.attrs[attr.index] = newAttr; const value2 = this._recalcVectorVal(); this.val.next(value2); return; } this.attr = attr; this.serVal = attr?.val === void 0 || attr?.val === null ? "" : attr.val; let value = void 0; if (this.attr?.val) { value = JSON.parse(this.attr.val); } this.val.next(value); } }; // src/shared/tajriba_connection.ts var import_tajriba = require("@empirica/tajriba"); // src/utils/object.ts var import_rxjs2 = require("rxjs"); function bs(init) { return new import_rxjs2.BehaviorSubject(init); } function bsu(init = void 0) { return new import_rxjs2.BehaviorSubject(init); } // src/shared/tajriba_connection.ts var ErrNotConnected = new Error("not connected"); var TajribaConnection = class { constructor(url) { this.url = url; this._connected = bs(false); this._connecting = bs(true); this._stopped = bs(false); this.tajriba = import_tajriba.Tajriba.connect(this.url); this._connected.next(this.tajriba.connected); this.tajriba.on("connected", () => { this._connected.next(true); this._connecting.next(false); }); this.tajriba.on("disconnected", () => { if (this._connected.getValue()) { this._connected.next(false); } if (!this._connecting.getValue()) { this._connecting.next(true); } }); this.tajriba.on("error", (err) => { error("connection error", err); }); } get connecting() { return this._connecting; } get connected() { return this._connected; } get stopped() { return this._stopped; } async sessionParticipant(token, pident) { if (!this._connected.getValue()) { throw ErrNotConnected; } return await this.tajriba.sessionParticipant(token, pident); } async sessionAdmin(token) { if (!this._connected.getValue()) { throw ErrNotConnected; } return await this.tajriba.sessionAdmin(token); } stop() { if (this._stopped.getValue()) { return; } if (this.tajriba) { this.tajriba.removeAllListeners("connected"); this.tajriba.removeAllListeners("disconnected"); this.tajriba.stop(); } this._connecting.next(false); this._connected.next(false); this._stopped.next(true); } }; // src/admin/attributes.ts var import_rxjs3 = require("rxjs"); var Attributes2 = class extends Attributes { constructor() { super(...arguments); this.attrsByKind = /* @__PURE__ */ new Map(); this.attribSubs = /* @__PURE__ */ new Map(); } subscribeAttribute(kind, key) { if (!this.attribSubs.has(kind)) { this.attribSubs.set(kind, /* @__PURE__ */ new Map()); } const keyMap = this.attribSubs.get(kind); let sub = keyMap.get(key); if (!sub) { sub = new import_rxjs3.ReplaySubject(); keyMap.set(key, sub); const attrByScopeID = this.attrsByKind.get(kind); setTimeout(() => { if (!attrByScopeID) { sub.next({ done: true }); return; } let attrs = []; for (const [_, attrByKey] of attrByScopeID?.entries()) { for (const [_2, attr] of attrByKey) { if (attr.key === key) { attrs.push(attr); } } } if (attrs.length > 0) { let count = 0; for (const attr of attrs) { count++; sub.next({ attribute: attr, done: count == attrs.length }); } } else { sub.next({ done: true }); } }, 0); } return sub; } next(scopeIDs) { const byKind = /* @__PURE__ */ new Map(); for (const [scopeID, attrs] of this.updates) { if (!scopeIDs.includes(scopeID)) { continue; } for (const [_, attr] of attrs) { if (typeof attr === "boolean") { continue; } const kind = attr.node?.kind; if (kind) { let kindAttrs = byKind.get(kind); if (!kindAttrs) { kindAttrs = []; byKind.set(kind, kindAttrs); } kindAttrs.push(attr); } } } const updates = []; for (const [kind, attrs] of byKind) { for (const attr of attrs) { if (!attr.nodeID && !attr.node?.id) { warn(`found attribute change without node ID`); continue; } if (!scopeIDs.includes(attr.nodeID || attr.node.id)) { continue; } updates.push([kind, attr.key, attr]); } } super.next(scopeIDs); for (const [kind, key, attrChange] of updates) { const nodeID = attrChange.nodeID || attrChange.node.id; if (!scopeIDs.includes(nodeID)) { continue; } const attr = this.attrs.get(nodeID).get(key); const sub = this.attribSubs.get(kind)?.get(key); if (sub) { sub.next({ attribute: attr, done: true }); } else { let kAttrs = this.attrsByKind.get(kind); if (!kAttrs) { kAttrs = /* @__PURE__ */ new Map(); this.attrsByKind.set(kind, kAttrs); } let kkAttrs = kAttrs.get(nodeID); if (!kkAttrs) { kkAttrs = /* @__PURE__ */ new Map(); kAttrs.set(nodeID, kkAttrs); } kkAttrs.set(key, attr); } } } }; // src/admin/connection.ts var import_rxjs5 = require("rxjs"); // src/admin/observables.ts var import_async_mutex = require("async-mutex"); var import_rxjs4 = require("rxjs"); function subscribeAsync(obs, fn) { const cancel = new import_rxjs4.Subject(); obs.pipe((0, import_rxjs4.concatMap)(fn), (0, import_rxjs4.takeUntil)(cancel)).subscribe(); return { closed: false, unsubscribe() { if (this.closed) { warn("closing a closed async observable subscription"); return; } this.closed = true; cancel.next(); cancel.unsubscribe(); } }; } // src/admin/connection.ts var AdminConnection = class { constructor(taj, tokens, resetToken) { this.resetToken = resetToken; this._tajriba = bsu(); this._connected = bs(false); this._connecting = bs(false); this._stopped = bs(false); let token; let connected = false; this.sub = subscribeAsync( (0, import_rxjs5.merge)(taj.connected, tokens), async (tokenOrConnected) => { if (typeof tokenOrConnected === "boolean") { connected = tokenOrConnected; } else { token = tokenOrConnected; } if (!token || !connected) { return; } if (this._connected.getValue()) { return; } this._connecting.next(true); try { const tajAdmin = await taj.sessionAdmin(token); this._tajriba.next(tajAdmin); this._connected.next(true); tajAdmin.on("connected", () => { if (!this._connected.getValue()) { this._connected.next(true); } }); tajAdmin.on("error", (err) => { error("connection error", err); }); tajAdmin.on("disconnected", () => { if (this._connected.getValue()) { this._connected.next(false); } }); tajAdmin.on("accessDenied", () => { if (this._connected.getValue()) { this._connected.next(false); } this.resetToken(); }); } catch (error2) { if (error2 !== ErrNotConnected) { this.resetToken(); } } this._connecting.next(false); } ); } stop() { if (this._stopped.getValue()) { return; } const taj = this._tajriba.getValue(); if (taj) { taj.removeAllListeners("connected"); taj.removeAllListeners("disconnected"); taj.stop(); this._tajriba.next(void 0); } this.sub.unsubscribe(); this._connecting.next(false); this._connected.next(false); this._stopped.next(true); } get connecting() { return this._connecting; } get connected() { return this._connected; } get stopped() { return this._stopped; } get admin() { return this._tajriba; } }; // src/admin/events.ts var TajribaEvent = /* @__PURE__ */ ((TajribaEvent2) => { TajribaEvent2["TransitionAdd"] = "TRANSITION_ADD"; TajribaEvent2["ParticipantConnect"] = "PARTICIPANT_CONNECT"; TajribaEvent2["ParticipantDisconnect"] = "PARTICIPANT_DISCONNECT"; return TajribaEvent2; })(TajribaEvent || {}); var placementString = /* @__PURE__ */ new Map(); placementString.set(0 /* Before */, "before"); placementString.set(1 /* None */, "on"); placementString.set(2 /* After */, "after"); function PlacementString(placement) { return placementString.get(placement); } function unique(kind, placement, callback) { return async (ctx, props) => { const attr = props.attribute; const scope = props[kind]; if (!attr.id || scope.get(`ran-${PlacementString(placement)}-${props.attrId}`)) { return; } await callback(ctx, props); scope.set(`ran-${PlacementString(placement)}-${props.attrId}`, true); }; } var ListenersCollector = class { constructor() { /** @internal */ this.starts = []; /** @internal */ this.readys = []; /** @internal */ this.tajEvents = []; /** @internal */ this.kindListeners = []; /** @internal */ this.attributeListeners = []; } /** @internal */ setFlusher(flusher) { this.flusher = flusher; } async flush() { if (!this.flusher) { return; } await this.flusher.flush(); } flushAfter(cb) { if (!this.flusher) { return; } return this.flusher.flushAfter(cb); } get unique() { return new ListenersCollectorProxy(this); } on(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback) { this.registerListerner( 1 /* None */, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback ); } before(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall) { this.registerListerner( 0 /* Before */, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall ); } after(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall) { this.registerListerner( 2 /* After */, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall ); } registerListerner(placement, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall = false) { if (kindOrEvent === "start") { if (callback) { throw new Error("start event only accepts 2 arguments"); } if (typeof keyOrNodeIDOrEventOrCallback !== "function") { throw new Error("second argument expected to be a callback"); } this.starts.push({ placement, callback: keyOrNodeIDOrEventOrCallback }); return; } if (kindOrEvent === "ready") { if (callback) { throw new Error("ready event only accepts 2 arguments"); } if (typeof keyOrNodeIDOrEventOrCallback !== "function") { throw new Error("second argument expected to be a callback"); } this.readys.push({ placement, callback: keyOrNodeIDOrEventOrCallback }); return; } if (Object.values(TajribaEvent).includes(kindOrEvent)) { if (typeof keyOrNodeIDOrEventOrCallback !== "function") { throw new Error("second argument expected to be a callback"); } this.tajEvents.push({ placement, event: kindOrEvent, callback: keyOrNodeIDOrEventOrCallback }); return; } if (typeof keyOrNodeIDOrEventOrCallback === "function") { this.kindListeners.push({ placement, kind: kindOrEvent, callback: keyOrNodeIDOrEventOrCallback }); } else { if (typeof keyOrNodeIDOrEventOrCallback !== "string") { throw new Error("second argument expected to be an attribute key"); } if (typeof callback !== "function") { throw new Error("third argument expected to be a callback"); } if (uniqueCall) { callback = unique(kindOrEvent, placement, callback); } this.attributeListeners.push({ placement, kind: kindOrEvent, key: keyOrNodeIDOrEventOrCallback, callback }); } } }; var ListenersCollectorProxy = class extends ListenersCollector { constructor(coll) { super(); this.coll = coll; } registerListerner(placement, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback) { if (kindOrEvent === "start" || kindOrEvent === "ready" || Object.values(TajribaEvent).includes(kindOrEvent) || typeof keyOrNodeIDOrEventOrCallback === "function") { throw new Error("only attribute listeners can be unique"); } super.registerListerner( placement, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, true ); while (true) { const listener = this.attributeListeners.pop(); if (!listener) { break; } this.coll.attributeListeners.push(listener); } } }; var EventContext = class { constructor(subs, taj, scopes, flusher) { this.subs = subs; this.taj = taj; this.scopes = scopes; this.flusher = flusher; } async flush() { await this.flusher.flush(); } flushAfter(cb) { return this.flusher.flushAfter(cb); } scopesByKind(kind) { return this.scopes.byKind(kind); } scopesByKindID(kind, id) { return this.scopes.byKind(kind).get(id); } scopesByKindMatching(kind, key, val) { const scopes = Array.from(this.scopes.byKind(kind).values()); return scopes.filter((s) => s.get(key) === val); } scopeSub(...inputs) { for (const input of inputs) { this.subs.scopeSub(input); } } participantsSub() { this.subs.participantsSub(); } transitionsSub(stepID) { this.subs.transitionsSub(stepID); } // c8 ignore: the TajribaAdminAccess proxy functions are tested elswhere /* c8 ignore next 3 */ addScopes(input) { return this.taj.addScopes(input); } /* c8 ignore next 3 */ addGroups(input) { return this.taj.addGroups(input); } /* c8 ignore next 3 */ addLinks(input) { return this.taj.addLinks(input); } /* c8 ignore next 3 */ addSteps(input) { return this.taj.addSteps(input); } /* c8 ignore next 3 */ addTransitions(input) { return this.taj.addTransitions(input); } addFinalizer(cb) { this.taj.addFinalizer(cb); } /* c8 ignore next 3 */ get globals() { return this.taj.globals; } }; // src/admin/participants.ts var import_tajriba2 = require("@empirica/tajriba"); // src/admin/promises.ts function promiseHandle() { let ret = {}; ret.promise = new Promise((r) => { ret.result = r; }); return ret; } // src/admin/participants.ts async function participantsSub(taj, connections, participants) { let handle = promiseHandle(); taj.onEvent({ eventTypes: [import_tajriba2.EventType.ParticipantConnected] }).subscribe({ next({ node, done }) { if (!node) { if (done) { if (handle) { handle?.result(); connections.next({ done: true }); } return; } error(`received no participant on connected`); return; } if (node.__typename !== "Participant") { error(`received non-participant on connected`); return; } const part = { id: node.id, identifier: node.identifier }; participants.set(node.id, part); connections.next({ connection: { participant: part, connected: true }, done }); if (handle && done) { handle.result(); } } }); taj.onEvent({ eventTypes: [import_tajriba2.EventType.ParticipantDisconnect] }).subscribe({ next({ node }) { if (!node) { error(`received no participant on disconnect`); return; } if (node.__typename !== "Participant") { error(`received non-participant on disconnect`); return; } participants.delete(node.id); connections.next({ connection: { participant: { id: node.id, identifier: node.identifier }, connected: false }, done: true }); } }); await handle.promise; handle = void 0; } // src/admin/scopes.ts var import_rxjs7 = require("rxjs"); // src/shared/scopes.ts var import_rxjs6 = require("rxjs"); var Scopes = class { constructor(scopesObs, donesObs, ctx, kinds, attributes) { this.ctx = ctx; this.kinds = kinds; this.attributes = attributes; this.scopes = /* @__PURE__ */ new Map(); // newScopes is used to track scopes that have appeared for the first time. this.newScopes = /* @__PURE__ */ new Map(); this.scopesByKind = /* @__PURE__ */ new Map(); this.kindUpdated = /* @__PURE__ */ new Set(); scopesObs.subscribe({ next: ({ scope, removed }) => { this.update(scope, removed); } }); donesObs.subscribe({ next: (scopeIDs) => { this.next(scopeIDs); } }); } scope(id) { return this.scopes.get(id)?.getValue(); } scopeObs(id) { return this.scopes.get(id); } byKind(kind) { let map = this.scopesByKind.get(kind); if (!map) { map = /* @__PURE__ */ new Map(); this.scopesByKind.set(kind, map); } return map; } kindWasUpdated(kind) { return this.kindUpdated.has(kind); } next(scopeIDs) { this.kindUpdated.clear(); for (const [_, scopeSubject] of this.scopes) { const scope = scopeSubject.getValue(); if ((scope._updated || this.attributes.scopeWasUpdated(scope.id)) && scopeIDs.includes(scope.id)) { scope._updated = false; scopeSubject.next(scope); } } } update(scope, removed) { const existing = this.scopes.get(scope.id)?.getValue(); if (removed) { if (!existing) { warn("scopes: missing scope on removal", scope.id, scope.kind); return; } existing._deleted = true; existing._updated = true; this.scopes.delete(scope.id); if (!scope.kind) { warn("scopes: scope missing kind on scope on removal"); return; } const kind2 = scope.kind; this.scopesByKind.get(kind2).delete(scope.id); this.kindUpdated.add(kind2); return; } if (existing) { existing._deleted = false; return; } if (!scope.kind) { warn("scopes: scope missing kind on scope"); return; } const kind = scope.kind; const scopeClass = this.kinds[kind]; if (!scopeClass) { warn(`scopes: unknown scope kind: ${scope.kind}`); return; } const obj = this.create(scopeClass, scope); const subj = new import_rxjs6.BehaviorSubject(obj); this.scopes.set(scope.id, subj); this.newScopes.set(scope.id, true); let skm = this.scopesByKind.get(kind); if (!skm) { skm = /* @__PURE__ */ new Map(); this.scopesByKind.set(kind, skm); } skm.set(scope.id, obj); obj._updated = true; this.kindUpdated.add(kind); } create(scopeClass, scope) { return new scopeClass(this.ctx, scope, this.attributes); } }; var Scope = class { constructor(ctx, scope, attributes) { this.ctx = ctx; this.scope = scope; this.attributes = attributes; /** * @internal */ this._deleted = false; /** * @internal */ this._updated = false; } get id() { return this.scope.id; } /** * @internal */ get kind() { return this.scope.kind; } get(key) { return this.attributes.attribute(this.scope.id, key).value; } getAttribute(key) { return this.attributes.attribute(this.scope.id, key); } obs(key) { return this.attributes.attribute(this.scope.id, key).obs; } set(keyOrAttributes, value, ao) { if (typeof keyOrAttributes === "string") { if (value === void 0) { value = null; } return this.attributes.attribute(this.scope.id, keyOrAttributes).set(value, ao); } const nextProps = []; for (const attr of keyOrAttributes) { const at = this.attributes.attribute(this.scope.id, attr.key)._prepSet(attr.value, attr.ao); if (!at) { continue; } nextProps.push(at); } if (nextProps.length === 0) { return; } this.attributes.setAttributes(nextProps); } append(key, value, ao) { if (!ao) { ao = {}; } ao.append = true; return this.attributes.attribute(this.scope.id, key).set(value, ao); } inspect() { const attrs = this.attributes.attributes(this.scope.id); const out = {}; for (const attr of attrs) { out[attr.key] = attr.value; } return out; } /** * @internal */ hasUpdated() { return this._updated || this.attributes.scopeWasUpdated(this.id); } }; // src/admin/scopes.ts var Scopes2 = class extends Scopes { constructor(scopesObs, donesObs, ctx, kinds, attributes, taj) { super(scopesObs, donesObs, ctx, kinds, attributes); this.taj = taj; this.kindSubs = /* @__PURE__ */ new Map(); } /** @internal */ subscribeKind(kind) { let sub = this.kindSubs.get(kind); if (!sub) { sub = new import_rxjs7.ReplaySubject(); this.kindSubs.set(kind, sub); const scopes = this.byKind(kind); setTimeout(() => { if (scopes.size === 0) { sub.next({ done: true }); return; } let count = 0; for (const [_, scope] of scopes) { count++; sub.next({ scope, done: scopes.size === count }); } }, 0); } return sub; } next(scopeIDs) { for (const [_, scopeReplaySubject] of this.scopes) { const scope = scopeReplaySubject.getValue(); if (this.newScopes.get(scope.id) && scopeIDs.includes(scope.id)) { const kindSub = this.kindSubs.get(scope.kind); if (kindSub) { kindSub.next({ scope, done: true }); } this.newScopes.set(scope.id, false); } } super.next(scopeIDs); } create(scopeClass, scope) { return new scopeClass(this.ctx, scope, this, this.attributes); } }; var Scope2 = class extends Scope { constructor(ctx, scope, scopes, attributes) { super(ctx, scope, attributes); this.scopes = scopes; this.taj = scopes.taj; } scopeByID(id) { return this.scopes.scope(id); } scopeByKey(key) { const id = this.get(key); if (!id || typeof id !== "string") { return; } return this.scopes.scope(id); } scopesByKind(kind) { return this.scopes.byKind(kind); } scopesByKindID(kind, id) { return this.scopes.byKind(kind).get(id); } scopesByKindMatching(kind, key, val) { const scopes = Array.from(this.scopes.byKind(kind).values()); return scopes.filter((s) => s.get(key) === val); } addScopes(input) { return this.taj.addScopes(input); } addGroups(input) { return this.taj.addGroups(input); } addLinks(input) { return this.taj.addLinks(input); } addSteps(input) { return this.taj.addSteps(input); } addTransitions(input) { return this.taj.addTransitions(input); } addFinalizer(cb) { this.taj.addFinalizer(cb); } /** * @internal */ get globals() { return this.taj.globals; } }; // src/admin/subscriptions.ts function kvstr(kv) { return kv.key + "-" + kv.val; } var Subscriptions = class { constructor() { this.scopeKinds = /* @__PURE__ */ new Set(); this.scopeIDs = /* @__PURE__ */ new Set(); this.scopeNames = /* @__PURE__ */ new Set(); this.scopeKeys = /* @__PURE__ */ new Set(); this.scopeKVSet = /* @__PURE__ */ new Set(); this.scopeKVs = []; this.participantSub = false; this.transitionsSubs = /* @__PURE__ */ new Set(); this.dirty = false; this.last = { participants: false, scopes: { ids: [], kinds: [], names: [], keys: [], kvs: [] }, transitions: [] }; } get subs() { return { participants: this.participantSub, scopes: { kinds: Array.from(this.scopeKinds.values()), ids: Array.from(this.scopeIDs.values()), names: Array.from(this.scopeNames.values()), keys: Array.from(this.scopeKeys.values()), kvs: [...this.scopeKVs] }, transitions: Array.from(this.transitionsSubs.values()) }; } // newSubs will return only new subs since the last call. newSubs() { if (!this.dirty) { return; } const current = this.subs; const { scopes: { ids, kinds, names, keys, kvs }, participants, transitions } = this.last; const kvsstrs = kvs.map((kv) => kvstr(kv)); const next = { participants: this.participantSub && !participants, scopes: { ids: current.scopes.ids.filter((id) => !ids.includes(id)), kinds: current.scopes.kinds.filter((kind) => !kinds.includes(kind)), names: current.scopes.names.filter((name) => !names.includes(name)), keys: current.scopes.keys.filter((key) => !keys.includes(key)), kvs: current.scopes.kvs.filter((kv) => !kvsstrs.includes(kvstr(kv))) }, transitions: current.transitions.filter( (id) => !transitions.includes(id) ) }; this.last = current; this.dirty = false; return next; } scopeSub(input) { if (input.ids) { for (const id of input.ids) { if (!this.scopeIDs.has(id)) { this.scopeIDs.add(id); this.dirty = true; } } } if (input.kinds) { for (const id of input.kinds) { if (!this.scopeKinds.has(id)) { this.scopeKinds.add(id); this.dirty = true; } } } if (input.names) { for (const name of input.names) { if (!this.scopeNames.has(name)) { this.scopeNames.add(name); this.dirty = true; } } } if (input.keys) { for (const key of input.keys) { if (!this.scopeKeys.has(key)) { this.scopeKeys.add(key); this.dirty = true; } } } if (input.kvs) { for (const kv of input.kvs) { const kvKey = kvstr(kv); if (!this.scopeKVSet.has(kvKey)) { this.scopeKVSet.add(kvKey); this.scopeKVs.push(kv); this.dirty = true; } } } } participantsSub() { if (!this.participantSub) { this.dirty = true; this.participantSub = true; } } transitionsSub(nodeID) { if (!this.transitionsSubs.has(nodeID)) { this.transitionsSubs.add(nodeID); this.dirty = true; } } }; // src/admin/transitions.ts var import_tajriba3 = require("@empirica/tajriba"); function transitionsSub(taj, transitions, nodeID) { taj.onEvent({ eventTypes: [import_tajriba3.EventType.TransitionAdd], nodeID }).subscribe({ next({ node }) { if (!node) { return; } if (node.__typename !== "Transition") { error(`received non-transition`); return; } if (node.node.__typename !== "Step") { error(`received non-step transition`, node.node); return; } transitions.next({ id: node.id, to: node.to, from: node.from, step: { id: node.node.id, duration: node.node.duration, state: node.node.state } }); } }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AdminConnection, Attribute, Attributes, EventContext, ListenersCollector, Scope, Scopes, Subscriptions, TajribaConnection, TajribaEvent, participantsSub, transitionsSub }); //# sourceMappingURL=user.cjs.map