Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions dist/cjs/internals.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,24 @@ exports.default = (0, sbp_1.default)('sbp/selectors/register', {
return;
this.config.reactiveSet(targetState._volatile, 'pendingKeyRequests', targetState._volatile.pendingKeyRequests.filter((pkr) => pkr?.name !== signingKey.name));
},
'chelonia/private/operationHook': function (contractID, contractName, message, state, atomicIndex) {
if (this.config.skipActionProcessing)
return;
const manifestHash = this.config.contracts.manifests[contractName];
if (manifestHash) {
const hook = `${manifestHash}/${contractName}/hook/${message.opType()}`;
// Check if a hook is defined
if ((0, sbp_1.default)('sbp/selectors/fn', hook)) {
// And call it
try {
(0, sbp_1.default)(hook, { contractID, message, state, atomicIndex });
}
catch (e) {
console.error(`[chelonia/private/operationHook] Error at operation hook for ${contractID}`, e);
}
}
}
},
'chelonia/private/in/processMessage': async function (message, state, internalSideEffectStack, contractName) {
const [opT, opV] = message.op();
const hash = message.hash();
Expand Down Expand Up @@ -767,6 +785,7 @@ exports.default = (0, sbp_1.default)('sbp/selectors/register', {
throw new Error('Inside OP_ATOMIC: no matching signing key was defined');
}
await opFns[u[0]](u[1]);
(0, sbp_1.default)('chelonia/private/operationHook', contractID, contractName, message, state, i);
}
catch (e_) {
const e = e_;
Expand Down Expand Up @@ -1312,24 +1331,24 @@ exports.default = (0, sbp_1.default)('sbp/selectors/register', {
},
[SPMessage_js_1.SPMessage.OP_PROTOCOL_UPGRADE]: notImplemented
};
const rootState = (0, sbp_1.default)(this.config.stateSelector);
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
(0, turtledash_1.has)(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
(0, turtledash_1.has)(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage_js_1.SPMessage.OP_CONTRACT
? opV.type
: '';
}
if (!contractName) {
throw new Error(`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`);
}
if (!this.config.skipActionProcessing && !this.manifestToContract[manifestHash]) {
const rootState = (0, sbp_1.default)(this.config.stateSelector);
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
(0, turtledash_1.has)(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
(0, turtledash_1.has)(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage_js_1.SPMessage.OP_CONTRACT
? opV.type
: '';
}
if (!contractName) {
throw new Error(`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`);
}
await (0, sbp_1.default)('chelonia/private/loadManifest', contractName, manifestHash);
}
let processOp = true;
Expand Down Expand Up @@ -1368,6 +1387,7 @@ exports.default = (0, sbp_1.default)('sbp/selectors/register', {
}
if (processOp) {
await opFns[opT](opV);
(0, sbp_1.default)('chelonia/private/operationHook', contractID, contractName, message, state);
config.postOp?.(message, state);
config[`postOp_${opT}`]?.(message, state); // hack to fix syntax highlighting `
}
Expand Down
54 changes: 37 additions & 17 deletions dist/esm/internals.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,24 @@ export default sbp('sbp/selectors/register', {
return;
this.config.reactiveSet(targetState._volatile, 'pendingKeyRequests', targetState._volatile.pendingKeyRequests.filter((pkr) => pkr?.name !== signingKey.name));
},
'chelonia/private/operationHook': function (contractID, contractName, message, state, atomicIndex) {
if (this.config.skipActionProcessing)
return;
const manifestHash = this.config.contracts.manifests[contractName];
if (manifestHash) {
const hook = `${manifestHash}/${contractName}/hook/${message.opType()}`;
// Check if a hook is defined
if (sbp('sbp/selectors/fn', hook)) {
// And call it
try {
sbp(hook, { contractID, message, state, atomicIndex });
}
catch (e) {
console.error(`[chelonia/private/operationHook] Error at operation hook for ${contractID}`, e);
}
}
}
},
'chelonia/private/in/processMessage': async function (message, state, internalSideEffectStack, contractName) {
const [opT, opV] = message.op();
const hash = message.hash();
Expand Down Expand Up @@ -732,6 +750,7 @@ export default sbp('sbp/selectors/register', {
throw new Error('Inside OP_ATOMIC: no matching signing key was defined');
}
await opFns[u[0]](u[1]);
sbp('chelonia/private/operationHook', contractID, contractName, message, state, i);
}
catch (e_) {
const e = e_;
Expand Down Expand Up @@ -1277,24 +1296,24 @@ export default sbp('sbp/selectors/register', {
},
[SPMessage.OP_PROTOCOL_UPGRADE]: notImplemented
};
const rootState = sbp(this.config.stateSelector);
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
has(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
has(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage.OP_CONTRACT
? opV.type
: '';
}
if (!contractName) {
throw new Error(`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`);
}
if (!this.config.skipActionProcessing && !this.manifestToContract[manifestHash]) {
const rootState = sbp(this.config.stateSelector);
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
has(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
has(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage.OP_CONTRACT
? opV.type
: '';
}
if (!contractName) {
throw new Error(`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`);
}
await sbp('chelonia/private/loadManifest', contractName, manifestHash);
}
let processOp = true;
Expand Down Expand Up @@ -1333,6 +1352,7 @@ export default sbp('sbp/selectors/register', {
}
if (processOp) {
await opFns[opT](opV);
sbp('chelonia/private/operationHook', contractID, contractName, message, state);
config.postOp?.(message, state);
config[`postOp_${opT}`]?.(message, state); // hack to fix syntax highlighting `
}
Expand Down
66 changes: 47 additions & 19 deletions src/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,32 @@ export default sbp('sbp/selectors/register', {
targetState._volatile.pendingKeyRequests.filter((pkr) => pkr?.name !== signingKey.name)
)
},
'chelonia/private/operationHook': function (
this: CheloniaContext,
contractID: string,
contractName: string,
message: SPMessage,
state: ChelContractState,
atomicIndex?: number
) {
if (this.config.skipActionProcessing) return
const manifestHash = this.config.contracts.manifests[contractName]
if (manifestHash) {
Comment on lines +950 to +951
Copy link
Member

@taoeffect taoeffect Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook will only run on the latest version of the contract, is that intentional?

EDIT: I think we should make this behave like contract methods, which are version controlled. We don't want the hook to only run for the latest version, but for all versions, and for it to be consistent for those specific versions. See how the contract methods are implemented for details.

const hook = `${manifestHash}/${contractName}/hook/${message.opType()}`
// Check if a hook is defined
if (sbp('sbp/selectors/fn', hook)) {
// And call it
try {
sbp(hook, { contractID, message, state, atomicIndex })
} catch (e) {
console.error(
`[chelonia/private/operationHook] Error at operation hook for ${contractID}`,
e
)
}
}
}
},
'chelonia/private/in/processMessage': async function (
this: CheloniaContext,
message: SPMessage,
Expand Down Expand Up @@ -1005,6 +1031,7 @@ export default sbp('sbp/selectors/register', {
throw new Error('Inside OP_ATOMIC: no matching signing key was defined')
}
await (opFns[u[0]] as (x: unknown) => Promise<void>)(u[1])
sbp('chelonia/private/operationHook', contractID, contractName, message, state, i)
Copy link
Member Author

@corrideat corrideat Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something that should also be done for config.preOp and config.postOp? I mean, should this be something like this? (ignore the broken processOp part, which would need to be adjusted)

   if (config[`preOp_${opT}`]) {
      processOp = config[`preOp_${opT}`]!(message, state, i) !== false && processOp
    }
    if (processOp) {
      await (opFns[u[0]] as (x: unknown) => Promise<void>)(u[1])
      sbp('chelonia/private/operationHook', contractID, contractName, message, state, i)
      config.postOp?.(message, state, i)
      config[`postOp_${opT}`]?.(message, state, i) // hack to fix syntax highlighting `
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean to ask whether we should also call postOp/preOp here inside of the OP_ATOMIC, no, I think that's excessive.

} catch (e_) {
const e = e_ as Error
if (e && typeof e === 'object') {
Expand Down Expand Up @@ -1718,26 +1745,26 @@ export default sbp('sbp/selectors/register', {
},
[SPMessage.OP_PROTOCOL_UPGRADE]: notImplemented
}
const rootState = sbp(this.config.stateSelector) as ChelRootState
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
has(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
has(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage.OP_CONTRACT
? (opV as SPOpContract).type
: ''
}
if (!contractName) {
throw new Error(
`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`
)
}
if (!this.config.skipActionProcessing && !this.manifestToContract[manifestHash]) {
const rootState = sbp(this.config.stateSelector) as ChelRootState
// Having rootState.contracts[contractID] is not enough to determine we
// have previously synced this contract, as reference counts are also
// stored there. Hence, we check for the presence of 'type'
if (!contractName) {
contractName =
has(rootState.contracts, contractID) &&
rootState.contracts[contractID] &&
has(rootState.contracts[contractID], 'type')
? rootState.contracts[contractID].type
: opT === SPMessage.OP_CONTRACT
? (opV as SPOpContract).type
: ''
}
if (!contractName) {
throw new Error(
`Unable to determine the name for a contract and refusing to load it (contract ID was ${contractID} and its manifest hash was ${manifestHash})`
)
}
await sbp('chelonia/private/loadManifest', contractName, manifestHash)
}
let processOp = true
Expand Down Expand Up @@ -1782,6 +1809,7 @@ export default sbp('sbp/selectors/register', {
}
if (processOp) {
await (opFns[opT] as (op: unknown) => Promise<void>)(opV)
sbp('chelonia/private/operationHook', contractID, contractName, message, state)
config.postOp?.(message, state)
config[`postOp_${opT}`]?.(message, state) // hack to fix syntax highlighting `
}
Expand Down