@effindomv2/fui-as 0.1.2 → 0.1.4

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.
@@ -498,7 +498,7 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
498
498
  }
499
499
 
500
500
  function encodeTypedArrayArg(
501
- type: "bytes" | "i32_array" | "f64_array",
501
+ type: "bytes" | "i32_array" | "u32_array" | "i64_array" | "u64_array" | "f64_array",
502
502
  arg: unknown,
503
503
  context: string,
504
504
  ): { bytes: Uint8Array; elementCount: number; alignment: number } {
@@ -518,6 +518,36 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
518
518
  alignment: 4,
519
519
  };
520
520
  }
521
+ if (type === "u32_array") {
522
+ if (!(arg instanceof Uint32Array)) {
523
+ throw new Error(`${context} must be a Uint32Array.`);
524
+ }
525
+ return {
526
+ bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
527
+ elementCount: arg.length,
528
+ alignment: 4,
529
+ };
530
+ }
531
+ if (type === "i64_array") {
532
+ if (!(arg instanceof BigInt64Array)) {
533
+ throw new Error(`${context} must be a BigInt64Array.`);
534
+ }
535
+ return {
536
+ bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
537
+ elementCount: arg.length,
538
+ alignment: 8,
539
+ };
540
+ }
541
+ if (type === "u64_array") {
542
+ if (!(arg instanceof BigUint64Array)) {
543
+ throw new Error(`${context} must be a BigUint64Array.`);
544
+ }
545
+ return {
546
+ bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
547
+ elementCount: arg.length,
548
+ alignment: 8,
549
+ };
550
+ }
521
551
  if (!(arg instanceof Float64Array)) {
522
552
  throw new Error(`${context} must be a Float64Array.`);
523
553
  }
@@ -555,7 +585,7 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
555
585
  }
556
586
  continue;
557
587
  }
558
- if (type === "bytes" || type === "i32_array" || type === "f64_array") {
588
+ if (type === "bytes" || type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
559
589
  const payload = encodeTypedArrayArg(type, arg, context);
560
590
  if (payload.bytes.length > 0) {
561
591
  const alignedOffset = alignOffset(byteOffset, payload.alignment);
@@ -578,6 +608,19 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
578
608
  callArgs.push(arg ? 1 : 0);
579
609
  continue;
580
610
  }
611
+ if (type === "i64" || type === "u64") {
612
+ if (typeof arg !== "bigint") {
613
+ throw new Error(`${context} must be a bigint.`);
614
+ }
615
+ if (type === "i64" && (arg < -9223372036854775808n || arg > 9223372036854775807n)) {
616
+ throw new Error(`${context} must be a signed 64-bit integer.`);
617
+ }
618
+ if (type === "u64" && (arg < 0n || arg > 18446744073709551615n)) {
619
+ throw new Error(`${context} must be an unsigned 64-bit integer.`);
620
+ }
621
+ callArgs.push(arg);
622
+ continue;
623
+ }
581
624
  if (typeof arg !== 'number' || Number.isNaN(arg)) {
582
625
  throw new Error(`${context} must be a number.`);
583
626
  }
@@ -585,6 +628,10 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
585
628
  if (!Number.isInteger(arg) || arg < -2147483648 || arg > 2147483647) {
586
629
  throw new Error(`${context} must be a signed 32-bit integer.`);
587
630
  }
631
+ } else if (type === "u32") {
632
+ if (!Number.isInteger(arg) || arg < 0 || arg > 4294967295) {
633
+ throw new Error(`${context} must be an unsigned 32-bit integer.`);
634
+ }
588
635
  }
589
636
  callArgs.push(arg);
590
637
  }
@@ -5,7 +5,11 @@ type HostEventTypeValue<T extends HostServiceTypeName> =
5
5
  T extends "bool" ? boolean :
6
6
  T extends "bytes" ? Uint8Array :
7
7
  T extends "i32_array" ? Int32Array :
8
+ T extends "u32_array" ? Uint32Array :
9
+ T extends "i64_array" ? BigInt64Array :
10
+ T extends "u64_array" ? BigUint64Array :
8
11
  T extends "f64_array" ? Float64Array :
12
+ T extends "i64" | "u64" ? bigint :
9
13
  T extends "void" ? void :
10
14
  number;
11
15
 
@@ -66,9 +70,15 @@ function validateEventType(type: string, context: string): asserts type is HostS
66
70
  type === "string" ||
67
71
  type === "bool" ||
68
72
  type === "i32" ||
73
+ type === "u32" ||
74
+ type === "i64" ||
75
+ type === "u64" ||
69
76
  type === "f64" ||
70
77
  type === "bytes" ||
71
78
  type === "i32_array" ||
79
+ type === "u32_array" ||
80
+ type === "i64_array" ||
81
+ type === "u64_array" ||
72
82
  type === "f64_array"
