@gtkx/runtime 0.0.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +171 -3
  3. package/dist/arg.d.ts +13 -0
  4. package/dist/arg.d.ts.map +1 -0
  5. package/dist/arg.js +6 -0
  6. package/dist/arg.js.map +1 -0
  7. package/dist/bind.d.ts +5 -0
  8. package/dist/bind.d.ts.map +1 -0
  9. package/dist/bind.js +19 -0
  10. package/dist/bind.js.map +1 -0
  11. package/dist/callback.d.ts +13 -0
  12. package/dist/callback.d.ts.map +1 -0
  13. package/dist/callback.js +86 -0
  14. package/dist/callback.js.map +1 -0
  15. package/dist/descriptors.d.ts +158 -0
  16. package/dist/descriptors.d.ts.map +1 -0
  17. package/dist/descriptors.js +135 -0
  18. package/dist/descriptors.js.map +1 -0
  19. package/dist/error.d.ts +24 -0
  20. package/dist/error.d.ts.map +1 -0
  21. package/dist/error.js +33 -0
  22. package/dist/error.js.map +1 -0
  23. package/dist/exit-hook.d.ts +2 -0
  24. package/dist/exit-hook.d.ts.map +1 -0
  25. package/dist/exit-hook.js +3 -0
  26. package/dist/exit-hook.js.map +1 -0
  27. package/dist/fn.d.ts +10 -0
  28. package/dist/fn.d.ts.map +1 -0
  29. package/dist/fn.js +90 -0
  30. package/dist/fn.js.map +1 -0
  31. package/dist/index.d.ts +16 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +16 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/internal.d.ts +5 -0
  36. package/dist/internal.d.ts.map +1 -0
  37. package/dist/internal.js +5 -0
  38. package/dist/internal.js.map +1 -0
  39. package/dist/library.d.ts +8 -0
  40. package/dist/library.d.ts.map +1 -0
  41. package/dist/library.js +14 -0
  42. package/dist/library.js.map +1 -0
  43. package/dist/lifecycle.d.ts +42 -0
  44. package/dist/lifecycle.d.ts.map +1 -0
  45. package/dist/lifecycle.js +70 -0
  46. package/dist/lifecycle.js.map +1 -0
  47. package/dist/listeners.d.ts +36 -0
  48. package/dist/listeners.d.ts.map +1 -0
  49. package/dist/listeners.js +85 -0
  50. package/dist/listeners.js.map +1 -0
  51. package/dist/mixin.d.ts +13 -0
  52. package/dist/mixin.d.ts.map +1 -0
  53. package/dist/mixin.js +49 -0
  54. package/dist/mixin.js.map +1 -0
  55. package/dist/native-value.d.ts +13 -0
  56. package/dist/native-value.d.ts.map +1 -0
  57. package/dist/native-value.js +105 -0
  58. package/dist/native-value.js.map +1 -0
  59. package/dist/object.d.ts +32 -0
  60. package/dist/object.d.ts.map +1 -0
  61. package/dist/object.js +66 -0
  62. package/dist/object.js.map +1 -0
  63. package/dist/promisify.d.ts +21 -0
  64. package/dist/promisify.d.ts.map +1 -0
  65. package/dist/promisify.js +39 -0
  66. package/dist/promisify.js.map +1 -0
  67. package/dist/properties.d.ts +14 -0
  68. package/dist/properties.d.ts.map +1 -0
  69. package/dist/properties.js +79 -0
  70. package/dist/properties.js.map +1 -0
  71. package/dist/register-class.d.ts +25 -0
  72. package/dist/register-class.d.ts.map +1 -0
  73. package/dist/register-class.js +178 -0
  74. package/dist/register-class.js.map +1 -0
  75. package/dist/registry.d.ts +69 -0
  76. package/dist/registry.d.ts.map +1 -0
  77. package/dist/registry.js +186 -0
  78. package/dist/registry.js.map +1 -0
  79. package/dist/signal.d.ts +35 -0
  80. package/dist/signal.d.ts.map +1 -0
  81. package/dist/signal.js +95 -0
  82. package/dist/signal.js.map +1 -0
  83. package/dist/t.d.ts +50 -0
  84. package/dist/t.d.ts.map +1 -0
  85. package/dist/t.js +49 -0
  86. package/dist/t.js.map +1 -0
  87. package/dist/tuple.d.ts +7 -0
  88. package/dist/tuple.d.ts.map +1 -0
  89. package/dist/tuple.js +26 -0
  90. package/dist/tuple.js.map +1 -0
  91. package/dist/type.d.ts +88 -0
  92. package/dist/type.d.ts.map +1 -0
  93. package/dist/type.js +194 -0
  94. package/dist/type.js.map +1 -0
  95. package/dist/value.d.ts +29 -0
  96. package/dist/value.d.ts.map +1 -0
  97. package/dist/value.js +292 -0
  98. package/dist/value.js.map +1 -0
  99. package/package.json +54 -13
  100. package/src/arg.ts +15 -0
  101. package/src/bind.ts +31 -0
  102. package/src/callback.ts +149 -0
  103. package/src/descriptors.ts +350 -0
  104. package/src/error.ts +58 -0
  105. package/src/exit-hook.ts +3 -0
  106. package/src/fn.ts +139 -0
  107. package/src/index.ts +59 -0
  108. package/src/internal.ts +12 -0
  109. package/src/library.ts +17 -0
  110. package/src/lifecycle.ts +101 -0
  111. package/src/listeners.ts +112 -0
  112. package/src/mixin.ts +73 -0
  113. package/src/native-value.ts +135 -0
  114. package/src/object.ts +95 -0
  115. package/src/promisify.ts +67 -0
  116. package/src/properties.ts +115 -0
  117. package/src/register-class.ts +305 -0
  118. package/src/registry.ts +311 -0
  119. package/src/signal.ts +192 -0
  120. package/src/t.ts +90 -0
  121. package/src/tuple.ts +37 -0
  122. package/src/type.ts +290 -0
  123. package/src/value.ts +430 -0
