@sequenceholdings/managed-functions 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # `@sequenceholdings/managed-functions`
2
2
 
3
+ ## Group-member directory lookup (internal rollout)
4
+
5
+ Functions can resolve selected members of existing Atlas groups to active,
6
+ canonical user identities. This is an opt-in read capability, not a staff
7
+ registry, permission grant, arbitrary directory search, or authentication API.
8
+
9
+ ```yaml
10
+ capabilities:
11
+ directory:
12
+ groups: ["<existing-group-id>"]
13
+ ```
14
+
15
+ ```ts
16
+ import { createFunctionDirectoryClient } from '@sequenceholdings/managed-functions'
17
+
18
+ const directory = createFunctionDirectoryClient({
19
+ baseUrl: process.env.ATLAS_ORIGIN!,
20
+ request: req,
21
+ })
22
+ const { members } = await directory.resolveMembers({
23
+ groupId,
24
+ subs: [selectedCanonicalSubject],
25
+ })
26
+ ```
27
+
28
+ Use `userIds` instead of `subs` to resolve row IDs selected from the existing
29
+ group picker; never submit both. Each call accepts 1–50 selectors. Missing,
30
+ disabled, tombstoned, wrong-tenant, or nonmember selections are omitted. Exact
31
+ subject lookup does not accept an email alias. For a save, require the selected
32
+ subject to appear exactly in the response; a failed request is not an empty
33
+ successful lookup. Historical display labels may retain inactive identities,
34
+ but cannot validate a new assignment.
35
+
36
+ Atlas supplies a dedicated five-minute `x-sequence-directory-token` only for
37
+ declared groups on direct human invocations. System runners and service accounts
38
+ are not supported. Each read rechecks current function invocation access,
39
+ active version, declared group scope, active caller, caller group visibility,
40
+ and active target identities inside the function's tenant. No service identity
41
+ widens the caller's directory visibility. The function still enforces its own
42
+ application action permissions. Validation is point-in-time, not atomic with
43
+ an application's later transaction.
44
+
45
+ Directory and process-read clients share the SDK's invocation transport and
46
+ `{ request, baseUrl, fetchImpl? }` options. Both accept Fetch `Headers` or Node
47
+ request headers (case-insensitively), reject duplicate credentials, check expiry
48
+ before each read, and validate responses. `FunctionDirectoryReadError` exposes
49
+ `status` and `code`, like `FunctionProcessReadError`; failures are never an empty
50
+ member list. Directory replies must contain only distinct requested identities.
51
+ Sharing transport does not share authority: process reads retain service-account
52
+ credentials, while directory reads retain their separate human-caller credential.
53
+
54
+ The internal SDK bridge is `POST /api/managed-functions/directory/members`;
55
+ ordinary Auth0 bearers, storage tokens and ORM tokens do not authenticate it.
56
+ Results are not cached; successful reads emit an audit event. The client never
57
+ follows redirects or forwards an unrelated credential. Allow the Atlas origin
58
+ in the function's existing egress policy. Reviewed Atlas/CLI and SDK releases
59
+ are required before consumers opt in (SDK 0.4.1+, CLI 0.5.2+, and Atlas
60
+ advertising `directory_members`). Preflight rejects absent/disabled support
61
+ and incompatible tooling before upload; older deployments do not supply the
62
+ header. This addition does not activate or redeploy any consumer. The existing
63
+ package root remains Preview/blocked on publication; the raw HTTP route is
64
+ internal, not a new public API contract.
65
+
66
+ Tenant deployments reuse `ATLAS_ARTIFACT_STUDIO_SIGNING_KEY`, already provided
67
+ by the OpCo secrets catalog for Managed Functions. Delegation uses a separate
68
+ HMAC domain, audience, environment, function version and invocation binding.
69
+ There are no new secrets, migrations, groups or grants. Missing signing
70
+ configuration fails closed, including local development.
71
+
72
+
3
73
  `@sequenceholdings/managed-functions` is the TypeScript SDK for Managed
4
74
  Functions. It provides caller-scoped ORM access, explicit service-account ORM
5
75
  access, and opt-in caching. The
@@ -13,6 +83,29 @@ function consumer or branch on a function ID.
13
83
 
14
84
  ## ORM as the invoking user
15
85
 
86
+ ### Default SQL (planned SDK 0.4.0)
87
+
88
+ With the matching Atlas runtime, use the same caller-scoped executor without
89
+ an operation manifest:
90
+
91
+ ```ts
92
+ const orm = createFunctionOrmExecutor({ request, baseUrl: platformOrigin, namespace: 'lending' })
93
+ const result = await orm.sql({
94
+ sql: `SELECT l.id, c.name FROM lending.loans l
95
+ JOIN party.customers c ON c.id = l."customerId"`,
96
+ })
97
+ ```
98
+
99
+ The invocation still needs its existing declared data bindings covering every
100
+ read object, including policy dependencies. SQL does not grant extra access.
101
+ Namespace access, row policies, foreign exports and optional consumer contracts
102
+ remain enforced. Results contain `columns`, array-valued `rows`, and `truncated`;
103
+ numeric/bigint SQL results are always exact strings. Read-only SELECT queries
104
+ are bounded to 1,000 rows / 8 MiB. Source versions are not proof of publication
105
+ or server rollout; the registered-operation workflow below remains supported.
106
+
107
+ ### Registered operations
108
+
16
109
  Available in SDK **0.2.0 and later**. Install the SDK in the function package;
17
110
  the separate ORM authoring/compiler package is not needed in the deployed
18
111
  function. Copy the namespace's generated `operations.manifest.json` into that
@@ -13,6 +13,13 @@ export declare const MANAGED_FUNCTION_CAPABILITIES: {
13
13
  };
14
14
  };
15
15
  readonly features: {
16
+ readonly directory_members: {
17
+ readonly field: "capabilities.directory.groups";
18
+ readonly supported: true;
19
+ readonly limit: "1 to 16 concrete groups; resolve 1 to 50 selected active members per call";
20
+ readonly min_cli: "0.5.2";
21
+ readonly min_sdk: "0.4.1";
22
+ };
16
23
  readonly processes_read: {
17
24
  readonly field: "capabilities.processes.read";
18
25
  readonly supported: true;
@@ -57,6 +64,13 @@ export interface CapabilityTooling {
57
64
  export interface ManagedFunctionCapabilities {
58
65
  tooling: CapabilityTooling;
59
66
  features: {
67
+ directory_members?: {
68
+ field: string;
69
+ supported: boolean;
70
+ limit: string;
71
+ min_cli: string;
72
+ min_sdk: string;
73
+ };
60
74
  processes_read?: {
61
75
  field: string;
62
76
  supported: boolean;
@@ -108,6 +122,7 @@ export interface PreflightManifest {
108
122
  data?: Readonly<Record<string, unknown>>;
109
123
  storage?: unknown;
110
124
  processes?: unknown;
125
+ directory?: unknown;
111
126
  };
112
127
  }
113
128
  export interface PreflightIssue {
@@ -15,6 +15,13 @@ exports.MANAGED_FUNCTION_CAPABILITIES = {
15
15
  sdk: { package: exports.MANAGED_FUNCTION_SDK_PACKAGE, min: '0.3.0' },
16
16
  },
17
17
  features: {
18
+ directory_members: {
19
+ field: 'capabilities.directory.groups',
20
+ supported: true,
21
+ limit: '1 to 16 concrete groups; resolve 1 to 50 selected active members per call',
22
+ min_cli: '0.5.2',
23
+ min_sdk: '0.4.1',
24
+ },
18
25
  processes_read: {
19
26
  field: 'capabilities.processes.read',
20
27
  supported: true,
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { DIRECTORY_TOKEN_HEADER, directoryGroupIdSchema, directoryScopesSchema, directoryMembersInput, directoryMembersResponse, readDirectoryGroups } from './directory-config';
1
2
  export { FUNCTION_PROCESS_TOKEN_HEADER, FUNCTION_PROCESS_READ_PATH, functionProcessReadScopesSchema, readFunctionProcessScopes, functionProcessReadRequestSchema, functionProcessRunSummarySchema } from './processes-contract';
2
3
  export { shouldReportAtlasHttp } from './http-tracing';
3
4
  import { z } from 'zod';
package/dist/config.js CHANGED
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cacheManifestSchema = exports.CACHE_MAX_TTL_SECONDS = exports.preflightManagedFunction = exports.parseDeployedPolicy = exports.versionIsOlder = exports.sdkVersionLowerBound = exports.sdkVersionFromPackageJson = exports.isPostgresEgress = exports.formatValidationDetail = exports.MANAGED_FUNCTION_SDK_PACKAGE = exports.MANAGED_FUNCTION_CLI_PACKAGE = exports.MANAGED_FUNCTION_CAPABILITIES = exports.managedFunctionWorkloadLimitsSchema = exports.MF_LIMITS_DEFAULTS = exports.MF_LIMITS_CAPS = exports.MANAGED_FUNCTION_LIMITS = exports.shouldReportAtlasHttp = exports.functionProcessRunSummarySchema = exports.functionProcessReadRequestSchema = exports.readFunctionProcessScopes = exports.functionProcessReadScopesSchema = exports.FUNCTION_PROCESS_READ_PATH = exports.FUNCTION_PROCESS_TOKEN_HEADER = void 0;
3
+ exports.cacheManifestSchema = exports.CACHE_MAX_TTL_SECONDS = exports.preflightManagedFunction = exports.parseDeployedPolicy = exports.versionIsOlder = exports.sdkVersionLowerBound = exports.sdkVersionFromPackageJson = exports.isPostgresEgress = exports.formatValidationDetail = exports.MANAGED_FUNCTION_SDK_PACKAGE = exports.MANAGED_FUNCTION_CLI_PACKAGE = exports.MANAGED_FUNCTION_CAPABILITIES = exports.managedFunctionWorkloadLimitsSchema = exports.MF_LIMITS_DEFAULTS = exports.MF_LIMITS_CAPS = exports.MANAGED_FUNCTION_LIMITS = exports.shouldReportAtlasHttp = exports.functionProcessRunSummarySchema = exports.functionProcessReadRequestSchema = exports.readFunctionProcessScopes = exports.functionProcessReadScopesSchema = exports.FUNCTION_PROCESS_READ_PATH = exports.FUNCTION_PROCESS_TOKEN_HEADER = exports.readDirectoryGroups = exports.directoryMembersResponse = exports.directoryMembersInput = exports.directoryScopesSchema = exports.directoryGroupIdSchema = exports.DIRECTORY_TOKEN_HEADER = void 0;
4
4
  exports.readCacheConfiguration = readCacheConfiguration;
5
+ var directory_config_1 = require("./directory-config");
6
+ Object.defineProperty(exports, "DIRECTORY_TOKEN_HEADER", { enumerable: true, get: function () { return directory_config_1.DIRECTORY_TOKEN_HEADER; } });
7
+ Object.defineProperty(exports, "directoryGroupIdSchema", { enumerable: true, get: function () { return directory_config_1.directoryGroupIdSchema; } });
8
+ Object.defineProperty(exports, "directoryScopesSchema", { enumerable: true, get: function () { return directory_config_1.directoryScopesSchema; } });
9
+ Object.defineProperty(exports, "directoryMembersInput", { enumerable: true, get: function () { return directory_config_1.directoryMembersInput; } });
10
+ Object.defineProperty(exports, "directoryMembersResponse", { enumerable: true, get: function () { return directory_config_1.directoryMembersResponse; } });
11
+ Object.defineProperty(exports, "readDirectoryGroups", { enumerable: true, get: function () { return directory_config_1.readDirectoryGroups; } });
5
12
  var processes_contract_1 = require("./processes-contract");
6
13
  Object.defineProperty(exports, "FUNCTION_PROCESS_TOKEN_HEADER", { enumerable: true, get: function () { return processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER; } });
7
14
  Object.defineProperty(exports, "FUNCTION_PROCESS_READ_PATH", { enumerable: true, get: function () { return processes_contract_1.FUNCTION_PROCESS_READ_PATH; } });
@@ -42,9 +42,23 @@ function normalizeCapabilities(value) {
42
42
  http_headers: normalizeHeaders(features.http_headers),
43
43
  invoke_timeout: normalizeTimeout(features.invoke_timeout),
44
44
  processes_read: normalizeProcesses(features.processes_read),
45
+ directory_members: normalizeDirectory(features.directory_members),
45
46
  },
46
47
  };
47
48
  }
49
+ function normalizeDirectory(value) {
50
+ if (!value || typeof value.supported !== 'boolean' || typeof value.limit !== 'string')
51
+ return undefined;
52
+ if (typeof value.min_cli !== 'string' || typeof value.min_sdk !== 'string')
53
+ return undefined;
54
+ return {
55
+ field: typeof value.field === 'string' ? value.field : 'capabilities.directory.groups',
56
+ supported: value.supported,
57
+ limit: value.limit,
58
+ min_cli: value.min_cli,
59
+ min_sdk: value.min_sdk,
60
+ };
61
+ }
48
62
  function normalizeProcesses(value) {
49
63
  if (!value || typeof value.supported !== 'boolean' || typeof value.limit !== 'string')
50
64
  return undefined;
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export declare const DIRECTORY_TOKEN_HEADER = "x-sequence-directory-token";
3
+ export declare const directoryGroupIdSchema: z.ZodString;
4
+ export declare const directoryScopesSchema: z.ZodObject<{
5
+ groups: z.ZodArray<z.ZodString>;
6
+ }, z.core.$strict>;
7
+ export declare const directoryMembersInput: z.ZodUnion<readonly [z.ZodObject<{
8
+ groupId: z.ZodString;
9
+ userIds: z.ZodArray<z.ZodString>;
10
+ }, z.core.$strict>, z.ZodObject<{
11
+ groupId: z.ZodString;
12
+ subs: z.ZodArray<z.ZodString>;
13
+ }, z.core.$strict>]>;
14
+ export declare const directoryMembersResponse: z.ZodObject<{
15
+ members: z.ZodArray<z.ZodObject<{
16
+ id: z.ZodString;
17
+ sub: z.ZodString;
18
+ name: z.ZodString;
19
+ email: z.ZodString;
20
+ deactivated: z.ZodLiteral<false>;
21
+ }, z.core.$strict>>;
22
+ }, z.core.$strict>;
23
+ export declare function readDirectoryGroups(manifest: unknown): string[];
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.directoryMembersResponse = exports.directoryMembersInput = exports.directoryScopesSchema = exports.directoryGroupIdSchema = exports.DIRECTORY_TOKEN_HEADER = void 0;
4
+ exports.readDirectoryGroups = readDirectoryGroups;
5
+ const zod_1 = require("zod");
6
+ exports.DIRECTORY_TOKEN_HEADER = 'x-sequence-directory-token';
7
+ exports.directoryGroupIdSchema = zod_1.z.string().regex(/^(?:[a-f0-9]{24}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i);
8
+ exports.directoryScopesSchema = zod_1.z.object({
9
+ groups: zod_1.z.array(exports.directoryGroupIdSchema).min(1).max(16),
10
+ }).strict();
11
+ const identity = zod_1.z.string().min(1).max(256).regex(/^[^\x00-\x1f\x7f]+$/);
12
+ exports.directoryMembersInput = zod_1.z.union([
13
+ zod_1.z.object({ groupId: exports.directoryGroupIdSchema, userIds: zod_1.z.array(identity).min(1).max(50) }).strict(),
14
+ zod_1.z.object({ groupId: exports.directoryGroupIdSchema, subs: zod_1.z.array(identity).min(1).max(50) }).strict(),
15
+ ]);
16
+ exports.directoryMembersResponse = zod_1.z.object({ members: zod_1.z.array(zod_1.z.object({
17
+ id: zod_1.z.string(), sub: identity, name: zod_1.z.string(), email: zod_1.z.string(), deactivated: zod_1.z.literal(false),
18
+ }).strict()).max(50) }).strict();
19
+ function readDirectoryGroups(manifest) {
20
+ const parsed = zod_1.z.object({ capabilities: zod_1.z.object({ directory: exports.directoryScopesSchema.optional() }).optional() }).safeParse(manifest);
21
+ return parsed.success ? parsed.data.capabilities?.directory?.groups ?? [] : [];
22
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import { directoryMembersInput, directoryMembersResponse } from './directory-config';
3
+ import { type FunctionInvocationClientOptions } from './invocation-client';
4
+ export type FunctionDirectoryOptions = FunctionInvocationClientOptions;
5
+ export type FunctionDirectoryMembersQuery = z.infer<typeof directoryMembersInput>;
6
+ export type FunctionDirectoryMembers = z.infer<typeof directoryMembersResponse>;
7
+ export interface FunctionDirectoryClient {
8
+ resolveMembers(input: FunctionDirectoryMembersQuery): Promise<FunctionDirectoryMembers>;
9
+ }
10
+ export declare class FunctionDirectoryReadError extends Error {
11
+ readonly status: number;
12
+ readonly code: string;
13
+ constructor(status: number, code: string, message: string);
14
+ }
15
+ export declare function createFunctionDirectoryClient({ request, baseUrl, fetchImpl, }: FunctionDirectoryOptions): FunctionDirectoryClient;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FunctionDirectoryReadError = void 0;
4
+ exports.createFunctionDirectoryClient = createFunctionDirectoryClient;
5
+ const zod_1 = require("zod");
6
+ const directory_config_1 = require("./directory-config");
7
+ const invocation_client_1 = require("./invocation-client");
8
+ class FunctionDirectoryReadError extends Error {
9
+ status;
10
+ code;
11
+ constructor(status, code, message) {
12
+ super(message);
13
+ this.status = status;
14
+ this.code = code;
15
+ this.name = 'FunctionDirectoryReadError';
16
+ }
17
+ }
18
+ exports.FunctionDirectoryReadError = FunctionDirectoryReadError;
19
+ function createFunctionDirectoryClient({ request, baseUrl, fetchImpl, }) {
20
+ const credential = (0, invocation_client_1.readInvocationCredential)({
21
+ request,
22
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
23
+ maxBytes: 8192,
24
+ invalidContext: () => new FunctionDirectoryReadError(401, 'invalid_context', 'This invocation has no valid directory-read credential'),
25
+ claimsSchema: zod_1.z.object({
26
+ audience: zod_1.z.literal('atlas-function-group-directory-v1'),
27
+ sub: zod_1.z
28
+ .string()
29
+ .min(1)
30
+ .max(256)
31
+ .refine((sub) => sub.includes('|') &&
32
+ !sub.startsWith('service-account|') &&
33
+ !sub.endsWith('@clients')),
34
+ functionId: zod_1.z.string().min(1),
35
+ invocationId: zod_1.z.string().min(1),
36
+ exp: zod_1.z.number().int(),
37
+ }),
38
+ });
39
+ const read = (0, invocation_client_1.createInvocationReadTransport)({
40
+ baseUrl,
41
+ fetchImpl,
42
+ path: '/api/managed-functions/directory/members',
43
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
44
+ credential,
45
+ timeoutMs: 10_000,
46
+ error: (status, code, message) => new FunctionDirectoryReadError(status, code, message),
47
+ failure: {
48
+ code: 'directory_read_failed',
49
+ message: 'Directory read failed',
50
+ },
51
+ });
52
+ return {
53
+ async resolveMembers(input) {
54
+ const body = directory_config_1.directoryMembersInput.parse(input);
55
+ const result = await read({ body, schema: directory_config_1.directoryMembersResponse });
56
+ const selectors = new Set('userIds' in body ? body.userIds : body.subs);
57
+ const returned = result.members.map((member) => 'userIds' in body ? member.id : member.sub);
58
+ if (new Set(returned).size !== returned.length ||
59
+ returned.some((value) => !selectors.has(value))) {
60
+ throw new FunctionDirectoryReadError(502, 'malformed_response', 'Atlas returned duplicate or unrequested directory members');
61
+ }
62
+ return result;
63
+ },
64
+ };
65
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { createFunctionDirectoryClient, FunctionDirectoryReadError } from './directory';
2
+ export type { FunctionDirectoryClient, FunctionDirectoryOptions, FunctionDirectoryMembers, FunctionDirectoryMembersQuery } from './directory';
3
+ export type { FunctionInvocationClientOptions } from './invocation-client';
1
4
  export { createFunctionServiceProcessesClient, FunctionProcessReadError } from './processes';
2
5
  export type { FunctionServiceProcessesClient, FunctionServiceProcessesOptions, FunctionProcessRunQuery, FunctionProcessRunSummary, FunctionProcessRunStatus, FunctionProcessRunsPage, FunctionProcessRunsPageQuery, FunctionProcessReadRequest } from './processes';
3
6
  export { installFunctionTracing, runWithFunctionTracing } from './tracing';
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = void 0;
3
+ exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = exports.FunctionDirectoryReadError = exports.createFunctionDirectoryClient = void 0;
4
4
  exports.createManagedFunctionContext = createManagedFunctionContext;
5
+ var directory_1 = require("./directory");
6
+ Object.defineProperty(exports, "createFunctionDirectoryClient", { enumerable: true, get: function () { return directory_1.createFunctionDirectoryClient; } });
7
+ Object.defineProperty(exports, "FunctionDirectoryReadError", { enumerable: true, get: function () { return directory_1.FunctionDirectoryReadError; } });
5
8
  var processes_1 = require("./processes");
6
9
  Object.defineProperty(exports, "createFunctionServiceProcessesClient", { enumerable: true, get: function () { return processes_1.createFunctionServiceProcessesClient; } });
7
10
  Object.defineProperty(exports, "FunctionProcessReadError", { enumerable: true, get: function () { return processes_1.FunctionProcessReadError; } });
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export interface FunctionInvocationClientOptions {
3
+ request: {
4
+ headers: Pick<Headers, 'get'> | Readonly<Record<string, string | readonly string[] | undefined>>;
5
+ };
6
+ baseUrl: string;
7
+ fetchImpl?: typeof globalThis.fetch;
8
+ }
9
+ type ClientErrorFactory = (status: number, code: string, message: string) => Error;
10
+ export declare function readInvocationCredential<T extends {
11
+ exp: number;
12
+ }>({ request, header, maxBytes, claimsSchema, invalidContext, }: Pick<FunctionInvocationClientOptions, 'request'> & {
13
+ header: string;
14
+ maxBytes: number;
15
+ claimsSchema: z.ZodType<T>;
16
+ invalidContext: () => Error;
17
+ }): {
18
+ token: string;
19
+ expiresAt: number;
20
+ };
21
+ export declare function createInvocationReadTransport({ baseUrl, fetchImpl, path, header, credential, timeoutMs, error, failure, }: Pick<FunctionInvocationClientOptions, 'baseUrl' | 'fetchImpl'> & {
22
+ path: string;
23
+ header: string;
24
+ credential: {
25
+ token: string;
26
+ expiresAt: number;
27
+ };
28
+ timeoutMs: number;
29
+ error: ClientErrorFactory;
30
+ failure: {
31
+ code: string;
32
+ message: string;
33
+ };
34
+ }): <T>({ body, schema, }: {
35
+ body: unknown;
36
+ schema: z.ZodType<T>;
37
+ }) => Promise<T>;
38
+ export {};
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readInvocationCredential = readInvocationCredential;
4
+ exports.createInvocationReadTransport = createInvocationReadTransport;
5
+ const zod_1 = require("zod");
6
+ function readInvocationCredential({ request, header, maxBytes, claimsSchema, invalidContext, }) {
7
+ const { headers } = request;
8
+ const matches = typeof headers.get === 'function'
9
+ ? [headers.get(header)]
10
+ : Object.entries(headers)
11
+ .filter(([name]) => name.toLowerCase() === header)
12
+ .map(([, value]) => value);
13
+ const token = matches[0];
14
+ if (matches.length !== 1 ||
15
+ typeof token !== 'string' ||
16
+ token.length > maxBytes)
17
+ throw invalidContext();
18
+ const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
19
+ if (!envelope?.[1])
20
+ throw invalidContext();
21
+ let value;
22
+ try {
23
+ value = JSON.parse(Buffer.from(envelope[1], 'base64url').toString('utf8'));
24
+ }
25
+ catch {
26
+ throw invalidContext();
27
+ }
28
+ const parsed = claimsSchema.safeParse(value);
29
+ if (!parsed.success)
30
+ throw invalidContext();
31
+ return { token, expiresAt: parsed.data.exp };
32
+ }
33
+ function trustedOrigin({ baseUrl, error, }) {
34
+ let url;
35
+ try {
36
+ url = new URL(baseUrl);
37
+ }
38
+ catch {
39
+ throw error(400, 'invalid_configuration', 'Configure a trusted Atlas origin');
40
+ }
41
+ const loopback = url.protocol === 'http:' &&
42
+ ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
43
+ if ((!loopback && url.protocol !== 'https:') ||
44
+ url.username ||
45
+ url.password ||
46
+ url.pathname !== '/' ||
47
+ url.search ||
48
+ url.hash) {
49
+ throw error(400, 'invalid_configuration', 'Configure an HTTPS Atlas origin without credentials, path, query or fragment');
50
+ }
51
+ return url.origin;
52
+ }
53
+ const errorResponseSchema = zod_1.z.object({
54
+ code: zod_1.z.string().max(128).optional(),
55
+ detail: zod_1.z.string().max(2000).optional(),
56
+ });
57
+ function createInvocationReadTransport({ baseUrl, fetchImpl = globalThis.fetch, path, header, credential, timeoutMs, error, failure, }) {
58
+ const url = `${trustedOrigin({ baseUrl, error })}${path}`;
59
+ return async function read({ body, schema, }) {
60
+ if (credential.expiresAt <= Date.now() / 1000) {
61
+ throw error(401, 'expired_context', 'The invocation credential expired; start a new invocation');
62
+ }
63
+ const response = await fetchImpl(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'content-type': 'application/json',
67
+ [header]: credential.token,
68
+ },
69
+ body: JSON.stringify(body),
70
+ redirect: 'error',
71
+ signal: AbortSignal.timeout(timeoutMs),
72
+ });
73
+ const result = await response.json().catch(() => null);
74
+ if (!response.ok) {
75
+ const parsed = errorResponseSchema.safeParse(result);
76
+ throw error(response.status, parsed.success ? (parsed.data.code ?? failure.code) : failure.code, parsed.success
77
+ ? (parsed.data.detail ?? failure.message)
78
+ : failure.message);
79
+ }
80
+ const validated = schema.safeParse(result);
81
+ if (!validated.success)
82
+ throw error(502, 'malformed_response', 'Atlas returned an invalid capability response');
83
+ return validated.data;
84
+ };
85
+ }
package/dist/orm.d.ts CHANGED
@@ -15,6 +15,29 @@ declare class OrmOperationError extends OrmClientError {
15
15
  readonly name = "OrmOperationError";
16
16
  constructor(message: string, status: number | null, code: string | null, errors?: readonly OperationErrorEntry[]);
17
17
  }
18
+ type SqlQueryParameter = {
19
+ type: 'text' | 'uuid' | 'date' | 'timestamptz' | 'numeric' | 'bigint';
20
+ value: string | null;
21
+ } | {
22
+ type: 'integer' | 'double';
23
+ value: number | null;
24
+ } | {
25
+ type: 'boolean';
26
+ value: boolean | null;
27
+ };
28
+ type SqlQueryVariables = {
29
+ sql: string;
30
+ parameters?: Record<string, SqlQueryParameter>;
31
+ };
32
+ interface SqlQueryResult {
33
+ columns: Array<{
34
+ name: string;
35
+ type: string;
36
+ nullable: boolean;
37
+ }>;
38
+ rows: Array<Array<string | number | boolean | null>>;
39
+ truncated: boolean;
40
+ }
18
41
 
19
42
  /** One entry of `operations.manifest.json` (generated by `orm generate`). */
20
43
  interface ManifestOperation {
@@ -38,6 +61,11 @@ interface OperationExecutor {
38
61
  /** Execute a manifest operation by name (or a raw `sha256:` hash). */
39
62
  execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>, options?: OperationExecutionOptions): Promise<TData>;
40
63
  }
64
+ /** Additive factory result; existing host wrappers implementing OperationExecutor remain valid. */
65
+ interface SqlOperationExecutor extends OperationExecutor {
66
+ /** Default read-only ontology SQL; no authored operation or manifest needed. */
67
+ sql(variables: SqlQueryVariables): Promise<SqlQueryResult>;
68
+ }
41
69
 
42
70
  type FunctionOrmErrorCode = 'INVALID_CONFIGURATION' | 'MISSING_CALLER_CONTEXT' | 'INVALID_CALLER_CONTEXT' | 'UNSUPPORTED_CALLER' | 'EXPIRED_CALLER_CONTEXT';
43
71
  declare class FunctionOrmError extends Error {
@@ -55,11 +83,11 @@ interface FunctionOrmExecutorOptions {
55
83
  request: FunctionOrmRequest;
56
84
  baseUrl: string;
57
85
  namespace: string;
58
- manifest: unknown;
86
+ manifest?: unknown;
59
87
  fetchImpl?: typeof globalThis.fetch;
60
88
  jsonScalars?: FunctionOrmJsonScalars;
61
89
  }
62
- declare function createFunctionOrmExecutor({ request, fetchImpl, ...options }: FunctionOrmExecutorOptions): OperationExecutor;
63
- declare function createFunctionServiceOrmExecutor({ request, fetchImpl, ...options }: FunctionOrmExecutorOptions): OperationExecutor;
90
+ declare function createFunctionOrmExecutor({ request, fetchImpl, ...options }: FunctionOrmExecutorOptions): SqlOperationExecutor;
91
+ declare function createFunctionServiceOrmExecutor({ request, fetchImpl, ...options }: FunctionOrmExecutorOptions): SqlOperationExecutor;
64
92
 
65
- export { FunctionOrmError, type FunctionOrmErrorCode, type FunctionOrmExecutorOptions, type FunctionOrmJsonScalars, type FunctionOrmRequest, ORM_JSON_SCALARS_HEADER, ORM_LOSSLESS_JSON_SCALARS, type OperationExecutor, type OperationsManifest, OrmClientError, OrmOperationError, createFunctionOrmExecutor, createFunctionServiceOrmExecutor };
93
+ export { FunctionOrmError, type FunctionOrmErrorCode, type FunctionOrmExecutorOptions, type FunctionOrmJsonScalars, type FunctionOrmRequest, ORM_JSON_SCALARS_HEADER, ORM_LOSSLESS_JSON_SCALARS, type OperationExecutor, type OperationsManifest, OrmClientError, OrmOperationError, type SqlOperationExecutor, createFunctionOrmExecutor, createFunctionServiceOrmExecutor };
package/dist/orm.js CHANGED
@@ -82,6 +82,7 @@ function readOperationResponse({
82
82
  }
83
83
  return payload.data;
84
84
  }
85
+ var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
85
86
  function validateIdempotencyKey(key) {
86
87
  if (key !== void 0 && !/^[A-Za-z0-9._:-]{1,128}$/.test(key)) {
87
88
  throw new OrmClientError("idempotencyKey must be 1\u2013128 letters, digits, dots, underscores, colons or hyphens");
@@ -103,13 +104,13 @@ function readManifestHashes(manifest) {
103
104
  }
104
105
  function createOperationExecutorWithTransport({ namespace, manifest, transport }) {
105
106
  if (typeof namespace !== "string" || !NAMESPACE_PATTERN.test(namespace)) throw new OrmClientError("invalid ORM namespace \u2014 expected a lowercase identifier of at most 41 characters");
106
- const hashes = readManifestHashes(manifest);
107
+ const hashes = manifest === void 0 ? /* @__PURE__ */ new Map() : readManifestHashes(manifest);
107
108
  const knownHashes = new Set(hashes.values());
108
109
  async function execute(op, variables = {}, options = {}) {
109
110
  validateIdempotencyKey(options.idempotencyKey);
110
111
  if (typeof op !== "string" && op.namespace !== namespace) throw new OrmClientError("operation belongs to a different namespace");
111
112
  const selector = typeof op === "string" ? op : op.hash;
112
- const hash = hashes.get(selector) ?? (knownHashes.has(selector) ? selector : void 0);
113
+ const hash = selector === BUILTIN_SQL_READ_HASH ? selector : hashes.get(selector) ?? (knownHashes.has(selector) ? selector : void 0);
113
114
  if (!hash) throw new OrmClientError(`unknown operation "${op}" \u2014 not in the manifest (${hashes.size} known). Re-run seq-studio orm generate.`);
114
115
  if (!isRecord(variables)) throw new OrmClientError("operation variables must be an object");
115
116
  const response = await transport({
@@ -123,7 +124,7 @@ function createOperationExecutorWithTransport({ namespace, manifest, transport }
123
124
  }
124
125
  return readOperationResponse(response);
125
126
  }
126
- return { execute };
127
+ return { execute, sql: (variables) => execute(BUILTIN_SQL_READ_HASH, variables) };
127
128
  }
128
129
 
129
130
  // src/orm.ts
@@ -0,0 +1,10 @@
1
+ import { type CapabilityTooling, type ManagedFunctionCapabilities, type PreflightIssue, type PreflightManifest } from './capabilities';
2
+ export declare function checkDirectory({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues, }: {
3
+ manifest: PreflightManifest;
4
+ capabilities: ManagedFunctionCapabilities | null;
5
+ tooling: CapabilityTooling;
6
+ cliVersion: string;
7
+ sdkVersion: string | null;
8
+ checkDeployment: boolean;
9
+ issues: PreflightIssue[];
10
+ }): void;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkDirectory = checkDirectory;
4
+ const capabilities_1 = require("./capabilities");
5
+ const preflight_issue_1 = require("./preflight-issue");
6
+ const directory_config_1 = require("./directory-config");
7
+ const sdk_range_1 = require("./sdk-range");
8
+ function checkDirectory({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues, }) {
9
+ const directory = manifest.capabilities?.directory;
10
+ if (directory === undefined)
11
+ return;
12
+ const deployed = capabilities?.features.directory_members;
13
+ const feature = deployed ?? capabilities_1.MANAGED_FUNCTION_CAPABILITIES.features.directory_members;
14
+ const details = {
15
+ limit: feature.limit, tooling, requiredCli: feature.min_cli,
16
+ requiredSdk: feature.min_sdk, cliVersion, sdkVersion,
17
+ };
18
+ const parsed = directory_config_1.directoryScopesSchema.safeParse(directory);
19
+ if (!parsed.success) {
20
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
21
+ ...details, field: 'capabilities.directory', problem: (0, capabilities_1.formatValidationDetail)(parsed.error.issues),
22
+ }));
23
+ return;
24
+ }
25
+ if (checkDeployment && deployed?.supported !== true) {
26
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
27
+ ...details, field: feature.field, problem: 'group-member directory reads are not supported by this deployment',
28
+ }));
29
+ return;
30
+ }
31
+ if ((0, capabilities_1.versionIsOlder)(cliVersion, feature.min_cli) || (sdkVersion && (0, sdk_range_1.sdkConstraintIsOlder)(sdkVersion, feature.min_sdk))) {
32
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
33
+ ...details, field: feature.field, problem: 'the installed tooling does not support managed function group-member directory reads',
34
+ }));
35
+ }
36
+ }