diff --git a/README.md b/README.md
index 7a66fb2..81972dd 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,48 @@
-# webidl
+# WebIDL helpers for JavaScript
-📚 WebIDL infrastructure for making JavaScript ↔️ JavaScript bindings
+📚 [WebIDL] infrastructure for making JavaScript ↔️ JavaScript bindings
+
+
+
+![]()
+
+
+
+## Installation
+
+```sh
+npm install @webfill/webidl
+```
+
+## Usage
+
+```js
+import { bindInterface, defineOperation, types } from "@webfill/webidl";
+
+class Dog {
+ bark() {
+ console.log("woof");
+ }
+ eat(food) {
+ console.log(`eating ${food}`);
+ }
+}
+Dog = bindInterface(Dog, "Dog");
+defineOperation(Dog.prototype, "bark", [], types.undefined)
+defineOperation(Dog.prototype, "eat", [types.DOMString], types.undefined);
+
+const dog = new Dog();
+//=> Uncaught TypeError: Illegal constructor
+
+const dog = Object.create(Dog.prototype);
+dog.bark();
+//=> 'woof'
+
+dog.eat();
+//=> Uncaught TypeError: Failed to execute 'eat' on 'Dog': 1 argument required, but only 0 present.
+
+dog.eat("🥩");
+//=> 'eating 🥩'
+```
+
+[WebIDL]: https://webidl.spec.whatwg.org/
diff --git a/package-lock.json b/package-lock.json
index b734a6f..c3d4ab1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@jcbhmr/webidl",
- "version": "1.0.0",
+ "version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@jcbhmr/webidl",
- "version": "1.0.0",
+ "version": "0.1.0",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.2.5",
diff --git a/package.json b/package.json
index 89f19c1..def692f 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,12 @@
"type": "module",
"exports": {
".": "./dist/index.js",
+ "./DOMException.js": {
+ "deno": "./src/DOMException.js",
+ "bun": "./src/DOMException.js",
+ "node": "./src/DOMException-node.js",
+ "default": "./src/DOMException.js"
+ },
"./*.js": "./dist/*.js",
"./internal/*": null
},
diff --git a/src/DOMException-node-16.js b/src/DOMException-node-16.js
new file mode 100644
index 0000000..1e42968
--- /dev/null
+++ b/src/DOMException-node-16.js
@@ -0,0 +1,9 @@
+/** @type {typeof globalThis.DOMException} */
+let DOMException;
+try {
+ atob(1);
+} catch (error) {
+ DOMException = error.constructor;
+}
+
+export default DOMException;
diff --git a/src/DOMException-node.js b/src/DOMException-node.js
new file mode 100644
index 0000000..422a9bc
--- /dev/null
+++ b/src/DOMException-node.js
@@ -0,0 +1,9 @@
+/** @type {typeof globalThis.DOMException} */
+let DOMException;
+if (process.version.startsWith("v16")) {
+ ({ default: DOMException } = await import("./DOMException-node-16.js"));
+} else {
+ DOMException = globalThis.DOMException;
+}
+
+export default DOMException;
diff --git a/src/DOMException.js b/src/DOMException.js
new file mode 100644
index 0000000..3c01dfb
--- /dev/null
+++ b/src/DOMException.js
@@ -0,0 +1 @@
+export default DOMException;
diff --git a/src/bindInterface.js b/src/bindInterface.js
new file mode 100644
index 0000000..3a0c9dd
--- /dev/null
+++ b/src/bindInterface.js
@@ -0,0 +1,31 @@
+/**
+ * @template {{ new (...a: any[]): any }} T
+ * @param {T} Class
+ * @param {string} name
+ * @param {boolean} [constructable]
+ * @returns {T}
+ */
+export default function bindInterface(Class, name, constructable = undefined) {
+ Object.defineProperty(Class, "name", { value: name, configurable: true });
+ Object.defineProperty(Class.prototype, Symbol.toStringTag, {
+ value: name,
+ configurable: true,
+ });
+
+ if (constructable == null) {
+ constructable = /\Wconstructor\(/.test(
+ Function.prototype.toString.call(Class)
+ );
+ }
+
+ if (!constructable) {
+ Object.defineProperty(Class, "length", { value: 0, configurable: true });
+ Class = new Proxy(Class, {
+ construct() {
+ throw new TypeError(`Illegal constructor`);
+ },
+ });
+ }
+
+ return Class;
+}
diff --git a/src/defineAttribute.js b/src/defineAttribute.js
new file mode 100644
index 0000000..5ca6aad
--- /dev/null
+++ b/src/defineAttribute.js
@@ -0,0 +1,32 @@
+/**
+ * @template {object} O
+ * @template {keyof O | string} N
+ * @template {{ from(x: unknown): any }[]} T
+ * @param {O} object
+ * @param {N} name
+ * @param {T} [attrType]
+ * @returns {O}
+ */
+export default function defineAttribute(object, name, attrType = undefined) {
+ const { get, set } = Object.getOwnPropertyDescriptor(object, name);
+ Object.defineProperty(object, name, {
+ get() {
+ let x = get.call(this);
+ if (attrType) {
+ x = attrType.from(x);
+ }
+ return x;
+ },
+ ...(set && {
+ set(x) {
+ if (attrType) {
+ x = attrType.from(x);
+ }
+ set.call(this, x);
+ },
+ }),
+ enumerable: true,
+ configurable: true,
+ });
+ return object;
+}
diff --git a/src/defineExtendedAttribute.js b/src/defineExtendedAttribute.js
new file mode 100644
index 0000000..ba5f73a
--- /dev/null
+++ b/src/defineExtendedAttribute.js
@@ -0,0 +1,17 @@
+/**
+ * @template {object} O
+ * @param {O} object
+ * @param {keyof O | string} name
+ * @param {(t: "descriptor", d: PropertyDescriptor) => PropertyDescriptor | null | undefined} ExtendedAttribute
+ * @returns {O}
+ */
+export default function defineExtendedAttribute(
+ object,
+ name,
+ ExtendedAttribute
+) {
+ let d = Object.getOwnPropertyDescriptor(object, name);
+ d = ExtendedAttribute("descriptor", d) ?? d;
+ Object.defineProperty(object, name, d);
+ return object;
+}
diff --git a/src/defineOperation.js b/src/defineOperation.js
new file mode 100644
index 0000000..2104123
--- /dev/null
+++ b/src/defineOperation.js
@@ -0,0 +1,41 @@
+/**
+ * @template {object} O
+ * @template {keyof O | string} N
+ * @template {{ from(x: unknown): any }[]} A
+ * @template {{ from(x: any): any }} R
+ * @param {O} object
+ * @param {N} name
+ * @param {A} [argTypes]
+ * @param {R} [returnType]
+ * @returns {O}
+ */
+export default function defineOperation(
+ object,
+ name,
+ argTypes = undefined,
+ returnType = undefined
+) {
+ const original = object[name];
+ const { value } = {
+ value() {
+ if (argTypes) {
+ for (const [i, argType] of argTypes.entries()) {
+ arguments[i] = argType.from(arguments[i]);
+ }
+ }
+ let result = original.apply(this, arguments);
+ if (returnType) {
+ result = returnType.from(result);
+ }
+ return result;
+ },
+ };
+ Object.defineProperties(value, Object.getOwnPropertyDescriptors(original));
+ Object.defineProperty(object, name, {
+ value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ return object;
+}
diff --git a/src/extended-attributes/Global.js b/src/extended-attributes/Global.js
new file mode 100644
index 0000000..4d01717
--- /dev/null
+++ b/src/extended-attributes/Global.js
@@ -0,0 +1,20 @@
+/**
+ * @param {string | string[]} nameOrNames
+ * @returns {(t: "value", n: string, v: any) => void}
+ */
+export default function Global(nameOrNames) {
+ const names = Array.isArray(nameOrNames) ? nameOrNames : [nameOrNames];
+ return (t, n, v) => {
+ for (const name of names) {
+ if (isInstanceOf(globalThis, name)) {
+ Object.defineProperty(globalThis, n, {
+ value: v,
+ writable: true,
+ configurable: true,
+ enumerable: true,
+ });
+ return;
+ }
+ }
+ };
+}
diff --git a/src/index.ts b/src/index.js
similarity index 63%
rename from src/index.ts
rename to src/index.js
index c40b671..d60147f 100644
--- a/src/index.ts
+++ b/src/index.js
@@ -1,3 +1,3 @@
export * as types from "./types/index.js";
-export { default as interface_ } from "./interface_.js";
+export { default as interface_ } from "./interface.js";
export { default as operation } from "./operation.js";
diff --git a/src/interface_.ts b/src/interface_.ts
deleted file mode 100644
index 3ec046c..0000000
--- a/src/interface_.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Class } from "./internal/REF-type-fest/basic.js";
-
-export default function interface_>(
- name: string,
- Class: T
-): T {
- Object.defineProperty(Class, "name", { value: name, configurable: true });
- Object.defineProperty(Class.prototype, Symbol.toStringTag, {
- value: name,
- configurable: true,
- });
-
- if (/\Wconstructor\(/.test(Function.prototype.toString.call(Class))) {
- return Class;
- } else {
- Object.defineProperty(Class, "length", { value: 0, configurable: true });
- return new Proxy(Class, {
- construct() {
- throw new TypeError(`Illegal constructor`);
- },
- });
- }
-}
diff --git a/src/internal/REF-type-fest/README.md b/src/internal/REF-type-fest/README.md
deleted file mode 100644
index 34c2849..0000000
--- a/src/internal/REF-type-fest/README.md
+++ /dev/null
@@ -1 +0,0 @@
-https://github.com/sindresorhus/type-fest#readme
diff --git a/src/internal/REF-type-fest/basic.d.ts b/src/internal/REF-type-fest/basic.d.ts
deleted file mode 100644
index 41c972d..0000000
--- a/src/internal/REF-type-fest/basic.d.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Matches a
- * [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
- *
- * @category Class
- */
-export type Class = Constructor<
- T,
- Arguments
-> & { prototype: T };
-
-/**
- * Matches a [`class`
- * constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
- *
- * @category Class
- */
-export type Constructor = new (
- ...arguments_: Arguments
-) => T;
diff --git a/src/internal/isInstanceOf.js b/src/internal/isInstanceOf.js
new file mode 100644
index 0000000..e37b19b
--- /dev/null
+++ b/src/internal/isInstanceOf.js
@@ -0,0 +1,8 @@
+export function isInstanceOf(value, classOrClassName) {
+ for (let p = value; p; p = Object.getPrototypeOf(p)) {
+ if (Object.prototype.toString.call(p).slice(8, -1) === classOrClassName) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/operation.ts b/src/operation.ts
deleted file mode 100644
index 143cf6d..0000000
--- a/src/operation.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-type Coercer = { from(x: unknown): T };
-
-function operation<
- R extends Coercer,
- A extends Coercer[],
- F extends (...a: any[]) => any
->(returnType: R, argumentTypes: A, function_: F): F {
- function w(this: ThisType, ...a: Parameters): ReturnType {
- if (a.length < function_.length) {
- throw new TypeError(
- `Failed to execute '${function_.name}' on '${this}': ${
- function_.length
- } argument${function_.length === 1 ? "" : "s"} required, but only ${
- a.length
- } present.`
- );
- }
-
- const c = a.map((x, i) =>
- i < argumentTypes.length ? argumentTypes[i].from(x) : x
- );
- const r = function_.call(this, ...c);
- return returnType.from(r) as ReturnType;
- }
- const s = Object.getOwnPropertyDescriptors(function_);
- delete s.arguments;
- delete s.caller;
- delete s.callee;
- delete s.prototype;
- Object.defineProperties(w, s);
- return w as F;
-}
-
-export default operation;
diff --git a/src/types/DOMString.js b/src/types/DOMString.js
new file mode 100644
index 0000000..08af636
--- /dev/null
+++ b/src/types/DOMString.js
@@ -0,0 +1,11 @@
+/** @typedef {string} DOMString */
+const DOMString = {
+ /**
+ * @param {unknown} x
+ * @returns {DOMString}
+ */
+ from(x) {
+ return `${x}`;
+ },
+};
+export default DOMString;
diff --git a/src/types/DOMString.ts b/src/types/DOMString.ts
deleted file mode 100644
index 09264a3..0000000
--- a/src/types/DOMString.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-type DOMString = string;
-const DOMString = {
- from(x: unknown): DOMString {
- return `${x}`;
- },
-};
-
-export default DOMString;
diff --git a/src/types/index.ts b/src/types/index.js
similarity index 63%
rename from src/types/index.ts
rename to src/types/index.js
index d938506..7273233 100644
--- a/src/types/index.ts
+++ b/src/types/index.js
@@ -1,3 +1,3 @@
export { default as DOMString } from "./DOMString.js";
export { default as long } from "./long.js";
-export { default as undefined_ } from "./undefined_.js";
+export { default as undefined } from "./undefined.js";
diff --git a/src/types/long.ts b/src/types/long.js
similarity index 99%
rename from src/types/long.ts
rename to src/types/long.js
index e7f80b6..819d9da 100644
--- a/src/types/long.ts
+++ b/src/types/long.js
@@ -5,5 +5,4 @@ const long = {
return Math.trunc(x);
},
};
-
export default long;
diff --git a/src/types/undefined_.ts b/src/types/undefined.js
similarity index 99%
rename from src/types/undefined_.ts
rename to src/types/undefined.js
index a6b0d95..d8ae7cb 100644
--- a/src/types/undefined_.ts
+++ b/src/types/undefined.js
@@ -4,5 +4,4 @@ const undefined_ = {
return undefined;
},
};
-
export default undefined_;
diff --git a/test/types/index.test.ts b/test/types/index.test.ts
deleted file mode 100644
index b9cf6d1..0000000
--- a/test/types/index.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import test from "node:test";
-import assert from "node:assert";
-import * as types from "../../src/types/index.js";
-
-test("types", async (t) => {
- console.log(types);
-});
diff --git a/tsconfig.json b/tsconfig.json
deleted file mode 100644
index a587366..0000000
--- a/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "outDir": "dist",
- "declaration": true,
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "nodenext",
- "allowSyntheticDefaultImports": true,
- "esModuleInterop": true,
- "isolatedModules": true,
- "strict": true,
- "skipLibCheck": true
- },
- "include": ["src"],
- "exclude": ["node_modules", "dist"],
- "typedocOptions": {
- "entryPoints": ["src/index.ts"],
- "out": "docs/dist",
- "skipErrorChecking": true
- }
-}
diff --git a/wiki/Decorators.md b/wiki/Decorators.md
deleted file mode 100644
index 0a5566d..0000000
--- a/wiki/Decorators.md
+++ /dev/null
@@ -1,105 +0,0 @@
-https://github.com/tc39/proposal-decorators
-
-```ts
-type Decorator = (
- value: Input,
- context: {
- kind: string;
- name: string | symbol;
- access: {
- get?(): unknown;
- set?(value: unknown): void;
- };
- private?: boolean;
- static?: boolean;
- addInitializer?(initializer: () => void): void;
- }
-) => Output | void;
-```
-
-```ts
-type ClassMethodDecorator = (
- value: Function,
- context: {
- kind: "method";
- name: string | symbol;
- access: { get(): unknown };
- static: boolean;
- private: boolean;
- addInitializer(initializer: () => void): void;
- }
-) => Function | void;
-```
-
-```ts
-type ClassGetterDecorator = (
- value: Function,
- context: {
- kind: "getter";
- name: string | symbol;
- access: { get(): unknown };
- static: boolean;
- private: boolean;
- addInitializer(initializer: () => void): void;
- }
-) => Function | void;
-```
-
-```ts
-type ClassSetterDecorator = (
- value: Function,
- context: {
- kind: "setter";
- name: string | symbol;
- access: { set(value: unknown): void };
- static: boolean;
- private: boolean;
- addInitializer(initializer: () => void): void;
- }
-) => Function | void;
-```
-
-```ts
-type ClassFieldDecorator = (
- value: undefined,
- context: {
- kind: "field";
- name: string | symbol;
- access: { get(): unknown; set(value: unknown): void };
- static: boolean;
- private: boolean;
- }
-) => (initialValue: unknown) => unknown | void;
-```
-
-```ts
-type ClassDecorator = (
- value: Function,
- context: {
- kind: "class";
- name: string | undefined;
- addInitializer(initializer: () => void): void;
- }
-) => Function | void;
-```
-
-```ts
-type ClassAutoAccessorDecorator = (
- value: {
- get: () => unknown;
- set(value: unknown) => void;
- },
- context: {
- kind: "accessor";
- name: string | symbol;
- access: { get(): unknown, set(value: unknown): void };
- static: boolean;
- private: boolean;
- addInitializer(initializer: () => void): void;
- }
-) => {
- get?: () => unknown;
- set?: (value: unknown) => void;
- init?: (initialValue: unknown) => unknown;
-} | void;
-```