@@ -0,0 +1,305 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+ import {
3
+ registerClass as nativeRegisterClass,
4
+ type RegisterClassOptions as NativeRegisterClassOptions,
5
+ type RegisterClassVfunc as NativeRegisterClassVfunc,
6
+ } from "@gtkx/native";
7
+ import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
8
+ import { wrapCallback } from "./callback.js";
9
+ import {
10
+ buildAccessors,
11
+ makeGetProperty,
12
+ makeSetProperty,
13
+ type PropertyAccessor,
14
+ type PropertySpec,
15
+ toNativeProperties,
16
+ } from "./properties.js";
17
+ import {
18
+ getClassType,
19
+ getInterfaceVfuncRegistry,
20
+ getVfuncRegistry,
21
+ registerClassType,
22
+ type VfuncDescriptor,
23
+ } from "./registry.js";
24
+ import { TYPE_INVALID, typeInterfaces } from "./type.js";
25
+
26
+ type RegisterClassOptions = {
27
+ typeName?: string;
28
+ /**
29
+ * Properties to install on the new type, keyed by canonical property name. Each value is the
30
+ * `GObject.ParamSpec` describing it, for example `GObject.paramSpecString(...)`. Accessors are
31
+ * generated on the prototype for the dashed, underscored and camelCased spellings of the name
32
+ * unless the class already defines one, and writing through them emits `notify`.
33
+ */
34
+ properties?: Record<string, PropertySpec>;
35
+ };
36
+
37
+ type VfuncFn = NativeRegisterClassVfunc["fn"];
38
+ type DiscoveredVfunc<K extends "class" | "interface"> = VfuncDescriptor<K> & { methodName: string; fn: VfuncFn };
39
+ type DiscoveredClassVfunc = DiscoveredVfunc<"class">;
40
+ type DiscoveredInterfaceVfunc = DiscoveredVfunc<"interface">;
41
+ type InterfaceVfuncBinding = { gtype: bigint; vtableSize: number; vfuncs: DiscoveredInterfaceVfunc[] };
42
+
43
+ const UNSUPPORTED_CONSTRUCT_VFUNCS: Set<string> = new Set(["constructed", "setProperty", "getProperty"]);
44
+ const VALUE_ARG_INDEX = 2;
45
+
46
+ /**
47
+ * Registers a subclass of a wrapper class as a new GType, wiring up any class and
48
+ * inherited-interface virtual functions it overrides. Throws if the class does not
49
+ * extend a registered wrapper class, has no derivable type name, or overrides an
50
+ * unsupported construct-time vtable slot.
51
+ *
52
+ * @param klass The subclass to register.
53
+ * @param options Registration options, such as an explicit type name.
54
+ * @returns The same class, now registered.
55
+ */
56
+ function registerClass<T extends AnyClass>(klass: T, options: RegisterClassOptions = {}): T {
57
+ const parentType = resolveParentType(klass);
58
+
59
+ if (parentType === TYPE_INVALID) {
60
+ throw new TypeError(`registerClass: ${klass.name} must extend a registered wrapper class`);
61
+ }
62
+
63
+ const name = options.typeName ?? klass.name;
64
+
65
+ if (!name) {
66
+ throw new Error("registerClass: cannot derive a GType name (anonymous class with no typeName option)");
67
+ }
68
+
69
+ const properties = options.properties ?? {};
70
+ const accessors = buildAccessors(klass, properties);
71
+ const classVfuncs = [...discoverClassVfuncs(klass), ...propertyVfuncs(klass, accessors)];
72
+ const claimedMethodNames = new Set(classVfuncs.map((vfunc) => vfunc.methodName));
73
+ const interfaceBindings = discoverInheritedInterfaceVfuncs(klass, parentType, claimedMethodNames);
74
+ const nativeOptions = toNativeOptions(classVfuncs, interfaceBindings, properties);
75
+ const newType: bigint = nativeRegisterClass(name, parentType, nativeOptions);
76
+ registerClassType(klass, newType);
77
+
78
+ return klass;
79
+ }
80
+
81
+ function resolveParentType(klass: AnyClass): bigint {
82
+ return (
83
+ walkClassChain(getParentClass(klass), (cls) => {
84
+ const gtype = getClassType(cls);
85
+
86
+ return gtype === TYPE_INVALID ? undefined : gtype;
87
+ }) ?? TYPE_INVALID
88
+ );
89
+ }
90
+
91
+ function ownInstanceMethodNames(klass: AnyClass): string[] {
92
+ const proto = (klass as { prototype?: object }).prototype;
93
+
94
+ if (!proto) {
95
+ return [];
96
+ }
97
+
98
+ return Object.getOwnPropertyNames(proto).filter((name) => {
99
+ if (name === "constructor") {
100
+ return false;
101
+ }
102
+
103
+ return typeof Object.getOwnPropertyDescriptor(proto, name)?.value === "function";
104
+ });
105
+ }
106
+
107
+ function buildDiscoveredVfunc<K extends "class" | "interface">(
108
+ proto: Record<string, VfuncFn>,
109
+ methodName: string,
110
+ resolveDescriptor: (methodName: string) => VfuncDescriptor<K> | undefined,
111
+ skip: Set<string> | undefined,
112
+ ): DiscoveredVfunc<K> | undefined {
113
+ if (skip?.has(methodName)) {
114
+ return undefined;
115
+ }
116
+
117
+ const descriptor = resolveDescriptor(methodName);
118
+
119
+ if (!descriptor) {
120
+ return undefined;
121
+ }
122
+
123
+ const fn = proto[methodName];
124
+
125
+ if (!fn) {
126
+ return undefined;
127
+ }
128
+
129
+ return {
130
+ ...descriptor,
131
+ methodName,
132
+ fn: wrapVfunc(fn, descriptor.argDescriptors, descriptor.returnDescriptor),
133
+ };
134
+ }
135
+
136
+ function collectDiscoveredVfuncs<K extends "class" | "interface">(
137
+ klass: AnyClass,
138
+ resolveDescriptor: (methodName: string) => VfuncDescriptor<K> | undefined,
139
+ skip?: Set<string>,
140
+ ): DiscoveredVfunc<K>[] {
141
+ const proto = (klass as { prototype: Record<string, VfuncFn> }).prototype;
142
+ const result: DiscoveredVfunc<K>[] = [];
143
+
144
+ for (const methodName of ownInstanceMethodNames(klass)) {
145
+ const discovered = buildDiscoveredVfunc(proto, methodName, resolveDescriptor, skip);
146
+
147
+ if (discovered) {
148
+ result.push(discovered);
149
+ }
150
+ }
151
+
152
+ return result;
153
+ }
154
+
155
+ function discoverClassVfuncs(klass: AnyClass): DiscoveredClassVfunc[] {
156
+ return collectDiscoveredVfuncs(klass, (methodName) => {
157
+ const descriptor = findClassVfuncDescriptor(klass, methodName);
158
+
159
+ if (descriptor && UNSUPPORTED_CONSTRUCT_VFUNCS.has(methodName)) {
160
+ throw new Error(
161
+ `registerClass: overriding the GObject construct-time vtable slot '${methodName}' is not ` +
162
+ "supported; run construct-time initialization in the subclass constructor, after " +
163
+ "super(...), instead",
164
+ );
165
+ }
166
+
167
+ return descriptor ?? undefined;
168
+ });
169
+ }
170
+
171
+ function wrapVfunc(
172
+ fn: VfuncFn,
173
+ argDescriptors: NativeRegisterClassVfunc["argDescriptors"],
174
+ returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"],
175
+ ): VfuncFn {
176
+ return wrapCallback(fn as (...args: unknown[]) => unknown, { argDescriptors, returnDescriptor }, "this");
177
+ }
178
+
179
+ function discoverInheritedInterfaceVfuncs(
180
+ klass: AnyClass,
181
+ parentGtype: bigint,
182
+ claimedMethodNames: Set<string>,
183
+ ): InterfaceVfuncBinding[] {
184
+ const bindings: InterfaceVfuncBinding[] = [];
185
+
186
+ for (const interfaceGtype of typeInterfaces(parentGtype)) {
187
+ const [first, ...rest] = discoverInterfaceVfuncs(klass, interfaceGtype, claimedMethodNames);
188
+
189
+ if (first !== undefined) {
190
+ bindings.push({ gtype: interfaceGtype, vtableSize: first.vtableSize, vfuncs: [first, ...rest] });
191
+ }
192
+ }
193
+
194
+ return bindings;
195
+ }
196
+
197
+ function discoverInterfaceVfuncs(
198
+ klass: AnyClass,
199
+ interfaceGtype: bigint,
200
+ claimedMethodNames: Set<string>,
201
+ ): DiscoveredInterfaceVfunc[] {
202
+ const vfuncRegistry = getInterfaceVfuncRegistry(interfaceGtype);
203
+
204
+ if (!vfuncRegistry) {
205
+ return [];
206
+ }
207
+
208
+ return collectDiscoveredVfuncs(
209
+ klass,
210
+ (methodName) => {
211
+ const entry = vfuncRegistry[methodName];
212
+
213
+ return entry?.kind === "interface" ? entry : undefined;
214
+ },
215
+ claimedMethodNames,
216
+ );
217
+ }
218
+
219
+ function findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDescriptor<"class"> | null {
220
+ return (
221
+ walkClassChain(getParentClass(klass), (cls) => {
222
+ const entry = getVfuncRegistry(cls)?.[methodName];
223
+
224
+ return entry?.kind === "class" ? entry : undefined;
225
+ }) ?? null
226
+ );
227
+ }
228
+
229
+ function propertyVfuncs(klass: AnyClass, accessors: PropertyAccessor[]): DiscoveredClassVfunc[] {
230
+ if (accessors.length === 0) {
231
+ return [];
232
+ }
233
+
234
+ return [
235
+ buildPropertyVfunc(klass, "getProperty", makeGetProperty(accessors), true),
236
+ buildPropertyVfunc(klass, "setProperty", makeSetProperty(accessors), false),
237
+ ];
238
+ }
239
+
240
+ // `get_property` fills a GValue the caller owns, so the marshalled copy handed to JavaScript has to
241
+ // be written back into it; `set_property` only reads its GValue and needs no copy back.
242
+ function markValueCallerAllocated(argDescriptors: Descriptor[]): Descriptor[] {
243
+ return argDescriptors.map((arg, index) =>
244
+ index === VALUE_ARG_INDEX ? { ...arg, callerAllocated: true } : arg);
245
+ }
246
+
247
+ function buildPropertyVfunc(
248
+ klass: AnyClass,
249
+ methodName: string,
250
+ fn: VfuncFn,
251
+ isValueOut: boolean,
252
+ ): DiscoveredClassVfunc {
253
+ const descriptor = findClassVfuncDescriptor(klass, methodName);
254
+
255
+ if (!descriptor) {
256
+ throw new Error(`registerClass: the parent class exposes no '${methodName}' vtable slot`);
257
+ }
258
+
259
+ const argDescriptors = isValueOut
260
+ ? markValueCallerAllocated(descriptor.argDescriptors)
261
+ : descriptor.argDescriptors;
262
+
263
+ return {
264
+ ...descriptor,
265
+ methodName,
266
+ argDescriptors,
267
+ fn: wrapVfunc(fn, argDescriptors, descriptor.returnDescriptor),
268
+ };
269
+ }
270
+
271
+ function toNativeOptions(
272
+ classVfuncs: DiscoveredClassVfunc[],
273
+ interfaceBindings: InterfaceVfuncBinding[],
274
+ properties: Record<string, PropertySpec>,
275
+ ): NativeRegisterClassOptions | undefined {
276
+ const hasInterfaces = interfaceBindings.length > 0;
277
+ const hasClassVfuncs = classVfuncs.length > 0;
278
+ const hasProperties = Object.keys(properties).length > 0;
279
+
280
+ if (!hasClassVfuncs && !hasInterfaces && !hasProperties) {
281
+ return undefined;
282
+ }
283
+
284
+ const options: NativeRegisterClassOptions = {};
285
+
286
+ if (hasProperties) {
287
+ options.properties = toNativeProperties(properties);
288
+ }
289
+
290
+ if (hasClassVfuncs) {
291
+ options.vfuncs = [...classVfuncs];
292
+ }
293
+
294
+ if (hasInterfaces) {
295
+ options.interfaces = interfaceBindings.map((binding) => ({
296
+ type: binding.gtype,
297
+ vtableSize: binding.vtableSize,
298
+ vfuncs: [...binding.vfuncs],
299
+ }));
300
+ }
301
+
302
+ return options;
303
+ }
304
+
305
+ export { registerClass };
@@ -0,0 +1,311 @@
1
+ import type { AnyClass } from "@gtkx/utils";
2
+ import {
3
+ type ExternalObject,
4
+ getType,
5
+ getWrapper,
6
+ type Handle,
7
+ type RegisterClassVfunc as NativeRegisterClassVfunc,
8
+ setWrapper,
9
+ } from "@gtkx/native";
10
+ import type { Mixin, MixinReceiver } from "./mixin.js";
11
+ import { TYPE_INVALID, type TypedClass, typeInterfaces, typeIsA, typeName, typeParent } from "./type.js";
12
+
13
+ /**
14
+ * Static side of class `C` with its construct signature preserved but the member
15
+ * named `K` (default `"new"`) removed.
16
+ */
17
+ type StaticBase<C, K extends PropertyKey = "new"> = Omit<C, K> &
18
+ (C extends new (...args: infer A) => infer R ? new (...args: A) => R : never);
19
+
20
+ type VfuncDescriptor<K extends "class" | "interface"> = {
21
+ kind: K;
22
+ className: string;
23
+ vfuncName: string;
24
+ byteOffset: number;
25
+ vtableSize: number;
26
+ argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
27
+ returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"];
28
+ };
29
+
30
+ type VfuncRegistry = Record<string, VfuncDescriptor<"class"> | VfuncDescriptor<"interface">>;
31
+
32
+ const classRegistry: Map<bigint, AnyClass> = new Map();
33
+ const interfaceMixinRegistry: Map<bigint, Mixin> = new Map();
34
+ const composedClassRegistry: Map<bigint, AnyClass> = new Map();
35
+ const handleMap: WeakMap<object, ExternalObject<Handle>> = new WeakMap();
36
+ const vfuncRegistry: WeakMap<object, VfuncRegistry> = new WeakMap();
37
+ const interfaceVfuncRegistry: Map<bigint, VfuncRegistry> = new Map();
38
+
39
+ function setClassType(cls: AnyClass, type: bigint): void {
40
+ (cls.prototype as { [K in keyof TypedClass]: TypedClass[K] }).__type__ = type;
41
+ }
42
+
43
+ function getClassType(cls: AnyClass): bigint {
44
+ const proto: object = cls.prototype;
45
+
46
+ return Object.hasOwn(proto, "__type__") ? (proto as TypedClass).__type__ : TYPE_INVALID;
47
+ }
48
+
49
+ /** Returns the GType tag of the given wrapper instance. */
50
+ function getInstanceType(instance: object): bigint {
51
+ return getClassType(instance.constructor as AnyClass);
52
+ }
53
+
54
+ function registerClassType(cls: AnyClass, type: bigint): void {
55
+ if (type === TYPE_INVALID) {
56
+ return;
57
+ }
58
+
59
+ classRegistry.set(type, cls);
60
+ setClassType(cls, type);
61
+ }
62
+
63
+ /**
64
+ * Registers a wrapper class as the JS representation of a GType, optionally
65
+ * installing a registry of virtual functions.
66
+ * @param cls Wrapper class to associate with the type.
67
+ * @param type GType the class wraps.
68
+ * @param vfuncs Virtual functions the class overrides.
69
+ */
70
+ function registerWrapperClass(cls: AnyClass, type: bigint, vfuncs?: VfuncRegistry): void {
71
+ registerClassType(cls, type);
72
+
73
+ if (vfuncs) {
74
+ registerVfuncRegistry(cls, vfuncs);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Registers a GInterface, associating its GType with a mixin used to compose the
80
+ * interface onto wrapper classes and an optional virtual function registry.
81
+ * @param cls Class carrying the interface's GType tag.
82
+ * @param type GType of the interface.
83
+ * @param mixin Mixin that applies the interface to a wrapper class.
84
+ * @param vfuncs Virtual functions the interface exposes.
85
+ */
86
+ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, vfuncs?: VfuncRegistry): void {
87
+ if (type === TYPE_INVALID) {
88
+ return;
89
+ }
90
+
91
+ setClassType(cls, type);
92
+ interfaceMixinRegistry.set(type, mixin);
93
+
94
+ if (vfuncs) {
95
+ registerInterfaceVfuncRegistry(type, vfuncs);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Wraps a native handle in a JS wrapper instance. With no class, resolves and
101
+ * reuses the wrapper for the handle's runtime GType (composing interface mixins);
102
+ * with an explicit class, creates a bare instance backed by the handle. Returns
103
+ * null for a null or undefined handle.
104
+ * @param handle Native handle to wrap.
105
+ * @param cls Wrapper class to instantiate, or omitted to resolve it from the runtime type.
106
+ */
107
+ function wrapHandle(handle: null | undefined, cls?: AnyClass): null;
108
+ function wrapHandle<T extends object>(handle: ExternalObject<Handle>, cls: AnyClass<T>): T;
109
+
110
+ function wrapHandle<T extends object>(
111
+ handle: ExternalObject<Handle> | null | undefined,
112
+ cls: AnyClass<T>,
113
+ ): T | null;
114
+
115
+ function wrapHandle(handle: ExternalObject<Handle>, cls?: AnyClass): TypedClass;
116
+ function wrapHandle(handle: ExternalObject<Handle> | null | undefined, cls?: AnyClass): TypedClass | null;
117
+
118
+ function wrapHandle(handle: ExternalObject<Handle> | null | undefined, cls?: AnyClass): object | null {
119
+ if (handle === null || handle === undefined) {
120
+ return null;
121
+ }
122
+
123
+ if (cls === undefined) {
124
+ return getOrCreateWrapper(handle);
125
+ }
126
+
127
+ const instance: object = Object.create(cls.prototype) as object;
128
+ setHandle(instance, handle);
129
+
130
+ return instance;
131
+ }
132
+
133
+ /**
134
+ * Returns the wrapper class registered for a GType, walking up to ancestor types,
135
+ * and throws if none is registered.
136
+ */
137
+ function getWrapperClass(type: bigint): AnyClass {
138
+ const cls = resolveWrapperClass(type);
139
+
140
+ if (!cls) {
141
+ throw new Error(`No registered wrapper class for type '${typeName(type) ?? String(type)}'`);
142
+ }
143
+
144
+ return cls;
145
+ }
146
+
147
+ function resolveWrapperClass(type: bigint): AnyClass | null {
148
+ let currentType = type;
149
+
150
+ while (currentType !== TYPE_INVALID) {
151
+ const cls = classRegistry.get(currentType);
152
+
153
+ if (cls) {
154
+ return cls;
155
+ }
156
+
157
+ currentType = typeParent(currentType);
158
+ }
159
+
160
+ return null;
161
+ }
162
+
163
+ function applyInterfaceMixin(cls: AnyClass, type: bigint, baseType: bigint, applied: Set<bigint>): AnyClass {
164
+ if (applied.has(type) || typeIsA(baseType, type)) {
165
+ return cls;
166
+ }
167
+
168
+ const mixin = interfaceMixinRegistry.get(type);
169
+
170
+ if (mixin === undefined) {
171
+ return cls;
172
+ }
173
+
174
+ applied.add(type);
175
+
176
+ return mixin(cls as AnyClass<MixinReceiver>);
177
+ }
178
+
179
+ function createComposedClass(base: AnyClass, runtimeType: bigint): AnyClass {
180
+ const baseType = getClassType(base);
181
+ const applied: Set<bigint> = new Set();
182
+ let cls: AnyClass = base;
183
+
184
+ for (const type of typeInterfaces(runtimeType)) {
185
+ cls = applyInterfaceMixin(cls, type, baseType, applied);
186
+ }
187
+
188
+ return applied.size === 0 ? base : cls;
189
+ }
190
+
191
+ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
192
+ const exact = classRegistry.get(runtimeType);
193
+
194
+ if (exact) {
195
+ return exact;
196
+ }
197
+
198
+ const cached = composedClassRegistry.get(runtimeType);
199
+
200
+ if (cached) {
201
+ return cached;
202
+ }
203
+
204
+ const base = resolveWrapperClass(runtimeType);
205
+
206
+ if (base === null) {
207
+ return null;
208
+ }
209
+
210
+ const composed = createComposedClass(base, runtimeType);
211
+
212
+ if (composed === base) {
213
+ return base;
214
+ }
215
+
216
+ setClassType(composed, runtimeType);
217
+ composedClassRegistry.set(runtimeType, composed);
218
+
219
+ return composed;
220
+ }
221
+
222
+ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
223
+ const existing = getWrapper(handle);
224
+
225
+ if (existing) {
226
+ return existing;
227
+ }
228
+
229
+ const runtimeType: bigint = getType(handle);
230
+
231
+ if (runtimeType === TYPE_INVALID) {
232
+ throw new Error("Cannot resolve runtime GLib type from handle");
233
+ }
234
+
235
+ const cls = resolveComposedClass(runtimeType);
236
+
237
+ if (!cls) {
238
+ throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
239
+ }
240
+
241
+ const instance: object = Object.create(cls.prototype) as object;
242
+ registerWrapper(handle, instance);
243
+
244
+ return instance;
245
+ }
246
+
247
+ /** Returns the native handle bound to a wrapper instance, throwing if none is set. */
248
+ function getHandle(instance: object): ExternalObject<Handle> {
249
+ const handle = handleMap.get(instance);
250
+
251
+ if (handle === undefined) {
252
+ const name = (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
253
+ throw new Error(`No native handle associated with ${name}`);
254
+ }
255
+
256
+ return handle;
257
+ }
258
+
259
+ /** Returns the native handle bound to an instance, or undefined when there is none or the instance is null. */
260
+ function tryGetHandle(instance: object | null | undefined): ExternalObject<Handle> | undefined {
261
+ return instance == null ? undefined : handleMap.get(instance);
262
+ }
263
+
264
+ /** Associates a native handle with a wrapper instance. */
265
+ function setHandle(instance: object, handle: ExternalObject<Handle>): void {
266
+ handleMap.set(instance, handle);
267
+ }
268
+
269
+ function registerWrapper(handle: ExternalObject<Handle>, instance: object): void {
270
+ setHandle(instance, handle);
271
+ setWrapper(handle, instance);
272
+ }
273
+
274
+ function registerVfuncRegistry(cls: object, registry: VfuncRegistry): void {
275
+ vfuncRegistry.set(cls, registry);
276
+ }
277
+
278
+ function getVfuncRegistry(cls: object): VfuncRegistry | undefined {
279
+ return vfuncRegistry.get(cls);
280
+ }
281
+
282
+ function registerInterfaceVfuncRegistry(type: bigint, registry: VfuncRegistry): void {
283
+ if (type === TYPE_INVALID) {
284
+ return;
285
+ }
286
+
287
+ interfaceVfuncRegistry.set(type, registry);
288
+ }
289
+
290
+ function getInterfaceVfuncRegistry(type: bigint): VfuncRegistry | undefined {
291
+ return interfaceVfuncRegistry.get(type);
292
+ }
293
+
294
+ export {
295
+ getClassType,
296
+ getInstanceType,
297
+ registerClassType,
298
+ registerWrapperClass,
299
+ registerInterface,
300
+ wrapHandle,
301
+ getWrapperClass,
302
+ resolveWrapperClass,
303
+ getHandle,
304
+ tryGetHandle,
305
+ setHandle,
306
+ getVfuncRegistry,
307
+ getInterfaceVfuncRegistry,
308
+ type StaticBase,
309
+ type VfuncDescriptor,
310
+ type VfuncRegistry,
311
+ };