73
83
  ) {
74
84
  return;
@@ -2,9 +2,15 @@ export type HostServiceTypeName =
2
2
  | "string"
3
3
  | "bool"
4
4
  | "i32"
5
+ | "u32"
6
+ | "i64"
7
+ | "u64"
5
8
  | "f64"
6
9
  | "bytes"
7
10
  | "i32_array"
11
+ | "u32_array"
12
+ | "i64_array"
13
+ | "u64_array"
8
14
  | "f64_array"
9
15
  | "void";
10
16
 
@@ -13,7 +19,11 @@ type HostServiceTypeValue<T extends HostServiceTypeName> =
13
19
  T extends "bool" ? boolean :
14
20
  T extends "bytes" ? Uint8Array :
15
21
  T extends "i32_array" ? Int32Array :
22
+ T extends "u32_array" ? Uint32Array :
23
+ T extends "i64_array" ? BigInt64Array :
24
+ T extends "u64_array" ? BigUint64Array :
16
25
  T extends "f64_array" ? Float64Array :
26
+ T extends "i64" | "u64" ? bigint :
17
27
  T extends "void" ? void :
18
28
  number;
19
29
 
@@ -25,6 +35,7 @@ export interface HostServiceMethodDefinition<
25
35
  TArgs extends readonly HostServiceTypeName[] = readonly HostServiceTypeName[],
26
36
  TResult extends HostServiceTypeName = HostServiceTypeName,
27
37
  > {
38
+ readonly importName?: string;
28
39
  readonly args: TArgs;
29
40
  readonly returns: TResult;
30
41
  readonly implementation: (...args: HostServiceArgsValues<TArgs>) => HostServiceTypeValue<TResult>;
@@ -80,9 +91,15 @@ function validateServiceType(type: string, context: string): asserts type is Hos
80
91
  type === "string" ||
81
92
  type === "bool" ||
82
93
  type === "i32" ||
94
+ type === "u32" ||
95
+ type === "i64" ||
96
+ type === "u64" ||
83
97
  type === "f64" ||
84
98
  type === "bytes" ||
85
99
  type === "i32_array" ||
100
+ type === "u32_array" ||
101
+ type === "i64_array" ||
102
+ type === "u64_array" ||
86
103
  type === "f64_array" ||
87
104
  type === "void"
88
105
  ) {
@@ -101,7 +118,8 @@ export function listHostServiceMethods(services: HostServicesDefinition | undefi
101
118
  assertIdentifier(serviceName, "Host service");
102
119
  for (const [methodName, definition] of Object.entries(serviceMethods)) {
103
120
  assertIdentifier(methodName, `Host service ${serviceName} method`);
104
- const importName = buildImportName(serviceName, methodName);
121
+ const importName = definition.importName ?? buildImportName(serviceName, methodName);
122
+ assertIdentifier(importName, `Host service ${serviceName}.${methodName} import`);
105
123
  if (seenImports.has(importName)) {
106
124
  throw new Error(`Duplicate host-service import name "${importName}".`);
107
125
  }
@@ -169,6 +187,27 @@ function expectFloat64Array(value: unknown, context: string): Float64Array {
169
187
  return value;
170
188
  }
171
189
 
190
+ function expectBigInt64Array(value: unknown, context: string): BigInt64Array {
191
+ if (!(value instanceof BigInt64Array)) {
192
+ throw new Error(`${context} must be a BigInt64Array.`);
193
+ }
194
+ return value;
195
+ }
196
+
197
+ function expectBigUint64Array(value: unknown, context: string): BigUint64Array {
198
+ if (!(value instanceof BigUint64Array)) {
199
+ throw new Error(`${context} must be a BigUint64Array.`);
200
+ }
201
+ return value;
202
+ }
203
+
204
+ function expectUint32Array(value: unknown, context: string): Uint32Array {
205
+ if (!(value instanceof Uint32Array)) {
206
+ throw new Error(`${context} must be a Uint32Array.`);
207
+ }
208
+ return value;
209
+ }
210
+
172
211
  function expectI32(value: unknown, context: string): number {
173
212
  const numberValue = expectNumber(value, context);
174
213
  if (!Number.isInteger(numberValue) || numberValue < -2147483648 || numberValue > 2147483647) {
@@ -177,6 +216,34 @@ function expectI32(value: unknown, context: string): number {
177
216
  return numberValue;
178
217
  }
179
218
 
219
+ function expectU32(value: unknown, context: string): number {
220
+ const numberValue = expectNumber(value, context);
221
+ if (!Number.isInteger(numberValue) || numberValue < 0 || numberValue > 4294967295) {
222
+ throw new Error(`${context} must be an unsigned 32-bit integer.`);
223
+ }
224
+ return numberValue;
225
+ }
226
+
227
+ function expectI64(value: unknown, context: string): bigint {
228
+ if (typeof value !== "bigint") {
229
+ throw new Error(`${context} must be a bigint.`);
230
+ }
231
+ if (value < -9223372036854775808n || value > 9223372036854775807n) {
232
+ throw new Error(`${context} must be a signed 64-bit integer.`);
233
+ }
234
+ return value;
235
+ }
236
+
237
+ function expectU64(value: unknown, context: string): bigint {
238
+ if (typeof value !== "bigint") {
239
+ throw new Error(`${context} must be a bigint.`);
240
+ }
241
+ if (value < 0n || value > 18446744073709551615n) {
242
+ throw new Error(`${context} must be an unsigned 64-bit integer.`);
243
+ }
244
+ return value;
245
+ }
246
+
180
247
  function expectLength(value: unknown, context: string): number {
181
248
  const length = expectNumber(value, context);
182
249
  if (!Number.isInteger(length) || length < 0) {
@@ -194,6 +261,15 @@ function bytesToI32Array(bytes: Uint8Array, context: string): Int32Array {
194
261
  return values;
195
262
  }
196
263
 
264
+ function bytesToU32Array(bytes: Uint8Array, context: string): Uint32Array {
265
+ if ((bytes.byteLength & 3) !== 0) {
266
+ throw new Error(`${context} payload length must be divisible by 4.`);
267
+ }
268
+ const values = new Uint32Array(bytes.byteLength >>> 2);
269
+ new Uint8Array(values.buffer).set(bytes);
270
+ return values;
271
+ }
272
+
197
273
  function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
198
274
  if ((bytes.byteLength & 7) !== 0) {
199
275
  throw new Error(`${context} payload length must be divisible by 8.`);
@@ -203,14 +279,42 @@ function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
203
279
  return values;
204
280
  }
205
281
 
206
- function typedArrayBytes(value: Uint8Array | Int32Array | Float64Array): Uint8Array {
282
+ function bytesToI64Array(bytes: Uint8Array, context: string): BigInt64Array {
283
+ if ((bytes.byteLength & 7) !== 0) {
284
+ throw new Error(`${context} payload length must be divisible by 8.`);
285
+ }
286
+ const values = new BigInt64Array(bytes.byteLength >>> 3);
287
+ new Uint8Array(values.buffer).set(bytes);
288
+ return values;
289
+ }
290
+
291
+ function bytesToU64Array(bytes: Uint8Array, context: string): BigUint64Array {
292
+ if ((bytes.byteLength & 7) !== 0) {
293
+ throw new Error(`${context} payload length must be divisible by 8.`);
294
+ }
295
+ const values = new BigUint64Array(bytes.byteLength >>> 3);
296
+ new Uint8Array(values.buffer).set(bytes);
297
+ return values;
298
+ }
299
+
300
+ function typedArrayBytes(
301
+ value: Uint8Array | Int32Array | Uint32Array | BigInt64Array | BigUint64Array | Float64Array,
302
+ ): Uint8Array {
207
303
  return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
208
304
  }
209
305
 
210
306
  function consumedRawArgCount(method: NormalizedHostServiceMethod): number {
211
307
  let count = 0;
212
308
  method.args.forEach((type) => {
213
- count += type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array" ? 2 : 1;
309
+ count += type === "string" ||
310
+ type === "bytes" ||
311
+ type === "i32_array" ||
312
+ type === "u32_array" ||
313
+ type === "i64_array" ||
314
+ type === "u64_array" ||
315
+ type === "f64_array"
316
+ ? 2
317
+ : 1;
214
318
  });
215
319
  return count;
216
320
  }
@@ -246,6 +350,14 @@ function decodeHostServiceArgs(
246
350
  index += 2;
247
351
  return;
248
352
  }
353
+ if (type === "u32_array") {
354
+ const ptr = expectNumber(rawArgs[index], `${context} ptr`);
355
+ const len = expectLength(rawArgs[index + 1], `${context} len`);
356
+ const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 2);
357
+ decodedArgs.push(bytesToU32Array(payload, context));
358
+ index += 2;
359
+ return;
360
+ }
249
361
  if (type === "f64_array") {
250
362
  const ptr = expectNumber(rawArgs[index], `${context} ptr`);
251
363
  const len = expectLength(rawArgs[index + 1], `${context} len`);
@@ -254,11 +366,33 @@ function decodeHostServiceArgs(
254
366
  index += 2;
255
367
  return;
256
368
  }
369
+ if (type === "i64_array") {
370
+ const ptr = expectNumber(rawArgs[index], `${context} ptr`);
371
+ const len = expectLength(rawArgs[index + 1], `${context} len`);
372
+ const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
373
+ decodedArgs.push(bytesToI64Array(payload, context));
374
+ index += 2;
375
+ return;
376
+ }
377
+ if (type === "u64_array") {
378
+ const ptr = expectNumber(rawArgs[index], `${context} ptr`);
379
+ const len = expectLength(rawArgs[index + 1], `${context} len`);
380
+ const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
381
+ decodedArgs.push(bytesToU64Array(payload, context));
382
+ index += 2;
383
+ return;
384
+ }
257
385
  const rawValue = rawArgs[index];
258
386
  if (type === "bool") {
259
387
  decodedArgs.push(expectNumber(rawValue, context) !== 0);
260
388
  } else if (type === "i32") {
261
389
  decodedArgs.push(expectI32(rawValue, context));
390
+ } else if (type === "u32") {
391
+ decodedArgs.push(expectU32(rawValue, context));
392
+ } else if (type === "i64") {
393
+ decodedArgs.push(expectI64(rawValue, context));
394
+ } else if (type === "u64") {
395
+ decodedArgs.push(expectU64(rawValue, context));
262
396
  } else if (type === "f64") {
263
397
  decodedArgs.push(expectNumber(rawValue, context));
264
398
  } else {
@@ -272,10 +406,10 @@ function decodeHostServiceArgs(
272
406
  export function createHostServiceImportModule(
273
407
  services: HostServicesDefinition | undefined,
274
408
  io: HostServiceImportIo,
275
- ): Record<string, (...rawArgs: Array<unknown>) => number | void> {
276
- const module: Record<string, (...rawArgs: Array<unknown>) => number | void> = {};
409
+ ): Record<string, (...rawArgs: Array<unknown>) => number | bigint | void> {
410
+ const module: Record<string, (...rawArgs: Array<unknown>) => number | bigint | void> = {};
277
411
  for (const method of listHostServiceMethods(services)) {
278
- module[method.importName] = (...rawArgs: Array<unknown>): number | void => {
412
+ module[method.importName] = (...rawArgs: Array<unknown>): number | bigint | void => {
279
413
  const decodedArgs = decodeHostServiceArgs(method, rawArgs, io);
280
414
  const result = method.implementation(...decodedArgs);
281
415
  const resultContext = `Host service ${method.serviceName}.${method.methodName} result`;
@@ -300,18 +434,45 @@ export function createHostServiceImportModule(
300
434
  const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
301
435
  return io.writeBytes(ptr, capacity, typedArrayBytes(expectInt32Array(result, resultContext)), resultContext);
302
436
  }
437
+ if (method.returns === "u32_array") {
438
+ const outputIndex = consumedRawArgCount(method);
439
+ const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
440
+ const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
441
+ return io.writeBytes(ptr, capacity, typedArrayBytes(expectUint32Array(result, resultContext)), resultContext);
442
+ }
303
443
  if (method.returns === "f64_array") {
304
444
  const outputIndex = consumedRawArgCount(method);
305
445
  const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
306
446
  const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
307
447
  return io.writeBytes(ptr, capacity, typedArrayBytes(expectFloat64Array(result, resultContext)), resultContext);
308
448
  }
449
+ if (method.returns === "i64_array") {
450
+ const outputIndex = consumedRawArgCount(method);
451
+ const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
452
+ const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
453
+ return io.writeBytes(ptr, capacity, typedArrayBytes(expectBigInt64Array(result, resultContext)), resultContext);
454
+ }
455
+ if (method.returns === "u64_array") {
456
+ const outputIndex = consumedRawArgCount(method);
457
+ const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
458
+ const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
459
+ return io.writeBytes(ptr, capacity, typedArrayBytes(expectBigUint64Array(result, resultContext)), resultContext);
460
+ }
309
461
  if (method.returns === "bool") {
310
462
  return expectBoolean(result, resultContext) ? 1 : 0;
311
463
  }
312
464
  if (method.returns === "i32") {
313
465
  return expectI32(result, resultContext);
314
466
  }
467
+ if (method.returns === "u32") {
468
+ return expectU32(result, resultContext);
469
+ }
470
+ if (method.returns === "i64") {
471
+ return expectI64(result, resultContext);
472
+ }
473
+ if (method.returns === "u64") {
474
+ return expectU64(result, resultContext);
475
+ }
315
476
  if (method.returns === "f64") {
316
477
  return expectNumber(result, resultContext);
317
478
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/fui-as",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
6
6
  "description": "EffinDom v2 AssemblyScript frontend framework SDK and browser harness",
@@ -11,7 +11,7 @@ HOST_SERVICE_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-services.mjs"
11
11
  HOST_EVENT_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-events.mjs"
12
12
  BUILD_TARGET="${1:-all}"
13
13
 
14
- mkdir -p "${PACKAGE_DIR}/build" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/generated"
14
+ mkdir -p "${PACKAGE_DIR}/build" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/host/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/host/generated"
15
15
  cd "${PACKAGE_DIR}"
16
16
 
17
17
  build_demo_app() {
@@ -30,6 +30,7 @@ generate_host_services() {
30
30
  local export_name="$2"
31
31
  local output_file="$3"
32
32
  local primitives_import="${4:-}"
33
+ local host_import_module="${5:-}"
33
34
 
34
35
  npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
35
36
  --bundle \
@@ -39,8 +40,13 @@ generate_host_services() {
39
40
  --packages=external \
40
41
  --outfile="${HOST_SERVICE_GENERATOR_BUILD}"
41
42
 
42
- if [ -n "${primitives_import}" ]; then
43
+ if [ -n "${primitives_import}" ] && [ -n "${host_import_module}" ]; then
44
+ node "${HOST_SERVICE_GENERATOR_BUILD}" \
45
+ "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}" "${host_import_module}"
46
+ elif [ -n "${primitives_import}" ]; then
43
47
  node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
48
+ elif [ -n "${host_import_module}" ]; then
49
+ node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "" "${host_import_module}"
44
50
  else
45
51
  node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
46
52
  fi
@@ -70,10 +76,11 @@ generate_host_events() {
70
76
  generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
71
77
  generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
72
78
  generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
73
- generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/generated/HostServices.ts" "../FuiPrimitives"
74
- generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/generated/HostEvents.ts" "../FuiPrimitives"
75
- generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/generated/HostServices.ts" "../FuiPrimitives"
76
- generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/generated/HostEvents.ts" "../FuiPrimitives"
79
+ generate_host_services "scripts/framework-host-services.ts" "frameworkHostServices" "src/core/generated/FrameworkHostServices.ts" "" "fui_host"
80
+ generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
81
+ generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
82
+ generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
83
+ generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
77
84
 
78
85
  case "${BUILD_TARGET}" in
79
86
  all)
@@ -81,8 +88,8 @@ case "${BUILD_TARGET}" in
81
88
  build_demo_app "demo/src/routes/demo_home.ts" "${DEMO_OUT_DIR}/home.wasm"
82
89
  build_demo_app "demo/src/routes/demo_advanced_controls.ts" "${DEMO_OUT_DIR}/advanced-controls.wasm"
83
90
  build_demo_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
84
- build_demo_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
85
- build_demo_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
91
+ build_demo_app "templates/demo-mvc/src/routes/HomeApp.ts" "${MVC_OUT_DIR}/home.wasm"
92
+ build_demo_app "templates/demo-mvc/src/routes/SettingsApp.ts" "${MVC_OUT_DIR}/settings.wasm"
86
93
  build_demo_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
87
94
  ;;
88
95
  dashboard)
@@ -97,18 +104,18 @@ case "${BUILD_TARGET}" in
97
104
  templated-controls|templated)
98
105
  build_demo_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
99
106
  ;;
100
- mvc-home|mvc-home-page)
101
- build_demo_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
107
+ routed-home|route-home|home-route)
108
+ build_demo_app "templates/demo-mvc/src/routes/HomeApp.ts" "${MVC_OUT_DIR}/home.wasm"
102
109
  ;;
103
- mvc-settings|mvc-settings-page)
104
- build_demo_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
110
+ routed-settings|route-settings|settings-route)
111
+ build_demo_app "templates/demo-mvc/src/routes/SettingsApp.ts" "${MVC_OUT_DIR}/settings.wasm"
105
112
  ;;
106
113
  hello-world|hello)
107
114
  build_demo_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
108
115
  ;;
109
116
  *)
110
117
  echo "Unknown build target: ${BUILD_TARGET}" >&2
111
- echo "Usage: bash scripts/build-demo-as.sh [all|dashboard|home|advanced-controls|templated-controls|mvc-home|mvc-settings|hello-world]" >&2
118
+ echo "Usage: bash scripts/build-demo-as.sh [all|dashboard|home|advanced-controls|templated-controls|routed-home|routed-settings|hello-world]" >&2
112
119
  exit 1
113
120
  ;;
114
121
  esac
package/scripts/build.sh CHANGED
@@ -24,7 +24,7 @@ RUNTIME_CONFIG_FILE="effindom-runtime-config.js"
24
24
  DEFAULT_MANIFEST_PATH="./runtime/dist/effindom.v2.manifest.json"
25
25
 
26
26
  rm -rf "${OUT_DIR}"
27
- mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${WORKER_BUILD_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/generated"
27
+ mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${WORKER_BUILD_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/host/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/host/generated"
28
28
 
29
29
  cd "${PACKAGE_DIR}"
30
30
 
@@ -66,6 +66,7 @@ generate_host_services() {
66
66
  local export_name="$2"
67
67
  local output_file="$3"
68
68
  local primitives_import="${4:-}"
69
+ local host_import_module="${5:-}"
69
70
 
70
71
  npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
71
72
  --bundle \
@@ -75,8 +76,13 @@ generate_host_services() {
75
76
  --packages=external \
76
77
  --outfile="${HOST_SERVICE_GENERATOR_BUILD}"
77
78
 
78
- if [ -n "${primitives_import}" ]; then
79
+ if [ -n "${primitives_import}" ] && [ -n "${host_import_module}" ]; then
80
+ node "${HOST_SERVICE_GENERATOR_BUILD}" \
81
+ "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}" "${host_import_module}"
82
+ elif [ -n "${primitives_import}" ]; then
79
83
  node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
84
+ elif [ -n "${host_import_module}" ]; then
85
+ node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "" "${host_import_module}"
80
86
  else
81
87
  node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
82
88
  fi
@@ -246,18 +252,19 @@ copy_worker_assets() {
246
252
  generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
247
253
  generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
248
254
  generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
249
- generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/generated/HostServices.ts" "../FuiPrimitives"
250
- generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/generated/HostEvents.ts" "../FuiPrimitives"
251
- generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/generated/HostServices.ts" "../FuiPrimitives"
252
- generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/generated/HostEvents.ts" "../FuiPrimitives"
255
+ generate_host_services "scripts/framework-host-services.ts" "frameworkHostServices" "src/core/generated/FrameworkHostServices.ts" "" "fui_host"
256
+ generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
257
+ generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
258
+ generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
259
+ generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
253
260
 
254
261
  build_app "tests/fixtures/smoke/app.ts" "${OUT_DIR}/app.wasm"
255
262
  build_app "demo/src/dashboard.ts" "${DEMO_OUT_DIR}/demo.wasm"
256
263
  build_app "demo/src/routes/demo_home.ts" "${DEMO_OUT_DIR}/home.wasm"
257
264
  build_app "demo/src/routes/demo_advanced_controls.ts" "${DEMO_OUT_DIR}/advanced-controls.wasm"
258
265
  build_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
259
- build_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
260
- build_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
266
+ build_app "templates/demo-mvc/src/routes/HomeApp.ts" "${MVC_OUT_DIR}/home.wasm"
267
+ build_app "templates/demo-mvc/src/routes/SettingsApp.ts" "${MVC_OUT_DIR}/settings.wasm"
261
268
  build_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
262
269
  build_workers
263
270
  write_worker_manifest
@@ -343,19 +350,19 @@ cp "${PACKAGE_DIR}/demo/demo-secondary-texture.png" "${DEMO_OUT_DIR}/demo-second
343
350
  mkdir -p "${DEMO_OUT_DIR}/advanced-controls" "${DEMO_OUT_DIR}/templated-controls"
344
351
  cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/advanced-controls/index.html"
345
352
  cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/templated-controls/index.html"
346
- mkdir -p "${MVC_OUT_DIR}/mvc-home" "${MVC_OUT_DIR}/mvc-settings"
347
- cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/mvc-home/index.html"
348
- cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/mvc-settings/index.html"
353
+ mkdir -p "${MVC_OUT_DIR}/home" "${MVC_OUT_DIR}/settings"
354
+ cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/home/index.html"
355
+ cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/settings/index.html"
349
356
  cat > "${MVC_OUT_DIR}/index.html" <<'EOF'
350
357
  <!doctype html>
351
358
  <html lang="en">
352
359
  <head>
353
360
  <meta charset="utf-8" />
354
- <meta http-equiv="refresh" content="0; url=./mvc-home/" />
355
- <title>FUI-AS MVC Demo</title>
361
+ <meta http-equiv="refresh" content="0; url=./home/" />
362
+ <title>FUI-AS Routed Demo</title>
356
363
  </head>
357
364
  <body>
358
- <p>Redirecting to <a href="./mvc-home/">MVC Home</a>…</p>
365
+ <p>Redirecting to <a href="./home/">Home</a>…</p>
359
366
  </body>
360
367
  </html>
361
368
  EOF
@@ -374,5 +381,5 @@ write_runtime_config "${MVC_OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
374
381
  write_runtime_config "${HELLO_OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
375
382
  write_runtime_config "${DEMO_OUT_DIR}/advanced-controls" "../runtime/dist/effindom.v2.manifest.json"
376
383
  write_runtime_config "${DEMO_OUT_DIR}/templated-controls" "../runtime/dist/effindom.v2.manifest.json"
377
- write_runtime_config "${MVC_OUT_DIR}/mvc-home" "../runtime/dist/effindom.v2.manifest.json"
378
- write_runtime_config "${MVC_OUT_DIR}/mvc-settings" "../runtime/dist/effindom.v2.manifest.json"
384
+ write_runtime_config "${MVC_OUT_DIR}/home" "../runtime/dist/effindom.v2.manifest.json"
385
+ write_runtime_config "${MVC_OUT_DIR}/settings" "../runtime/dist/effindom.v2.manifest.json"
@@ -0,0 +1,40 @@
1
+ import { defineHostServices, hostService } from "../browser/src/host-services";
2
+
3
+ function unsupported(): never {
4
+ throw new Error("Framework host service definitions are generator metadata only.");
5
+ }
6
+
7
+ export const frameworkHostServices = defineHostServices({
8
+ frameworkHost: {
9
+ nowMs: hostService({
10
+ importName: "fui_now_ms",
11
+ args: [] as const,
12
+ returns: "f64",
13
+ implementation: () => unsupported(),
14
+ }),
15
+ isDarkMode: hostService({
16
+ importName: "fui_is_dark_mode",
17
+ args: [] as const,
18
+ returns: "bool",
19
+ implementation: () => unsupported(),
20
+ }),
21
+ accentColor: hostService({
22
+ importName: "fui_get_accent_color",
23
+ args: [] as const,
24
+ returns: "u32",
25
+ implementation: () => unsupported(),
26
+ }),
27
+ platformFamily: hostService({
28
+ importName: "fui_get_platform_family",
29
+ args: [] as const,
30
+ returns: "u32",
31
+ implementation: () => unsupported(),
32
+ }),
33
+ isCoarsePointer: hostService({
34
+ importName: "fui_is_coarse_pointer",
35
+ args: [] as const,
36
+ returns: "bool",
37
+ implementation: () => unsupported(),
38
+ }),
39
+ },
40
+ });
@@ -22,6 +22,11 @@ function relativeImport(fromFile: string, targetFile: string): string {
22
22
  return toPosix(relative);
23
23
  }
24
24
 
25
+ function sourcePathForHeader(sourceModulePath: string): string {
26
+ const relative = path.relative(process.cwd(), sourceModulePath);
27
+ return toPosix(relative.startsWith(".") ? relative : `./${relative}`);
28
+ }
29
+
25
30
  function asTypeName(type: HostServiceTypeName): string {
26
31
  switch (type) {
27
32
  case "string":
@@ -30,12 +35,24 @@ function asTypeName(type: HostServiceTypeName): string {
30
35
  return "bool";
31
36
  case "i32":
32
37
  return "i32";
38
+ case "u32":
39
+ return "u32";
40
+ case "i64":
41
+ return "i64";
42
+ case "u64":
43
+ return "u64";
33
44
  case "f64":
34
45
  return "f64";
35
46
  case "bytes":
36
47
  return "Uint8Array";
37
48
  case "i32_array":
38
49
  return "Int32Array";
50
+ case "u32_array":
51
+ return "Uint32Array";
52
+ case "i64_array":
53
+ return "Int64Array";
54
+ case "u64_array":
55
+ return "Uint64Array";
39
56
  case "f64_array":
40
57
  return "Float64Array";
41
58
  case "void":
@@ -59,7 +76,15 @@ function callbackTypeFor(method: NormalizedHostEventMethod): string {
59
76
  function emitExportArgs(method: NormalizedHostEventMethod): string {
60
77
  const parts: Array<string> = [];
61
78
  method.args.forEach((type, index) => {
62
- if (type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array") {
79
+ if (
80
+ type === "string" ||
81
+ type === "bytes" ||
82
+ type === "i32_array" ||
83
+ type === "u32_array" ||
84
+ type === "i64_array" ||
85
+ type === "u64_array" ||
86
+ type === "f64_array"
87
+ ) {
63
88
  parts.push(`arg${String(index)}Ptr: usize`, `arg${String(index)}Len: u32`);
64
89
  return;
65
90
  }
@@ -91,6 +116,27 @@ function emitDecodedArgs(method: NormalizedHostEventMethod): Array<string> {
91
116
  lines.push(` if (arg${String(index)}Len > 0) {`);
92
117
  lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
93
118
  lines.push(" }");
119
+ return;
120
+ }
121
+ if (type === "u32_array") {
122
+ lines.push(` const arg${String(index)} = new Uint32Array(<i32>arg${String(index)}Len);`);
123
+ lines.push(` if (arg${String(index)}Len > 0) {`);
124
+ lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 2);`);
125
+ lines.push(" }");
126
+ return;
127
+ }
128
+ if (type === "i64_array") {
129
+ lines.push(` const arg${String(index)} = new Int64Array(<i32>arg${String(index)}Len);`);
130
+ lines.push(` if (arg${String(index)}Len > 0) {`);
131
+ lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
132
+ lines.push(" }");
133
+ return;
134
+ }
135
+ if (type === "u64_array") {
136
+ lines.push(` const arg${String(index)} = new Uint64Array(<i32>arg${String(index)}Len);`);
137
+ lines.push(` if (arg${String(index)}Len > 0) {`);
138
+ lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
139
+ lines.push(" }");
94
140
  }
95
141
  return;
96
142
  }
@@ -171,7 +217,7 @@ function emitBindingsFile(
171
217
  ): string {
172
218
  const callbackImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
173
219
  const blocks: Array<string> = [
174
- `// Generated by scripts/generate-host-events.ts from ${toPosix(sourceModulePath)}#${exportName}.`,
220
+ `// Generated by scripts/generate-host-events.ts from ${sourcePathForHeader(sourceModulePath)}#${exportName}.`,
175
221
  `import { Callback0, Callback1, Callback2 } from "${callbackImport}";`,
176
222
  "",
177
223
  ];
@@ -21,6 +21,11 @@ function relativeImport(fromFile: string, targetFile: string): string {
21
21
  return toPosix(relative);
22
22
  }
23
23
 
24
+ function sourcePathForHeader(sourceModulePath: string): string {
25
+ const relative = path.relative(process.cwd(), sourceModulePath);
26
+ return toPosix(relative.startsWith(".") ? relative : `./${relative}`);
27
+ }
28
+
24
29
  function asTypeName(type: HostServiceTypeName): string {
25
30
  switch (type) {
26
31
  case "string":
@@ -29,12 +34,24 @@ function asTypeName(type: HostServiceTypeName): string {
29
34
  return "bool";
30
35
  case "i32":
31
36
  return "i32";
37
+ case "u32":
38
+ return "u32";
39
+ case "i64":
40
+ return "i64";
41
+ case "u64":
42
+ return "u64";
32
43
  case "f64":
33
44
  return "f64";
34
45
  case "bytes":
35
46
  return "Uint8Array";
36
47
  case "i32_array":
37
48
  return "Int32Array";
49
+ case "u32_array":
50
+ return "Uint32Array";
51
+ case "i64_array":
52
+ return "Int64Array";
53
+ case "u64_array":
54
+ return "Uint64Array";
38
55
  case "f64_array":
39
56
  return "Float64Array";
40
57
  case "void":
@@ -43,14 +60,31 @@ function asTypeName(type: HostServiceTypeName): string {
43
60
  }
44
61
 
45
62
  function isPointerLengthType(type: HostServiceTypeName): boolean {
46
- return type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array";
63
+ return type === "string" ||
64
+ type === "bytes" ||
65
+ type === "i32_array" ||
66
+ type === "u32_array" ||
67
+ type === "i64_array" ||
68
+ type === "u64_array" ||
69
+ type === "f64_array";
47
70
  }
48
71
 
49
72
  function returnsBufferType(type: HostServiceTypeName): boolean {
50
- return type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array";
73
+ return type === "string" ||
74
+ type === "bytes" ||
75
+ type === "i32_array" ||
76
+ type === "u32_array" ||
77
+ type === "i64_array" ||
78
+ type === "u64_array" ||
79
+ type === "f64_array";
51
80
  }
52
81
 
53
- function emitExternalSignature(importName: string, args: readonly HostServiceTypeName[], returns: HostServiceTypeName): string {
82
+ function emitExternalSignature(
83
+ importName: string,
84
+ args: readonly HostServiceTypeName[],
85
+ returns: HostServiceTypeName,
86
+ moduleName: string,
87
+ ): string {
54
88
  const signatureParts: Array<string> = [];
55
89
  args.forEach((type, index) => {
56
90
  if (isPointerLengthType(type)) {
@@ -64,7 +98,7 @@ function emitExternalSignature(importName: string, args: readonly HostServiceTyp
64
98
  }
65
99
  const returnType = returnsBufferType(returns) ? "u32" : asTypeName(returns);
66
100
  return [
67
- `@external("fui_host_service", "${importName}")`,
101
+ `@external("${moduleName}", "${importName}")`,
68
102
  `declare function __host_${importName}(${signatureParts.join(", ")}): ${returnType};`,
69
103
  ].join("\n");
70
104
  }
@@ -77,7 +111,7 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
77
111
  lines.push(` const arg${String(index)}Bytes = Uint8Array.wrap(String.UTF8.encode(arg${String(index)}, false));`);
78
112
  return;
79
113
  }
80
- if (type === "bytes" || type === "i32_array" || type === "f64_array") {
114
+ if (type === "bytes" || type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
81
115
  lines.push(` const arg${String(index)}Bytes = arg${String(index)};`);
82
116
  }
83
117
  });
@@ -93,7 +127,7 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
93
127
  callArgs.push(`<u32>arg${String(index)}Bytes.length`);
94
128
  return;
95
129
  }
96
- if (type === "i32_array" || type === "f64_array") {
130
+ if (type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
97
131
  callArgs.push(`arg${String(index)}Bytes.length > 0 ? arg${String(index)}Bytes.dataStart : 0`);
98
132
  callArgs.push(`<u32>arg${String(index)}Bytes.length`);
99
133
  return;
@@ -111,6 +145,12 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
111
145
  lines.push(` return decodeHostServiceBytesResult(resultPtr, resultLen, "${importName}");`);
112
146
  } else if (returns === "i32_array") {
113
147
  lines.push(` return decodeHostServiceI32ArrayResult(resultPtr, resultLen, "${importName}");`);
148
+ } else if (returns === "u32_array") {
149
+ lines.push(` return decodeHostServiceU32ArrayResult(resultPtr, resultLen, "${importName}");`);
150
+ } else if (returns === "i64_array") {
151
+ lines.push(` return decodeHostServiceI64ArrayResult(resultPtr, resultLen, "${importName}");`);
152
+ } else if (returns === "u64_array") {
153
+ lines.push(` return decodeHostServiceU64ArrayResult(resultPtr, resultLen, "${importName}");`);
114
154
  } else {
115
155
  lines.push(` return decodeHostServiceF64ArrayResult(resultPtr, resultLen, "${importName}");`);
116
156
  }
@@ -155,10 +195,11 @@ function emitBindingsFile(
155
195
  outputPath: string,
156
196
  methods: ReturnType<typeof listHostServiceMethods>,
157
197
  primitivesImportOverride: string | undefined,
198
+ moduleName: string,
158
199
  ): string {
159
200
  const runtimeImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
160
201
  const blocks: Array<string> = [
161
- `// Generated by scripts/generate-host-services.ts from ${toPosix(sourceModulePath)}#${exportName}.`,
202
+ `// Generated by scripts/generate-host-services.ts from ${sourcePathForHeader(sourceModulePath)}#${exportName}.`,
162
203
  ];
163
204
  if (methods.some((method) => returnsBufferType(method.returns))) {
164
205
  const helpers = [
@@ -178,6 +219,15 @@ function emitBindingsFile(
178
219
  if (type === "i32_array") {
179
220
  return "decodeHostServiceI32ArrayResult";
180
221
  }
222
+ if (type === "u32_array") {
223
+ return "decodeHostServiceU32ArrayResult";
224
+ }
225
+ if (type === "i64_array") {
226
+ return "decodeHostServiceI64ArrayResult";
227
+ }
228
+ if (type === "u64_array") {
229
+ return "decodeHostServiceU64ArrayResult";
230
+ }
181
231
  return "decodeHostServiceF64ArrayResult";
182
232
  }),
183
233
  ),
@@ -188,7 +238,7 @@ function emitBindingsFile(
188
238
  blocks.push("");
189
239
  }
190
240
  methods.forEach((method, index) => {
191
- blocks.push(emitExternalSignature(method.importName, method.args, method.returns));
241
+ blocks.push(emitExternalSignature(method.importName, method.args, method.returns, moduleName));
192
242
  blocks.push("");
193
243
  blocks.push(emitWrapper(method.importName, method.args, method.returns));
194
244
  if (index + 1 < methods.length) {
@@ -199,15 +249,24 @@ function emitBindingsFile(
199
249
  }
200
250
 
201
251
  async function main(): Promise<void> {
202
- const [moduleArg, exportName, outputArg, primitivesImportArg] = process.argv.slice(2);
252
+ const [moduleArg, exportName, outputArg, primitivesImportArg, hostModuleArg] = process.argv.slice(2);
203
253
  if (moduleArg === undefined || exportName === undefined || outputArg === undefined) {
204
- throw new Error("Usage: generate-host-services <module-path> <export-name> <output-path> [primitives-import]");
254
+ throw new Error(
255
+ "Usage: generate-host-services <module-path> <export-name> <output-path> [primitives-import] [host-import-module]",
256
+ );
205
257
  }
206
258
  const modulePath = path.resolve(process.cwd(), moduleArg);
207
259
  const outputPath = path.resolve(process.cwd(), outputArg);
208
260
  const registry = await loadHostServices(modulePath, exportName);
209
261
  const methods = listHostServiceMethods(registry as never);
210
- const content = emitBindingsFile(modulePath, exportName, outputPath, methods, primitivesImportArg);
262
+ const content = emitBindingsFile(
263
+ modulePath,
264
+ exportName,
265
+ outputPath,
266
+ methods,
267
+ primitivesImportArg,
268
+ hostModuleArg ?? "fui_host_service",
269
+ );
211
270
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
212
271
  await fs.writeFile(outputPath, content, "utf8");
213
272
  }
package/src/Fui.ts CHANGED
@@ -1,4 +1,10 @@
1
- export { Application, ApplicationRegistration, createApplication, createManagedApplication } from "./core/Application";
1
+ export {
2
+ Application,
3
+ ApplicationRegistration,
4
+ ManagedApplicationController,
5
+ createApplication,
6
+ createManagedApplication,
7
+ } from "./core/Application";
2
8
  export { Action, CallbackAction, HandlerAction, NodeAction, SignalHandler } from "./core/Action";
3
9
  export { ObjectDisposedError } from "./core/Errors";
4
10
  export {
@@ -15,8 +15,11 @@ export { clearCurrentSelection, tryGetBounds } from "./bindings/ui";
15
15
  export {
16
16
  decodeHostServiceBytesResult,
17
17
  decodeHostServiceF64ArrayResult,
18
+ decodeHostServiceI64ArrayResult,
18
19
  decodeHostServiceI32ArrayResult,
19
20
  decodeHostServiceStringResult,
21
+ decodeHostServiceU64ArrayResult,
22
+ decodeHostServiceU32ArrayResult,
20
23
  hostServiceResultBufferPtr,
21
24
  hostServiceResultBufferSize,
22
25
  } from "./host-services/runtime";
@@ -271,6 +271,20 @@ export class Application<TPage> {
271
271
  }
272
272
  }
273
273
 
274
+ export class ManagedApplicationController {
275
+ getRoot(): Node {
276
+ throw new Error("ManagedApplicationController.getRoot() must be overridden.");
277
+ }
278
+
279
+ mount(): void {
280
+ Application.mount(this.getRoot());
281
+ }
282
+
283
+ dispose(): void {
284
+ Application.unmount();
285
+ }
286
+ }
287
+
274
288
  function getNodeRoot(page: Node): Node {
275
289
  return page;
276
290
  }
@@ -283,11 +297,21 @@ export function createApplication(buildPage: () => Node): Application<Node> {
283
297
 
284
298
  export function createManagedApplication<TPage>(
285
299
  buildPage: () => TPage,
286
- getRoot: (page: TPage) => Node,
287
- mountPage: ((page: TPage) => void) | null,
288
- disposePage: ((page: TPage) => void) | null,
300
+ getRoot: ((page: TPage) => Node) | null = null,
301
+ mountPage: ((page: TPage) => void) | null = null,
302
+ disposePage: ((page: TPage) => void) | null = null,
289
303
  ): Application<TPage> {
290
- const app = new Application<TPage>(buildPage, getRoot, mountPage, disposePage);
304
+ let managedGetRootFn: (page: TPage) => Node;
305
+ let managedMountPageFn = mountPage;
306
+ let managedDisposePageFn = disposePage;
307
+ if (getRoot === null) {
308
+ managedGetRootFn = (page: TPage): Node => changetype<ManagedApplicationController>(page).getRoot();
309
+ managedMountPageFn = (page: TPage): void => changetype<ManagedApplicationController>(page).mount();
310
+ managedDisposePageFn = (page: TPage): void => changetype<ManagedApplicationController>(page).dispose();
311
+ } else {
312
+ managedGetRootFn = getRoot;
313
+ }
314
+ const app = new Application<TPage>(buildPage, managedGetRootFn, managedMountPageFn, managedDisposePageFn);
291
315
  exportedApplication = changetype<Application<Node>>(app);
292
316
  return app;
293
317
  }
package/src/core/ffi.ts CHANGED
@@ -1,3 +1,11 @@
1
+ export {
2
+ fui_get_accent_color,
3
+ fui_get_platform_family,
4
+ fui_is_coarse_pointer,
5
+ fui_is_dark_mode,
6
+ fui_now_ms,
7
+ } from "./generated/FrameworkHostServices";
8
+
1
9
  export enum HandleValue {
2
10
  Invalid = 0,
3
11
  }
@@ -569,24 +577,9 @@ export declare function fui_start_timer(timerId: u32, delayMs: i32): void;
569
577
  @external("fui_host", "fui_cancel_timer")
570
578
  export declare function fui_cancel_timer(timerId: u32): void;
571
579
 
572
- @external("fui_host", "fui_now_ms")
573
- export declare function fui_now_ms(): f64;
574
-
575
580
  @external("fui_host", "fui_set_cursor")
576
581
  export declare function fui_set_cursor(style: u32): void;
577
582
 
578
- @external("fui_host", "fui_is_dark_mode")
579
- export declare function fui_is_dark_mode(): bool;
580
-
581
- @external("fui_host", "fui_get_accent_color")
582
- export declare function fui_get_accent_color(): u32;
583
-
584
- @external("fui_host", "fui_get_platform_family")
585
- export declare function fui_get_platform_family(): u32;
586
-
587
- @external("fui_host", "fui_is_coarse_pointer")
588
- export declare function fui_is_coarse_pointer(): bool;
589
-
590
583
  @external("fui_host", "fui_show_url_preview")
591
584
  export declare function fui_show_url_preview(ptr: usize, len: u32): void;
592
585
 
@@ -0,0 +1,36 @@
1
+ // Generated by scripts/generate-host-services.ts from ./scripts/framework-host-services.ts#frameworkHostServices.
2
+
3
+ @external("fui_host", "fui_get_accent_color")
4
+ declare function __host_fui_get_accent_color(): u32;
5
+
6
+ export function fui_get_accent_color(): u32 {
7
+ return __host_fui_get_accent_color();
8
+ }
9
+
10
+ @external("fui_host", "fui_get_platform_family")
11
+ declare function __host_fui_get_platform_family(): u32;
12
+
13
+ export function fui_get_platform_family(): u32 {
14
+ return __host_fui_get_platform_family();
15
+ }
16
+
17
+ @external("fui_host", "fui_is_coarse_pointer")
18
+ declare function __host_fui_is_coarse_pointer(): bool;
19
+
20
+ export function fui_is_coarse_pointer(): bool {
21
+ return __host_fui_is_coarse_pointer();
22
+ }
23
+
24
+ @external("fui_host", "fui_is_dark_mode")
25
+ declare function __host_fui_is_dark_mode(): bool;
26
+
27
+ export function fui_is_dark_mode(): bool {
28
+ return __host_fui_is_dark_mode();
29
+ }
30
+
31
+ @external("fui_host", "fui_now_ms")
32
+ declare function __host_fui_now_ms(): f64;
33
+
34
+ export function fui_now_ms(): f64 {
35
+ return __host_fui_now_ms();
36
+ }
@@ -49,6 +49,42 @@ export function decodeHostServiceI32ArrayResult(resultPtr: usize, resultLen: u32
49
49
  return values;
50
50
  }
51
51
 
52
+ export function decodeHostServiceU32ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Uint32Array {
53
+ assertResultByteLength(resultLen, importName);
54
+ if ((resultLen & 3) != 0) {
55
+ throw new Error("Host service " + importName + " returned misaligned Uint32Array byte length.");
56
+ }
57
+ const values = new Uint32Array(<i32>(resultLen >> 2));
58
+ if (resultLen > 0) {
59
+ memory.copy(values.dataStart, resultPtr, <usize>resultLen);
60
+ }
61
+ return values;
62
+ }
63
+
64
+ export function decodeHostServiceI64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Int64Array {
65
+ assertResultByteLength(resultLen, importName);
66
+ if ((resultLen & 7) != 0) {
67
+ throw new Error("Host service " + importName + " returned misaligned Int64Array byte length.");
68
+ }
69
+ const values = new Int64Array(<i32>(resultLen >> 3));
70
+ if (resultLen > 0) {
71
+ memory.copy(values.dataStart, resultPtr, <usize>resultLen);
72
+ }
73
+ return values;
74
+ }
75
+
76
+ export function decodeHostServiceU64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Uint64Array {
77
+ assertResultByteLength(resultLen, importName);
78
+ if ((resultLen & 7) != 0) {
79
+ throw new Error("Host service " + importName + " returned misaligned Uint64Array byte length.");
80
+ }
81
+ const values = new Uint64Array(<i32>(resultLen >> 3));
82
+ if (resultLen > 0) {
83
+ memory.copy(values.dataStart, resultPtr, <usize>resultLen);
84
+ }
85
+ return values;
86
+ }
87
+
52
88
  export function decodeHostServiceF64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Float64Array {
53
89
  assertResultByteLength(resultLen, importName);
54
90
  if ((resultLen & 7) != 0) {