@gtkx/runtime 1.0.0-rc.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 (116) hide show
  1. package/dist/arg.d.ts +6 -5
  2. package/dist/arg.d.ts.map +1 -1
  3. package/dist/arg.js +5 -4
  4. package/dist/arg.js.map +1 -1
  5. package/dist/bind.d.ts +3 -2
  6. package/dist/bind.d.ts.map +1 -1
  7. package/dist/bind.js +5 -3
  8. package/dist/bind.js.map +1 -1
  9. package/dist/callback.d.ts +3 -3
  10. package/dist/callback.d.ts.map +1 -1
  11. package/dist/callback.js +51 -33
  12. package/dist/callback.js.map +1 -1
  13. package/dist/descriptors.d.ts +67 -64
  14. package/dist/descriptors.d.ts.map +1 -1
  15. package/dist/descriptors.js +92 -60
  16. package/dist/descriptors.js.map +1 -1
  17. package/dist/error.d.ts +5 -6
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/error.js +10 -6
  20. package/dist/error.js.map +1 -1
  21. package/dist/exit-hook.d.ts +2 -0
  22. package/dist/exit-hook.d.ts.map +1 -0
  23. package/dist/exit-hook.js +3 -0
  24. package/dist/exit-hook.js.map +1 -0
  25. package/dist/fn.d.ts +2 -2
  26. package/dist/fn.d.ts.map +1 -1
  27. package/dist/fn.js +26 -15
  28. package/dist/fn.js.map +1 -1
  29. package/dist/index.d.ts +3 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/library.d.ts +6 -5
  34. package/dist/library.d.ts.map +1 -1
  35. package/dist/library.js +6 -5
  36. package/dist/library.js.map +1 -1
  37. package/dist/lifecycle.d.ts +10 -9
  38. package/dist/lifecycle.d.ts.map +1 -1
  39. package/dist/lifecycle.js +38 -23
  40. package/dist/lifecycle.js.map +1 -1
  41. package/dist/listeners.d.ts +8 -8
  42. package/dist/listeners.d.ts.map +1 -1
  43. package/dist/listeners.js +28 -15
  44. package/dist/listeners.js.map +1 -1
  45. package/dist/mixin.d.ts +5 -11
  46. package/dist/mixin.d.ts.map +1 -1
  47. package/dist/mixin.js +24 -14
  48. package/dist/mixin.js.map +1 -1
  49. package/dist/native-value.d.ts +3 -2
  50. package/dist/native-value.d.ts.map +1 -1
  51. package/dist/native-value.js +75 -42
  52. package/dist/native-value.js.map +1 -1
  53. package/dist/object.d.ts +4 -3
  54. package/dist/object.d.ts.map +1 -1
  55. package/dist/object.js +8 -5
  56. package/dist/object.js.map +1 -1
  57. package/dist/promisify.d.ts +9 -1
  58. package/dist/promisify.d.ts.map +1 -1
  59. package/dist/promisify.js +18 -15
  60. package/dist/promisify.js.map +1 -1
  61. package/dist/properties.d.ts +14 -0
  62. package/dist/properties.d.ts.map +1 -0
  63. package/dist/properties.js +79 -0
  64. package/dist/properties.js.map +1 -0
  65. package/dist/register-class.d.ts +10 -2
  66. package/dist/register-class.d.ts.map +1 -1
  67. package/dist/register-class.js +83 -29
  68. package/dist/register-class.js.map +1 -1
  69. package/dist/registry.d.ts +23 -21
  70. package/dist/registry.d.ts.map +1 -1
  71. package/dist/registry.js +54 -35
  72. package/dist/registry.js.map +1 -1
  73. package/dist/signal.d.ts +7 -6
  74. package/dist/signal.d.ts.map +1 -1
  75. package/dist/signal.js +19 -12
  76. package/dist/signal.js.map +1 -1
  77. package/dist/t.d.ts +2 -2
  78. package/dist/t.d.ts.map +1 -1
  79. package/dist/t.js +2 -1
  80. package/dist/t.js.map +1 -1
  81. package/dist/tuple.d.ts +3 -2
  82. package/dist/tuple.d.ts.map +1 -1
  83. package/dist/tuple.js +7 -4
  84. package/dist/tuple.js.map +1 -1
  85. package/dist/type.d.ts +48 -41
  86. package/dist/type.d.ts.map +1 -1
  87. package/dist/type.js +107 -87
  88. package/dist/type.js.map +1 -1
  89. package/dist/value.d.ts +19 -10
  90. package/dist/value.d.ts.map +1 -1
  91. package/dist/value.js +164 -99
  92. package/dist/value.js.map +1 -1
  93. package/package.json +4 -6
  94. package/src/arg.ts +7 -5
  95. package/src/bind.ts +13 -4
  96. package/src/callback.ts +97 -35
  97. package/src/descriptors.ts +227 -85
  98. package/src/error.ts +18 -9
  99. package/src/exit-hook.ts +3 -0
  100. package/src/fn.ts +53 -22
  101. package/src/index.ts +4 -2
  102. package/src/library.ts +7 -5
  103. package/src/lifecycle.ts +54 -26
  104. package/src/listeners.ts +46 -18
  105. package/src/mixin.ts +39 -14
  106. package/src/native-value.ts +103 -46
  107. package/src/object.ts +19 -7
  108. package/src/promisify.ts +42 -15
  109. package/src/properties.ts +115 -0
  110. package/src/register-class.ts +157 -30
  111. package/src/registry.ts +144 -54
  112. package/src/signal.ts +53 -13
  113. package/src/t.ts +3 -1
  114. package/src/tuple.ts +16 -4
  115. package/src/type.ts +183 -93
  116. package/src/value.ts +231 -108
