-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
[js] Add serialization and domain layer #17927
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
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you 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. | ||
|
|
||
| export interface EventDescriptor<T> { | ||
| readonly method: string | ||
| readonly type?: { fromWire(payload: unknown): T } | ||
| } | ||
|
|
||
| export function event<T>(method: string, type?: { fromWire(payload: unknown): T }): EventDescriptor<T> | ||
|
|
||
| /** Internal construction guard — only a generated `Class.create(driver)` passes this. Never use directly. */ | ||
| export const DOMAIN_TOKEN: unique symbol | ||
|
|
||
| export declare class Domain { | ||
| protected constructor(bidi: unknown, token: typeof DOMAIN_TOKEN) | ||
| protected static connect(driver: unknown): Promise<unknown> | ||
| protected send(method: string, params: Record<string, unknown>): Promise<unknown> | ||
| addCallback<T>( | ||
| descriptor: EventDescriptor<T>, | ||
| handler: (params: T) => void, | ||
| ): Promise<{ id: string; unsubscribe(): Promise<void> }> | ||
| removeCallback(subscriptionId: string): Promise<void> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you 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. | ||
|
|
||
| const { getBidiConnection } = require('../lib/bidi_connection') | ||
|
|
||
| // Gates Domain's constructor so `new Network(someRandomThing)` fails loudly | ||
| // instead of silently producing a broken instance. A Symbol can't be forged | ||
| // or guessed, so this is real runtime enforcement, not just a TS annotation — | ||
| // only a generated `Class.create(driver)` (and this package's own tests) may | ||
| // pass it. It's exported deliberately, not hidden: the point is to stop | ||
| // accidental misuse of the normal `new Network(x)` shape, not to defend | ||
| // against someone who deliberately imports and passes this. | ||
| const DOMAIN_TOKEN = Symbol('Domain internal construction token — obtained only via Class.create(driver)') | ||
|
|
||
| /** | ||
| * @param {string} method | ||
| * @param {{fromWire(payload: unknown): unknown}} [type] Runtime record/union | ||
| * class for the event's params, if the schema declares one. When present, | ||
| * addCallback() parses each delivered payload through it before the | ||
| * caller's handler runs — inbound wire payloads are validated against | ||
| * their resolved type; an event's params is such a payload just as much | ||
| * as a command's result is. | ||
| * @returns {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}} | ||
| */ | ||
| function event(method, type) { | ||
| return { method, type } | ||
| } | ||
|
|
||
| /** Shared base for every generated BiDi domain class. See domain.d.ts for the typed surface. */ | ||
| class Domain { | ||
| #bidi | ||
|
|
||
| constructor(bidi, token) { | ||
| if (token !== DOMAIN_TOKEN) { | ||
| throw new TypeError(`${new.target.name} must be constructed via ${new.target.name}.create(driver), not \`new\``) | ||
| } | ||
| this.#bidi = bidi | ||
| } | ||
|
|
||
| static async connect(driver) { | ||
| return getBidiConnection(driver) | ||
| } | ||
|
|
||
| async send(method, params) { | ||
| const response = await this.#bidi.send({ method, params }) | ||
| if (response?.error !== undefined) { | ||
| throw new Error(`${response.error}: ${response.message}`) | ||
| } | ||
| return response?.result | ||
| } | ||
|
|
||
| async addCallback(descriptor, handler) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. domain callbacks lack jsdoc The exported Domain class adds public addCallback and removeCallback methods without immediately preceding JSDoc blocks. Their parameters and non-void promise return values are therefore undocumented. Agent Prompt
|
||
| const dispatch = descriptor.type === undefined ? handler : (params) => handler(descriptor.type.fromWire(params)) | ||
| return this.#bidi.addCallback(descriptor.method, dispatch) | ||
| } | ||
|
|
||
| async removeCallback(subscriptionId) { | ||
| return this.#bidi.removeCallback(subscriptionId) | ||
|
Comment on lines
+66
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 7. Callback transport methods missing Domain.addCallback() and removeCallback() invoke methods that the real BiDi connection does not implement, so every generated event registration or removal fails with a TypeError. The connection is an EventEmitter exposing protocol events through on/off, while its subscription methods are named subscribe and unsubscribe. Agent Prompt
|
||
| } | ||
| } | ||
|
|
||
| module.exports = { Domain, event, DOMAIN_TOKEN } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you 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. | ||
|
|
||
| export interface EnumEntry<T extends string> { | ||
| readonly values: readonly T[] | ||
| includes(value: unknown): value is T | ||
| } | ||
|
|
||
| export function defineEnum<T extends string>(name: string, values: readonly T[]): EnumEntry<T> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you 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. | ||
|
|
||
| const { register } = require('./registry') | ||
|
|
||
| /** | ||
| * @param {string} name Schema type name, e.g. 'network.InterceptPhase'. | ||
| * @param {string[]} values | ||
| */ | ||
| function defineEnum(name, values) { | ||
|
Comment on lines
+20
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. defineenum jsdoc incomplete The exported defineEnum function's JSDoc has no free-text description and omits documentation for its non-void return value. Consumers are not told that the function returns the registered enum entry. Agent Prompt
|
||
| const allowed = new Set(values) | ||
| const entry = { kind: 'enum', values, includes: (value) => allowed.has(value) } | ||
| register(name, entry) | ||
| return entry | ||
| } | ||
|
|
||
| module.exports = { defineEnum } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you 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. | ||
|
|
||
| // Mirrors bidi_schema.json's type-ref vocabulary (see project_bidi_schema.mjs). | ||
| export interface TypeNode { | ||
| primitive?: string | ||
| const?: unknown | ||
| ref?: string | ||
| enum?: string[] | ||
| list?: TypeNode | ||
| map?: TypeNode | ||
| union?: TypeNode[] | ||
| nullable?: boolean | ||
| // Present on an inline union with a bare-scalar arm — the primitive(s) that | ||
| // arm accepts (see unionNode() in project_bidi_schema.mjs). Not consumed by | ||
| // validateValue yet; declared so embedding a real schema node type-checks. | ||
| scalar?: string | string[] | ||
| // The exact `const` literals a bare-scalar union arm admits (e.g. | ||
| // input.Origin's "viewport"/"pointer") — see unionNode(). Same status as | ||
| // `scalar`: not yet consumed by validateValue, declared for the embed. | ||
| scalarValues?: unknown[] | ||
| } | ||
|
|
||
| export interface FieldSpec { | ||
| name: string | ||
| wire: string | ||
| required: boolean | ||
| type: TypeNode | ||
| } | ||
|
|
||
| export interface RecordOptions { | ||
| extensible?: boolean | ||
| } | ||
|
|
||
| export declare class ValidationError extends Error {} | ||
|
|
||
| export interface RecordClass<T> { | ||
| new (data: T): Readonly<T> | ||
| fromWire(payload: unknown): Readonly<T> | ||
| } | ||
|
|
||
| export function defineRecord<T>(name: string, fields: FieldSpec[], options?: RecordOptions): RecordClass<T> | ||
|
|
||
| export function defineAlias(name: string, type: TypeNode): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. event jsdoc lacks description
📘 Rule violation✧ QualityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools