one
Version:
One is a React framework for web, iOS, and Android apps.
107 lines (96 loc) • 3.9 kB
text/typescript
// shared by the swift and kotlin source renderers: methods without a wire
// keep the historical one-liner byte-identical, while wired methods encode
// arguments and revive results around the same native call.
//
// the contract carries structs as an array, while the runtime codec walks a
// name-keyed record: normalize once here so every emitter (method wrappers,
// event payloads, view wires) embeds the record shape.
export interface WireStructField {
name: string
optional: boolean
wire?: unknown
}
export interface WireStruct {
name: string
fields: WireStructField[]
}
export function toNativeSourceStructs(
structs: WireStruct[]
): Record<string, { fields: { name: string; optional: boolean; wire?: unknown }[] }> {
const out: Record<string, { fields: { name: string; optional: boolean; wire?: unknown }[] }> = {}
for (const struct of structs ?? []) {
out[struct.name] = {
fields: struct.fields.map((field) =>
field.wire === undefined
? { name: field.name, optional: field.optional }
: { name: field.name, optional: field.optional, wire: field.wire }
),
}
}
return out
}
export interface WireMethodParam {
wire?: unknown
}
export interface WireMethod {
name: string
parameters: (WireMethodParam & { name: string })[]
resultWire?: unknown
}
export interface WireViewProp {
name: string
wire?: unknown
callback: { wire?: unknown }[] | null
}
export interface WireView {
props: WireViewProp[]
}
export function methodWired(method: {
parameters: { wire?: unknown }[]
resultWire?: unknown
}): boolean {
return method.parameters.some((param) => param.wire) || method.resultWire !== undefined
}
export function methodSubscribed(method: { subscription?: unknown }): boolean {
return method.subscription !== undefined
}
export function renderMethodCall(
target: string,
method: {
name: string
parameters: { name: string; wire?: unknown }[]
resultWire?: unknown
subscription?: unknown
},
hash: string
): string {
if (method.subscription) {
const payload = method.subscription as { payload: { wire?: unknown }[] }
const payloadWires = JSON.stringify(payload.payload.map((arg) => arg.wire ?? null))
return `${method.name}: (listener) => subscribeNativeSourceEvent(${JSON.stringify(target)}, ${JSON.stringify(method.name)}, listener, ${JSON.stringify(hash)}, ${payloadWires}, nativeSourceStructs)`
}
if (!methodWired(method)) {
return `${method.name}: (...args) => callNativeSource(${JSON.stringify(target)}, ${JSON.stringify(method.name)}, args, ${JSON.stringify(hash)})`
}
const paramWires = JSON.stringify(method.parameters.map((param) => param.wire ?? null))
const resultWire = JSON.stringify(method.resultWire ?? null)
return `${method.name}: (...args) => callNativeSource(${JSON.stringify(target)}, ${JSON.stringify(method.name)}, encodeNativeSourceArgs(args, ${paramWires}, nativeSourceStructs), ${JSON.stringify(hash)}).then((value) => decodeNativeSourceResult(value, ${resultWire}, nativeSourceStructs))`
}
// element wires for one imported view: prop wires encode props before the
// host stringifies them, callback wires revive callback arguments, and the
// structs table walks both. views without a wire render exactly as before.
export function renderViewWires(
view: WireView,
structs: WireStruct[]
): string {
const props: Record<string, unknown> = {}
const callbacks: Record<string, unknown> = {}
for (const prop of view.props) {
if (prop.wire) props[prop.name] = prop.wire
if (prop.callback && prop.callback.some((arg) => arg.wire)) {
callbacks[prop.name] = prop.callback.map((arg) => arg.wire ?? null)
}
}
if (Object.keys(props).length === 0 && Object.keys(callbacks).length === 0) return ''
return `, wires: ${JSON.stringify({ props, callbacks, structs: toNativeSourceStructs(structs) })}`
}