Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored binding wrapper #6820

Merged
merged 15 commits into from
Aug 14, 2024
Merged

Refactored binding wrapper #6820

merged 15 commits into from
Aug 14, 2024

Conversation

kraenhansen
Copy link
Member

@kraenhansen kraenhansen commented Jul 26, 2024

What, How & Why?

As a prerequisite for the rebase of the WASM / browser binding, I had to rework the generated code that wraps the native module and expose it in a TypeScript friendlier way than free functions. Instead of having platform specific wrappers that implement platform specific code to load the native module I suggest exporting an injectNativeModule function which takes the native module (from somewhere) and wraps it into the binding namespace / object.

This has multiple benefits:

  • We'll be able to maintain the platform specific code which load the native module as part of our src/platform files, which enables us to leverage TypeScript to achieve type-safety.
  • We'll be able to call this injectNativeModule once the native module has loaded (possibly asynchronously as in the case of the WASM module).
  • I've implemented this as a TypeScript source file, instead of the JS + d.ts separation that the current wrappers use. This had the potential to become more cumbersome to write, but it turned out fairly simple as the native module just has any type at the point of injection. This has the added benefit that we can get rid of a lot of the complexity in the ./binding directory as well as the babel transpile step require to get a CommonJS variant of the wrappers.

☑️ ToDos

  • 📝 Changelog entry
  • 📝 Compatibility label is updated or copied from previous entry
  • 📝 Update COMPATIBILITY.md
  • 🚦 Tests
  • 📦 Updated internal package version in consuming package.jsons (if updating internal packages)
  • 📱 Check the React Native/other sample apps work if necessary
  • 💥 Breaking label has been applied or is not necessary
  • 🔔 Mention @realm/devdocs if documentation changes are needed

@kraenhansen
Copy link
Member Author

I rebased this on main as #6814 was merged.

@kraenhansen kraenhansen marked this pull request as ready for review August 12, 2024 19:31
@kraenhansen kraenhansen added no-changelog no-jira-ticket Skip checking the PR title for Jira reference labels Aug 12, 2024
@@ -0,0 +1,405 @@
import { TemplateContext } from "@realm/bindgen/context";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the platform specific wrappers and the addition of this file are the biggest part of the changes proposed here. Originally we had the thought that keeping these separate could allow for platform specific optimizations but in reality we haven't relied on that and the separation gives rise to extra unneeded complexity.

Copy link
Contributor

@elle-j elle-j left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor 🙂 LGTM after addressing the comments 👌

@@ -0,0 +1,405 @@
import { TemplateContext } from "@realm/bindgen/context";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { TemplateContext } from "@realm/bindgen/context";
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
import { TemplateContext } from "@realm/bindgen/context";

Copy link
Member Author

@kraenhansen kraenhansen Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing because the eslint config is different for these files: https://github.com/realm/realm-js/blob/nh/wasm/emscripten_target/packages/realm/bindgen/.eslintrc.json

We could probably delete that .eslintrc.json and commit the --fix but I'll defer that to another PR.

return `export const enum ${e.jsName} { ${e.enumerators.map(({ jsName, value }) => `${jsName} = ${value}`)} };`;
}

const PRIMITIVES_MAPPING: Record<string, string | undefined> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason for including undefined here?

Suggested change
const PRIMITIVES_MAPPING: Record<string, string | undefined> = {
const PRIMITIVES_MAPPING: Record<string, string> = {

Copy link
Member Author

@kraenhansen kraenhansen Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because not all possible string properties result in a string upon lookup.

If omitted PRIMITIVES_MAPPING["invalid"] would have the type string when in fact it'll be undefined. I often forget this myself (and should probably add it to TEMPLATE_MAPPING as well 🤔)

}
}

function generateRecordDeclaration(spec: BoundSpec, record: Struct) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that generateXDeclaration logic has been pulled out 👍

Comment on lines +194 to +209
// Check the support for primitives used
for (const primitive of rawSpec.primitives) {
if (primitive === "Mixed") continue;
assert(
Object.keys(PRIMITIVES_MAPPING).includes(primitive),
`Spec declares an unsupported primitive: "${primitive}"`,
);
}

// Check the support for template instances used
for (const template of Object.keys(rawSpec.templates)) {
assert(
Object.keys(TEMPLATE_MAPPING).includes(template),
`Spec declares an unsupported template instance: "${template}"`,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Alternatively, this could be refactored into some assertSupportedTypes() (or the like) function (with the rawSpec param), and let generate() only focus on generating output.

Copy link
Member Author

@kraenhansen kraenhansen Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this could be simplified, but I think I'll let it pass for now as it's a copy-paste from the former typescript.ts template:

// Check the support for primitives used
for (const primitive of rawSpec.primitives) {
if (primitive === "Mixed") continue;
assert(
Object.keys(PRIMITIVES_MAPPING).includes(primitive),
`Spec declares an unsupported primitive: "${primitive}"`,
);
}
// Check the support for template instances used
for (const template of Object.keys(rawSpec.templates)) {
assert(
Object.keys(TEMPLATE_MAPPING).includes(template),
`Spec declares an unsupported template instance: "${template}"`,
);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice simplification here

packages/realm/src/binding/NativeBigInt.ts Outdated Show resolved Hide resolved
import type { binding } from "./wrapper.generated";
import { assert } from "../assert";

export const PolyfilledBigInt: typeof binding.Int64 = Object.freeze({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting solution for the RN issue 🙂

packages/realm/src/binding/PolyfilledBigInt.ts Outdated Show resolved Hide resolved
packages/realm/src/binding/utils.ts Outdated Show resolved Hide resolved
Comment on lines +36 to +46
WeakRef: class WeakRef {
private native: unknown;
constructor(obj: unknown) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- See "createWeakRef" protocol in the jsi bindgen template
this.native = (nativeModule as any).createWeakRef(obj);
}
deref() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- See "createWeakRef" protocol in the jsi bindgen template
return (nativeModule as any).lockWeakRef(this.native);
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this also a polyfill? If so, perhaps we could declare and define it PolyfillWeakRef similar to the bigint polyfill.

Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com>
@kraenhansen kraenhansen merged commit 211f623 into main Aug 14, 2024
34 checks passed
@kraenhansen kraenhansen deleted the kh/wrapper-refactored branch August 14, 2024 14:31
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes no-changelog no-jira-ticket Skip checking the PR title for Jira reference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants