refactor(drivers): one per-method policy validator and resolver; drop @Driver

Four policy blocks (rateLimit, concurrent, requireSubscription,
requireReputation) share the { default, methods } shape but each had its
own validator and resolver. They become validatePerMethod(value, label,
policy) and resolvePerMethod(cfg, method); error messages are unchanged.

The @Driver class decorator and the prototype keys it wrote had no users
outside its own tests; drivers declare their fields, and resolveDriverMeta
reads only those. DriverController takes the driver name from the meta it
already resolved instead of probing the prototype.
resolveDriverMethodRateLimit/Concurrent stay as deprecated aliases for the
email extension.
This commit is contained in:
Daniel Salazar committed 2026-10-10 19:37:12 -04:00
1 parent 0d4dc29d0a
commit d0100a5c66
7 files changed
+249 -748

No files matched your search

@@ -41,10 +41,7 @@ import {
isDriverStreamResult,
resolveCallableMethods,
resolveDriverMeta,
resolveDriverMethodConcurrent,
resolveDriverMethodRateLimit,
resolveDriverMethodRequireReputation,
resolveDriverMethodRequireSubscription,
resolvePerMethod,
} from '../../drivers/meta.js';
import { assertActorHasSubscription } from '../../services/metering/enforcement.js';
import type { PermissionService } from '../../services/permission/PermissionService.js';
@@ -301,16 +298,10 @@ export class DriverController extends PuterController {
}
const fn = driver[method];
// Resolve the concrete driver name for permission keys, falling
// back through prototype metadata → instance field → requested name.
const resolvedDriverName =
(driver as Record<string, unknown>).driverName ??
(Object.getPrototypeOf(driver) as Record<string, unknown>)
.__driverName ??
requestedDriver ??
'unknown';
const driverMeta = this.#meta.get(driver);
// The concrete driver name, not an alias, keys the permission check.
const resolvedDriverName =
driverMeta?.driverName ?? requestedDriver ?? 'unknown';
// Drivers flagged `noUserSession` refuse the bare
// account-session ("root") token: callers must present an app or
@@ -355,13 +346,13 @@ export class DriverController extends PuterController {
}
// Methods that ask for a trusted-enough account. Declared per-driver
// (`@Driver({ requireReputation })`) for the same reason the
// (`requireReputation`) for the same reason the
// subscription block is. Checked ahead of the plan and rate-limit
// gates, matching the route chain: whether this account should be
// reaching the method at all is settled before what it pays for or how
// often it may ask. Inert unless the running config gives the named
// tier a score.
const reputationRequirement = resolveDriverMethodRequireReputation(
const reputationRequirement = resolvePerMethod(
driverMeta?.requireReputation,
method,
);
@@ -373,13 +364,13 @@ export class DriverController extends PuterController {
);
}
// Subscriber-only methods. Declared per-driver
// (`@Driver({ requireSubscription })`) because `/drivers/call` is a
// single route and a route option would apply to every driver at once.
// Subscriber-only methods. Declared per-driver (`requireSubscription`)
// because `/drivers/call` is a single route and a route option would
// apply to every driver at once.
// Checked before the rate limit — the same order the route chain uses
// — so a caller whose plan never included the method is told that
// rather than spending a bucket on it.
const subscriptionRequirement = resolveDriverMethodRequireSubscription(
const subscriptionRequirement = resolvePerMethod(
driverMeta?.requireSubscription,
method,
);
@@ -398,16 +389,12 @@ export class DriverController extends PuterController {
}
// Per-method rate-limit and concurrent specs both live on the
// driver's resolved meta (set by `@Driver({ rateLimit, concurrent })`
// or imperative fields). Rate-limit is single-shot; concurrent
// driver's resolved meta. Rate-limit is single-shot; concurrent
// acquires a slot that must be released when the response is done
// — we hook `res.finish` / `res.close` for that so streamed
// responses hold their slot until the stream drains, and aborted
// requests still give the slot back.
const rateLimitSpec = resolveDriverMethodRateLimit(
driverMeta?.rateLimit,
method,
);
const rateLimitSpec = resolvePerMethod(driverMeta?.rateLimit, method);
if (
!(await checkDriverRateLimit(req, ifaceName, method, rateLimitSpec))
) {
@@ -418,10 +405,7 @@ export class DriverController extends PuterController {
});
}
const concurrentSpec = resolveDriverMethodConcurrent(
driverMeta?.concurrent,
method,
);
const concurrentSpec = resolvePerMethod(driverMeta?.concurrent, method);
// Only acquire (and attach release listeners) when the driver
// actually declared a concurrency cap. Skipping in the unbounded
// case keeps the hot path free of needless event-listener churn
-127
View File
@@ -1,127 +0,0 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
DRIVER_CONCURRENT_KEY,
DRIVER_DEFAULT_KEY,
DRIVER_INTERFACE_KEY,
DRIVER_NAME_KEY,
DRIVER_NO_USER_SESSION_KEY,
DRIVER_RATE_LIMIT_KEY,
DRIVER_REQUIRE_REPUTATION_KEY,
DRIVER_REQUIRE_SUBSCRIPTION_KEY,
validateDriverConcurrent,
validateDriverRateLimit,
validateDriverRequireReputation,
validateDriverRequireSubscription,
type DriverConcurrentConfig,
type DriverRateLimitConfig,
type DriverRequireReputationConfig,
type DriverRequireSubscriptionConfig,
} from './meta';
/**
* Options for the `@Driver` class decorator. Each policy takes `{ default?,
* methods? }`; a method covered by neither is ungated (rate limits fall through
* to the global driver default). See `DriverMeta` for semantics.
*/
export interface DriverOptions {
/** Unique within the interface. Defaults to the class name. */
name?: string;
/** The default driver for its interface. */
default?: boolean;
rateLimit?: DriverRateLimitConfig;
concurrent?: DriverConcurrentConfig;
/** Reject bare account-session tokens on `/drivers/call`. */
noUserSession?: boolean;
/**
* `true` accepts any non-free plan; an array of `SubscriptionPolicy.id`s
* only those.
*/
requireSubscription?: DriverRequireSubscriptionConfig;
/** Reputation tier names; thresholds come from `reputationGate.tiers`. */
requireReputation?: DriverRequireReputationConfig;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyCtor = new (...args: any[]) => any;
/**
* Class decorator that marks a driver implementation and records its interface
*
* - Name on the prototype.
*
* Equivalent imperative approach (no decorator needed):
*
* ```ts
* class MyDriver extends PuterDriver {
* readonly driverInterface = 'puter-chat-completion';
* readonly driverName = 'my-impl';
* readonly isDefault = true;
* }
* ```
*
* Usage:
*
* ```ts
* @Driver('puter-chat-completion', { name: 'openai-completion', default: true })
* class OpenAIChatDriver extends PuterDriver {
* async complete(args) { ... }
* }
* ```
*/
export function Driver(interfaceName: string, opts: DriverOptions = {}) {
// Validate eagerly at decoration time so a malformed rateLimit /
// concurrent block surfaces during module load — not when the first
// request hits the route and the controller resolves driver meta.
const label = `@Driver('${interfaceName}'${opts.name ? `, name='${opts.name}'` : ''})`;
const rateLimit =
opts.rateLimit !== undefined
? validateDriverRateLimit(opts.rateLimit, label)
: undefined;
const concurrent =
opts.concurrent !== undefined
? validateDriverConcurrent(opts.concurrent, label)
: undefined;
const requireSubscription =
opts.requireSubscription !== undefined
? validateDriverRequireSubscription(opts.requireSubscription, label)
: undefined;
const requireReputation =
opts.requireReputation !== undefined
? validateDriverRequireReputation(opts.requireReputation, label)
: undefined;
return <T extends AnyCtor>(
value: T,
_context: ClassDecoratorContext<T>,
): void => {
const proto = value.prototype as Record<string, unknown>;
proto[DRIVER_INTERFACE_KEY] = interfaceName;
proto[DRIVER_NAME_KEY] = opts.name ?? value.name;
proto[DRIVER_DEFAULT_KEY] = opts.default ?? false;
if (rateLimit) proto[DRIVER_RATE_LIMIT_KEY] = rateLimit;
if (concurrent) proto[DRIVER_CONCURRENT_KEY] = concurrent;
if (requireSubscription)
proto[DRIVER_REQUIRE_SUBSCRIPTION_KEY] = requireSubscription;
if (requireReputation)
proto[DRIVER_REQUIRE_REPUTATION_KEY] = requireReputation;
if (opts.noUserSession) proto[DRIVER_NO_USER_SESSION_KEY] = true;
};
}
-1
View File
@@ -31,7 +31,6 @@ import { SubdomainDriver } from './subdomain/SubdomainDriver';
import type { IPuterDriverRegistry } from './types';
import { WorkerDriver } from './workers/WorkerDriver';
export { Driver } from './decorators';
export { resolveDriverMeta } from './meta';
export const puterDrivers = {
+119 -213
View File
@@ -18,29 +18,24 @@
*/
import { describe, expect, it } from 'vitest';
import { Driver } from './decorators.js';
import {
resolveDriverMeta,
resolveDriverMethodConcurrent,
resolveDriverMethodRateLimit,
resolveDriverMethodRequireReputation,
resolveDriverMethodRequireSubscription,
validateDriverConcurrent,
validateDriverRateLimit,
validateDriverRequireReputation,
validateDriverRequireSubscription,
resolvePerMethod,
validatePerMethod,
type DriverConcurrentConfig,
type DriverRateLimitConfig,
type DriverRequireReputationConfig,
type DriverRequireSubscriptionConfig,
} from './meta.js';
// ── validateDriverRateLimit ─────────────────────────────────────────
// ── validatePerMethod: rateLimit ─────────────────────────────────────────
describe('validateDriverRateLimit', () => {
describe('validatePerMethod: rateLimit', () => {
it('returns an empty config for null/undefined input', () => {
expect(validateDriverRateLimit(undefined, 't')).toEqual({});
expect(validateDriverRateLimit(null, 't')).toEqual({});
expect(validatePerMethod(undefined, 't', 'rateLimit')).toEqual({});
expect(validatePerMethod(null, 't', 'rateLimit')).toEqual({});
});
it('accepts a well-formed default + methods block', () => {
@@ -51,39 +46,45 @@ describe('validateDriverRateLimit', () => {
set: { limit: 100, window: 60_000, backend: 'redis' },
},
};
expect(validateDriverRateLimit(cfg, 't')).toBe(cfg);
expect(validatePerMethod(cfg, 't', 'rateLimit')).toBe(cfg);
});
it('rejects a non-object config', () => {
expect(() => validateDriverRateLimit(42, 't')).toThrow(
expect(() => validatePerMethod(42, 't', 'rateLimit')).toThrow(
/rateLimit must be an object/,
);
expect(() => validateDriverRateLimit([], 't')).toThrow(
expect(() => validatePerMethod([], 't', 'rateLimit')).toThrow(
/rateLimit must be an object/,
);
});
it('rejects non-positive / non-numeric limit and window', () => {
expect(() =>
validateDriverRateLimit({ default: { limit: 0, window: 1 } }, 't'),
validatePerMethod(
{ default: { limit: 0, window: 1 } },
't',
'rateLimit',
),
).toThrow(/limit: expected a positive number/);
expect(() =>
validateDriverRateLimit(
validatePerMethod(
{ default: { limit: 1, window: -10 } },
't',
'rateLimit',
),
).toThrow(/window: expected a positive number/);
expect(() =>
validateDriverRateLimit(
validatePerMethod(
{ default: { limit: 'x', window: 60_000 } },
't',
'rateLimit',
),
).toThrow(/limit: expected a positive number/);
});
it('rejects unknown backend names', () => {
expect(() =>
validateDriverRateLimit(
validatePerMethod(
{
default: {
limit: 1,
@@ -92,13 +93,14 @@ describe('validateDriverRateLimit', () => {
},
},
't',
'rateLimit',
),
).toThrow(/backend: expected one of/);
});
it('walks the methods map and labels the failing entry', () => {
expect(() =>
validateDriverRateLimit(
validatePerMethod(
{
methods: {
goodOne: { limit: 5, window: 60_000 },
@@ -106,13 +108,14 @@ describe('validateDriverRateLimit', () => {
},
},
'drv',
'rateLimit',
),
).toThrow(/drv\.rateLimit\.methods\.badOne\.window/);
});
it('rejects a non-object methods bag', () => {
expect(() =>
validateDriverRateLimit({ methods: [] as unknown }, 't'),
validatePerMethod({ methods: [] as unknown }, 't', 'rateLimit'),
).toThrow(/methods must be an object/);
});
@@ -126,13 +129,13 @@ describe('validateDriverRateLimit', () => {
},
},
};
expect(validateDriverRateLimit(cfg, 't')).toBe(cfg);
expect(validatePerMethod(cfg, 't', 'rateLimit')).toBe(cfg);
});
it('rejects malformed bySubscription entries on a rate-limit spec', () => {
// Symmetry with the concurrent validator — bad numbers fail loud.
expect(() =>
validateDriverRateLimit(
validatePerMethod(
{
default: {
limit: 1,
@@ -141,14 +144,15 @@ describe('validateDriverRateLimit', () => {
},
},
'drv',
'rateLimit',
),
).toThrow(/drv\.rateLimit\.default\.bySubscription\.user_free/);
});
});
// ── resolveDriverMethodRateLimit ────────────────────────────────────
// ── resolvePerMethod: rateLimit ────────────────────────────────────
describe('resolveDriverMethodRateLimit', () => {
describe('resolvePerMethod: rateLimit', () => {
const cfg: DriverRateLimitConfig = {
default: { limit: 100, window: 60_000 },
methods: {
@@ -157,7 +161,7 @@ describe('resolveDriverMethodRateLimit', () => {
};
it('returns the per-method spec when one is declared', () => {
expect(resolveDriverMethodRateLimit(cfg, 'get')).toEqual({
expect(resolvePerMethod(cfg, 'get')).toEqual({
limit: 1000,
window: 60_000,
backend: 'memory',
@@ -165,19 +169,19 @@ describe('resolveDriverMethodRateLimit', () => {
});
it('falls back to the default spec when no method override exists', () => {
expect(resolveDriverMethodRateLimit(cfg, 'set')).toEqual({
expect(resolvePerMethod(cfg, 'set')).toEqual({
limit: 100,
window: 60_000,
});
});
it('returns undefined when the driver declared no rate-limit at all', () => {
expect(resolveDriverMethodRateLimit(undefined, 'get')).toBeUndefined();
expect(resolvePerMethod(undefined, 'get')).toBeUndefined();
});
it('returns undefined when neither default nor a matching method is declared', () => {
expect(
resolveDriverMethodRateLimit(
resolvePerMethod(
{ methods: { other: { limit: 1, window: 1 } } },
'get',
),
@@ -185,53 +189,10 @@ describe('resolveDriverMethodRateLimit', () => {
});
});
// ── @Driver decorator: rateLimit propagation ────────────────────────
// ── resolveDriverMeta: rateLimit ────────────────────────────────────
describe('@Driver — rateLimit option', () => {
it('stamps a validated rateLimit block onto the prototype, surfacing via resolveDriverMeta', () => {
@Driver('test-iface', {
name: 'test-impl',
rateLimit: {
default: { limit: 50, window: 60_000 },
methods: {
chat: { limit: 10, window: 60_000, backend: 'redis' },
},
},
})
class FakeDriver {}
const inst = new FakeDriver();
const meta = resolveDriverMeta(
inst as unknown as Record<string, unknown> & {
onServerStart?: () => void;
onServerPrepareShutdown?: () => void;
onServerShutdown?: () => void;
},
);
expect(meta).not.toBeNull();
expect(meta?.rateLimit).toEqual({
default: { limit: 50, window: 60_000 },
methods: {
chat: { limit: 10, window: 60_000, backend: 'redis' },
},
});
});
it('throws at decoration time on a malformed rateLimit block', () => {
// The whole point of eager validation: bad config takes the
// module down at boot, not at the first request.
expect(() => {
@Driver('test-iface', {
name: 'broken',
rateLimit: { default: { limit: -1, window: 1_000 } },
})
class BrokenDriver {}
void BrokenDriver;
}).toThrow(/limit: expected a positive number/);
});
it('falls back to imperative `rateLimit` field when no decorator metadata is set', () => {
// Imperative drivers (no decorator) declare the field directly.
describe('resolveDriverMeta — rateLimit', () => {
it('reads the `rateLimit` field', () => {
class Imperative {
readonly driverInterface = 'imp-iface';
readonly driverName = 'imp';
@@ -253,7 +214,7 @@ describe('@Driver — rateLimit option', () => {
});
});
it('validates the imperative `rateLimit` field on first read (loud failure)', () => {
it('validates the `rateLimit` field on first read (loud failure)', () => {
class BadImperative {
readonly driverInterface = 'imp-iface';
readonly driverName = 'imp-bad';
@@ -274,12 +235,12 @@ describe('@Driver — rateLimit option', () => {
});
});
// ── validateDriverConcurrent ────────────────────────────────────────
// ── validatePerMethod: concurrent ────────────────────────────────────────
describe('validateDriverConcurrent', () => {
describe('validatePerMethod: concurrent', () => {
it('returns an empty config for null/undefined input', () => {
expect(validateDriverConcurrent(undefined, 't')).toEqual({});
expect(validateDriverConcurrent(null, 't')).toEqual({});
expect(validatePerMethod(undefined, 't', 'concurrent')).toEqual({});
expect(validatePerMethod(null, 't', 'concurrent')).toEqual({});
});
it('accepts a well-formed default + methods block with bySubscription', () => {
@@ -293,30 +254,31 @@ describe('validateDriverConcurrent', () => {
},
},
};
expect(validateDriverConcurrent(cfg, 't')).toBe(cfg);
expect(validatePerMethod(cfg, 't', 'concurrent')).toBe(cfg);
});
it('rejects non-positive / non-numeric limit', () => {
expect(() =>
validateDriverConcurrent({ default: { limit: 0 } }, 't'),
validatePerMethod({ default: { limit: 0 } }, 't', 'concurrent'),
).toThrow(/limit: expected a positive number/);
expect(() =>
validateDriverConcurrent({ default: { limit: 'x' } }, 't'),
validatePerMethod({ default: { limit: 'x' } }, 't', 'concurrent'),
).toThrow(/limit: expected a positive number/);
});
it('rejects unknown backend names', () => {
expect(() =>
validateDriverConcurrent(
validatePerMethod(
{ default: { limit: 1, backend: 'sqlite' } },
't',
'concurrent',
),
).toThrow(/backend: expected one of/);
});
it('rejects malformed bySubscription entries with a labelled path', () => {
expect(() =>
validateDriverConcurrent(
validatePerMethod(
{
default: {
limit: 5,
@@ -324,13 +286,14 @@ describe('validateDriverConcurrent', () => {
},
},
'drv',
'concurrent',
),
).toThrow(/drv\.concurrent\.default\.bySubscription\.user_free/);
});
it('walks the methods map and labels the failing entry', () => {
expect(() =>
validateDriverConcurrent(
validatePerMethod(
{
methods: {
goodOne: { limit: 5 },
@@ -338,14 +301,15 @@ describe('validateDriverConcurrent', () => {
},
},
'drv',
'concurrent',
),
).toThrow(/drv\.concurrent\.methods\.badOne\.backend/);
});
});
// ── resolveDriverMethodConcurrent ───────────────────────────────────
// ── resolvePerMethod: concurrent ───────────────────────────────────
describe('resolveDriverMethodConcurrent', () => {
describe('resolvePerMethod: concurrent', () => {
const cfg: DriverConcurrentConfig = {
default: { limit: 3 },
methods: {
@@ -354,77 +318,28 @@ describe('resolveDriverMethodConcurrent', () => {
};
it('returns the per-method spec when one is declared', () => {
expect(resolveDriverMethodConcurrent(cfg, 'heavy')).toEqual({
expect(resolvePerMethod(cfg, 'heavy')).toEqual({
limit: 1,
backend: 'redis',
});
});
it('falls back to default for methods not in the map', () => {
expect(resolveDriverMethodConcurrent(cfg, 'light')).toEqual({
expect(resolvePerMethod(cfg, 'light')).toEqual({
limit: 3,
});
});
it('returns undefined when nothing is declared', () => {
expect(
resolveDriverMethodConcurrent(undefined, 'anything'),
).toBeUndefined();
expect(resolveDriverMethodConcurrent({}, 'anything')).toBeUndefined();
expect(resolvePerMethod(undefined, 'anything')).toBeUndefined();
expect(resolvePerMethod({}, 'anything')).toBeUndefined();
});
});
// ── @Driver — concurrent option ─────────────────────────────────────
// ── resolveDriverMeta: concurrent ───────────────────────────────────
describe('@Driver — concurrent option', () => {
it('stamps a validated concurrent block onto the prototype', () => {
@Driver('test-iface', {
name: 'cdec',
concurrent: {
default: { limit: 4 },
methods: {
chat: {
limit: 5,
bySubscription: { user_free: 1 },
backend: 'redis',
},
},
},
})
class FakeDriver {}
const inst = new FakeDriver();
const meta = resolveDriverMeta(
inst as unknown as Record<string, unknown> & {
onServerStart?: () => void;
onServerPrepareShutdown?: () => void;
onServerShutdown?: () => void;
},
);
expect(meta?.concurrent).toEqual({
default: { limit: 4 },
methods: {
chat: {
limit: 5,
bySubscription: { user_free: 1 },
backend: 'redis',
},
},
});
});
it('throws at decoration time on a malformed concurrent block', () => {
expect(() => {
@Driver('test-iface', {
name: 'bad-concurrent',
concurrent: { default: { limit: -1 } },
})
class BrokenDriver {}
void BrokenDriver;
}).toThrow(/limit: expected a positive number/);
});
it('falls back to imperative `concurrent` field when decorator metadata is absent', () => {
describe('resolveDriverMeta — concurrent', () => {
it('reads the `concurrent` field', () => {
class Imperative {
readonly driverInterface = 'imp-iface';
readonly driverName = 'imp-c';
@@ -445,10 +360,12 @@ describe('@Driver — concurrent option', () => {
// ── requireSubscription ─────────────────────────────────────────────
describe('validateDriverRequireSubscription', () => {
describe('validatePerMethod: requireSubscription', () => {
it('returns an empty config for null/undefined input', () => {
expect(validateDriverRequireSubscription(undefined, 't')).toEqual({});
expect(validateDriverRequireSubscription(null, 't')).toEqual({});
expect(
validatePerMethod(undefined, 't', 'requireSubscription'),
).toEqual({});
expect(validatePerMethod(null, 't', 'requireSubscription')).toEqual({});
});
it('accepts booleans and id allowlists', () => {
@@ -456,70 +373,58 @@ describe('validateDriverRequireSubscription', () => {
default: false,
methods: { generate: true, generateLong: ['business', 'pro'] },
};
expect(validateDriverRequireSubscription(cfg, 't')).toBe(cfg);
expect(validatePerMethod(cfg, 't', 'requireSubscription')).toBe(cfg);
});
it('rejects a non-object config', () => {
expect(() => validateDriverRequireSubscription(42, 't')).toThrow(
expect(() => validatePerMethod(42, 't', 'requireSubscription')).toThrow(
/requireSubscription must be an object/,
);
expect(() => validateDriverRequireSubscription([], 't')).toThrow(
expect(() => validatePerMethod([], 't', 'requireSubscription')).toThrow(
/requireSubscription must be an object/,
);
});
it('rejects requirements that name nothing', () => {
expect(() =>
validateDriverRequireSubscription({ default: [] }, 't'),
validatePerMethod({ default: [] }, 't', 'requireSubscription'),
).toThrow(/at least one subscription id/);
expect(() =>
validateDriverRequireSubscription({ methods: { a: [1] } }, 't'),
validatePerMethod(
{ methods: { a: [1] } },
't',
'requireSubscription',
),
).toThrow(/must be strings/);
expect(() =>
validateDriverRequireSubscription({ methods: { a: 'pro' } }, 't'),
validatePerMethod(
{ methods: { a: 'pro' } },
't',
'requireSubscription',
),
).toThrow(/expected true\/false or an array of ids/);
});
});
describe('resolveDriverMethodRequireSubscription', () => {
describe('resolvePerMethod: requireSubscription', () => {
const cfg: DriverRequireSubscriptionConfig = {
default: true,
methods: { list: false, generateLong: ['pro'] },
};
it('prefers a per-method entry over the default', () => {
expect(resolveDriverMethodRequireSubscription(cfg, 'list')).toBe(false);
expect(
resolveDriverMethodRequireSubscription(cfg, 'generateLong'),
).toEqual(['pro']);
expect(resolvePerMethod(cfg, 'list')).toBe(false);
expect(resolvePerMethod(cfg, 'generateLong')).toEqual(['pro']);
});
it('falls back to the default, and to undefined with no config', () => {
expect(resolveDriverMethodRequireSubscription(cfg, 'generate')).toBe(
true,
);
expect(
resolveDriverMethodRequireSubscription(undefined, 'generate'),
).toBeUndefined();
expect(resolveDriverMethodRequireSubscription({}, 'generate')).toBe(
undefined,
);
expect(resolvePerMethod(cfg, 'generate')).toBe(true);
expect(resolvePerMethod(undefined, 'generate')).toBeUndefined();
expect(resolvePerMethod({}, 'generate')).toBe(undefined);
});
});
describe('resolveDriverMeta — requireSubscription', () => {
it('reads the block off the decorator', () => {
@Driver('test-iface', {
name: 'decorated',
requireSubscription: { methods: { generate: true } },
})
class Decorated {}
expect(resolveDriverMeta(new Decorated() as never)).toMatchObject({
requireSubscription: { methods: { generate: true } },
});
});
it('validates an imperatively declared block', () => {
const driver = {
driverInterface: 'test-iface',
@@ -549,10 +454,12 @@ describe('resolveDriverMeta — requireSubscription', () => {
// ── requireReputation ───────────────────────────────────────────────
describe('validateDriverRequireReputation', () => {
describe('validatePerMethod: requireReputation', () => {
it('returns an empty config for null/undefined input', () => {
expect(validateDriverRequireReputation(undefined, 't')).toEqual({});
expect(validateDriverRequireReputation(null, 't')).toEqual({});
expect(validatePerMethod(undefined, 't', 'requireReputation')).toEqual(
{},
);
expect(validatePerMethod(null, 't', 'requireReputation')).toEqual({});
});
it('accepts tier names and explicit opt-outs', () => {
@@ -560,70 +467,54 @@ describe('validateDriverRequireReputation', () => {
default: false,
methods: { generate: 'standard' },
};
expect(validateDriverRequireReputation(cfg, 't')).toBe(cfg);
expect(validatePerMethod(cfg, 't', 'requireReputation')).toBe(cfg);
});
it('rejects a non-object config', () => {
expect(() => validateDriverRequireReputation(42, 't')).toThrow(
expect(() => validatePerMethod(42, 't', 'requireReputation')).toThrow(
/requireReputation must be an object/,
);
expect(() => validateDriverRequireReputation([], 't')).toThrow(
expect(() => validatePerMethod([], 't', 'requireReputation')).toThrow(
/requireReputation must be an object/,
);
});
it('rejects requirements that name no tier', () => {
expect(() =>
validateDriverRequireReputation({ default: '' }, 't'),
validatePerMethod({ default: '' }, 't', 'requireReputation'),
).toThrow(/non-empty tier name/);
expect(() =>
validateDriverRequireReputation({ methods: { a: true } }, 't'),
validatePerMethod(
{ methods: { a: true } },
't',
'requireReputation',
),
).toThrow(/expected a tier name, or false/);
expect(() =>
validateDriverRequireReputation({ methods: { a: 60 } }, 't'),
validatePerMethod({ methods: { a: 60 } }, 't', 'requireReputation'),
).toThrow(/expected a tier name, or false/);
});
});
describe('resolveDriverMethodRequireReputation', () => {
describe('resolvePerMethod: requireReputation', () => {
const cfg: DriverRequireReputationConfig = {
default: 'standard',
methods: { list: false, generateLong: 'trusted' },
};
it('prefers a per-method entry over the default', () => {
expect(resolveDriverMethodRequireReputation(cfg, 'list')).toBe(false);
expect(resolveDriverMethodRequireReputation(cfg, 'generateLong')).toBe(
'trusted',
);
expect(resolvePerMethod(cfg, 'list')).toBe(false);
expect(resolvePerMethod(cfg, 'generateLong')).toBe('trusted');
});
it('falls back to the default, and to undefined with no config', () => {
expect(resolveDriverMethodRequireReputation(cfg, 'generate')).toBe(
'standard',
);
expect(
resolveDriverMethodRequireReputation(undefined, 'generate'),
).toBeUndefined();
expect(
resolveDriverMethodRequireReputation({}, 'generate'),
).toBeUndefined();
expect(resolvePerMethod(cfg, 'generate')).toBe('standard');
expect(resolvePerMethod(undefined, 'generate')).toBeUndefined();
expect(resolvePerMethod({}, 'generate')).toBeUndefined();
});
});
describe('resolveDriverMeta — requireReputation', () => {
it('reads the block off the decorator', () => {
@Driver('test-iface', {
name: 'decorated-reputation',
requireReputation: { methods: { generate: 'standard' } },
})
class Decorated {}
expect(resolveDriverMeta(new Decorated() as never)).toMatchObject({
requireReputation: { methods: { generate: 'standard' } },
});
});
it('validates an imperatively declared block', () => {
const driver = {
driverInterface: 'test-iface',
@@ -650,3 +541,18 @@ describe('resolveDriverMeta — requireReputation', () => {
).toBeUndefined();
});
});
describe('deprecated per-method resolver names', () => {
it('still resolve, for extensions that import them', () => {
const cfg = {
default: { limit: 1, window: 1_000 },
methods: { send: { limit: 2, window: 1_000 } },
};
expect(resolveDriverMethodRateLimit(cfg, 'send')).toBe(
cfg.methods.send,
);
expect(
resolveDriverMethodConcurrent({ default: { limit: 3 } }, 'x'),
).toEqual({ limit: 3 });
});
});
+100 -342
View File
@@ -54,27 +54,18 @@ export function isDriverStreamResult(v: unknown): v is DriverStreamResult {
);
}
// -- Driver metadata keys --------------------------------------------
// -- Per-method driver policies ----------------------------------------
//
// Metadata keys stored on driver prototypes by the `@Driver` decorator.
// Imperative drivers set these as instance properties instead.
// Every policy a driver declares has the same shape: a `default` entry and
// per-method overrides. `DriverController` resolves the entry for the called
// method, so methods can differ in limits, plan and reputation floor.
export const DRIVER_INTERFACE_KEY = '__driverInterface' as const;
export const DRIVER_NAME_KEY = '__driverName' as const;
export const DRIVER_DEFAULT_KEY = '__driverDefault' as const;
export const DRIVER_ALIASES_KEY = '__driverAliases' as const;
export const DRIVER_RATE_LIMIT_KEY = '__driverRateLimit' as const;
export const DRIVER_CONCURRENT_KEY = '__driverConcurrent' as const;
export const DRIVER_NO_USER_SESSION_KEY = '__driverNoUserSession' as const;
export const DRIVER_REQUIRE_SUBSCRIPTION_KEY =
'__driverRequireSubscription' as const;
export const DRIVER_REQUIRE_REPUTATION_KEY =
'__driverRequireReputation' as const;
// -- Driver rate-limit config ----------------------------------------
//
// Declared per driver; `DriverController` resolves the spec for the requested
// method, so methods can differ in limits and storage backend.
/** A driver policy: `default` for any method not listed in `methods`. */
export interface PerMethodConfig<T> {
default?: T;
/** Keys are driver method names. */
methods?: Record<string, T>;
}
export const RATE_LIMIT_BACKEND_NAMES = ['memory', 'redis', 'kv'] as const;
export type RateLimitBackend = (typeof RATE_LIMIT_BACKEND_NAMES)[number];
@@ -90,42 +81,36 @@ export interface DriverRateLimitSpec {
backend?: RateLimitBackend;
}
export interface DriverRateLimitConfig {
/** Applied to any method not listed in `methods`. */
default?: DriverRateLimitSpec;
/** Per-method overrides. Keys are driver method names. */
methods?: Record<string, DriverRateLimitSpec>;
export interface DriverConcurrentSpec {
/** Maximum simultaneous in-flight requests. */
limit: number;
/** Per-`SubscriptionPolicy.id` overrides for `limit`. */
bySubscription?: Record<string, number>;
/** `memory` is per-process; use `redis` on multi-node deployments. */
backend?: RateLimitBackend;
}
/** Validate a driver's `rateLimit` block; throws at boot on a bad shape. */
export function validateDriverRateLimit(
export type DriverRateLimitConfig = PerMethodConfig<DriverRateLimitSpec>;
export type DriverConcurrentConfig = PerMethodConfig<DriverConcurrentSpec>;
/**
* Per-method counterpart of `RouteOptions.requireSubscription`: `/drivers/call`
* is one shared route, so a route option would apply to every driver at once.
* `undefined` for a method means it is open to every plan.
*/
export type DriverRequireSubscriptionConfig =
PerMethodConfig<SubscriptionRequirement>;
/**
* Per-method counterpart of `RouteOptions.requireReputation`. A tier is only a
* name; the score it takes is deployment config.
*/
export type DriverRequireReputationConfig =
PerMethodConfig<ReputationRequirement>;
function validateLimitSpec(
value: unknown,
label: string,
): DriverRateLimitConfig {
if (value == null) return {};
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label}: rateLimit must be an object`);
}
const cfg = value as Record<string, unknown>;
if (cfg.default !== undefined) {
validateSpec(cfg.default, `${label}.rateLimit.default`);
}
if (cfg.methods !== undefined) {
if (
typeof cfg.methods !== 'object' ||
cfg.methods === null ||
Array.isArray(cfg.methods)
) {
throw new Error(`${label}.rateLimit.methods must be an object`);
}
for (const [name, spec] of Object.entries(cfg.methods)) {
validateSpec(spec, `${label}.rateLimit.methods.${name}`);
}
}
return cfg as DriverRateLimitConfig;
}
function validateSpec(value: unknown, label: string): void {
{ windowed }: { windowed: boolean },
): void {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`${label}: expected an object`);
}
@@ -138,9 +123,10 @@ function validateSpec(value: unknown, label: string): void {
throw new Error(`${label}.limit: expected a positive number`);
}
if (
typeof spec.window !== 'number' ||
!Number.isFinite(spec.window) ||
spec.window <= 0
windowed &&
(typeof spec.window !== 'number' ||
!Number.isFinite(spec.window) ||
spec.window <= 0)
) {
throw new Error(`${label}.window: expected a positive number (ms)`);
}
@@ -172,52 +158,43 @@ function validateBySubscription(value: unknown, label: string): void {
}
}
/**
* Resolve the spec that applies to a given method on a driver. Per-method entry
* wins over `default`; returns `undefined` if neither is set so the caller can
* apply its own fallback.
*/
export function resolveDriverMethodRateLimit(
cfg: DriverRateLimitConfig | undefined,
method: string,
): DriverRateLimitSpec | undefined {
if (!cfg) return undefined;
return cfg.methods?.[method] ?? cfg.default;
}
/** The entry validator for each policy a driver may declare. */
const POLICY_ENTRY_VALIDATORS = {
rateLimit: (value: unknown, label: string) =>
validateLimitSpec(value, label, { windowed: true }),
concurrent: (value: unknown, label: string) =>
validateLimitSpec(value, label, { windowed: false }),
requireSubscription: validateSubscriptionRequirement,
requireReputation: validateReputationRequirement,
};
// -- Driver concurrent-limit config ----------------------------------
export type DriverPolicyName = keyof typeof POLICY_ENTRY_VALIDATORS;
export interface DriverConcurrentSpec {
/** Maximum simultaneous in-flight requests. */
limit: number;
/** Per-`SubscriptionPolicy.id` overrides for `limit`. */
bySubscription?: Record<string, number>;
/** `memory` is per-process; use `redis` on multi-node deployments. */
backend?: RateLimitBackend;
}
export interface DriverConcurrentConfig {
/** Applied to any method not listed in `methods`. */
default?: DriverConcurrentSpec;
/** Per-method overrides. Keys are driver method names. */
methods?: Record<string, DriverConcurrentSpec>;
interface DriverPolicyConfigs {
rateLimit: DriverRateLimitConfig;
concurrent: DriverConcurrentConfig;
requireSubscription: DriverRequireSubscriptionConfig;
requireReputation: DriverRequireReputationConfig;
}
/**
* Validate a `concurrent` block. Mirrors `validateDriverRateLimit` — throws
* with a labelled path so a malformed entry surfaces at boot.
* Validate a driver's `policy` block. Throws with a labelled path at
* registration, so a malformed entry surfaces at boot rather than on the first
* call.
*/
export function validateDriverConcurrent(
export function validatePerMethod<K extends DriverPolicyName>(
value: unknown,
label: string,
): DriverConcurrentConfig {
policy: K,
): DriverPolicyConfigs[K] {
if (value == null) return {};
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label}: concurrent must be an object`);
throw new Error(`${label}: ${policy} must be an object`);
}
const validateEntry = POLICY_ENTRY_VALIDATORS[policy];
const cfg = value as Record<string, unknown>;
if (cfg.default !== undefined) {
validateConcurrentSpec(cfg.default, `${label}.concurrent.default`);
validateEntry(cfg.default, `${label}.${policy}.default`);
}
if (cfg.methods !== undefined) {
if (
@@ -225,193 +202,38 @@ export function validateDriverConcurrent(
cfg.methods === null ||
Array.isArray(cfg.methods)
) {
throw new Error(`${label}.concurrent.methods must be an object`);
throw new Error(`${label}.${policy}.methods must be an object`);
}
for (const [name, spec] of Object.entries(cfg.methods)) {
validateConcurrentSpec(spec, `${label}.concurrent.methods.${name}`);
for (const [name, entry] of Object.entries(cfg.methods)) {
validateEntry(entry, `${label}.${policy}.methods.${name}`);
}
}
return cfg as DriverConcurrentConfig;
}
function validateConcurrentSpec(value: unknown, label: string): void {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`${label}: expected an object`);
}
const spec = value as Record<string, unknown>;
if (
typeof spec.limit !== 'number' ||
!Number.isFinite(spec.limit) ||
spec.limit <= 0
) {
throw new Error(`${label}.limit: expected a positive number`);
}
if (spec.backend !== undefined) {
if (
typeof spec.backend !== 'string' ||
!RATE_LIMIT_BACKEND_NAMES.includes(spec.backend as RateLimitBackend)
) {
throw new Error(
`${label}.backend: expected one of ${RATE_LIMIT_BACKEND_NAMES.join(', ')}`,
);
}
}
if (spec.bySubscription !== undefined) {
validateBySubscription(spec.bySubscription, label);
}
return cfg as DriverPolicyConfigs[K];
}
/**
* Resolve the concurrent spec for a given method on a driver. Same precedence
* as `resolveDriverMethodRateLimit`: per-method wins over `default`;
* `undefined` means no concurrency cap is declared, and the caller should leave
* the method unbounded.
* The entry that applies to `method`: its own, else `default`, else `undefined`
* (the caller decides what an undeclared policy means).
*/
export function resolveDriverMethodConcurrent(
cfg: DriverConcurrentConfig | undefined,
export function resolvePerMethod<T>(
cfg: PerMethodConfig<T> | undefined,
method: string,
): DriverConcurrentSpec | undefined {
): T | undefined {
if (!cfg) return undefined;
return cfg.methods?.[method] ?? cfg.default;
}
// -- Driver subscription requirement ---------------------------------
//
// Per-method counterpart of `RouteOptions.requireSubscription`. It lives on the
// driver for the same reason `noUserSession` does: `/drivers/call` is one
// shared route, so a requirement declared in route options would apply to every
// driver at once.
export interface DriverRequireSubscriptionConfig {
/** Applied to any method not listed in `methods`. */
default?: SubscriptionRequirement;
/** Per-method overrides. Keys are driver method names. */
methods?: Record<string, SubscriptionRequirement>;
}
/** @deprecated Use `resolvePerMethod`; kept for extensions still on it. */
export const resolveDriverMethodRateLimit =
resolvePerMethod<DriverRateLimitSpec>;
/** @deprecated Use `resolvePerMethod`; kept for extensions still on it. */
export const resolveDriverMethodConcurrent =
resolvePerMethod<DriverConcurrentSpec>;
/**
* Validate a `requireSubscription` block. Same loud-at-boot contract as the
* rate-limit and concurrent validators.
*/
export function validateDriverRequireSubscription(
value: unknown,
label: string,
): DriverRequireSubscriptionConfig {
if (value == null) return {};
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label}: requireSubscription must be an object`);
}
const cfg = value as Record<string, unknown>;
if (cfg.default !== undefined) {
validateSubscriptionRequirement(
cfg.default,
`${label}.requireSubscription.default`,
);
}
if (cfg.methods !== undefined) {
if (
typeof cfg.methods !== 'object' ||
cfg.methods === null ||
Array.isArray(cfg.methods)
) {
throw new Error(
`${label}.requireSubscription.methods must be an object`,
);
}
for (const [name, requirement] of Object.entries(cfg.methods)) {
validateSubscriptionRequirement(
requirement,
`${label}.requireSubscription.methods.${name}`,
);
}
}
return cfg as DriverRequireSubscriptionConfig;
}
/**
* Resolve the subscription requirement for a method. Same precedence as the
* other per-method resolvers: an entry in `methods` wins over `default`, and
* `undefined` means the method is open to every plan.
*/
export function resolveDriverMethodRequireSubscription(
cfg: DriverRequireSubscriptionConfig | undefined,
method: string,
): SubscriptionRequirement | undefined {
if (!cfg) return undefined;
return cfg.methods?.[method] ?? cfg.default;
}
// -- Driver reputation requirement -----------------------------------
//
// Per-method counterpart of `RouteOptions.requireReputation`, on the driver
// for the same reason the subscription block is: `/drivers/call` is one shared
// route, so a requirement in route options would apply to every driver at
// once. The tier named here is only a name — the score it takes is deployment
// config, so a driver never carries the number it is worth.
export interface DriverRequireReputationConfig {
/** Applied to any method not listed in `methods`. */
default?: ReputationRequirement;
/** Per-method overrides. Keys are driver method names. */
methods?: Record<string, ReputationRequirement>;
}
/**
* Validate a `requireReputation` block. Same loud-at-boot contract as the
* rate-limit, concurrent, and subscription validators.
*/
export function validateDriverRequireReputation(
value: unknown,
label: string,
): DriverRequireReputationConfig {
if (value == null) return {};
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label}: requireReputation must be an object`);
}
const cfg = value as Record<string, unknown>;
if (cfg.default !== undefined) {
validateReputationRequirement(
cfg.default,
`${label}.requireReputation.default`,
);
}
if (cfg.methods !== undefined) {
if (
typeof cfg.methods !== 'object' ||
cfg.methods === null ||
Array.isArray(cfg.methods)
) {
throw new Error(
`${label}.requireReputation.methods must be an object`,
);
}
for (const [name, requirement] of Object.entries(cfg.methods)) {
validateReputationRequirement(
requirement,
`${label}.requireReputation.methods.${name}`,
);
}
}
return cfg as DriverRequireReputationConfig;
}
/**
* Resolve the reputation requirement for a method. Same precedence as the other
* per-method resolvers: an entry in `methods` wins over `default`, and
* `undefined` means the method asks for no floor.
*/
export function resolveDriverMethodRequireReputation(
cfg: DriverRequireReputationConfig | undefined,
method: string,
): ReputationRequirement | undefined {
if (!cfg) return undefined;
return cfg.methods?.[method] ?? cfg.default;
}
/**
* Resolved metadata for a registered driver, from decorator metadata or
* imperative instance properties. The gates live here rather than on a route
* because every driver shares the `/drivers/call` dispatch route.
* Resolved metadata for a registered driver, read from its instance properties.
* The gates live here rather than on a route because every driver shares the
* `/drivers/call` dispatch route.
*/
export interface DriverMeta {
/** E.g. 'puter-chat-completion'. */
@@ -441,97 +263,33 @@ export interface DriverMeta {
}
/**
* Extract driver metadata from a driver instance. Checks decorator-set
* prototype metadata first, then falls back to instance properties. Returns
* `null` if the driver doesn't declare an interface.
* Extract a driver's metadata, validating its policy blocks so a malformed one
* fails loud at registration. Returns `null` if the driver doesn't declare an
* interface and name.
*/
export function resolveDriverMeta(
driver: WithLifecycle & Record<string, unknown>,
): DriverMeta | null {
const proto = Object.getPrototypeOf(driver) as Record<string, unknown>;
const interfaceName =
(proto[DRIVER_INTERFACE_KEY] as string | undefined) ??
(driver.driverInterface as string | undefined);
const driverName =
(proto[DRIVER_NAME_KEY] as string | undefined) ??
(driver.driverName as string | undefined);
const isDefault =
(proto[DRIVER_DEFAULT_KEY] as boolean | undefined) ??
(driver.isDefault as boolean | undefined) ??
false;
const aliases =
(proto[DRIVER_ALIASES_KEY] as string[] | undefined) ??
(driver.driverAliases as string[] | undefined) ??
[];
// Decorator stashes a validated config on the prototype; imperative
// drivers declare a raw object on the instance, which we validate here
// so a malformed `rateLimit` field still fails loud at registration.
const protoRateLimit = proto[DRIVER_RATE_LIMIT_KEY] as
DriverRateLimitConfig | undefined;
let rateLimit: DriverRateLimitConfig | undefined;
if (protoRateLimit) {
rateLimit = protoRateLimit;
} else if (driver.rateLimit !== undefined) {
rateLimit = validateDriverRateLimit(
driver.rateLimit,
`driver '${driverName ?? '(unnamed)'}'`,
);
}
const protoConcurrent = proto[DRIVER_CONCURRENT_KEY] as
DriverConcurrentConfig | undefined;
let concurrent: DriverConcurrentConfig | undefined;
if (protoConcurrent) {
concurrent = protoConcurrent;
} else if (driver.concurrent !== undefined) {
concurrent = validateDriverConcurrent(
driver.concurrent,
`driver '${driverName ?? '(unnamed)'}'`,
);
}
const protoRequireSubscription = proto[DRIVER_REQUIRE_SUBSCRIPTION_KEY] as
DriverRequireSubscriptionConfig | undefined;
let requireSubscription: DriverRequireSubscriptionConfig | undefined;
if (protoRequireSubscription) {
requireSubscription = protoRequireSubscription;
} else if (driver.requireSubscription !== undefined) {
requireSubscription = validateDriverRequireSubscription(
driver.requireSubscription,
`driver '${driverName ?? '(unnamed)'}'`,
);
}
const protoRequireReputation = proto[DRIVER_REQUIRE_REPUTATION_KEY] as
DriverRequireReputationConfig | undefined;
let requireReputation: DriverRequireReputationConfig | undefined;
if (protoRequireReputation) {
requireReputation = protoRequireReputation;
} else if (driver.requireReputation !== undefined) {
requireReputation = validateDriverRequireReputation(
driver.requireReputation,
`driver '${driverName ?? '(unnamed)'}'`,
);
}
const noUserSession =
(proto[DRIVER_NO_USER_SESSION_KEY] as boolean | undefined) ??
(driver.noUserSession as boolean | undefined) ??
false;
const interfaceName = driver.driverInterface as string | undefined;
const driverName = driver.driverName as string | undefined;
if (!interfaceName || !driverName) return null;
const label = `driver '${driverName}'`;
const policy = <K extends DriverPolicyName>(name: K) =>
driver[name] === undefined
? undefined
: validatePerMethod(driver[name], label, name);
return {
interfaceName,
driverName,
isDefault,
aliases,
rateLimit,
concurrent,
noUserSession,
requireSubscription,
requireReputation,
isDefault: (driver.isDefault as boolean | undefined) ?? false,
aliases: (driver.driverAliases as string[] | undefined) ?? [],
rateLimit: policy('rateLimit'),
concurrent: policy('concurrent'),
noUserSession: (driver.noUserSession as boolean | undefined) ?? false,
requireSubscription: policy('requireSubscription'),
requireReputation: policy('requireReputation'),
};
}
+8 -22
View File
@@ -47,41 +47,27 @@ export type IPuterDriver<T extends WithCostsReporting = WithCostsReporting> =
/**
* Base class for drivers. A driver implements a named interface (e.g.
* `puter-chat-completion`); several drivers may implement the same one. Declare
* it with `@Driver(interface, options)` or by setting the readonly fields below
* imperatively.
* `puter-chat-completion`); several drivers may implement the same one, and
* declares itself through the readonly fields below.
*/
export const PuterDriver = class PuterDriver implements WithCostsReporting {
/** The interface this driver implements. Set by `@Driver` or override. */
/** The interface this driver implements. */
declare readonly driverInterface?: string;
/** Unique name within its interface. Set by `@Driver` or override. */
/** Unique name within its interface. */
declare readonly driverName?: string;
/** When true, this is the default driver for its interface. */
declare readonly isDefault?: boolean;
/**
* Rate-limit policy applied to RPC calls into this driver. Set by
* `@Driver({ rateLimit: ... })` or declared imperatively. See
* `DriverRateLimitConfig` in `./meta` for the shape.
*/
/** Rate-limit policy for RPC calls into this driver; see `./meta`. */
declare readonly rateLimit?: DriverRateLimitConfig;
/**
* Concurrent in-flight policy applied to RPC calls into this driver. Set by
* `@Driver({ concurrent: ... })` or declared imperatively. See
* `DriverConcurrentConfig` in `./meta` for the shape.
*/
/** In-flight cap for RPC calls into this driver; see `./meta`. */
declare readonly concurrent?: DriverConcurrentConfig;
/**
* When true, `/drivers/call` rejects bare account-session ("root") tokens
* for this driver — callers need an app/worker token or a dashboard-minted
* API token. Set by `@Driver({ noUserSession: true })` or declared
* imperatively. See `DriverMeta.noUserSession` in `./meta`.
* API token. See `DriverMeta.noUserSession` in `./meta`.
*/
declare readonly noUserSession?: boolean;
/**
* Subscriber-only methods on this driver. Set by `@Driver({
* requireSubscription: ... })` or declared imperatively. See
* `DriverRequireSubscriptionConfig` in `./meta` for the shape.
*/
/** Subscriber-only methods on this driver; see `./meta`. */
declare readonly requireSubscription?: DriverRequireSubscriptionConfig;
constructor(
+9 -14
View File
@@ -22,12 +22,7 @@ import {
DEFAULT_FREE_SUBSCRIPTION,
DEFAULT_TEMP_SUBSCRIPTION,
} from '../../services/metering/consts.js';
import {
resolveDriverMethodConcurrent,
resolveDriverMethodRateLimit,
validateDriverConcurrent,
validateDriverRateLimit,
} from '../meta.js';
import { resolvePerMethod, validatePerMethod } from '../meta.js';
import { AI_CONCURRENT, AI_RATE_LIMIT } from './aiLimits.js';
// The shared AI policy is consumed verbatim by 8 drivers — these tests
@@ -46,16 +41,16 @@ describe('AI_RATE_LIMIT', () => {
});
});
it('passes the same validator the @Driver decorator runs at boot', () => {
it('passes the validator drivers run at boot', () => {
// If validation ever tightens, the AI policy must keep up.
expect(() =>
validateDriverRateLimit(AI_RATE_LIMIT, 'AI_RATE_LIMIT'),
validatePerMethod(AI_RATE_LIMIT, 'AI_RATE_LIMIT', 'rateLimit'),
).not.toThrow();
});
it('resolves the same spec for any method since only `default` is set', () => {
const a = resolveDriverMethodRateLimit(AI_RATE_LIMIT, 'complete');
const b = resolveDriverMethodRateLimit(AI_RATE_LIMIT, 'generate');
const a = resolvePerMethod(AI_RATE_LIMIT, 'complete');
const b = resolvePerMethod(AI_RATE_LIMIT, 'generate');
expect(a).toEqual(b);
expect(a).toBe(AI_RATE_LIMIT.default);
});
@@ -83,17 +78,17 @@ describe('AI_CONCURRENT', () => {
});
});
it('passes the same validator the @Driver decorator runs at boot', () => {
it('passes the validator drivers run at boot', () => {
expect(() =>
validateDriverConcurrent(AI_CONCURRENT, 'AI_CONCURRENT'),
validatePerMethod(AI_CONCURRENT, 'AI_CONCURRENT', 'concurrent'),
).not.toThrow();
});
it('resolves the same spec for any method since only `default` is set', () => {
expect(resolveDriverMethodConcurrent(AI_CONCURRENT, 'complete')).toBe(
expect(resolvePerMethod(AI_CONCURRENT, 'complete')).toBe(
AI_CONCURRENT.default,
);
expect(resolveDriverMethodConcurrent(AI_CONCURRENT, 'generate')).toBe(
expect(resolvePerMethod(AI_CONCURRENT, 'generate')).toBe(
AI_CONCURRENT.default,
);
});