@sequenceholdings/managed-functions 0.1.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 ADDED
@@ -0,0 +1,115 @@
1
+ # `@sequenceholdings/managed-functions`
2
+
3
+ `@sequenceholdings/managed-functions` is the TypeScript SDK for cache-enabled
4
+ Managed Functions. Function code imports its context and cache types; Atlas and
5
+ the local harness use its runtime helpers to construct the optional third
6
+ handler argument. Ordinary two-argument handlers do not need this package.
7
+
8
+ Managed Functions is a workflow-agnostic platform primitive. The runtime owns
9
+ generic invocation, isolation, secrets, egress, caching, and observability;
10
+ deployed functions own business logic. Primitive code must not identify a
11
+ function consumer or branch on a function ID. See
12
+ [Platform primitive boundaries](../../../docs/platform/platform-primitives.md).
13
+
14
+ Opt-in caching inside a Managed Function. When the manifest declares a `cache` block, the platform passes a process-reused `context` as the HTTP handler's third argument. Existing two-argument handlers keep working.
15
+
16
+ ```yaml
17
+ cache:
18
+ enabled: true
19
+ tier: custom-pico
20
+ replicas: 0
21
+ default_ttl_seconds: 30
22
+ ```
23
+
24
+ Each function/environment gets a dedicated private Valkey instance. The baseline is one pico node with no replicas or persistence. Instances incur charges while idle. Only Sequence organization members can change an enabled cache to a nonbaseline tier or replica count; other authors can retain an already-approved configuration or return to baseline. Supported tiers: `custom-pico`, `custom-micro`, `custom-mini`; replicas: 0–5.
25
+
26
+ ```ts
27
+ import type { ManagedFunctionContext } from '@sequenceholdings/managed-functions'
28
+ import type { Request, Response } from '@google-cloud/functions-framework'
29
+
30
+ export async function handler(req: Request, res: Response, context: ManagedFunctionContext) {
31
+ // Authenticate and authorize EVERY invocation before accessing shared cached data.
32
+ // Include tenant, permission scope, arguments, and authoritative data version
33
+ // in keys whenever they affect the result.
34
+ const result = await context.cache.getOrCompute('public-catalog:v1', { ttlSeconds: 30 }, async () => {
35
+ const response = await fetch('https://api.example.com/catalog')
36
+ if (!response.ok) throw new Error('Catalog unavailable')
37
+ return response.json()
38
+ })
39
+ res.json(result)
40
+ }
41
+ ```
42
+
43
+ ## Contract
44
+
45
+ All methods are asynchronous. Values are lossless JSON, at most 1 MiB; keys are nonempty strings, at most 512 UTF-8 bytes. TTL is an integer from 1 to 86,400 seconds. `CacheOptions` is `{ ttlSeconds?: number }`, defaulting to the manifest TTL. Generic reads assert a type; they do not validate your schema.
46
+
47
+ | Method | Result |
48
+ | --- | --- |
49
+ | `get<T>(key): Promise<T \| undefined>` | Missing/expired → `undefined`; cached `null` is a hit. Does not extend TTL. |
50
+ | `set(key, value, options?): Promise<void>` | Atomic upsert with a new TTL. |
51
+ | `setIfAbsent(key, value, options?): Promise<boolean>` | Atomic insert+TTL; `false` leaves the existing value and TTL unchanged. |
52
+ | `delete(key): Promise<boolean>` | `true` if removed, otherwise `false`. |
53
+ | `getOrCompute<T>(key, options, loader): Promise<T>` | Hit or computed value; stores successful JSON results. |
54
+
55
+ Explicit CRUD throws `CacheError` with `DISABLED`, `UNAVAILABLE`, `UNKNOWN_OUTCOME`, `INVALID_ARGUMENT`, or `INVALID_VALUE`. A lost mutation acknowledgement has an unknown outcome and is never replayed automatically. `getOrCompute` bypasses disabled/unavailable caching and failed cache writes; loader errors and invalid inputs/results propagate. A cache failure never retries the loader. Coalescing is bounded and process-local: duplicate computation is possible. This is not an idempotency or exactly-once primitive.
56
+
57
+ Authors own authorization, read/write choices, and invalidation. A concurrent loader can repopulate a deleted key with old data; use authoritative versioned keys or bypass the cache when freshness is required. Every activation gets a fresh key namespace; old entries expire naturally.
58
+
59
+ ## Local development
60
+
61
+ Run `seq-studio functions dev --dir . --port 8080` after installing dependencies. Enabled caching without a Valkey connection uses bounded process-local TTL/LRU storage (16 MiB encoded payloads, 10,000 entries); it is reset on restart and not shared across processes. The harness compiles TypeScript with the project's installed compiler. A deployed enabled cache without connection configuration fails startup; it never falls back silently.
62
+
63
+ For an explicit private Valkey development connection, set `SEQUENCE_FUNCTION_CACHE_CONFIG` to JSON containing `cache` plus `connection: { host, port, serverIdentity, caCertificates, keyPrefix }`. Use ADC with permission on that test instance and network connectivity to its PSC endpoint. `serverIdentity` must match the server certificate. Do not put passwords or tokens in configuration. The deployed runtime always obtains short-lived tokens from its attached service account.
64
+
65
+ ## Runtime bounds
66
+
67
+ One multiplexed TLS connection per process, at most 128 pending operations, 150 ms command and 1.5 s connection deadlines. Three failures open a five-second circuit breaker. New connections acquire a valid IAM token; live authenticated connections can outlast token expiry. TLS verifies the trusted certificate chain and expected DNS/IP identity. No offline queue, mutation replay, plaintext, or verification bypass.
68
+
69
+ Cloud Run emits aggregate `managed_function.cache` logs every 30 seconds of active use with operation/error counts and total/maximum operation latency. Keys, values, and credentials are never logged. Cache memory, evictions and hit rate appear beside function metrics; actual compute/cache spend, credits and billing freshness remain in Billing.
70
+
71
+ Functions without a `cache` manifest block retain their existing two-argument handler. An explicit `cache: { enabled: false }` supplies a disabled `context.cache`, allowing `getOrCompute` to run its loader without caching.
72
+
73
+ ## Runtime tracing
74
+
75
+ Newly deployed function entrypoints install request-scoped tracing automatically,
76
+ including two-argument handlers without caching. Ordinary global `fetch` calls to
77
+ the current platform propagate the invocation context; ORM and storage delegation
78
+ also retain the original initiator when a function explicitly acts as its service
79
+ account. Normal resource authorization still applies.
80
+
81
+ Global `fetch` calls to declared external hosts record hostname, method, status
82
+ and duration. Paths, queries, bodies, credentials and arbitrary errors are not
83
+ stored. Other networking transports and redirect hops are outside this capture.
84
+ External requests receive no platform attribution headers from this runtime.
85
+
86
+ Completed external and nonprimitive Atlas API spans are exported in small batches while the handler keeps
87
+ working (50 ms batching delay, 500 ms per batch including one retry). The final
88
+ batch starts fire-and-forget when the HTTP response ends (or the handler returns
89
+ or throws); the response and errors never wait for export. Calls limited to
90
+ instrumented primitive execution routes send no export. The runtime aborts
91
+ unfinished exports at their deadline or on client disconnect. Callback-style
92
+ handlers remain traced until their response ends.
93
+
94
+ Delivery remains best effort: a slow or unavailable collector, or CPU throttled
95
+ after the response, can leave spans unacknowledged. Each traced invocation that
96
+ captured egress logs one `traces.function_egress.summary` JSON line once its
97
+ export settles. Every captured span is `acknowledged`, `rejected`, `abandoned`,
98
+ or `unacknowledged`; `dropped` spans were never captured. Only known loss
99
+ (`rejected`, `abandoned`, or `dropped`) logs at `warn`; unacknowledged spans log
100
+ at `info` because the collector may have stored them after the response. A
101
+ refusal on the retry after an uncertain first attempt stays unacknowledged. The runtime
102
+ limits each invocation to 100 spans and 10 batches (20 POST attempts including
103
+ retries), preserving span IDs across a retry. Failures preserve the handler's
104
+ result. The platform allows its own collector hostname through managed egress,
105
+ without making collector availability a readiness dependency. Existing deployed
106
+ revisions need redeployment to pick up the entrypoint. Standalone `functions dev`
107
+ does not mint trusted invocation context.
108
+
109
+ Same-deployment Atlas API calls outside instrumented primitive execution routes
110
+ are reported as Atlas API dependencies with registered route templates. Query
111
+ strings, dynamic path values and request/response bodies are not persisted.
112
+ Successful reads of a Lattice process or run link to the owning process; direct
113
+ HTTP run starts already use server-recorded spans and preserve the request chain.
114
+ Transport classification is shared with the platform host; function authors
115
+ do not need to import or configure instrumentation.
@@ -0,0 +1,37 @@
1
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ export type CacheOptions = {
5
+ ttlSeconds?: number;
6
+ };
7
+ export type CacheErrorCode = 'DISABLED' | 'UNAVAILABLE' | 'UNKNOWN_OUTCOME' | 'INVALID_ARGUMENT' | 'INVALID_VALUE';
8
+ export declare class CacheError extends Error {
9
+ readonly code: CacheErrorCode;
10
+ constructor(code: CacheErrorCode);
11
+ }
12
+ export interface CacheStore {
13
+ get(key: string): Promise<string | null>;
14
+ set(args: {
15
+ key: string;
16
+ value: string;
17
+ ttlSeconds: number;
18
+ onlyIfAbsent: boolean;
19
+ }): Promise<boolean>;
20
+ delete(key: string): Promise<boolean>;
21
+ close?(): void;
22
+ }
23
+ export interface CacheManager {
24
+ get<T extends JsonValue = JsonValue>(key: string): Promise<T | undefined>;
25
+ set(key: string, value: JsonValue, options?: CacheOptions): Promise<void>;
26
+ setIfAbsent(key: string, value: JsonValue, options?: CacheOptions): Promise<boolean>;
27
+ delete(key: string): Promise<boolean>;
28
+ getOrCompute<T extends JsonValue>(key: string, options: CacheOptions, loader: () => T | Promise<T>): Promise<T>;
29
+ }
30
+ export declare const CACHE_MAX_VALUE_BYTES: number;
31
+ export declare const CACHE_MAX_KEY_BYTES: 512;
32
+ export declare function serializeCacheValue(value: unknown): string;
33
+ export declare function createCacheManager({ store, defaultTtlSeconds, coalesceWaitMs, }: {
34
+ store: CacheStore | null;
35
+ defaultTtlSeconds: number;
36
+ coalesceWaitMs?: number;
37
+ }): CacheManager;
package/dist/cache.js ADDED
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CACHE_MAX_KEY_BYTES = exports.CACHE_MAX_VALUE_BYTES = exports.CacheError = void 0;
4
+ exports.serializeCacheValue = serializeCacheValue;
5
+ exports.createCacheManager = createCacheManager;
6
+ const config_1 = require("./config");
7
+ class CacheError extends Error {
8
+ code;
9
+ constructor(code) {
10
+ super(`Managed Function cache: ${code}`);
11
+ this.code = code;
12
+ this.name = 'CacheError';
13
+ }
14
+ }
15
+ exports.CacheError = CacheError;
16
+ exports.CACHE_MAX_VALUE_BYTES = config_1.MANAGED_FUNCTION_LIMITS.cache.max_value_bytes.max;
17
+ exports.CACHE_MAX_KEY_BYTES = config_1.MANAGED_FUNCTION_LIMITS.cache.max_key_bytes.max;
18
+ function validateArrayKeys(value, keys) {
19
+ if (keys.length !== value.length + 1 ||
20
+ keys.some((key) => key !== 'length' &&
21
+ (typeof key !== 'string' ||
22
+ !/^(0|[1-9][0-9]*)$/.test(key) ||
23
+ Number(key) >= value.length))) {
24
+ throw new CacheError('INVALID_VALUE');
25
+ }
26
+ }
27
+ function validateJson(value, ancestors = new Set()) {
28
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
29
+ return;
30
+ if (typeof value === 'number' &&
31
+ Number.isFinite(value) &&
32
+ !Object.is(value, -0))
33
+ return;
34
+ if (typeof value !== 'object' || ancestors.has(value))
35
+ throw new CacheError('INVALID_VALUE');
36
+ if (!Array.isArray(value) &&
37
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
38
+ throw new CacheError('INVALID_VALUE');
39
+ ancestors.add(value);
40
+ const keys = Reflect.ownKeys(value);
41
+ if (keys.some((key) => typeof key === 'symbol'))
42
+ throw new CacheError('INVALID_VALUE');
43
+ if (Array.isArray(value))
44
+ validateArrayKeys(value, keys);
45
+ for (const key of keys) {
46
+ if (Array.isArray(value) && key === 'length')
47
+ continue;
48
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
49
+ if (!descriptor?.enumerable || !('value' in descriptor))
50
+ throw new CacheError('INVALID_VALUE');
51
+ validateJson(descriptor.value, ancestors);
52
+ }
53
+ ancestors.delete(value);
54
+ }
55
+ function serializeCacheValue(value) {
56
+ try {
57
+ validateJson(value);
58
+ const encoded = JSON.stringify(value);
59
+ if (Buffer.byteLength(encoded) > exports.CACHE_MAX_VALUE_BYTES)
60
+ throw new CacheError('INVALID_VALUE');
61
+ return encoded;
62
+ }
63
+ catch {
64
+ throw new CacheError('INVALID_VALUE');
65
+ }
66
+ }
67
+ function validateKey(key) {
68
+ if (typeof key !== 'string' ||
69
+ key.length === 0 ||
70
+ Buffer.byteLength(key) > exports.CACHE_MAX_KEY_BYTES)
71
+ throw new CacheError('INVALID_ARGUMENT');
72
+ }
73
+ function ttl(options, fallback) {
74
+ if (!options || typeof options !== 'object' || Array.isArray(options))
75
+ throw new CacheError('INVALID_ARGUMENT');
76
+ const seconds = options.ttlSeconds ?? fallback;
77
+ if (!Number.isInteger(seconds) ||
78
+ seconds < 1 ||
79
+ seconds > config_1.CACHE_MAX_TTL_SECONDS)
80
+ throw new CacheError('INVALID_ARGUMENT');
81
+ return seconds;
82
+ }
83
+ function bypassable(error) {
84
+ return (error instanceof CacheError &&
85
+ ['DISABLED', 'UNAVAILABLE', 'UNKNOWN_OUTCOME'].includes(error.code));
86
+ }
87
+ function decode(encoded) {
88
+ try {
89
+ if (Buffer.byteLength(encoded) > exports.CACHE_MAX_VALUE_BYTES)
90
+ throw new CacheError('INVALID_VALUE');
91
+ const value = JSON.parse(encoded);
92
+ validateJson(value);
93
+ return value;
94
+ }
95
+ catch {
96
+ throw new CacheError('INVALID_VALUE');
97
+ }
98
+ }
99
+ function createCacheManager({ store, defaultTtlSeconds, coalesceWaitMs = 1000, }) {
100
+ ttl({}, defaultTtlSeconds);
101
+ const pending = new Map();
102
+ function backend() {
103
+ if (!store)
104
+ throw new CacheError('DISABLED');
105
+ return store;
106
+ }
107
+ const cache = {
108
+ async get(key) {
109
+ validateKey(key);
110
+ const encoded = await backend().get(key);
111
+ return encoded === null ? undefined : decode(encoded);
112
+ },
113
+ async set(key, value, options = {}) {
114
+ validateKey(key);
115
+ const seconds = ttl(options, defaultTtlSeconds);
116
+ const encoded = serializeCacheValue(value);
117
+ await backend().set({
118
+ key,
119
+ value: encoded,
120
+ ttlSeconds: seconds,
121
+ onlyIfAbsent: false,
122
+ });
123
+ },
124
+ async setIfAbsent(key, value, options = {}) {
125
+ validateKey(key);
126
+ const seconds = ttl(options, defaultTtlSeconds);
127
+ const encoded = serializeCacheValue(value);
128
+ return backend().set({
129
+ key,
130
+ value: encoded,
131
+ ttlSeconds: seconds,
132
+ onlyIfAbsent: true,
133
+ });
134
+ },
135
+ async delete(key) {
136
+ validateKey(key);
137
+ return backend().delete(key);
138
+ },
139
+ async getOrCompute(key, options, loader) {
140
+ validateKey(key);
141
+ ttl(options, defaultTtlSeconds);
142
+ if (typeof loader !== 'function')
143
+ throw new CacheError('INVALID_ARGUMENT');
144
+ try {
145
+ const hit = await cache.get(key);
146
+ if (hit !== undefined)
147
+ return hit;
148
+ }
149
+ catch (error) {
150
+ if (!bypassable(error))
151
+ throw error;
152
+ }
153
+ const inFlight = pending.get(key);
154
+ if (inFlight) {
155
+ let timer;
156
+ try {
157
+ const result = await Promise.race([
158
+ inFlight,
159
+ new Promise((resolve) => {
160
+ timer = setTimeout(() => resolve(undefined), coalesceWaitMs);
161
+ }),
162
+ ]);
163
+ if (result !== undefined)
164
+ return decode(result);
165
+ }
166
+ finally {
167
+ clearTimeout(timer);
168
+ }
169
+ }
170
+ const computation = (async () => {
171
+ const result = await loader();
172
+ const encoded = serializeCacheValue(result);
173
+ try {
174
+ await cache.set(key, result, options);
175
+ }
176
+ catch (error) {
177
+ if (!bypassable(error))
178
+ throw error;
179
+ }
180
+ return encoded;
181
+ })();
182
+ if (pending.size < 1000)
183
+ pending.set(key, computation);
184
+ try {
185
+ return decode(await computation);
186
+ }
187
+ finally {
188
+ if (pending.get(key) === computation)
189
+ pending.delete(key);
190
+ }
191
+ },
192
+ };
193
+ return cache;
194
+ }
@@ -0,0 +1,17 @@
1
+ export { shouldReportAtlasHttp } from './http-tracing';
2
+ import { z } from 'zod';
3
+ export { MANAGED_FUNCTION_LIMITS, MF_LIMITS_CAPS, MF_LIMITS_DEFAULTS, managedFunctionWorkloadLimitsSchema } from './limits';
4
+ export type { ManagedFunctionLimitCatalog } from './limits';
5
+ export declare const CACHE_MAX_TTL_SECONDS: 86400;
6
+ export declare const cacheManifestSchema: z.ZodObject<{
7
+ enabled: z.ZodDefault<z.ZodBoolean>;
8
+ tier: z.ZodDefault<z.ZodEnum<{
9
+ "custom-pico": "custom-pico";
10
+ "custom-micro": "custom-micro";
11
+ "custom-mini": "custom-mini";
12
+ }>>;
13
+ replicas: z.ZodDefault<z.ZodNumber>;
14
+ default_ttl_seconds: z.ZodDefault<z.ZodNumber>;
15
+ }, z.core.$strict>;
16
+ export type CacheConfiguration = z.infer<typeof cacheManifestSchema>;
17
+ export declare function readCacheConfiguration(manifest: unknown): CacheConfiguration;
package/dist/config.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cacheManifestSchema = exports.CACHE_MAX_TTL_SECONDS = exports.managedFunctionWorkloadLimitsSchema = exports.MF_LIMITS_DEFAULTS = exports.MF_LIMITS_CAPS = exports.MANAGED_FUNCTION_LIMITS = exports.shouldReportAtlasHttp = void 0;
4
+ exports.readCacheConfiguration = readCacheConfiguration;
5
+ var http_tracing_1 = require("./http-tracing");
6
+ Object.defineProperty(exports, "shouldReportAtlasHttp", { enumerable: true, get: function () { return http_tracing_1.shouldReportAtlasHttp; } });
7
+ const zod_1 = require("zod");
8
+ const limits_1 = require("./limits");
9
+ var limits_2 = require("./limits");
10
+ Object.defineProperty(exports, "MANAGED_FUNCTION_LIMITS", { enumerable: true, get: function () { return limits_2.MANAGED_FUNCTION_LIMITS; } });
11
+ Object.defineProperty(exports, "MF_LIMITS_CAPS", { enumerable: true, get: function () { return limits_2.MF_LIMITS_CAPS; } });
12
+ Object.defineProperty(exports, "MF_LIMITS_DEFAULTS", { enumerable: true, get: function () { return limits_2.MF_LIMITS_DEFAULTS; } });
13
+ Object.defineProperty(exports, "managedFunctionWorkloadLimitsSchema", { enumerable: true, get: function () { return limits_2.managedFunctionWorkloadLimitsSchema; } });
14
+ const limits = limits_1.MANAGED_FUNCTION_LIMITS.cache;
15
+ exports.CACHE_MAX_TTL_SECONDS = limits.default_ttl_seconds.max;
16
+ exports.cacheManifestSchema = zod_1.z
17
+ .object({
18
+ enabled: zod_1.z.boolean().default(limits.enabled.default),
19
+ tier: zod_1.z
20
+ .enum(limits.tier.allowed)
21
+ .default(limits.tier.default),
22
+ replicas: zod_1.z.number().int().min(limits.replicas.min).max(limits.replicas.max).default(limits.replicas.default),
23
+ default_ttl_seconds: zod_1.z
24
+ .number()
25
+ .int()
26
+ .min(limits.default_ttl_seconds.min)
27
+ .max(exports.CACHE_MAX_TTL_SECONDS)
28
+ .default(limits.default_ttl_seconds.default),
29
+ })
30
+ .strict();
31
+ function readCacheConfiguration(manifest) {
32
+ const cache = typeof manifest === 'object' && manifest !== null
33
+ ? Reflect.get(manifest, 'cache')
34
+ : undefined;
35
+ return exports.cacheManifestSchema.parse(cache ?? {});
36
+ }
@@ -0,0 +1 @@
1
+ export declare function shouldReportAtlasHttp(pathname: string, method: string): boolean;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldReportAtlasHttp = shouldReportAtlasHttp;
4
+ function shouldReportAtlasHttp(pathname, method) {
5
+ if (!pathname.startsWith('/api/'))
6
+ return false;
7
+ if (/^\/api\/(runtime-traces|traces)(\/|$)/.test(pathname))
8
+ return false;
9
+ if (pathname === '/api/artifact-studio/trace-context')
10
+ return false;
11
+ if (/^\/api\/managed-functions\/[^/]+\/invoke\/?$/.test(pathname))
12
+ return false;
13
+ if (/^\/api\/data\/v2\/[^/]+\/?$/.test(pathname))
14
+ return false;
15
+ if (method === 'POST' &&
16
+ /^\/api\/data-pipelines\/stages\/[^/]+\/run\/?$/.test(pathname))
17
+ return false;
18
+ if (pathname.startsWith('/api/storage/'))
19
+ return false;
20
+ if (method === 'POST' &&
21
+ /^\/api\/lattice\/(runs|processes\/[^/]+\/runs)\/?$/.test(pathname))
22
+ return false;
23
+ return true;
24
+ }
@@ -0,0 +1,13 @@
1
+ export { installFunctionTracing, runWithFunctionTracing } from './tracing';
2
+ export { createFunctionStorageClient } from './storage';
3
+ import { type CacheManager } from "./cache";
4
+ export { CacheError, createCacheManager } from "./cache";
5
+ export type { CacheManager, CacheOptions, CacheErrorCode, JsonValue, } from "./cache";
6
+ export { cacheManifestSchema, readCacheConfiguration } from "./config";
7
+ export type { CacheConfiguration } from "./config";
8
+ export interface ManagedFunctionContext {
9
+ cache: CacheManager;
10
+ }
11
+ export declare function createManagedFunctionContext({ env, }?: {
12
+ env?: NodeJS.ProcessEnv;
13
+ }): ManagedFunctionContext;
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = void 0;
4
+ exports.createManagedFunctionContext = createManagedFunctionContext;
5
+ var tracing_1 = require("./tracing");
6
+ Object.defineProperty(exports, "installFunctionTracing", { enumerable: true, get: function () { return tracing_1.installFunctionTracing; } });
7
+ Object.defineProperty(exports, "runWithFunctionTracing", { enumerable: true, get: function () { return tracing_1.runWithFunctionTracing; } });
8
+ const zod_1 = require("zod");
9
+ var storage_1 = require("./storage");
10
+ Object.defineProperty(exports, "createFunctionStorageClient", { enumerable: true, get: function () { return storage_1.createFunctionStorageClient; } });
11
+ const config_1 = require("./config");
12
+ const cache_1 = require("./cache");
13
+ const memory_1 = require("./memory");
14
+ var cache_2 = require("./cache");
15
+ Object.defineProperty(exports, "CacheError", { enumerable: true, get: function () { return cache_2.CacheError; } });
16
+ Object.defineProperty(exports, "createCacheManager", { enumerable: true, get: function () { return cache_2.createCacheManager; } });
17
+ var config_2 = require("./config");
18
+ Object.defineProperty(exports, "cacheManifestSchema", { enumerable: true, get: function () { return config_2.cacheManifestSchema; } });
19
+ Object.defineProperty(exports, "readCacheConfiguration", { enumerable: true, get: function () { return config_2.readCacheConfiguration; } });
20
+ const connectionSchema = zod_1.z
21
+ .object({
22
+ host: zod_1.z.string().min(1),
23
+ port: zod_1.z.number().int().min(1).max(65535),
24
+ serverIdentity: zod_1.z.string().min(1),
25
+ caCertificates: zod_1.z.array(zod_1.z.string().min(1)).min(1),
26
+ keyPrefix: zod_1.z.string().min(1),
27
+ })
28
+ .strict();
29
+ const runtimeSchema = zod_1.z
30
+ .object({
31
+ cache: config_1.cacheManifestSchema,
32
+ connection: connectionSchema.optional(),
33
+ })
34
+ .strict();
35
+ function createManagedFunctionContext({ env = process.env, } = {}) {
36
+ const local = env.SEQUENCE_FUNCTION_LOCAL === "1" &&
37
+ !env.K_SERVICE &&
38
+ !env.FUNCTION_TARGET;
39
+ const { cache, connection } = runtimeSchema.parse(env.SEQUENCE_FUNCTION_CACHE_CONFIG
40
+ ? JSON.parse(env.SEQUENCE_FUNCTION_CACHE_CONFIG)
41
+ : { cache: { enabled: false } });
42
+ if (cache.enabled && !connection && !local)
43
+ throw new Error("Enabled Managed Function cache is missing its private Valkey connection configuration");
44
+ const store = !cache.enabled
45
+ ? null
46
+ : connection
47
+ ? lazyValkeyStore(connection, local)
48
+ : new memory_1.MemoryStore();
49
+ return {
50
+ cache: (0, cache_1.createCacheManager)({
51
+ store,
52
+ defaultTtlSeconds: cache.default_ttl_seconds,
53
+ }),
54
+ };
55
+ }
56
+ function lazyValkeyStore(connection, local) {
57
+ let pending;
58
+ const client = () => (pending ??= import("./valkey.js").then(({ ValkeyStore }) => new ValkeyStore(connection, local)));
59
+ return {
60
+ get: async (key) => (await client()).get(key),
61
+ set: async (args) => (await client()).set(args),
62
+ delete: async (key) => (await client()).delete(key),
63
+ };
64
+ }
@@ -0,0 +1,153 @@
1
+ import { z } from 'zod';
2
+ export declare const MANAGED_FUNCTION_LIMITS: {
3
+ readonly environment: {
4
+ readonly functions: {
5
+ readonly max: 100;
6
+ readonly scope: "Non-archived functions across all organizations; undeployed shells count";
7
+ };
8
+ };
9
+ readonly workload: {
10
+ readonly memory_mb: {
11
+ readonly min: 128;
12
+ readonly max: 2048;
13
+ readonly default: 256;
14
+ readonly unit: "MiB per instance";
15
+ };
16
+ readonly timeout_seconds: {
17
+ readonly min: 1;
18
+ readonly max: 540;
19
+ readonly default: 60;
20
+ readonly unit: "seconds";
21
+ };
22
+ readonly min_instances: {
23
+ readonly min: 0;
24
+ readonly max: 10;
25
+ readonly default: 1;
26
+ readonly scope: "Cannot exceed max_instances; warm instances incur idle charges";
27
+ };
28
+ readonly max_instances: {
29
+ readonly min: 2;
30
+ readonly max: 10;
31
+ readonly default: 3;
32
+ readonly scope: "Autoscaling ceiling; must allow at least two instances, and GCP may temporarily exceed it";
33
+ };
34
+ readonly invoke_rate_per_minute: {
35
+ readonly min: 1;
36
+ readonly max: 600;
37
+ readonly default: 60;
38
+ readonly window_seconds: 60;
39
+ readonly scope: "Per function across all Atlas replicas in the environment; Redis-backed fixed window. Redis unavailability rejects invocations (503); key loss resets the window";
40
+ };
41
+ readonly max_request_bytes: {
42
+ readonly max: number;
43
+ readonly unit: "bytes";
44
+ readonly configurable: false;
45
+ };
46
+ readonly max_response_bytes: {
47
+ readonly max: number;
48
+ readonly unit: "bytes";
49
+ readonly configurable: false;
50
+ };
51
+ readonly cpu: {
52
+ readonly configurable: true;
53
+ readonly default: "auto";
54
+ readonly min: 1;
55
+ readonly max: 2;
56
+ readonly min_memory_mb_for_multiple_cpus: 512;
57
+ readonly scope: "auto uses provider memory-derived allocation; explicit CPU must be a whole vCPU. Multiple CPUs require the minimum memory listed here";
58
+ };
59
+ readonly concurrency: {
60
+ readonly configurable: true;
61
+ readonly min: 1;
62
+ readonly max: 80;
63
+ readonly default: 1;
64
+ readonly scope: "Simultaneous requests per instance; values above 1 require explicit cpu (not auto). Requests share instance CPU and memory";
65
+ };
66
+ };
67
+ readonly cache: {
68
+ readonly enabled: {
69
+ readonly default: false;
70
+ };
71
+ readonly tier: {
72
+ readonly allowed: readonly ["custom-pico", "custom-micro", "custom-mini"];
73
+ readonly default: "custom-pico";
74
+ };
75
+ readonly replicas: {
76
+ readonly min: 0;
77
+ readonly max: 5;
78
+ readonly default: 0;
79
+ };
80
+ readonly default_ttl_seconds: {
81
+ readonly min: 1;
82
+ readonly max: 86400;
83
+ readonly default: 30;
84
+ readonly unit: "seconds";
85
+ };
86
+ readonly max_key_bytes: {
87
+ readonly max: 512;
88
+ readonly unit: "bytes";
89
+ };
90
+ readonly max_value_bytes: {
91
+ readonly max: number;
92
+ readonly unit: "bytes";
93
+ };
94
+ };
95
+ readonly bundle: {
96
+ readonly files: {
97
+ readonly max: 2000;
98
+ };
99
+ readonly file_bytes: {
100
+ readonly max: number;
101
+ readonly unit: "bytes";
102
+ };
103
+ readonly source_bytes: {
104
+ readonly max: number;
105
+ readonly unit: "bytes unpacked";
106
+ };
107
+ readonly request_bytes: {
108
+ readonly max: number;
109
+ readonly unit: "bytes in deploy request";
110
+ };
111
+ };
112
+ };
113
+ export type ManagedFunctionLimitCatalog = Record<string, Record<string, {
114
+ min?: number;
115
+ max?: number;
116
+ default?: number | string | boolean;
117
+ allowed?: readonly string[];
118
+ unit?: string;
119
+ configurable?: boolean;
120
+ min_memory_mb_for_multiple_cpus?: number;
121
+ window_seconds?: number;
122
+ scope?: string;
123
+ }>>;
124
+ export declare const managedFunctionWorkloadLimitsSchema: z.ZodPrefault<z.ZodObject<{
125
+ memory_mb: z.ZodDefault<z.ZodNumber>;
126
+ cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodNumber]>>;
127
+ concurrency: z.ZodDefault<z.ZodNumber>;
128
+ timeout_seconds: z.ZodDefault<z.ZodNumber>;
129
+ min_instances: z.ZodDefault<z.ZodNumber>;
130
+ max_instances: z.ZodDefault<z.ZodNumber>;
131
+ invoke_rate_per_minute: z.ZodDefault<z.ZodNumber>;
132
+ }, z.core.$strip>>;
133
+ export declare const MF_LIMITS_CAPS: {
134
+ readonly cpu: 2;
135
+ readonly concurrency: 80;
136
+ readonly memoryMb: 2048;
137
+ readonly timeoutSeconds: 540;
138
+ readonly maxInstances: 10;
139
+ readonly invokeRatePerMinute: 600;
140
+ readonly maxRequestBytes: number;
141
+ readonly maxResponseBytes: number;
142
+ };
143
+ export declare const MF_LIMITS_DEFAULTS: {
144
+ readonly cpu: "auto";
145
+ readonly concurrency: 1;
146
+ readonly memoryMb: 256;
147
+ readonly timeoutSeconds: 60;
148
+ readonly minInstances: 1;
149
+ readonly maxInstances: 3;
150
+ readonly invokeRatePerMinute: 60;
151
+ readonly maxRequestBytes: number;
152
+ readonly maxResponseBytes: number;
153
+ };