@lifesi/genesis 0.1.1 → 0.1.2

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.
@@ -0,0 +1,127 @@
1
+ import type { GenesisClient } from './client.js';
2
+ import { type HivesResourceOptions } from './hives-resources-transport.js';
3
+ export interface HivesCorsRule {
4
+ allowed_origins: string[];
5
+ allowed_methods: string[];
6
+ allowed_headers: string[];
7
+ exposed_headers: string[];
8
+ max_age_secs: number;
9
+ }
10
+ export interface HivesBucket {
11
+ id: string;
12
+ tenant_id: string;
13
+ project_id: string;
14
+ name: string;
15
+ region: string;
16
+ visibility: string;
17
+ status: string;
18
+ object_count: number | null;
19
+ size_bytes: number | null;
20
+ statistics_scope: 'unmeasured_provider_inventory' | 'retained_legacy_metadata';
21
+ created_at: string;
22
+ provider_binding: {
23
+ provider: 'gcs';
24
+ region: string;
25
+ state: string;
26
+ requires_reconciliation: boolean;
27
+ } | null;
28
+ operation_state: string | null;
29
+ }
30
+ export interface HivesObject {
31
+ key: string;
32
+ bucket_id: string;
33
+ size_bytes: number;
34
+ etag: string;
35
+ last_modified: string;
36
+ generation: string;
37
+ content_type: string | null;
38
+ metadata: Record<string, string> | null;
39
+ metadata_scope: string;
40
+ }
41
+ export interface HivesObjectAcknowledgement {
42
+ bucket_id: string;
43
+ key: string;
44
+ size_bytes: number;
45
+ etag: string;
46
+ generation: string;
47
+ content_type: string | null;
48
+ }
49
+ export interface HivesObjectList {
50
+ objects: HivesObject[];
51
+ common_prefixes: string[];
52
+ is_truncated: boolean;
53
+ next_continuation_token: string | null;
54
+ total: number | null;
55
+ }
56
+ export interface HivesBucketList {
57
+ data: HivesBucket[];
58
+ pagination: {
59
+ page: number;
60
+ per_page: number;
61
+ total: number;
62
+ total_pages: number;
63
+ has_next: boolean;
64
+ has_prev: boolean;
65
+ };
66
+ }
67
+ export interface HivesCreateBucket {
68
+ name: string;
69
+ region?: 'us-central1';
70
+ visibility?: 'private' | 'publicread';
71
+ cors_rules?: HivesCorsRule[];
72
+ }
73
+ export interface HivesPresign {
74
+ key: string;
75
+ method?: 'GET' | 'PUT';
76
+ expires_in_secs?: number;
77
+ content_type?: string;
78
+ content_length?: number;
79
+ }
80
+ export interface HivesPresignedUrl {
81
+ url: string;
82
+ expires_at: string;
83
+ method: 'GET' | 'PUT';
84
+ required_headers?: Record<string, string>;
85
+ }
86
+ export declare function hivesObjectKey(key: string): string;
87
+ export declare function hivesCells(client: GenesisClient): {
88
+ list(project: string, query?: {
89
+ page?: number;
90
+ per_page?: number;
91
+ }, options?: HivesResourceOptions): Promise<HivesBucketList>;
92
+ get(project: string, id: string, options?: HivesResourceOptions): Promise<HivesBucket>;
93
+ /** A pending provider_binding is an uncertain outcome, never a completed-create claim. */
94
+ create(project: string, body: HivesCreateBucket, options?: HivesResourceOptions): Promise<HivesBucket>;
95
+ remove(project: string, id: string, options?: HivesResourceOptions): Promise<{
96
+ id: string;
97
+ success: boolean;
98
+ message: string;
99
+ }>;
100
+ reconcile(project: string, id: string, options?: HivesResourceOptions): Promise<{
101
+ bucket: HivesBucket;
102
+ outcome: string;
103
+ }>;
104
+ cors: {
105
+ get: (project: string, id: string, options?: HivesResourceOptions) => Promise<HivesCorsRule[]>;
106
+ set: (project: string, id: string, rules: HivesCorsRule[], options?: HivesResourceOptions) => Promise<HivesCorsRule[]>;
107
+ };
108
+ objects: {
109
+ list(project: string, id: string, query?: {
110
+ prefix?: string;
111
+ delimiter?: string;
112
+ continuation_token?: string;
113
+ max_keys?: number;
114
+ }, options?: HivesResourceOptions): Promise<HivesObjectList>;
115
+ put(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: HivesResourceOptions & {
116
+ content_type?: string;
117
+ }): Promise<HivesObjectAcknowledgement>;
118
+ remove: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<void>;
119
+ download: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<Response>;
120
+ /** Existing flat-key path compatibility; nested keys use canonical collection methods above. */
121
+ putFlat(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: HivesResourceOptions & {
122
+ content_type?: string;
123
+ }): Promise<HivesObjectAcknowledgement>;
124
+ removeFlat: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<void>;
125
+ };
126
+ presign(project: string, id: string, body: HivesPresign, options?: HivesResourceOptions): Promise<HivesPresignedUrl>;
127
+ };
@@ -0,0 +1,151 @@
1
+ import { HIVES_OBJECT_BYTES, hivesResourceCall, hivesResourceId, hivesObjectBody, hivesContentType, resourceError } from './hives-resources-transport.js';
2
+ export function hivesObjectKey(key) {
3
+ const bytes = new TextEncoder().encode(key);
4
+ if (typeof key !== 'string' || !key || bytes.length > 1024 || new TextDecoder('utf-8', { fatal: true }).decode(bytes) !== key
5
+ || key.startsWith('/') || key.endsWith('/') || /[\u0000-\u001f\u007f-\u009f\\]/.test(key) || key.split('/').some(p => !p || p === '.' || p === '..'))
6
+ throw resourceError(0);
7
+ let binary = '';
8
+ for (const byte of bytes)
9
+ binary += String.fromCharCode(byte);
10
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
11
+ }
12
+ function cors(rules) {
13
+ if (!Array.isArray(rules) || rules.length > 100)
14
+ throw resourceError(0);
15
+ return rules.map(rule => {
16
+ if (!rule || typeof rule !== 'object' || Object.keys(rule).some(k => !['allowed_origins', 'allowed_methods', 'allowed_headers', 'exposed_headers', 'max_age_secs'].includes(k))
17
+ || !Array.isArray(rule.allowed_origins) || !rule.allowed_origins.length || rule.allowed_origins.length > 100
18
+ || !Array.isArray(rule.allowed_methods) || !rule.allowed_methods.length || rule.allowed_methods.length > 6
19
+ || !Array.isArray(rule.allowed_headers) || rule.allowed_headers.length !== 1 || rule.allowed_headers[0] !== '*'
20
+ || !Array.isArray(rule.exposed_headers) || rule.exposed_headers.length > 100
21
+ || !Number.isInteger(rule.max_age_secs) || rule.max_age_secs < 0 || rule.max_age_secs > 86400)
22
+ throw resourceError(0);
23
+ for (const origin of rule.allowed_origins) {
24
+ if (origin === '*')
25
+ continue;
26
+ if (typeof origin !== 'string')
27
+ throw resourceError(0);
28
+ let url;
29
+ try {
30
+ url = new URL(origin);
31
+ }
32
+ catch {
33
+ throw resourceError(0);
34
+ }
35
+ if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password || url.pathname !== '/' || url.search || url.hash)
36
+ throw resourceError(0);
37
+ }
38
+ if (rule.allowed_methods.some(m => !['GET', 'HEAD', 'PUT', 'POST', 'DELETE'].includes(m))
39
+ || rule.exposed_headers.some(h => typeof h !== 'string' || h.length > 256 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(h)))
40
+ throw resourceError(0);
41
+ return { allowed_origins: [...rule.allowed_origins], allowed_methods: [...rule.allowed_methods], allowed_headers: ['*'], exposed_headers: [...rule.exposed_headers], max_age_secs: rule.max_age_secs };
42
+ });
43
+ }
44
+ function bucket(value, project, id) {
45
+ if (!value || typeof value !== 'object' || value.project_id !== project || typeof value.id !== 'string' || id !== undefined && value.id !== id
46
+ || !['unmeasured_provider_inventory', 'retained_legacy_metadata'].includes(value.statistics_scope)
47
+ || [value.object_count, value.size_bytes].some(v => v !== null && (!Number.isSafeInteger(v) || v < 0))
48
+ || (value.statistics_scope === 'unmeasured_provider_inventory' && (value.object_count !== null || value.size_bytes !== null)))
49
+ throw resourceError(0);
50
+ if (value.statistics_scope === 'unmeasured_provider_inventory' && !value.provider_binding)
51
+ throw resourceError(0);
52
+ if (value.provider_binding !== null && (!value.provider_binding || value.provider_binding.provider !== 'gcs' || typeof value.provider_binding.state !== 'string' || typeof value.provider_binding.requires_reconciliation !== 'boolean'))
53
+ throw resourceError(0);
54
+ return Object.fromEntries(['id', 'tenant_id', 'project_id', 'name', 'region', 'visibility', 'status', 'object_count', 'size_bytes', 'statistics_scope', 'created_at', 'provider_binding', 'operation_state'].map(k => [k, value[k]]));
55
+ }
56
+ function object(value, id) {
57
+ if (!value || value.bucket_id !== id || typeof value.key !== 'string' || !Number.isSafeInteger(value.size_bytes) || value.size_bytes < 0 || typeof value.generation !== 'string')
58
+ throw resourceError(0);
59
+ hivesObjectKey(value.key);
60
+ return Object.fromEntries(['key', 'bucket_id', 'size_bytes', 'etag', 'last_modified', 'generation', 'content_type', 'metadata', 'metadata_scope'].map(k => [k, value[k]]));
61
+ }
62
+ function acknowledgement(value, key, id, size) {
63
+ if (!value || value.bucket_id !== id || value.key !== key || value.size_bytes !== size || !Number.isSafeInteger(value.size_bytes) || value.size_bytes < 0 || typeof value.etag !== 'string' || typeof value.generation !== 'string' || value.content_type !== null && typeof value.content_type !== 'string')
64
+ throw resourceError(0);
65
+ return { key: value.key, bucket_id: value.bucket_id, size_bytes: value.size_bytes, etag: value.etag, generation: value.generation, content_type: value.content_type };
66
+ }
67
+ export function hivesCells(client) {
68
+ const ids = (project_id, bucket_id) => ({ project_id: hivesResourceId(project_id), ...(bucket_id === undefined ? {} : { bucket_id: hivesResourceId(bucket_id) }) });
69
+ const call = (id, input, options, mode = 'json', limit = 2 * 1024 * 1024) => hivesResourceCall(client, id, input, options, mode, limit, 100_000);
70
+ return {
71
+ async list(project, query = {}, options) {
72
+ if (Object.keys(query).some(k => !['page', 'per_page'].includes(k)) || query.page !== undefined && (!Number.isInteger(query.page) || query.page < 1 || query.page > 10000) || query.per_page !== undefined && (!Number.isInteger(query.per_page) || query.per_page < 1 || query.per_page > 100))
73
+ throw resourceError(0);
74
+ const result = await call('hives_list_buckets', { ...query, ...ids(project) }, options);
75
+ if (!result || !Array.isArray(result.data) || !result.pagination)
76
+ throw resourceError(0);
77
+ return { ...result, data: result.data.map(row => bucket(row, project)) };
78
+ },
79
+ async get(project, id, options) { return bucket(await call('hives_get_bucket', ids(project, id), options), project, id); },
80
+ /** A pending provider_binding is an uncertain outcome, never a completed-create claim. */
81
+ async create(project, body, options) {
82
+ if (!body || typeof body.name !== 'string' || !/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(body.name) || body.region !== undefined && body.region !== 'us-central1'
83
+ || body.visibility !== undefined && !['private', 'publicread'].includes(body.visibility) || Object.keys(body).some(k => !['name', 'region', 'visibility', 'cors_rules'].includes(k)))
84
+ throw resourceError(0);
85
+ const checked = { ...body, ...(body.cors_rules === undefined ? {} : { cors_rules: cors(body.cors_rules) }) };
86
+ return bucket(await call('hives_create_bucket', { ...ids(project), body: checked }, options), project);
87
+ },
88
+ async remove(project, id, options) {
89
+ const result = await call('hives_delete_bucket', ids(project, id), options);
90
+ if (!result || result.id !== id || result.success !== true)
91
+ throw resourceError(0);
92
+ return result;
93
+ },
94
+ async reconcile(project, id, options) {
95
+ const result = await call('hives_reconcile', ids(project, id), options);
96
+ if (!result || typeof result.outcome !== 'string' || result.bucket?.id !== id)
97
+ throw resourceError(0);
98
+ return { ...result, bucket: bucket(result.bucket, project) };
99
+ },
100
+ cors: {
101
+ get: async (project, id, options) => cors(await call('hives_get_cors_config', ids(project, id), options)),
102
+ set: async (project, id, rules, options) => cors(await call('hives_set_cors_config', { ...ids(project, id), body: { rules: cors(rules) } }, options)),
103
+ },
104
+ objects: {
105
+ async list(project, id, query = {}, options) {
106
+ if (query.max_keys !== undefined && (!Number.isInteger(query.max_keys) || query.max_keys < 1 || query.max_keys > 1000))
107
+ throw resourceError(0);
108
+ const result = await call('hives_list_objects', { ...query, ...ids(project, id) }, options);
109
+ if (!result || !Array.isArray(result.objects) || !Array.isArray(result.common_prefixes) || typeof result.is_truncated !== 'boolean' || result.total !== null)
110
+ throw resourceError(0);
111
+ return { ...result, objects: result.objects.map(row => object(row, id)) };
112
+ },
113
+ async put(project, id, key, bytes, options = {}) {
114
+ const body = await hivesObjectBody(bytes);
115
+ const content_type = hivesContentType(options.content_type);
116
+ return acknowledgement(await call('hives_upload_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key), body, content_type }, options), key, id, body.byteLength);
117
+ },
118
+ remove: (project, id, key, options) => call('hives_delete_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key) }, options, 'empty'),
119
+ download: (project, id, key, options) => call('hives_download_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key) }, options, 'binary', HIVES_OBJECT_BYTES),
120
+ /** Existing flat-key path compatibility; nested keys use canonical collection methods above. */
121
+ async putFlat(project, id, key, bytes, options = {}) {
122
+ hivesObjectKey(key);
123
+ if (!/^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(key))
124
+ throw resourceError(0);
125
+ const body = await hivesObjectBody(bytes);
126
+ return acknowledgement(await call('hives_upload_object', { ...ids(project, id), key, body, content_type: hivesContentType(options.content_type) }, options), key, id, body.byteLength);
127
+ },
128
+ removeFlat: (project, id, key, options) => { hivesObjectKey(key); if (!/^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(key))
129
+ throw resourceError(0); return call('hives_delete_object', { ...ids(project, id), key }, options, 'empty'); },
130
+ },
131
+ async presign(project, id, body, options) {
132
+ hivesObjectKey(body.key);
133
+ const method = body.method ?? 'GET';
134
+ if (!['GET', 'PUT'].includes(method) || body.expires_in_secs !== undefined && (!Number.isInteger(body.expires_in_secs) || body.expires_in_secs < 60 || body.expires_in_secs > 900)
135
+ || method === 'PUT' && (body.content_length === undefined || !Number.isInteger(body.content_length) || body.content_length < 0 || body.content_length > HIVES_OBJECT_BYTES))
136
+ throw resourceError(0);
137
+ if (body.content_type !== undefined)
138
+ hivesContentType(body.content_type);
139
+ const result = await call('hives_generate_presigned_url', { ...ids(project, id), body }, options);
140
+ if (!result || typeof result.url !== 'string' || result.method !== method || typeof result.expires_at !== 'string')
141
+ throw resourceError(0);
142
+ const url = new URL(result.url);
143
+ if (url.protocol !== 'https:' || url.hostname !== 'storage.googleapis.com' || url.username || url.password || url.hash)
144
+ throw resourceError(0);
145
+ const headers = result.required_headers;
146
+ if (headers !== undefined && (!headers || typeof headers !== 'object' || Array.isArray(headers) || Object.entries(headers).some(([k, v]) => !['content-type', 'content-length', 'x-goog-if-generation-match'].includes(k.toLowerCase()) || typeof v !== 'string' || /[\u0000-\u001f\u007f]/.test(v))))
147
+ throw resourceError(0);
148
+ return { url: result.url, expires_at: result.expires_at, method: result.method, ...(headers ? { required_headers: { ...headers } } : {}) };
149
+ },
150
+ };
151
+ }
@@ -0,0 +1,28 @@
1
+ /** Environment metadata never carries stored values. Reveals are explicit. */
2
+ export declare function validateHivesEnvironment(input: unknown): Record<string, string>;
3
+ export declare function hivesEnvironmentMetadata(value: unknown, project: unknown, updated: boolean): {
4
+ project_id: string;
5
+ updated: {
6
+ name: string;
7
+ }[];
8
+ data?: undefined;
9
+ pagination?: undefined;
10
+ } | {
11
+ project_id: string;
12
+ data: {
13
+ name: string;
14
+ }[];
15
+ pagination: {
16
+ page: unknown;
17
+ per_page: unknown;
18
+ total: unknown;
19
+ total_pages: unknown;
20
+ has_next: boolean;
21
+ has_prev: boolean;
22
+ };
23
+ updated?: undefined;
24
+ };
25
+ export declare function hivesEnvironmentReveal(value: unknown, key: unknown): {
26
+ name: string;
27
+ value: string;
28
+ };
@@ -0,0 +1,58 @@
1
+ /** Environment metadata never carries stored values. Reveals are explicit. */
2
+ export function validateHivesEnvironment(input) {
3
+ if (!input || typeof input !== 'object' || Array.isArray(input))
4
+ throw new Error('Environment input must be a flat JSON map of names to string values.');
5
+ const entries = Object.entries(input);
6
+ if (entries.length < 1 || entries.length > 100)
7
+ throw new Error('Environment updates require 1–100 names.');
8
+ const result = Object.create(null);
9
+ for (const [name, value] of entries) {
10
+ if (name.length > 255 || !/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
11
+ throw new Error('Environment names must start with a letter and contain only letters, numbers and underscores (up to 255 characters).');
12
+ if (typeof value !== 'string' || new TextEncoder().encode(value).length > 65536)
13
+ throw new Error('Environment values must be strings of at most 65,536 UTF-8 bytes. Empty values are allowed.');
14
+ result[name] = value;
15
+ }
16
+ if (new TextEncoder().encode(JSON.stringify(result)).length > 2 * 1024 * 1024)
17
+ throw new Error('Environment input exceeds 2 MiB. Split it into smaller batches.');
18
+ return result;
19
+ }
20
+ function record(value) {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value))
22
+ throw new Error('Hives returned invalid environment metadata.');
23
+ return value;
24
+ }
25
+ function names(value) {
26
+ if (!Array.isArray(value))
27
+ throw new Error('Hives returned invalid environment metadata.');
28
+ return value.map((value) => {
29
+ const row = record(value);
30
+ if (typeof row.name !== 'string')
31
+ throw new Error('Hives returned invalid environment metadata.');
32
+ return { name: row.name };
33
+ });
34
+ }
35
+ export function hivesEnvironmentMetadata(value, project, updated) {
36
+ const data = record(value);
37
+ if (typeof project !== 'string' || data.project_id !== project)
38
+ throw new Error('Hives returned environment metadata for a different project.');
39
+ if (updated)
40
+ return { project_id: project, updated: names(data.updated) };
41
+ const pagination = record(data.pagination);
42
+ for (const key of ['page', 'per_page', 'total', 'total_pages']) {
43
+ if (!Number.isSafeInteger(pagination[key]) || pagination[key] < (key === 'page' || key === 'per_page' ? 1 : 0))
44
+ throw new Error('Hives returned invalid environment pagination.');
45
+ }
46
+ if (typeof pagination.has_next !== 'boolean' || typeof pagination.has_prev !== 'boolean')
47
+ throw new Error('Hives returned invalid environment pagination.');
48
+ return { project_id: project, data: names(data.data), pagination: {
49
+ page: pagination.page, per_page: pagination.per_page, total: pagination.total, total_pages: pagination.total_pages,
50
+ has_next: pagination.has_next, has_prev: pagination.has_prev,
51
+ } };
52
+ }
53
+ export function hivesEnvironmentReveal(value, key) {
54
+ const data = record(value);
55
+ if (typeof key !== 'string' || data.name !== key || typeof data.value !== 'string' || new TextEncoder().encode(data.value).length > 65536)
56
+ throw new Error('Hives returned invalid environment reveal data.');
57
+ return { name: data.name, value: data.value };
58
+ }
@@ -12,6 +12,8 @@ export async function closeHivesNodeTransport() {
12
12
  await current?.destroy();
13
13
  }
14
14
  export function hivesOperationBudget(id) {
15
+ if (['hives_list_buckets', 'hives_create_bucket', 'hives_get_bucket', 'hives_delete_bucket', 'hives_list_objects', 'hives_upload_object', 'hives_upload_object_by_key', 'hives_delete_object', 'hives_delete_object_by_key', 'hives_download_object_by_key', 'hives_get_cors_config', 'hives_set_cors_config', 'hives_generate_presigned_url', 'hives_reconcile'].includes(id))
16
+ return 100_000;
15
17
  if (id === 'hives_cancel_deployment' || id === 'hives_delete_deployment')
16
18
  return 330_000;
17
19
  if (['hives_create_deployment', 'hives_upload_deployment', 'hives_invoke_deployment', 'hives_invoke_function', 'hives_invoke_function_async'].includes(id))