diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..244f8f7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "oxc.enable": false, + "typescript.validate.enable": false +} \ No newline at end of file diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts index 0776029..2971ce1 100644 --- a/examples/basic/index.d.ts +++ b/examples/basic/index.d.ts @@ -11,7 +11,7 @@ * @param right - The second integer * @returns The sum of left and right */ -export function test_i32(left: number, right: number): number; +export declare function test_i32(left: number, right: number): number; /** * Adds two 32-bit floating-point numbers @@ -19,7 +19,7 @@ export function test_i32(left: number, right: number): number; * @param right - The second float * @returns The sum of left and right */ -export function test_f32(left: number, right: number): number; +export declare function test_f32(left: number, right: number): number; /** * Adds two 32-bit unsigned integers @@ -27,7 +27,7 @@ export function test_f32(left: number, right: number): number; * @param right - The second unsigned integer * @returns The sum of left and right */ -export function test_u32(left: number, right: number): number; +export declare function test_u32(left: number, right: number): number; // ============== String Functions ============== @@ -36,12 +36,12 @@ export function test_u32(left: number, right: number): number; * @param name - The name to greet * @returns A greeting string "Hello, {name}!" */ -export function hello(name: string): string; +export declare function hello(name: string): string; /** * A constant text string */ -export const text: string; +export declare const text: string; // ============== Error Functions ============== @@ -49,7 +49,7 @@ export const text: string; * Throws a test error * @throws {Error} Always throws an error with reason "test" */ -export function throw_error(): void; +export declare function throw_error(): void; // ============== Worker Functions ============== @@ -57,14 +57,14 @@ export function throw_error(): void; * Calculates fibonacci number asynchronously (fire and forget) * @param n - The fibonacci index */ -export function fib(n: number): void; +export declare function fib(n: number): void; /** * Calculates fibonacci number asynchronously with Promise * @param n - The fibonacci index * @returns A Promise that resolves when calculation is complete */ -export function fib_async(n: number): Promise; +export declare function fib_async(n: number): Promise; // ============== Array Functions ============== @@ -73,21 +73,23 @@ export function fib_async(n: number): Promise; * @param array - An array of numbers * @returns The same array */ -export function get_and_return_array(array: number[]): number[]; +export declare function get_and_return_array(array: number[]): number[]; /** * Takes a tuple array and returns it * @param array - A tuple of [number, boolean, string] * @returns The same tuple */ -export function get_named_array(array: [number, boolean, string]): [number, boolean, string]; +export declare function get_named_array( + array: [number, boolean, string] +): [number, boolean, string]; /** * Takes an ArrayList and returns it * @param array - An array of numbers * @returns The same array */ -export function get_arraylist(array: number[]): number[]; +export declare function get_arraylist(array: number[]): number[]; // ============== Object Types ============== @@ -123,34 +125,40 @@ export interface NullableField { * @param config - Object with name, age, and is_student * @returns The same object */ -export function get_object(config: FullField): FullField; +export declare function get_object(config: FullField): FullField; /** * Takes an object with optional fields * @param config - Object with name (required), age and is_student (optional) * @returns Object with default values applied (age: 18, is_student: true) */ -export function get_object_optional(config: OptionalField): OptionalField; +export declare function get_object_optional( + config: OptionalField +): OptionalField; /** * Takes an optional object and returns it * @param config - Object with optional fields * @returns The same object */ -export function get_optional_object_and_return_optional(config: OptionalField): OptionalField; +export declare function get_optional_object_and_return_optional( + config: OptionalField +): OptionalField; /** * Takes an object with nullable name field * @param config - Object with nullable name * @returns The same object */ -export function get_nullable_object(config: NullableField): NullableField; +export declare function get_nullable_object( + config: NullableField +): NullableField; /** * Returns a nullable object with null name * @returns Object with name set to null */ -export function return_nullable(): NullableField; +export declare function return_nullable(): NullableField; // ============== Function Types ============== @@ -166,7 +174,7 @@ export type CallbackFunction = (arg0: number, arg1: number) => number; * @param cb - A callback function that takes two numbers and returns a number * @returns The result of calling cb(1, 2) */ -export function call_function(cb: CallbackFunction): number; +export declare function call_function(cb: CallbackFunction): number; /** * Adds two numbers @@ -174,13 +182,13 @@ export function call_function(cb: CallbackFunction): number; * @param right - The second number * @returns The sum of left and right */ -export function basic_function(left: number, right: number): number; +export declare function basic_function(left: number, right: number): number; /** * Creates a new function that wraps basic_function * @returns A function that adds two numbers */ -export function create_function(): CallbackFunction; +export declare function create_function(): CallbackFunction; // ============== Thread Safe Function ============== @@ -188,14 +196,14 @@ export function create_function(): CallbackFunction; * Calls the thread safe function from multiple threads * @param tsfn - A thread-safe callback function */ -export function call_thread_safe_function(tsfn: CallbackFunction): void; +export declare function call_thread_safe_function(tsfn: CallbackFunction): void; // ============== Class Types ============== /** * Basic test class with name and age properties */ -export class TestClass { +export declare class TestClass { constructor(name: string, age: number); name: string; age: number; @@ -205,7 +213,7 @@ export class TestClass { * Test class with custom init function * Constructor takes (age, name) instead of field order */ -export class TestWithInitClass { +export declare class TestWithInitClass { constructor(age: number, name: string); name: string; age: number; @@ -215,7 +223,7 @@ export class TestWithInitClass { /** * Test class without constructor (abstract-like) */ -export class TestWithoutInitClass { +export declare class TestWithoutInitClass { private constructor(); name: string; age: number; @@ -225,7 +233,7 @@ export class TestWithoutInitClass { /** * Test class with factory method */ -export class TestFactoryClass { +export declare class TestFactoryClass { constructor(age: number, name: string); name: string; age: number; @@ -241,55 +249,27 @@ export class TestFactoryClass { /** * Tests hilog functionality (OpenHarmony logging) */ -export function test_hilog(): void; +export declare function test_hilog(): void; -// ============== Module Export ============== +// ============== Buffer Functions ============== -declare const hello: { - // Number - test_i32: typeof test_i32; - test_f32: typeof test_f32; - test_u32: typeof test_u32; - - // String - hello: typeof hello; - text: typeof text; - - // Error - throw_error: typeof throw_error; - - // Worker - fib: typeof fib; - fib_async: typeof fib_async; - - // Array - get_and_return_array: typeof get_and_return_array; - get_named_array: typeof get_named_array; - get_arraylist: typeof get_arraylist; - - // Object - get_object: typeof get_object; - get_object_optional: typeof get_object_optional; - get_optional_object_and_return_optional: typeof get_optional_object_and_return_optional; - get_nullable_object: typeof get_nullable_object; - return_nullable: typeof return_nullable; - - // Function - call_function: typeof call_function; - basic_function: typeof basic_function; - create_function: typeof create_function; - - // Thread Safe Function - call_thread_safe_function: typeof call_thread_safe_function; - - // Class - TestClass: typeof TestClass; - TestWithInitClass: typeof TestWithInitClass; - TestWithoutInitClass: typeof TestWithoutInitClass; - TestFactoryClass: typeof TestFactoryClass; +/** + * Creates a new buffer + * @param size - The size of the buffer + * @returns The new buffer + */ +export declare function create_buffer(): ArrayBuffer; - // Log - test_hilog: typeof test_hilog; -}; +/** + * Gets the buffer length + * @param buffer - The buffer + * @returns The buffer length + */ +export declare function get_buffer(buffer: ArrayBuffer): number; -export default hello; +/** + * Gets the buffer as a string + * @param buffer - The buffer + * @returns The buffer as a string + */ +export declare function get_buffer_as_string(buffer: ArrayBuffer): string; diff --git a/examples/basic/src/buffer.zig b/examples/basic/src/buffer.zig new file mode 100644 index 0000000..f8a80b4 --- /dev/null +++ b/examples/basic/src/buffer.zig @@ -0,0 +1,13 @@ +const napi = @import("napi"); + +pub fn create_buffer(env: napi.Env) !napi.Buffer { + return napi.Buffer.New(env, 1024); +} + +pub fn get_buffer(buf: napi.Buffer) !usize { + return buf.length(); +} + +pub fn get_buffer_as_string(buf: napi.Buffer) ![]u8 { + return buf.asSlice(); +} diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index b72b16a..4894801 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -10,6 +10,7 @@ const function = @import("function.zig"); const thread_safe_function = @import("thread_safe_function.zig"); const class = @import("class.zig"); const log = @import("log/log.zig"); +const buffer = @import("buffer.zig"); pub const test_i32 = number.test_i32; pub const test_f32 = number.test_f32; @@ -46,6 +47,10 @@ pub const TestFactoryClass = class.TestFactoryClass; pub const test_hilog = log.test_hilog; +pub const create_buffer = buffer.create_buffer; +pub const get_buffer = buffer.get_buffer; +pub const get_buffer_as_string = buffer.get_buffer_as_string; + comptime { napi.NODE_API_MODULE("hello", @This()); } diff --git a/examples/init/index.d.ts b/examples/init/index.d.ts index 096296c..8b99be0 100644 --- a/examples/init/index.d.ts +++ b/examples/init/index.d.ts @@ -12,7 +12,7 @@ * @param right - The second integer * @returns The sum of left and right */ -export function test_i32(left: number, right: number): number; +export declare function test_i32(left: number, right: number): number; /** * Adds two 32-bit floating-point numbers @@ -20,7 +20,7 @@ export function test_i32(left: number, right: number): number; * @param right - The second float * @returns The sum of left and right */ -export function test_f32(left: number, right: number): number; +export declare function test_f32(left: number, right: number): number; /** * Adds two 32-bit unsigned integers @@ -28,7 +28,7 @@ export function test_f32(left: number, right: number): number; * @param right - The second unsigned integer * @returns The sum of left and right */ -export function test_u32(left: number, right: number): number; +export declare function test_u32(left: number, right: number): number; // ============== Init Exports ============== @@ -38,78 +38,58 @@ export function test_u32(left: number, right: number): number; * @param right - The second number * @returns The sum of left and right */ -export function add(left: number, right: number): number; +export declare function add(left: number, right: number): number; /** * Returns a greeting message * @param name - The name to greet * @returns A greeting string "Hello, {name}!" */ -export function hello(name: string): string; +export declare function hello(name: string): string; /** * A constant text string "Hello" */ -export const text: string; +export declare const text: string; /** * Calculates fibonacci number asynchronously (fire and forget) * @param n - The fibonacci index */ -export function fib(n: number): void; +export declare function fib(n: number): void; /** * Calculates fibonacci number asynchronously with Promise * @param n - The fibonacci index * @returns A Promise that resolves when calculation is complete */ -export function fib_async(n: number): Promise; +export declare function fib_async(n: number): Promise; /** * Takes an array of numbers and returns it * @param array - An array of numbers * @returns The same array */ -export function get_and_return_array(array: number[]): number[]; +export declare function get_and_return_array(array: number[]): number[]; /** * Takes a tuple array and returns it * @param array - A tuple of [number, boolean, string] * @returns The same tuple */ -export function get_named_array(array: [number, boolean, string]): [number, boolean, string]; +export declare function get_named_array( + array: [number, boolean, string] +): [number, boolean, string]; /** * Takes an ArrayList and returns it * @param array - An array of numbers * @returns The same array */ -export function get_arraylist(array: number[]): number[]; +export declare function get_arraylist(array: number[]): number[]; /** * Throws a test error * @throws {Error} Always throws an error with reason "test" */ -export function throw_error(): void; - -// ============== Module Export ============== - -declare const hello: { - // Number (pub exports) - test_i32: typeof test_i32; - test_f32: typeof test_f32; - test_u32: typeof test_u32; - - // Init exports - add: typeof add; - hello: typeof hello; - text: typeof text; - fib: typeof fib; - fib_async: typeof fib_async; - get_and_return_array: typeof get_and_return_array; - get_named_array: typeof get_named_array; - get_arraylist: typeof get_arraylist; - throw_error: typeof throw_error; -}; - -export default hello; +export declare function throw_error(): void; diff --git a/harmony_example/.gitignore b/harmony_example/.gitignore new file mode 100644 index 0000000..7a555b1 --- /dev/null +++ b/harmony_example/.gitignore @@ -0,0 +1,13 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer +*.so \ No newline at end of file diff --git a/harmony_example/AppScope/app.json5 b/harmony_example/AppScope/app.json5 new file mode 100644 index 0000000..0a21739 --- /dev/null +++ b/harmony_example/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.zig_example", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/harmony_example/AppScope/resources/base/element/string.json b/harmony_example/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..ba579ff --- /dev/null +++ b/harmony_example/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "zig_example" + } + ] +} diff --git a/harmony_example/AppScope/resources/base/media/background.png b/harmony_example/AppScope/resources/base/media/background.png new file mode 100644 index 0000000..923f2b3 Binary files /dev/null and b/harmony_example/AppScope/resources/base/media/background.png differ diff --git a/harmony_example/AppScope/resources/base/media/foreground.png b/harmony_example/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000..eb94275 Binary files /dev/null and b/harmony_example/AppScope/resources/base/media/foreground.png differ diff --git a/harmony_example/AppScope/resources/base/media/layered_image.json b/harmony_example/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000..fb49920 --- /dev/null +++ b/harmony_example/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/harmony_example/build-profile.json5 b/harmony_example/build-profile.json5 new file mode 100644 index 0000000..3ed2fd7 --- /dev/null +++ b/harmony_example/build-profile.json5 @@ -0,0 +1,43 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "nativeCompiler": "BiSheng", + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/harmony_example/code-linter.json5 b/harmony_example/code-linter.json5 new file mode 100644 index 0000000..073990f --- /dev/null +++ b/harmony_example/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/harmony_example/entry/.gitignore b/harmony_example/entry/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/harmony_example/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/harmony_example/entry/build-profile.json5 b/harmony_example/entry/build-profile.json5 new file mode 100644 index 0000000..1bdab41 --- /dev/null +++ b/harmony_example/entry/build-profile.json5 @@ -0,0 +1,44 @@ +{ + "apiType": "stageMode", + "buildOption": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + }, + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/harmony_example/entry/hvigorfile.ts b/harmony_example/entry/hvigorfile.ts new file mode 100644 index 0000000..b0e3a1a --- /dev/null +++ b/harmony_example/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/harmony_example/entry/libs/arm64-v8a/.gitkeep b/harmony_example/entry/libs/arm64-v8a/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/harmony_example/entry/obfuscation-rules.txt b/harmony_example/entry/obfuscation-rules.txt new file mode 100644 index 0000000..272efb6 --- /dev/null +++ b/harmony_example/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/harmony_example/entry/oh-package-lock.json5 b/harmony_example/entry/oh-package-lock.json5 new file mode 100644 index 0000000..dd4113d --- /dev/null +++ b/harmony_example/entry/oh-package-lock.json5 @@ -0,0 +1,26 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "libentry.so@src/main/cpp/types/libentry": "libentry.so@src/main/cpp/types/libentry", + "libhello.so@src/main/cpp/types/libhello": "libhello.so@src/main/cpp/types/libhello" + }, + "packages": { + "libentry.so@src/main/cpp/types/libentry": { + "name": "libentry.so", + "version": "1.0.0", + "resolved": "", + "registryType": "local" + }, + "libhello.so@src/main/cpp/types/libhello": { + "name": "libhello.so", + "version": "1.0.0", + "resolved": "", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/harmony_example/entry/oh-package.json5 b/harmony_example/entry/oh-package.json5 new file mode 100644 index 0000000..d18a500 --- /dev/null +++ b/harmony_example/entry/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libentry.so": "file:./src/main/cpp/types/libentry", + "libhello.so": "file:./src/main/cpp/types/libhello" + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/cpp/CMakeLists.txt b/harmony_example/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..aab11c0 --- /dev/null +++ b/harmony_example/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,15 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(harmony_example) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED PACKAGE_FIND_FILE) + include(${PACKAGE_FIND_FILE}) +endif() + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include) + +add_library(entry SHARED napi_init.cpp) +target_link_libraries(entry PUBLIC libace_napi.z.so) \ No newline at end of file diff --git a/harmony_example/entry/src/main/cpp/napi_init.cpp b/harmony_example/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 0000000..987bd48 --- /dev/null +++ b/harmony_example/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,53 @@ +#include "napi/native_api.h" + +static napi_value Add(napi_env env, napi_callback_info info) +{ + size_t argc = 2; + napi_value args[2] = {nullptr}; + + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + napi_valuetype valuetype0; + napi_typeof(env, args[0], &valuetype0); + + napi_valuetype valuetype1; + napi_typeof(env, args[1], &valuetype1); + + double value0; + napi_get_value_double(env, args[0], &value0); + + double value1; + napi_get_value_double(env, args[1], &value1); + + napi_value sum; + napi_create_double(env, value0 + value1, &sum); + + return sum; + +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { + { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr } + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void*)0), + .reserved = { 0 }, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} diff --git a/harmony_example/entry/src/main/cpp/types/libentry/Index.d.ts b/harmony_example/entry/src/main/cpp/types/libentry/Index.d.ts new file mode 100644 index 0000000..e44f361 --- /dev/null +++ b/harmony_example/entry/src/main/cpp/types/libentry/Index.d.ts @@ -0,0 +1 @@ +export const add: (a: number, b: number) => number; \ No newline at end of file diff --git a/harmony_example/entry/src/main/cpp/types/libentry/oh-package.json5 b/harmony_example/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000..ea41072 --- /dev/null +++ b/harmony_example/entry/src/main/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libentry.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts b/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts new file mode 100644 index 0000000..2c60206 --- /dev/null +++ b/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts @@ -0,0 +1,277 @@ +import buffer from "@ohos.buffer"; + +/** + * Basic example module for zig-napi + * OpenHarmony/HarmonyNext native module written in Zig + */ + +// ============== Number Functions ============== + +/** + * Adds two 32-bit signed integers + * @param left - The first integer + * @param right - The second integer + * @returns The sum of left and right + */ +export declare function test_i32(left: number, right: number): number; + +/** + * Adds two 32-bit floating-point numbers + * @param left - The first float + * @param right - The second float + * @returns The sum of left and right + */ +export declare function test_f32(left: number, right: number): number; + +/** + * Adds two 32-bit unsigned integers + * @param left - The first unsigned integer + * @param right - The second unsigned integer + * @returns The sum of left and right + */ +export declare function test_u32(left: number, right: number): number; + +// ============== String Functions ============== + +/** + * Returns a greeting message + * @param name - The name to greet + * @returns A greeting string "Hello, {name}!" + */ +export declare function hello(name: string): string; + +/** + * A constant text string + */ +export declare const text: string; + +// ============== Error Functions ============== + +/** + * Throws a test error + * @throws {Error} Always throws an error with reason "test" + */ +export declare function throw_error(): void; + +// ============== Worker Functions ============== + +/** + * Calculates fibonacci number asynchronously (fire and forget) + * @param n - The fibonacci index + */ +export declare function fib(n: number): void; + +/** + * Calculates fibonacci number asynchronously with Promise + * @param n - The fibonacci index + * @returns A Promise that resolves when calculation is complete + */ +export declare function fib_async(n: number): Promise; + +// ============== Array Functions ============== + +/** + * Takes an array and returns it + * @param array - An array of numbers + * @returns The same array + */ +export declare function get_and_return_array(array: number[]): number[]; + +/** + * Takes a tuple array and returns it + * @param array - A tuple of [number, boolean, string] + * @returns The same tuple + */ +export declare function get_named_array( + array: [number, boolean, string] +): [number, boolean, string]; + +/** + * Takes an ArrayList and returns it + * @param array - An array of numbers + * @returns The same array + */ +export declare function get_arraylist(array: number[]): number[]; + +// ============== Object Types ============== + +/** + * Full field object with all required fields + */ +export interface FullField { + name: string; + age: number; + is_student: boolean; +} + +/** + * Object with optional fields + */ +export interface OptionalField { + name: string; + age?: number; + is_student?: boolean; +} + +/** + * Object with nullable field + */ +export interface NullableField { + name: string | null; +} + +// ============== Object Functions ============== + +/** + * Takes a full field object and returns it + * @param config - Object with name, age, and is_student + * @returns The same object + */ +export declare function get_object(config: FullField): FullField; + +/** + * Takes an object with optional fields + * @param config - Object with name (required), age and is_student (optional) + * @returns Object with default values applied (age: 18, is_student: true) + */ +export declare function get_object_optional( + config: OptionalField +): OptionalField; + +/** + * Takes an optional object and returns it + * @param config - Object with optional fields + * @returns The same object + */ +export declare function get_optional_object_and_return_optional( + config: OptionalField +): OptionalField; + +/** + * Takes an object with nullable name field + * @param config - Object with nullable name + * @returns The same object + */ +export declare function get_nullable_object( + config: NullableField +): NullableField; + +/** + * Returns a nullable object with null name + * @returns Object with name set to null + */ +export declare function return_nullable(): NullableField; + +// ============== Function Types ============== + +/** + * Callback function type that takes two numbers and returns a number + */ +export type CallbackFunction = (arg0: number, arg1: number) => number; + +// ============== Function Functions ============== + +/** + * Calls the provided callback function with (1, 2) + * @param cb - A callback function that takes two numbers and returns a number + * @returns The result of calling cb(1, 2) + */ +export declare function call_function(cb: CallbackFunction): number; + +/** + * Adds two numbers + * @param left - The first number + * @param right - The second number + * @returns The sum of left and right + */ +export declare function basic_function(left: number, right: number): number; + +/** + * Creates a new function that wraps basic_function + * @returns A function that adds two numbers + */ +export declare function create_function(): CallbackFunction; + +// ============== Thread Safe Function ============== + +/** + * Calls the thread safe function from multiple threads + * @param tsfn - A thread-safe callback function + */ +export declare function call_thread_safe_function(tsfn: CallbackFunction): void; + +// ============== Class Types ============== + +/** + * Basic test class with name and age properties + */ +export declare class TestClass { + constructor(name: string, age: number); + name: string; + age: number; +} + +/** + * Test class with custom init function + * Constructor takes (age, name) instead of field order + */ +export declare class TestWithInitClass { + constructor(age: number, name: string); + name: string; + age: number; + static readonly hello: string; +} + +/** + * Test class without constructor (abstract-like) + */ +export declare class TestWithoutInitClass { + private constructor(); + name: string; + age: number; + static readonly hello: string; +} + +/** + * Test class with factory method + */ +export declare class TestFactoryClass { + constructor(age: number, name: string); + name: string; + age: number; + /** + * Formats the object as a string + * @returns Formatted string representation + */ + format(): string; +} + +// ============== Log Functions ============== + +/** + * Tests hilog functionality (OpenHarmony logging) + */ +export declare function test_hilog(): void; + +// ============== Buffer Functions ============== + +/** + * Creates a new buffer + * @param size - The size of the buffer + * @returns The new buffer + */ +export declare function create_buffer(): ArrayBuffer; + +/** + * Gets the buffer as a string + * @param buffer - The buffer + * @returns The buffer as a string + */ +export declare function get_buffer(buffer: ArrayBuffer): number; + +/** + * Gets the buffer as a string + * @param buffer - The buffer + * @returns The buffer as a string + */ +export declare function get_buffer_as_string(buffer: ArrayBuffer): string; \ No newline at end of file diff --git a/harmony_example/entry/src/main/cpp/types/libhello/oh-package.json5 b/harmony_example/entry/src/main/cpp/types/libhello/oh-package.json5 new file mode 100644 index 0000000..fcde40c --- /dev/null +++ b/harmony_example/entry/src/main/cpp/types/libhello/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libhello.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/ets/entryability/EntryAbility.ets b/harmony_example/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000..091797f --- /dev/null +++ b/harmony_example/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,48 @@ +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/harmony_example/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000..8e4de99 --- /dev/null +++ b/harmony_example/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,16 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/ets/pages/Index.ets b/harmony_example/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000..9931cc8 --- /dev/null +++ b/harmony_example/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,41 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import testNapi from 'libhello.so'; +import buffer from '@ohos.buffer'; + +const DOMAIN = 0x0000; + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + aboutToAppear(): void { + const napi = testNapi; + + const buf = napi.create_buffer(); + + const buff = buffer.from([1,2,3]); + const ret = napi.get_buffer(buff.buffer); + console.log(`${ret}`) + + const buff3 = buffer.from("hello world"); + const ret1 = napi.get_buffer_as_string(buff3.buffer); + + console.log(`${ret1}`) + } + + build() { + Row() { + Column() { + Text(this.message) + .fontSize($r('app.float.page_text_font_size')) + .fontWeight(FontWeight.Bold) + .onClick(() => { + this.message = 'Welcome'; + }) + } + .width('100%') + } + .height('100%') + } +} diff --git a/harmony_example/entry/src/main/module.json5 b/harmony_example/entry/src/main/module.json5 new file mode 100644 index 0000000..53024e8 --- /dev/null +++ b/harmony_example/entry/src/main/module.json5 @@ -0,0 +1,50 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/resources/base/element/color.json b/harmony_example/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..3c71296 --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/resources/base/element/float.json b/harmony_example/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000..33ea223 --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/harmony_example/entry/src/main/resources/base/element/string.json b/harmony_example/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..f945955 --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/resources/base/media/background.png b/harmony_example/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000..923f2b3 Binary files /dev/null and b/harmony_example/entry/src/main/resources/base/media/background.png differ diff --git a/harmony_example/entry/src/main/resources/base/media/foreground.png b/harmony_example/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000..97014d3 Binary files /dev/null and b/harmony_example/entry/src/main/resources/base/media/foreground.png differ diff --git a/harmony_example/entry/src/main/resources/base/media/layered_image.json b/harmony_example/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000..fb49920 --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/resources/base/media/startIcon.png b/harmony_example/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..205ad8b Binary files /dev/null and b/harmony_example/entry/src/main/resources/base/media/startIcon.png differ diff --git a/harmony_example/entry/src/main/resources/base/profile/backup_config.json b/harmony_example/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000..78f40ae --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/harmony_example/entry/src/main/resources/base/profile/main_pages.json b/harmony_example/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..1898d94 --- /dev/null +++ b/harmony_example/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/harmony_example/entry/src/main/resources/dark/element/color.json b/harmony_example/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..79b11c2 --- /dev/null +++ b/harmony_example/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/harmony_example/entry/src/mock/Libentry.mock.ets b/harmony_example/entry/src/mock/Libentry.mock.ets new file mode 100644 index 0000000..c217171 --- /dev/null +++ b/harmony_example/entry/src/mock/Libentry.mock.ets @@ -0,0 +1,7 @@ +const NativeMock: Record = { + 'add': (a: number, b: number) => { + return a + b; + }, +}; + +export default NativeMock; \ No newline at end of file diff --git a/harmony_example/entry/src/mock/mock-config.json5 b/harmony_example/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000..6540976 --- /dev/null +++ b/harmony_example/entry/src/mock/mock-config.json5 @@ -0,0 +1,5 @@ +{ + "libentry.so": { + "source": "src/mock/Libentry.mock.ets" + } +} \ No newline at end of file diff --git a/harmony_example/entry/src/ohosTest/ets/test/Ability.test.ets b/harmony_example/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000..85c78f6 --- /dev/null +++ b/harmony_example/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/harmony_example/entry/src/ohosTest/ets/test/List.test.ets b/harmony_example/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..794c7dc --- /dev/null +++ b/harmony_example/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/harmony_example/entry/src/ohosTest/module.json5 b/harmony_example/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000..509a3a2 --- /dev/null +++ b/harmony_example/entry/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/harmony_example/entry/src/test/List.test.ets b/harmony_example/entry/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/harmony_example/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/harmony_example/entry/src/test/LocalUnit.test.ets b/harmony_example/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..165fc16 --- /dev/null +++ b/harmony_example/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/harmony_example/hvigor/hvigor-config.json5 b/harmony_example/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..7a7ab89 --- /dev/null +++ b/harmony_example/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/harmony_example/hvigorfile.ts b/harmony_example/hvigorfile.ts new file mode 100644 index 0000000..47113e2 --- /dev/null +++ b/harmony_example/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/harmony_example/oh-package-lock.json5 b/harmony_example/oh-package-lock.json5 new file mode 100644 index 0000000..1020c3e --- /dev/null +++ b/harmony_example/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.24": { + "name": "", + "version": "1.0.24", + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.24.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/harmony_example/oh-package.json5 b/harmony_example/oh-package.json5 new file mode 100644 index 0000000..c72aa05 --- /dev/null +++ b/harmony_example/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/src/napi.zig b/src/napi.zig index 8c7fe6a..f90fad8 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -7,11 +7,9 @@ const worker = @import("./napi/wrapper/worker.zig"); const err = @import("./napi/wrapper/error.zig"); const thread_safe_function = @import("./napi/wrapper/thread_safe_function.zig"); const class = @import("./napi/wrapper/class.zig"); - -const DefaultGlobalAllocator = @import("./napi/util/allocator.zig"); +const buffer = @import("./napi/wrapper/buffer.zig"); pub const napi_sys = @import("napi-sys"); - pub const Env = env.Env; pub const Object = value.Object; pub const Number = value.Number; @@ -35,6 +33,7 @@ pub const Worker = worker.Worker; pub const ThreadSafeFunction = thread_safe_function.ThreadSafeFunction; pub const Class = class.Class; pub const ClassWithoutInit = class.ClassWithoutInit; +pub const Buffer = buffer.Buffer; pub const NODE_API_MODULE = module.NODE_API_MODULE; pub const NODE_API_MODULE_WITH_INIT = module.NODE_API_MODULE_WITH_INIT; diff --git a/src/napi/util/napi.zig b/src/napi/util/napi.zig index 177acb1..9df35c5 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -7,12 +7,13 @@ const NapiError = @import("../wrapper/error.zig"); const Function = @import("../value/function.zig").Function; const ThreadSafeFunction = @import("../wrapper/thread_safe_function.zig").ThreadSafeFunction; const class = @import("../wrapper/class.zig"); +const Buffer = @import("../wrapper/buffer.zig").Buffer; pub const Napi = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { const infos = @typeInfo(T); switch (T) { - NapiValue.BigInt, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null => { + NapiValue.BigInt, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer => { return T.from_raw(env, raw); }, else => { @@ -134,7 +135,7 @@ pub const Napi = struct { const infos = @typeInfo(value_type); switch (value_type) { - NapiValue.BigInt, NapiValue.Bool, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null => { + NapiValue.BigInt, NapiValue.Bool, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer => { return value.raw; }, // If value is already a napi_value, return it directly diff --git a/src/napi/wrapper/buffer.zig b/src/napi/wrapper/buffer.zig new file mode 100644 index 0000000..1ad375e --- /dev/null +++ b/src/napi/wrapper/buffer.zig @@ -0,0 +1,243 @@ +const std = @import("std"); +const napi = @import("napi-sys").napi_sys; +const Env = @import("../env.zig").Env; +const NapiError = @import("error.zig"); +const GlobalAllocator = @import("../util/allocator.zig"); + +pub const Buffer = struct { + env: napi.napi_env, + raw: napi.napi_value, + data: [*]u8, + len: usize, + + /// Create a Buffer from a raw napi_value + pub fn from_raw(env: napi.napi_env, raw: napi.napi_value) Buffer { + var data: ?*anyopaque = null; + var len: usize = 0; + _ = napi.napi_get_buffer_info(env, raw, &data, &len); + if (len == 0) { + return Buffer{ + .env = env, + .raw = raw, + .data = &[_]u8{}, + .len = 0, + }; + } + return Buffer{ + .env = env, + .raw = raw, + .data = @ptrCast(data), + .len = len, + }; + } + + /// Convert from napi_value to the specified type ([]u8 or [N]u8) + pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { + const infos = @typeInfo(T); + + switch (infos) { + // Handle fixed-size array: [N]u8 + .array => |arr| { + if (arr.child != u8) { + @compileError("Buffer only supports u8 arrays, got: " ++ @typeName(arr.child)); + } + + var data: ?*anyopaque = null; + var len: usize = 0; + _ = napi.napi_get_buffer_info(env, raw, &data, &len); + + var result: T = undefined; + const copy_len = @min(len, arr.len); + const src: [*]const u8 = @ptrCast(data); + @memcpy(result[0..copy_len], src[0..copy_len]); + + // Zero-fill remaining bytes if buffer is smaller than array + if (copy_len < arr.len) { + @memset(result[copy_len..], 0); + } + + return result; + }, + // Handle slice: []u8 or []const u8 + .pointer => |ptr| { + if (ptr.size != .slice) { + @compileError("Buffer only supports slices, got pointer type: " ++ @typeName(T)); + } + if (ptr.child != u8) { + @compileError("Buffer only supports u8 slices, got: " ++ @typeName(ptr.child)); + } + + var data: ?*anyopaque = null; + var len: usize = 0; + _ = napi.napi_get_buffer_info(env, raw, &data, &len); + + const allocator = GlobalAllocator.globalAllocator(); + const buf = allocator.alloc(u8, len) catch @panic("OOM"); + const src: [*]const u8 = @ptrCast(data); + @memcpy(buf, src[0..len]); + + return buf; + }, + else => { + @compileError("Buffer.from_napi_value only supports []u8 or [N]u8, got: " ++ @typeName(T)); + }, + } + } + + /// Create a new Buffer from data using external buffer (zero-copy, transfers ownership) + /// Similar to napi-rs `Buffer::from(Vec)` which uses napi_create_external_buffer + /// + /// The data ownership is transferred to JavaScript. When the JS Buffer is garbage collected, + /// the finalize callback will free the memory using the global allocator. + /// + /// Example: + /// ```zig + /// const allocator = GlobalAllocator.globalAllocator(); + /// const owned_data = try allocator.alloc(u8, 1024); + /// // ... fill data ... + /// const buf = try Buffer.from(env, owned_data); // ownership transferred + /// // Don't free owned_data, it's now managed by JS + /// ``` + pub fn from(env: Env, data: []u8) !Buffer { + var result: napi.napi_value = undefined; + + // Store the slice info for the finalizer + const hint = BufferHint.create(data) catch { + return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); + }; + + const status = napi.napi_create_external_buffer( + env.raw, + data.len, + @ptrCast(data.ptr), + externalBufferFinalizer, + hint, + &result, + ); + + if (status != napi.napi_ok) { + // Clean up hint if buffer creation failed + hint.destroy(); + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + return Buffer{ + .env = env.raw, + .raw = result, + .data = data.ptr, + .len = data.len, + }; + } + + /// Create a new Buffer by copying data (no ownership transfer) + /// Similar to napi-rs `BufferSlice::copy_from` + /// + /// Use this when you want to keep ownership of the original data, + /// or when the data is on the stack/temporary. + /// + /// Example: + /// ```zig + /// const stack_data = [_]u8{ 1, 2, 3, 4 }; + /// const buf = try Buffer.copy(env, &stack_data); + /// ``` + pub fn copy(env: Env, data: []const u8) !Buffer { + var result: napi.napi_value = undefined; + var result_data: ?*anyopaque = null; + + const status = napi.napi_create_buffer_copy( + env.raw, + data.len, + @ptrCast(data.ptr), + &result_data, + &result, + ); + + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + return Buffer{ + .env = env.raw, + .raw = result, + .data = @ptrCast(result_data), + .len = data.len, + }; + } + + /// Create a new uninitialized Buffer with the specified length + /// Similar to napi-rs `env.create_buffer(length)` + /// + /// Example: + /// ```zig + /// var buf = try Buffer.New(env, 1024); + /// @memset(buf.asSlice(), 0); // initialize + /// ``` + pub fn New(env: Env, len: usize) !Buffer { + var result: napi.napi_value = undefined; + var data: ?*anyopaque = null; + + const status = napi.napi_create_buffer(env.raw, len, &data, &result); + + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + return Buffer{ + .env = env.raw, + .raw = result, + .data = @ptrCast(data), + .len = len, + }; + } + + /// Get the buffer data as a mutable slice + pub fn asSlice(self: Buffer) []u8 { + return self.data[0..self.len]; + } + + /// Get the buffer data as a const slice + pub fn asConstSlice(self: Buffer) []const u8 { + return self.data[0..self.len]; + } + + /// Get the length of the buffer + pub fn length(self: Buffer) usize { + return self.len; + } +}; + +/// Helper struct to store buffer info for the finalizer +const BufferHint = struct { + ptr: [*]u8, + len: usize, + + fn create(data: []u8) !*BufferHint { + const allocator = GlobalAllocator.globalAllocator(); + const hint = try allocator.create(BufferHint); + hint.* = .{ + .ptr = data.ptr, + .len = data.len, + }; + return hint; + } + + fn destroy(self: *BufferHint) void { + const allocator = GlobalAllocator.globalAllocator(); + // Free the original buffer data + allocator.free(self.ptr[0..self.len]); + // Free the hint struct itself + allocator.destroy(self); + } +}; + +/// Callback invoked when the external buffer is garbage collected +fn externalBufferFinalizer( + _: napi.napi_env, + _: ?*anyopaque, + hint: ?*anyopaque, +) callconv(.C) void { + if (hint) |h| { + const buffer_hint: *BufferHint = @ptrCast(@alignCast(h)); + buffer_hint.destroy(); + } +}