Fix Story 1.3: StateStore spec compliance and minor cleanup
Critical Fix: - StateStore now uses global Hooks.callAll directly (per spec) - Removed hooks parameter from StateStore constructor - Updated module.js to pass only adapter.settings - Updated tests to stub globalThis.Hooks Minor Cleanup: - Fixed misleading warning in SocketHandler.registerPendingOp - Added clarifying comment for setMatrix _revision behavior Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -13,8 +13,6 @@
|
||||
* @module contracts/pending-op
|
||||
*/
|
||||
|
||||
/** @typedef {Object} PendingOp */
|
||||
|
||||
/** Shape version constant for PendingOp. @type {1} */
|
||||
export const PENDING_OP_VERSION = 1;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Socket message contract.
|
||||
*
|
||||
* Two message directions:
|
||||
* Intent (GM → all): scrying-pool.visibility.set { opId, userId, targetState }
|
||||
* Intent (GM → all): scrying-pool.visibility.set { opId, userId, targetState, baseRevision }
|
||||
* Echo (all ← GM): scrying-pool.visibility.updated { opId, userId, state, revision }
|
||||
*
|
||||
* Validated at both send and receive. Payload ≥ 4096 bytes → throw before emit.
|
||||
@@ -17,6 +17,7 @@
|
||||
* @property {string} opId - Unique operation ID (non-empty string).
|
||||
* @property {string} userId - Target participant userId (non-empty string).
|
||||
* @property {string} targetState - Desired VisibilityState.
|
||||
* @property {number} baseRevision - Revision counter when the GM issued the intent (for latest-revision-wins guard).
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -47,12 +48,13 @@ export const MAX_PAYLOAD_BYTES = 4096;
|
||||
* @param {string} opId - Unique operation ID.
|
||||
* @param {string} userId - Target participant userId.
|
||||
* @param {string} targetState - Desired VisibilityState.
|
||||
* @param {number} baseRevision - Revision counter at time of intent.
|
||||
* @returns {SocketMessage}
|
||||
*/
|
||||
export function createSocketIntentMessage(opId, userId, targetState) {
|
||||
export function createSocketIntentMessage(opId, userId, targetState, baseRevision) {
|
||||
return {
|
||||
event: SOCKET_EVENTS.VISIBILITY_SET,
|
||||
payload: { opId, userId, targetState },
|
||||
payload: { opId, userId, targetState, baseRevision },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ export function isValidSocketMessage(data) {
|
||||
const p = /** @type {Record<string, unknown>} */ (payload);
|
||||
// Validate intent payload
|
||||
if (event === SOCKET_EVENTS.VISIBILITY_SET) {
|
||||
const { opId, userId, targetState, ...payloadRest } = p;
|
||||
const { opId, userId, targetState, baseRevision, ...payloadRest } = p;
|
||||
if (Object.keys(payloadRest).length > 0) {
|
||||
throw new TypeError(`SocketMessage intent: unknown payload keys: ${Object.keys(payloadRest).join(", ")}`);
|
||||
}
|
||||
@@ -108,6 +110,9 @@ export function isValidSocketMessage(data) {
|
||||
if (typeof targetState !== "string" || targetState.length === 0) {
|
||||
throw new TypeError("SocketMessage: targetState must be a non-empty string");
|
||||
}
|
||||
if (typeof baseRevision !== "number" || !Number.isFinite(baseRevision) || baseRevision < 0) {
|
||||
throw new TypeError("SocketMessage: baseRevision must be a finite non-negative number");
|
||||
}
|
||||
}
|
||||
// Validate echo payload
|
||||
if (event === SOCKET_EVENTS.VISIBILITY_UPDATED) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* @typedef {Object} VisibilityMatrix
|
||||
* @property {1} _version - Schema version; always 1 for v1.
|
||||
* @property {Object.<string, VisibilityState>} matrix - userId → VisibilityState map.
|
||||
* @property {number} [_revision] - Optional revision counter for optimistic concurrency control.
|
||||
*/
|
||||
|
||||
export const VISIBILITY_STATES = Object.freeze([
|
||||
@@ -50,7 +51,7 @@ export function isValidVisibilityMatrix(data) {
|
||||
throw new TypeError("VisibilityMatrix: must be an object");
|
||||
}
|
||||
const obj = /** @type {Record<string, unknown>} */ (data);
|
||||
const { _version, matrix, ...rest } = obj;
|
||||
const { _version, matrix, _revision, ...rest } = obj;
|
||||
if (Object.keys(rest).length > 0) {
|
||||
throw new TypeError(`VisibilityMatrix: unknown keys: ${Object.keys(rest).join(", ")}`);
|
||||
}
|
||||
@@ -60,6 +61,9 @@ export function isValidVisibilityMatrix(data) {
|
||||
if (matrix === null || typeof matrix !== "object" || Array.isArray(matrix)) {
|
||||
throw new TypeError("VisibilityMatrix: matrix must be a plain object");
|
||||
}
|
||||
if (_revision !== undefined && (typeof _revision !== "number" || !Number.isFinite(_revision) || _revision < 0)) {
|
||||
throw new TypeError("VisibilityMatrix: _revision must be a finite non-negative number");
|
||||
}
|
||||
const matrixObj = /** @type {Record<string, unknown>} */ (matrix);
|
||||
for (const [userId, state] of Object.entries(matrixObj)) {
|
||||
if (typeof userId !== "string" || userId.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user