#127

Resource views and reconciliation dispatch inherited object methods

Opensdivyanshu90opened 18d ago

I found that LoadedResource.view and reconcileUsage dispatch inherited JavaScript methods when their names are absent from the resource doc.

A resource declaring no views or reconciliation hooks accepts constructor and returns the operation context as a successful result. toString returns a string, while hasOwnProperty throws an unwrapped TypeError. Hosts can receive a malformed usage reading or view result instead of the documented missing-operation error.

Reproduction

Save the following as repro-resource-methods.ts at the repository root and run:

deno run --allow-read --allow-env repro-resource-methods.ts

The transport is mocked; the reproduction makes no HTTP calls.

import { z } from "zod";
import { defineProvider, defineResource, presets, sealResourceUnit } from "@shared/core";
import { compileBundle } from "@shared/compiler";
import { directTransport, Engine, ENGINE_VERSION } from "@monid/connector-engine";

const bundle = await compileBundle([{
    provider: defineProvider({
        name: "demo",
        meta: { displayName: "Demo", summary: "Demo provider." },
        auth: { inject: presets.auth.header("x-demo-key") },
        request: { baseUrl: "https://api.demo.test" },
        usage: { model: { kind: "FREE" } },
    }),
    endpoints: [],
    resources: [{
        name: "widget",
        def: defineResource({
            slug: "widget",
            meta: { displayName: "Widget", summary: "Demo widget." },
            data: z.strictObject({ color: z.string() }),
            usage: {
                period: { unit: "MONTH", count: 1, anchor: "CREATION_TIME" },
                lines: { rent: { consumes: { credit: "default", amount: 1 } } },
            },
            lifecycle: {
                verify: async () => ({ active: true }),
                release: async () => ({ released: true }),
            },
        }),
    }],
}], {
    compilerVersion: "0.1.0", builtWithEngineVersion: ENGINE_VERSION,
    catalogVersion: "0.0.0-test", generatedAt: "1970-01-01T00:00:00.000Z",
    leafCategories: [],
});
const engine = new Engine({ transport: directTransport({
    params: () => Promise.resolve({ apiKey: "test" }),
    fetch: () => { throw new Error("No HTTP call expected"); },
}) });
const resource = await engine.loadResource(sealResourceUnit(bundle, "demo/widget"));
const owned = { resource: "demo/widget", externalId: "w-1", data: { color: "red" } };
console.log("view:", JSON.stringify(await resource.view("constructor", owned)));
console.log("meter:", JSON.stringify(await resource.reconcileUsage("constructor", owned, {
    startIso: "2026-01-01T00:00:00Z", endIso: "2026-02-01T00:00:00Z",
})));

Observed output:

view: {"resource":{"resource":"demo/widget","externalId":"w-1","data":{"color":"red"}}}
meter: {"resource":{"resource":"demo/widget","externalId":"w-1","data":{"color":"red"}},"window":{"startIso":"2026-01-01T00:00:00Z","endIso":"2026-02-01T00:00:00Z"}}

Expected: both operations reject with EngineErrorCode.NOT_ASYNC, just as an ordinary undeclared name does. In particular, reconciliation must not return a value lacking the UsageReading.consumes contract.

Cause and proposed fix

linkResourceFns initializes the dispatch maps as plain objects. In engine/engine.ts, LoadedResource.view and reconcileUsage read an entry by bracket access and only check whether it is truthy. Inherited functions pass this check and are invoked directly, outside the wrapped resource hook contracts.

Check Object.hasOwn before selecting either operation. Add regressions for ordinary missing names and inherited names on both methods, plus successful dispatch of declared views and meters.

This is separate from #121: it affects resource operation dispatch, without performing a JSONPath lookup.

Activity

0

No activity yet.