package/src/fn.ts CHANGED
@@ -24,61 +24,87 @@ type ArgSpec = {
24
24
  isOutParam: boolean;
25
25
  };
26
26
 
27
- const buildNativeArgTypes = (args: Arg[], throws: boolean): Descriptor[] => {
27
+ const buildNativeArgTypes = (args: Arg[], canThrow: boolean): Descriptor[] => {
28
28
  const nativeArgTypes = args.map((argSpec) =>
29
29
  argSpec.direction !== undefined && argSpec.callerAllocated !== true ? refT(argSpec.type) : argSpec.type,
30
30
  );
31
- if (throws)
31
+
32
+ if (canThrow) {
32
33
  nativeArgTypes.push(
33
34
  refT(boxedT("GError", { ownership: "full", sharedLibrary: LIB, getTypeFnName: "g_error_get_type" })),
34
35
  );
36
+ }
37
+
35
38
  return nativeArgTypes;
36
39
  };
37
40
 
38
41
  const buildArgSpecs = (args: Arg[]): ArgSpec[] => {
39
42
  let inputCursor = 0;
43
+
40
44
  return args.map((arg) => {
41
45
  const isRef = isRefArg(arg);
42
- const consumesInput = !isRef || isInoutArg(arg);
46
+ const isConsumesInput = !isRef || isInoutArg(arg);
43
47
  const isOutParam = isOutputArg(arg) && arg.consumed !== true;
48
+
44
49
  return {
45
50
  arg,
46
51
  isRef,
47
52
  isCallerAllocated: isCallerAllocatedArg(arg),
48
- consumesInput,
49
- inputIndex: consumesInput ? inputCursor++ : -1,
53
+ consumesInput: isConsumesInput,
54
+ inputIndex: isConsumesInput ? inputCursor++ : -1,
50
55
  isOutParam,
51
56
  };
52
57
  });
53
58
  };
54
59
 
60
+ const resolveCallerAllocated = (inputs: unknown[], inputIndex: number): unknown => {
61
+ const wrapper = inputs[inputIndex];
62
+
63
+ return wrapper == null ? wrapper : getHandle(wrapper);
64
+ };
65
+
66
+ const buildRefValue = (isInputConsumed: boolean, inputs: unknown[], inputIndex: number): Ref => ({
67
+ value: isInputConsumed ? inputs[inputIndex] : null,
68
+ });
69
+
70
+ const buildNativeValue = (spec: ArgSpec, inputs: unknown[]): unknown => {
71
+ const { arg, isRef, isCallerAllocated, consumesInput, inputIndex } = spec;
72
+
73
+ if (isCallerAllocated) {
74
+ return resolveCallerAllocated(inputs, inputIndex);
75
+ }
76
+
77
+ if (isRef) {
78
+ return buildRefValue(consumesInput, inputs, inputIndex);
79
+ }
80
+
81
+ if (arg.type.kind === "callback") {
82
+ return wrapCallbackValue(arg.type, inputs[inputIndex]);
83
+ }
84
+
85
+ return inputs[inputIndex];
86
+ };
87
+
55
88
  const buildNativeValues = (plans: ArgSpec[], inputs: unknown[]): unknown[] =>
56
- plans.map(({ arg, isRef, isCallerAllocated, consumesInput, inputIndex }) => {
57
- if (isCallerAllocated) {
58
- const wrapper = inputs[inputIndex];
59
- return wrapper == null ? wrapper : getHandle(wrapper as object);
60
- }
61
- if (isRef) {
62
- return { value: consumesInput ? inputs[inputIndex] : null };
63
- }
64
- if (arg.type.kind === "callback") {
65
- return wrapCallbackValue(arg.type, inputs[inputIndex]);
66
- }
67
- return inputs[inputIndex];
68
- });
89
+ plans.map((plan) => buildNativeValue(plan, inputs));
69
90
 
70
91
  const readOutParams = (plans: ArgSpec[], inputs: unknown[], nativeValues: unknown[]): unknown[] => {
71
92
  const outParams: unknown[] = [];
72
- plans.forEach(({ arg, isCallerAllocated, inputIndex, isOutParam }, index) => {
73
- if (!isOutParam) return;
93
+
94
+ for (const [index, { arg, isCallerAllocated, inputIndex, isOutParam }] of plans.entries()) {
95
+ if (!isOutParam) {
96
+ continue;
97
+ }
98
+
74
99
  outParams.push(
75
100
  isCallerAllocated ? inputs[inputIndex] : fromNative(arg.type, (nativeValues[index] as Ref).value),
76
101
  );
77
- });
102
+ }
103
+
78
104
  return outParams;
79
105
  };
80
106
 
81
- export function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inputs: unknown[]) => unknown {
107
+ function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inputs: unknown[]) => unknown {
82
108
  const { args, returns: returnDescriptor, throws = false } = spec;
83
109
  const nativeArgTypes = buildNativeArgTypes(args, throws);
84
110
  const nativeFn = bind(sharedLibrary, symbol, nativeArgTypes, returnDescriptor);
@@ -87,6 +113,7 @@ export function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inp
87
113
 
88
114
  const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown => {
89
115
  const primary = hasPrimary ? fromNative(returnDescriptor, nativeResult) : undefined;
116
+
90
117
  return packTupleResult(readOutParams(plans, inputs, nativeValues), primary, hasPrimary);
91
118
  };
92
119
 
@@ -97,12 +124,16 @@ export function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inp
97
124
  nativeValues.push(errorRef);
98
125
  const nativeResult = nativeFn(...nativeValues);
99
126
  checkError(errorRef);
127
+
100
128
  return shape(inputs, nativeValues, nativeResult);
101
129
  };
