@@ -5,7 +5,8 @@ import { objectMap } from '@endo/patterns';
5
5
6
6
import { defendPrototype , defendPrototypeKit } from './exo-tools.js' ;
7
7
8
- const { create, seal, freeze, defineProperty } = Object ;
8
+ const { Fail, quote : q } = assert ;
9
+ const { create, seal, freeze, defineProperty, entries, values } = Object ;
9
10
10
11
const { getEnvironmentOption } = makeEnvironmentCaptor ( globalThis ) ;
11
12
const DEBUG = getEnvironmentOption ( 'DEBUG' , '' ) ;
@@ -62,11 +63,24 @@ export const initEmpty = () => emptyRecord;
62
63
* Each property is distinct, is checked and changed separately.
63
64
*/
64
65
66
+ /**
67
+ * @callback Revoker
68
+ * @param {object } exo
69
+ * @returns {boolean }
70
+ */
71
+
72
+ /**
73
+ * @callback GetRevoker
74
+ * @param {Revoker } revoke
75
+ * @returns {void }
76
+ */
77
+
65
78
/**
66
79
* @template C
67
80
* @typedef {object } FarClassOptions
68
81
* @property {(context: C) => void } [finish]
69
82
* @property {StateShape } [stateShape]
83
+ * @property {GetRevoker } [getRevoker]
70
84
*/
71
85
72
86
/**
@@ -79,9 +93,15 @@ export const initEmpty = () => emptyRecord;
79
93
* @param {FarClassOptions<ClassContext<ReturnType<I>, M>> } [options]
80
94
* @returns {(...args: Parameters<I>) => (M & import('@endo/eventual-send').RemotableBrand<{}, M>) }
81
95
*/
82
- export const defineExoClass = ( tag , interfaceGuard , init , methods , options ) => {
96
+ export const defineExoClass = (
97
+ tag ,
98
+ interfaceGuard ,
99
+ init ,
100
+ methods ,
101
+ options = { } ,
102
+ ) => {
83
103
harden ( methods ) ;
84
- const { finish = undefined } = options || { } ;
104
+ const { finish = undefined , getRevoker = undefined } = options ;
85
105
/** @type {WeakMap<M,ClassContext<ReturnType<I>, M>> } */
86
106
const contextMap = new WeakMap ( ) ;
87
107
const proto = defendPrototype (
@@ -113,6 +133,13 @@ export const defineExoClass = (tag, interfaceGuard, init, methods, options) => {
113
133
self
114
134
) ;
115
135
} ;
136
+
137
+ if ( getRevoker ) {
138
+ const revoke = self => contextMap . delete ( self ) ;
139
+ harden ( revoke ) ;
140
+ getRevoker ( revoke ) ;
141
+ }
142
+
116
143
return harden ( makeInstance ) ;
117
144
} ;
118
145
harden ( defineExoClass ) ;
@@ -132,14 +159,14 @@ export const defineExoClassKit = (
132
159
interfaceGuardKit ,
133
160
init ,
134
161
methodsKit ,
135
- options ,
162
+ options = { } ,
136
163
) => {
137
164
harden ( methodsKit ) ;
138
- const { finish = undefined } = options || { } ;
165
+ const { finish = undefined , getRevoker = undefined } = options ;
139
166
const contextMapKit = objectMap ( methodsKit , ( ) => new WeakMap ( ) ) ;
140
167
const getContextKit = objectMap (
141
- methodsKit ,
142
- ( _v , name ) => facet => contextMapKit [ name ] . get ( facet ) ,
168
+ contextMapKit ,
169
+ contextMap => facet => contextMap . get ( facet ) ,
143
170
) ;
144
171
const prototypeKit = defendPrototypeKit (
145
172
tag ,
@@ -172,6 +199,34 @@ export const defineExoClassKit = (
172
199
}
173
200
return context . facets ;
174
201
} ;
202
+
203
+ if ( getRevoker ) {
204
+ const revoke = aFacet => {
205
+ let seenTrue = false ;
206
+ let facets ;
207
+ for ( const contextMap of values ( contextMapKit ) ) {
208
+ if ( contextMap . has ( aFacet ) ) {
209
+ seenTrue = true ;
210
+ facets = contextMap . get ( aFacet ) . facets ;
211
+ break ;
212
+ }
213
+ }
214
+ if ( ! seenTrue ) {
215
+ return false ;
216
+ }
217
+ // eslint-disable-next-line no-use-before-define
218
+ for ( const [ facetName , facet ] of entries ( facets ) ) {
219
+ const seen = contextMapKit [ facetName ] . delete ( facet ) ;
220
+ if ( seen === false ) {
221
+ Fail `internal: inconsistent facet revocation ${ q ( facetName ) } ` ;
222
+ }
223
+ }
224
+ return seenTrue ;
225
+ } ;
226
+ harden ( revoke ) ;
227
+ getRevoker ( revoke ) ;
228
+ }
229
+
175
230
return harden ( makeInstanceKit ) ;
176
231
} ;
177
232
harden ( defineExoClassKit ) ;
0 commit comments