102
130
  }
103
131
 
104
132
  return (...inputs) => {
105
133
  const nativeValues = buildNativeValues(plans, inputs);
134
+
106
135
  return shape(inputs, nativeValues, nativeFn(...nativeValues));
107
136
  };
108
137
  }
138
+
139
+ export { fn };
package/src/index.ts CHANGED
@@ -1,11 +1,12 @@
1
- export { alloc, type ExternalObject, type Handle, read, setWrapper, write } from "@gtkx/native";
1
+ import "./exit-hook.js";
2
+
2
3
  export { createErrorDomain, type ErrorDomain } from "./error.js";
3
4
  export { type ApplicationLike, onExit, quit, quitApplication, runApplication } from "./lifecycle.js";
4
5
  export { offSignal, onceSignal, onSignal } from "./listeners.js";
5
6
  export { installMixins, type Mixin } from "./mixin.js";
6
7
  export { fromNative } from "./native-value.js";
7
8
  export { getObjectProperty, newObjectWithProperties, setObjectProperty } from "./object.js";
8
- export { promisify } from "./promisify.js";
9
+ export { type FinishResult, promisify } from "./promisify.js";
9
10
  export { registerClass } from "./register-class.js";
10
11
  export {
11
12
  getHandle,
@@ -55,3 +56,4 @@ export {
55
56
  valueIsA,
56
57
  } from "./type.js";
57
58
  export { getBoxedValue, setBoxedValue } from "./value.js";
59
+ export { alloc, type ExternalObject, type Handle, read, setWrapper, write } from "@gtkx/native";
package/src/library.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { type BoxedDescriptor, boxedT, type FundamentalDescriptor, fundamentalT } from "./descriptors.js";
2
2
 
3
- export const LIB = "libgobject-2.0.so.0,libglib-2.0.so.0";
4
- export const VALUE_SIZE = 24;
5
- export const VALUE_T: BoxedDescriptor = boxedT("GValue", { sharedLibrary: LIB, getTypeFnName: "g_value_get_type" });
3
+ const LIB = "libgobject-2.0.so.0,libglib-2.0.so.0";
4
+ const VALUE_SIZE = 24;
5
+ const VALUE_T: BoxedDescriptor = boxedT("GValue", { sharedLibrary: LIB, getTypeFnName: "g_value_get_type" });
6
6
 
7
- export const PARAM_T: FundamentalDescriptor = fundamentalT(LIB, "g_param_spec_ref", "g_param_spec_unref", {
7
+ const PARAM_T: FundamentalDescriptor = fundamentalT(LIB, "g_param_spec_ref", "g_param_spec_unref", {
8
8
  ownership: "borrowed",
9
9
  typeName: "GParam",
10
10
  });
11
11
 
12
- export const VARIANT_T: FundamentalDescriptor = fundamentalT(LIB, "g_variant_ref", "g_variant_unref", {
12
+ const VARIANT_T: FundamentalDescriptor = fundamentalT(LIB, "g_variant_ref", "g_variant_unref", {
13
13
  ownership: "borrowed",
14
14
  typeName: "GVariant",
15
15
  });
16
+
17
+ export { LIB, VALUE_SIZE, VALUE_T, PARAM_T, VARIANT_T };
package/src/lifecycle.ts CHANGED
@@ -5,7 +5,7 @@ import { blockMatchedSignalHandlers } from "./signal.js";
5
5
  * Minimal structural interface of a GTK4/GIO application needed to register, run,
6
6
  * and shut it down, plus the signals its lifecycle helpers connect to.
7
7
  */
8
- export type ApplicationLike = {
8
+ type ApplicationLike = {
9
9
  getIsRegistered(): boolean;
10
10
  register(cancellable: null): boolean;
11
11
  activate(): void;
@@ -17,7 +17,29 @@ export type ApplicationLike = {
17
17
  };
18
18
 
19
19
  const shutdownCallbacks: (() => void)[] = [];
20
- let hasQuit = false;
20
+ /**
21
+ * Runs every registered exit callback and shuts down the native runtime. Safe to
22
+ * call more than once; only the first call takes effect.
23
+ */
24
+ const quit: () => void = createQuit();
25
+
26
+ function createQuit(): () => void {
27
+ let hasQuit = false;
28
+
29
+ return () => {
30
+ if (hasQuit) {
31
+ return;
32
+ }
33
+
34
+ hasQuit = true;
35
+
36
+ for (const callback of shutdownCallbacks) {
37
+ callback();
38
+ }
39
+
40
+ nativeQuit();
41
+ };
42
+ }
21
43
 
22
44
  /**
23
45
  * Registers a callback to run once when the process quits, before the native
@@ -25,36 +47,29 @@ let hasQuit = false;
25
47
  *
26
48
  * @param callback Invoked during shutdown.
27
49
  */
28
- export const onExit = (callback: () => void): void => {
50
+ const onExit = (callback: () => void): void => {
29
51
  shutdownCallbacks.push(callback);
30
52
  };
31
53
 
32
- /**
33
- * Runs every registered exit callback and shuts down the native runtime. Safe to
34
- * call more than once; only the first call takes effect.
35
- */
36
- export const quit = (): void => {
37
- if (hasQuit) return;
38
- hasQuit = true;
39
-
40
- for (const callback of shutdownCallbacks) callback();
41
-
42
- nativeQuit();
43
- };
44
-
45
- process.on("exit", quit);
46
-
47
54
  /**
48
55
  * Registers the application if needed and activates it, keeping the runtime alive
49
56
  * while it is active and releasing it on shutdown.
50
57
  *
51
58
  * @param application The application to register and activate.
52
59
  */
53
- export const runApplication = (application: ApplicationLike): void => {
54
- application.on("activate", () => keepAlive(true));
55
- application.on("shutdown", () => keepAlive(false));
60
+ const runApplication = (application: ApplicationLike): void => {
61
+ application.on("activate", () => {
62
+ keepAlive(true);
63
+ });
64
+
65
+ application.on("shutdown", () => {
66
+ keepAlive(false);
67
+ });
68
+
69
+ if (!application.getIsRegistered()) {
70
+ application.register(null);
71
+ }
56
72
 
57
- if (!application.getIsRegistered()) application.register(null);
58
73
  application.activate();
59
74
  };
60
75
 
@@ -64,10 +79,23 @@ export const runApplication = (application: ApplicationLike): void => {
64
79
  *
65
80
  * @param application The application to shut down.
66
81
  */
67
- export const quitApplication = (application: ApplicationLike): void => {
68
- if (!application.getIsRegistered()) return;
69
- for (const window of application.getWindows?.() ?? []) application.removeWindow?.(window);
70
- application.on("shutdown", () => application.quit());
82
+ const quitApplication = (application: ApplicationLike): void => {
83
+ if (!application.getIsRegistered()) {
84
+ return;
85
+ }
86
+
87
+ const windows = application.getWindows?.() ?? [];
88
+
89
+ for (const window of windows) {
90
+ application.removeWindow?.(window);
91
+ }
92
+
93
+ application.on("shutdown", () => {
94
+ application.quit();
95
+ });
96
+
71
97
  blockMatchedSignalHandlers(application, "activate");
72
98
  application.run([]);
73
99
  };
100
+
101
+ export { onExit, quit, runApplication, quitApplication, type ApplicationLike };
package/src/listeners.ts CHANGED
@@ -1,34 +1,50 @@
1
- import type { SignalHandler } from "./signal.js";
1
+ import { isSignalHandlerConnected, type SignalHandler } from "./signal.js";
2
2
 
3
3
  type SignalConnectable = {
4
- connect(signal: string, handler: SignalHandler, after?: boolean): number;
4
+ connect(signal: string, handler: SignalHandler, isAfter?: boolean): number;
5
5
  disconnect(handlerId: number): void;
6
6
  };
7
7
 
8
- const listenerTable = new WeakMap<object, Map<string, Map<SignalHandler, number>>>();
8
+ const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number>>> = new WeakMap();
9
9
 
10
10
  const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
11
11
  listenerTable.get(instance)?.get(signal)?.get(handler);
12
12
 
13
13
  const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
14
14
  let bySignal = listenerTable.get(instance);
15
+
15
16
  if (!bySignal) {
16
17
  bySignal = new Map();
17
18
  listenerTable.set(instance, bySignal);
18
19
  }
20
+
19
21
  let byHandler = bySignal.get(signal);
22
+
20
23
  if (!byHandler) {
21
24
  byHandler = new Map();
22
25
  bySignal.set(signal, byHandler);
23
26
  }
27
+
24
28
  byHandler.set(handler, handlerId);
25
29
  };
26
30
 
27
- const untrackListener = (instance: object, signal: string, handler: SignalHandler): void => {
31
+ const untrackHandlerId = (instance: object, signal: string, handlerId: number): void => {
28
32
  const bySignal = listenerTable.get(instance);
29
33
  const byHandler = bySignal?.get(signal);
30
- byHandler?.delete(handler);
31
- if (byHandler?.size === 0) bySignal?.delete(signal);
34
+
35
+ if (byHandler === undefined) {
36
+ return;
37
+ }
38
+
39
+ for (const [handler, id] of byHandler) {
40
+ if (id === handlerId) {
41
+ byHandler.delete(handler);
42
+ }
43
+ }
44
+
45
+ if (byHandler.size === 0) {
46
+ bySignal?.delete(signal);
47
+ }
32
48
  };
33
49
 
34
50
  /**
@@ -38,10 +54,10 @@ const untrackListener = (instance: object, signal: string, handler: SignalHandle
38
54
  * @param instance The object emitting the signal.
39
55
  * @param signal The signal name to connect to.
40
56
  * @param handler The callback invoked on each emission.
41
- * @param after When true, run the handler after the default handler.
57
+ * @param isAfter When true, run the handler after the default handler.
42
58
  */
43
- export function onSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, after?: boolean): void {
44
- const handlerId = instance.connect(signal, handler, after);
59
+ function onSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, isAfter?: boolean): void {
60
+ const handlerId = instance.connect(signal, handler, isAfter);
45
61
  trackListener(instance, signal, handler, handlerId);
46
62
  }
47
63
 
@@ -52,17 +68,19 @@ export function onSignal(instance: SignalConnectable, signal: string, handler: S
52
68
  * @param instance The object emitting the signal.
53
69
  * @param signal The signal name to connect to.
54
70
  * @param handler The callback invoked on the first emission.
55
- * @param after When true, run the handler after the default handler.
71
+ * @param isAfter When true, run the handler after the default handler.
56
72
  */
57
- export function onceSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, after?: boolean): void {
73
+ function onceSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, isAfter?: boolean): void {
58
74
  let handlerId = 0;
75
+
59
76
  const wrapped: SignalHandler = (...args) => {
60
- untrackListener(instance, signal, wrapped);
61
- untrackListener(instance, signal, handler);
77
+ untrackHandlerId(instance, signal, handlerId);
62
78
  instance.disconnect(handlerId);
79
+
63
80
  return handler(...args);
64
81
  };
65
- handlerId = instance.connect(signal, wrapped, after);
82
+
83
+ handlerId = instance.connect(signal, wrapped, isAfter);
66
84
  trackListener(instance, signal, wrapped, handlerId);
67
85
  trackListener(instance, signal, handler, handlerId);
68
86
  }
@@ -75,10 +93,20 @@ export function onceSignal(instance: SignalConnectable, signal: string, handler:
75
93
  * @param signal The signal name the handler was connected to.
76
94
  * @param handler The handler to disconnect.
77
95
  */
78
- export function offSignal(instance: SignalConnectable, signal: string, handler: SignalHandler): void {
96
+ function offSignal(instance: SignalConnectable, signal: string, handler: SignalHandler): void {
79
97
  const handlerId = findListenerHandlerId(instance, signal, handler);
80
- if (handlerId !== undefined) {
81
- instance.disconnect(handlerId);
82
- untrackListener(instance, signal, handler);
98
+
99
+ if (handlerId === undefined) {
100
+ return;
83
101
  }
102
+
103
+ untrackHandlerId(instance, signal, handlerId);
104
+
105
+ if (!isSignalHandlerConnected(instance, handlerId)) {
106
+ return;
107
+ }
108
+
109
+ instance.disconnect(handlerId);
84
110
  }
111
+
112
+ export { onSignal, onceSignal, offSignal };
package/src/mixin.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AnyClass } from "@gtkx/utils";
2
2
 
3
- export type MixinReceiver = {
4
- connect(signal: string, handler: (...args: unknown[]) => unknown, after?: boolean): number;
3
+ type MixinReceiver = {
4
+ connect(signal: string, handler: (...args: unknown[]) => unknown, isAfter?: boolean): number;
5
5
  emit(signal: string, ...args: unknown[]): unknown;
6
6
  };
7
7
 
@@ -9,17 +9,38 @@ export type MixinReceiver = {
9
9
  * A factory that, given a base class, returns a subclass adding extra members to
10
10
  * be merged onto a target prototype.
11
11
  */
12
- export type Mixin = (base: AnyClass<MixinReceiver>) => AnyClass;
12
+ type Mixin = (base: AnyClass<MixinReceiver>) => AnyClass;
13
13
 
14
- function definedInClassChain(prototype: object, key: string): boolean {
14
+ function isDefinedInClassChain(prototype: object, key: string): boolean {
15
15
  let current: object | null = prototype;
16
+
16
17
  while (current !== null && current !== Object.prototype) {
17
- if (Object.hasOwn(current, key)) return true;
18
- current = Object.getPrototypeOf(current);
18
+ if (Object.hasOwn(current, key)) {
19
+ return true;
20
+ }
21
+
22
+ current = Reflect.getPrototypeOf(current);
19
23
  }
24
+
20
25
  return false;
21
26
  }
22
27
 
28
+ function copyLayerMember(target: AnyClass, layer: object, key: string): void {
29
+ if (key === "constructor") {
30
+ return;
31
+ }
32
+
33
+ if (isDefinedInClassChain(target.prototype, key)) {
34
+ return;
35
+ }
36
+
37
+ const descriptor = Object.getOwnPropertyDescriptor(layer, key);
38
+
39
+ if (descriptor !== undefined) {
40
+ Object.defineProperty(target.prototype, key, descriptor);
41
+ }
42
+ }
43
+
23
44
  /**
24
45
  * Copies each mixin's prototype members onto the target class prototype, skipping
25
46
  * any member already defined anywhere in the target's class chain.
@@ -27,22 +48,26 @@ function definedInClassChain(prototype: object, key: string): boolean {
27
48
  * @param target The class whose prototype receives the mixin members.
28
49
  * @param mixins The mixins to apply, in order.
29
50
  */
30
- export function installMixins(target: AnyClass, mixins: Mixin[]): void {
51
+ function copyLayerMembers(target: AnyClass, layer: object): void {
52
+ for (const key of Object.getOwnPropertyNames(layer)) {
53
+ copyLayerMember(target, layer, key);
54
+ }
55
+ }
56
+
57
+ function installMixins(target: AnyClass, mixins: Mixin[]): void {
31
58
  const empty: AnyClass<MixinReceiver> = class {
32
59
  connect(): number {
33
60
  return 0;
34
61
  }
62
+
35
63
  emit(): unknown {
36
64
  return undefined;
37
65
  }
38
66
  };
67
+
39
68
  for (const mixin of mixins) {
40
- const layer: object = mixin(empty).prototype;
41
- for (const key of Object.getOwnPropertyNames(layer)) {
42
- if (key === "constructor") continue;
43
- if (definedInClassChain(target.prototype, key)) continue;
44
- const descriptor = Object.getOwnPropertyDescriptor(layer, key);
45
- if (descriptor !== undefined) Object.defineProperty(target.prototype, key, descriptor);
46
- }
69
+ copyLayerMembers(target, mixin(empty).prototype);
47
70
  }
48
71
  }
72
+
73
+ export { installMixins, type MixinReceiver, type Mixin };
@@ -1,12 +1,65 @@
1
1
  import type { Descriptor, ExternalObject, Handle } from "@gtkx/native";
2
- import type { ArrayDescriptor, FundamentalDescriptor, StructDescriptor } from "./descriptors.js";
2
+ import type { ArrayDescriptor, FundamentalDescriptor, HashTableDescriptor, StructDescriptor } from "./descriptors.js";
3
3
  import { getWrapperClass, tryGetHandle, wrapHandle } from "./registry.js";
4
4
  import { resolveDescriptorType } from "./type.js";
5
5
 
6
- const collectionFromNative = (descriptor: ArrayDescriptor, value: unknown): unknown => {
7
- if (value === null) return null;
8
- return (value as unknown[]).map((item) => fromNative(descriptor.itemDescriptor, item));
9
- };
6
+ type MarshalledKind = "object" | "struct" | "boxed" | "fundamental" | "array" | "hashtable";
7
+ type MarshalledDescriptor = Extract<Descriptor, { kind: MarshalledKind }>;
8
+
9
+ const MARSHALLED_KINDS: Set<Descriptor["kind"]> = new Set<MarshalledKind>([
10
+ "object",
11
+ "struct",
12
+ "boxed",
13
+ "fundamental",
14
+ "array",
15
+ "hashtable",
16
+ ]);
17
+
18
+ function isMarshalledDescriptor(descriptor: Descriptor): descriptor is MarshalledDescriptor {
19
+ return MARSHALLED_KINDS.has(descriptor.kind);
20
+ }
21
+
22
+ function mapCollection(
23
+ descriptor: ArrayDescriptor,
24
+ value: unknown,
25
+ convert: (itemDescriptor: Descriptor, item: unknown) => unknown,
26
+ ): unknown[] {
27
+ return (value as unknown[]).map((item) => convert(descriptor.itemDescriptor, item));
28
+ }
29
+
30
+ function collectionFromNative(descriptor: ArrayDescriptor, value: unknown): unknown {
31
+ return value === null ? null : mapCollection(descriptor, value, fromNative);
32
+ }
33
+
34
+ function boxedFromNative(descriptor: Descriptor, value: unknown): unknown {
35
+ return value == null
36
+ ? null
37
+ : wrapHandle(value as ExternalObject<Handle>, getWrapperClass(resolveDescriptorType(descriptor)));
38
+ }
39
+
40
+ function fundamentalFromNative(descriptor: FundamentalDescriptor, value: unknown): unknown {
41
+ return value == null
42
+ ? null
43
+ : wrapHandle(
44
+ value as ExternalObject<Handle>,
45
+ descriptor.wrapperClass ?? getWrapperClass(resolveDescriptorType(descriptor)),
46
+ );
47
+ }
48
+
49
+ function hashTableFromNative(descriptor: HashTableDescriptor, value: unknown): unknown {
50
+ if (value === null) {
51
+ return null;
52
+ }
53
+
54
+ const entries = value as [unknown, unknown][];
55
+
56
+ return new Map(
57
+ entries.map(([key, val]): [unknown, unknown] => [
58
+ fromNative(descriptor.keyDescriptor, key),
59
+ fromNative(descriptor.valueDescriptor, val),
60
+ ]),
61
+ );
62
+ }
10
63
 
11
64
  /**
12
65
  * Converts a raw value returned from native code into its JavaScript form,
@@ -16,63 +69,67 @@ const collectionFromNative = (descriptor: ArrayDescriptor, value: unknown): unkn
16
69
  * @param descriptor Describes the native type of the value.
17
70
  * @param value The raw native value to convert.
18
71
  */
19
- export function fromNative(descriptor: Descriptor, value: unknown): unknown {
72
+ function fromNative(descriptor: Descriptor, value: unknown): unknown {
73
+ if (!isMarshalledDescriptor(descriptor)) {
74
+ return value;
75
+ }
76
+
20
77
  switch (descriptor.kind) {
21
- case "object":
22
- return wrapHandle(value as ExternalObject<Handle> | null, undefined);
23
- case "struct":
78
+ case "object": {
79
+ return wrapHandle(value as ExternalObject<Handle> | null);
80
+ }
81
+ case "struct": {
24
82
  return wrapHandle(value as ExternalObject<Handle> | null, (descriptor as StructDescriptor).wrapperClass);
25
- case "boxed":
26
- return value == null
27
- ? null
28
- : wrapHandle(value as ExternalObject<Handle>, getWrapperClass(resolveDescriptorType(descriptor)));
29
- case "fundamental":
30
- return value == null
31
- ? null
32
- : wrapHandle(
33
- value as ExternalObject<Handle>,
34
- (descriptor as FundamentalDescriptor).wrapperClass ??
35
- getWrapperClass(resolveDescriptorType(descriptor)),
36
- );
37
- case "array":
83
+ }
84
+ case "boxed": {
85
+ return boxedFromNative(descriptor, value);
86
+ }
87
+ case "fundamental": {
88
+ return fundamentalFromNative(descriptor, value);
89
+ }
90
+ case "array": {
38
91
  return collectionFromNative(descriptor, value);
92
+ }
39
93
  case "hashtable": {
40
- if (value === null) return null;
41
- const entries = value as [unknown, unknown][];
42
- return new Map(
43
- entries.map(([key, val]): [unknown, unknown] => [
44
- fromNative(descriptor.keyDescriptor, key),
45
- fromNative(descriptor.valueDescriptor, val),
46
- ]),
47
- );
94
+ return hashTableFromNative(descriptor, value);
48
95
  }
49
- default:
50
- return value;
51
96
  }
52
97
  }
53
98
 
54
- const collectionToNative = (descriptor: ArrayDescriptor, value: unknown): unknown => {
55
- if (value == null) return null;
56
- return (value as unknown[]).map((item) => toNative(descriptor.itemDescriptor, item));
57
- };
99
+ function collectionToNative(descriptor: ArrayDescriptor, value: unknown): unknown {
100
+ return value == null ? null : mapCollection(descriptor, value, toNative);
101
+ }
102
+
103
+ function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unknown {
104
+ if (value == null) {
105
+ return null;
106
+ }
107
+
108
+ return [...(value as Map<unknown, unknown>)].map(([key, val]): [unknown, unknown] => [
109
+ toNative(descriptor.keyDescriptor, key),
110
+ toNative(descriptor.valueDescriptor, val),
111
+ ]);
112
+ }
113
+
114
+ function toNative(descriptor: Descriptor, value: unknown): unknown {
115
+ if (!isMarshalledDescriptor(descriptor)) {
116
+ return value;
117
+ }
58
118
 
59
- export function toNative(descriptor: Descriptor, value: unknown): unknown {
60
119
  switch (descriptor.kind) {
61
120
  case "object":
62
121
  case "struct":
63
122
  case "boxed":
64
- case "fundamental":
123
+ case "fundamental": {
65
124
  return tryGetHandle(value as object | null | undefined) ?? null;
66
- case "array":
125
+ }
126
+ case "array": {
67
127
  return collectionToNative(descriptor, value);
128
+ }
68
129
  case "hashtable": {
69
- if (value == null) return null;
70
- return [...(value as Map<unknown, unknown>)].map(([key, val]): [unknown, unknown] => [
71
- toNative(descriptor.keyDescriptor, key),
72
- toNative(descriptor.valueDescriptor, val),
73
- ]);
130
+ return hashTableToNative(descriptor, value);
74
131
  }
75
- default:
76
- return value;
77
132
  }
78
133
  }
134
+
135
+ export { fromNative, toNative };