@sequenceholdings/storage 0.1.0

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,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storageErrorSchema = exports.storageAccessSchema = exports.updateStorageAccessSchema = exports.storagePrincipalSchema = exports.uploadSessionSchema = exports.completeUploadSchema = exports.startUploadSchema = exports.storageChecksum = exports.updateNodeSchema = exports.createFolderSchema = exports.listStorageSchema = exports.storagePage = exports.listStorageOperationsSchema = exports.storageOperationScope = exports.operationSchema = exports.versionSchema = exports.nodeSchema = exports.workspaceSchema = exports.workspaceManifestSchema = exports.storageMetadata = exports.storagePath = exports.storageName = exports.storageLifecycle = exports.storageRole = exports.storageId = void 0;
4
+ const zod_1 = require("zod");
5
+ const paths_js_1 = require("./paths.js");
6
+ exports.storageId = zod_1.z.uuid();
7
+ exports.storageRole = zod_1.z.enum(['viewer', 'editor', 'admin', 'owner']);
8
+ exports.storageLifecycle = zod_1.z.enum(['active', 'trashed', 'purging']);
9
+ exports.storageName = zod_1.z.string().transform((value, ctx) => {
10
+ try {
11
+ return (0, paths_js_1.normalizeStorageName)(value);
12
+ }
13
+ catch (error) {
14
+ ctx.addIssue({
15
+ code: 'custom',
16
+ message: error instanceof Error ? error.message : 'Invalid name',
17
+ });
18
+ return zod_1.z.NEVER;
19
+ }
20
+ });
21
+ exports.storagePath = zod_1.z.string().transform((value, ctx) => {
22
+ try {
23
+ return (0, paths_js_1.splitStoragePath)(value).join('/');
24
+ }
25
+ catch (error) {
26
+ ctx.addIssue({
27
+ code: 'custom',
28
+ message: error instanceof Error ? error.message : 'Invalid path',
29
+ });
30
+ return zod_1.z.NEVER;
31
+ }
32
+ });
33
+ exports.storageMetadata = zod_1.z
34
+ .record(zod_1.z.string().max(64), zod_1.z.string().max(1024))
35
+ .refine((value) => Object.keys(value).length <= 32, 'At most 32 metadata fields');
36
+ exports.workspaceManifestSchema = zod_1.z.strictObject({
37
+ workspace: zod_1.z.strictObject({
38
+ name: zod_1.z.string().regex(/^[a-z][a-z0-9-]{1,62}$/),
39
+ description: zod_1.z.string().max(2000).default(''),
40
+ }),
41
+ });
42
+ exports.workspaceSchema = zod_1.z.object({
43
+ id: exports.storageId,
44
+ name: zod_1.z.string(),
45
+ description: zod_1.z.string(),
46
+ environment: zod_1.z.string(),
47
+ state: exports.storageLifecycle,
48
+ revision: zod_1.z.number().int(),
49
+ createdAt: zod_1.z.string(),
50
+ role: exports.storageRole,
51
+ quotaBytes: zod_1.z.number(),
52
+ usedBytes: zod_1.z.number(),
53
+ reservedBytes: zod_1.z.number(),
54
+ });
55
+ exports.nodeSchema = zod_1.z.object({
56
+ id: exports.storageId,
57
+ workspaceId: exports.storageId,
58
+ parentId: exports.storageId.nullable(),
59
+ name: zod_1.z.string(),
60
+ kind: zod_1.z.enum(['folder', 'file']),
61
+ state: exports.storageLifecycle,
62
+ restricted: zod_1.z.boolean(),
63
+ revision: zod_1.z.number().int(),
64
+ currentVersionId: exports.storageId.nullable(),
65
+ metadata: exports.storageMetadata,
66
+ createdAt: zod_1.z.string(),
67
+ updatedAt: zod_1.z.string(),
68
+ role: exports.storageRole.exclude(['owner']),
69
+ size: zod_1.z.number().nullable(),
70
+ contentType: zod_1.z.string().nullable(),
71
+ });
72
+ exports.versionSchema = zod_1.z.object({
73
+ id: exports.storageId,
74
+ fileId: exports.storageId,
75
+ revision: zod_1.z.number().int(),
76
+ size: zod_1.z.number(),
77
+ checksum: zod_1.z.string(),
78
+ contentType: zod_1.z.string(),
79
+ createdAt: zod_1.z.string(),
80
+ createdBy: zod_1.z.string(),
81
+ state: zod_1.z.enum(['active', 'purging']),
82
+ });
83
+ exports.operationSchema = zod_1.z.object({
84
+ id: exports.storageId,
85
+ workspaceId: exports.storageId,
86
+ targetId: exports.storageId.nullable(),
87
+ kind: zod_1.z.enum(['purge', 'delete_version']),
88
+ status: zod_1.z.enum(['queued', 'running', 'succeeded', 'failed']),
89
+ completed: zod_1.z.number().int(),
90
+ total: zod_1.z.number().int(),
91
+ error: zod_1.z.string().nullable(),
92
+ createdAt: zod_1.z.string(),
93
+ updatedAt: zod_1.z.string(),
94
+ /** The caller may retry once failed: its initiator, the workspace Owner, or a Sequence operator. */
95
+ canRetry: zod_1.z.boolean(),
96
+ });
97
+ /** `mine` lists the caller's own jobs; `workspace` every job (Admins and Sequence operators). */
98
+ exports.storageOperationScope = zod_1.z.enum(['mine', 'workspace']);
99
+ exports.listStorageOperationsSchema = zod_1.z.object({
100
+ before: exports.storageId.optional(),
101
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(50),
102
+ scope: exports.storageOperationScope.default('mine'),
103
+ });
104
+ const storagePage = (item) => zod_1.z.object({
105
+ items: zod_1.z.array(item),
106
+ nextCursor: zod_1.z.string().nullable(),
107
+ });
108
+ exports.storagePage = storagePage;
109
+ exports.listStorageSchema = zod_1.z.object({
110
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(50),
111
+ cursor: zod_1.z.string().max(2048).optional(),
112
+ q: zod_1.z.string().max(255).default(''),
113
+ sort: zod_1.z.enum(['name', 'newest']).default('name'),
114
+ });
115
+ exports.createFolderSchema = zod_1.z.strictObject({
116
+ parentId: exports.storageId.nullable().default(null),
117
+ name: exports.storageName,
118
+ /** A replay by the same principal returns the folder this key created. */
119
+ idempotencyKey: zod_1.z.string().min(1).max(128).optional(),
120
+ });
121
+ exports.updateNodeSchema = zod_1.z.strictObject({
122
+ expectedRevision: zod_1.z.number().int().nonnegative(),
123
+ name: exports.storageName.optional(),
124
+ parentId: exports.storageId.nullable().optional(),
125
+ metadata: exports.storageMetadata.optional(),
126
+ });
127
+ /** CRC32C in GCS's encoding: base64 of the big-endian uint32. */
128
+ exports.storageChecksum = zod_1.z.string().regex(/^[A-Za-z0-9+/]{6}==$/);
129
+ exports.startUploadSchema = zod_1.z
130
+ .strictObject({
131
+ parentId: exports.storageId.nullable().default(null),
132
+ path: exports.storagePath,
133
+ fileId: exports.storageId.optional(),
134
+ expectedRevision: zod_1.z.number().int().nonnegative().optional(),
135
+ size: zod_1.z.number().int().nonnegative().safe().optional(),
136
+ contentType: zod_1.z
137
+ .string()
138
+ .max(255)
139
+ .regex(/^[\w!#$&^.+-]+\/[\w!#$&^.+-]+$/)
140
+ .default('application/octet-stream'),
141
+ /** CRC32C the producer knows in advance; completion rejects any other object. */
142
+ checksum: exports.storageChecksum.optional(),
143
+ idempotencyKey: zod_1.z.string().min(1).max(128),
144
+ })
145
+ .refine((v) => Boolean(v.fileId) === (v.expectedRevision !== undefined), {
146
+ message: 'Replacement requires both fileId and expectedRevision',
147
+ });
148
+ exports.completeUploadSchema = zod_1.z.strictObject({
149
+ /**
150
+ * CRC32C the SDK computed over the source it sent, from byte zero. Completion
151
+ * rejects and aborts the session when the stored object differs. Optional
152
+ * until every in-repo consumer sends it; then it becomes required.
153
+ */
154
+ crc32c: exports.storageChecksum.optional(),
155
+ });
156
+ exports.uploadSessionSchema = zod_1.z.object({
157
+ id: exports.storageId,
158
+ fileId: exports.storageId,
159
+ uploadUrl: zod_1.z.url().nullable(),
160
+ expiresAt: zod_1.z.string(),
161
+ state: zod_1.z.enum(['pending', 'complete', 'aborted']),
162
+ versionId: exports.storageId.nullable(),
163
+ maxBytes: zod_1.z.number().int(),
164
+ });
165
+ exports.storagePrincipalSchema = zod_1.z.strictObject({
166
+ type: zod_1.z.enum(['user', 'group', 'organization', 'service_account']),
167
+ id: zod_1.z.string().min(1).max(255),
168
+ role: exports.storageRole.exclude(['owner']),
169
+ });
170
+ exports.updateStorageAccessSchema = zod_1.z.strictObject({
171
+ expectedRevision: zod_1.z.number().int().nonnegative(),
172
+ restricted: zod_1.z.boolean(),
173
+ grants: zod_1.z.array(exports.storagePrincipalSchema).max(40),
174
+ });
175
+ exports.storageAccessSchema = zod_1.z.object({
176
+ revision: zod_1.z.number().int(),
177
+ restricted: zod_1.z.boolean(),
178
+ grants: zod_1.z.array(exports.storagePrincipalSchema),
179
+ owner: zod_1.z.string().nullable(),
180
+ inherited: zod_1.z.boolean(),
181
+ workspaceAdminOverride: zod_1.z.literal(true),
182
+ displayNames: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
183
+ });
184
+ exports.storageErrorSchema = zod_1.z.object({
185
+ error: zod_1.z.object({ code: zod_1.z.string(), message: zod_1.z.string() }),
186
+ });
@@ -0,0 +1,11 @@
1
+ /** Incremental CRC32C: `update` any split of the input, then `digest`. */
2
+ export declare class Crc32c {
3
+ #private;
4
+ update(bytes: Uint8Array): this;
5
+ /** The checksum of everything passed to `update` so far, as a uint32. */
6
+ value(): number;
7
+ /** The checksum in GCS's encoding. Does not reset; `update` may continue. */
8
+ digest(): string;
9
+ }
10
+ /** One-shot CRC32C of `bytes` in GCS's base64 encoding. */
11
+ export declare function crc32c(bytes: Uint8Array): string;
package/dist/crc32c.js ADDED
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Crc32c = void 0;
4
+ exports.crc32c = crc32c;
5
+ /**
6
+ * CRC32C (Castagnoli), the checksum GCS records for every object. Digests use
7
+ * GCS's encoding: base64 of the big-endian uint32, as in `x-goog-hash: crc32c=`
8
+ * and the object resource's `crc32c` field.
9
+ *
10
+ * Slicing-by-8 over eight 256-entry tables: one table lookup per input byte,
11
+ * eight bytes per loop iteration. Pure TypeScript so the SDK runs unchanged in
12
+ * browsers, Artifact sandboxes and Node without a native dependency.
13
+ */
14
+ const CASTAGNOLI_REFLECTED = 0x82f63b78;
15
+ function buildTables() {
16
+ const tables = Array.from({ length: 8 }, () => new Uint32Array(256));
17
+ const [base] = tables;
18
+ for (let n = 0; n < 256; n++) {
19
+ let c = n;
20
+ for (let k = 0; k < 8; k++)
21
+ c = c & 1 ? (c >>> 1) ^ CASTAGNOLI_REFLECTED : c >>> 1;
22
+ base[n] = c;
23
+ }
24
+ for (let n = 0; n < 256; n++) {
25
+ let c = base[n];
26
+ for (let k = 1; k < 8; k++) {
27
+ c = base[c & 0xff] ^ (c >>> 8);
28
+ tables[k][n] = c;
29
+ }
30
+ }
31
+ return tables;
32
+ }
33
+ const [T0, T1, T2, T3, T4, T5, T6, T7] = buildTables();
34
+ /** Incremental CRC32C: `update` any split of the input, then `digest`. */
35
+ class Crc32c {
36
+ #crc = 0xffffffff;
37
+ update(bytes) {
38
+ let crc = this.#crc;
39
+ let i = 0;
40
+ const end8 = bytes.length - (bytes.length % 8);
41
+ for (; i < end8; i += 8) {
42
+ const low = crc ^
43
+ (bytes[i] |
44
+ (bytes[i + 1] << 8) |
45
+ (bytes[i + 2] << 16) |
46
+ (bytes[i + 3] << 24));
47
+ crc =
48
+ T7[low & 0xff] ^
49
+ T6[(low >>> 8) & 0xff] ^
50
+ T5[(low >>> 16) & 0xff] ^
51
+ T4[low >>> 24] ^
52
+ T3[bytes[i + 4]] ^
53
+ T2[bytes[i + 5]] ^
54
+ T1[bytes[i + 6]] ^
55
+ T0[bytes[i + 7]];
56
+ }
57
+ for (; i < bytes.length; i++)
58
+ crc = T0[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
59
+ this.#crc = crc;
60
+ return this;
61
+ }
62
+ /** The checksum of everything passed to `update` so far, as a uint32. */
63
+ value() {
64
+ return (this.#crc ^ 0xffffffff) >>> 0;
65
+ }
66
+ /** The checksum in GCS's encoding. Does not reset; `update` may continue. */
67
+ digest() {
68
+ const value = this.value();
69
+ return btoa(String.fromCharCode(value >>> 24, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff));
70
+ }
71
+ }
72
+ exports.Crc32c = Crc32c;
73
+ /** One-shot CRC32C of `bytes` in GCS's base64 encoding. */
74
+ function crc32c(bytes) {
75
+ return new Crc32c().update(bytes).digest();
76
+ }
@@ -0,0 +1,6 @@
1
+ export * from './contracts.js';
2
+ export * from './paths.js';
3
+ export * from './client.js';
4
+ export * from './transfer.js';
5
+ export * from './crc32c.js';
6
+ export * from './scope.js';
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./contracts.js"), exports);
18
+ __exportStar(require("./paths.js"), exports);
19
+ __exportStar(require("./client.js"), exports);
20
+ __exportStar(require("./transfer.js"), exports);
21
+ __exportStar(require("./crc32c.js"), exports);
22
+ __exportStar(require("./scope.js"), exports);
@@ -0,0 +1,4 @@
1
+ export declare const MAX_STORAGE_DEPTH = 16;
2
+ /** Case-sensitive, NFC names. Paths never undergo URL decoding or traversal resolution. */
3
+ export declare function normalizeStorageName(name: string): string;
4
+ export declare function splitStoragePath(path: string): string[];
package/dist/paths.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_STORAGE_DEPTH = void 0;
4
+ exports.normalizeStorageName = normalizeStorageName;
5
+ exports.splitStoragePath = splitStoragePath;
6
+ // Leave room for role and group rewrites under OpenFGA’s 25-node resolve limit.
7
+ // The maximum inherited chain is covered by authz/model.fga.yaml.
8
+ exports.MAX_STORAGE_DEPTH = 16;
9
+ /** Case-sensitive, NFC names. Paths never undergo URL decoding or traversal resolution. */
10
+ function normalizeStorageName(name) {
11
+ const normalized = name.normalize('NFC');
12
+ if (!normalized ||
13
+ normalized !== normalized.trim() ||
14
+ normalized === '.' ||
15
+ normalized === '..' ||
16
+ /[\/\\\u0000-\u001f\u007f]/u.test(normalized) ||
17
+ new TextEncoder().encode(normalized).byteLength > 255)
18
+ throw new Error('Names must be 1–255 UTF-8 bytes, without separators, control characters, or surrounding whitespace');
19
+ return normalized;
20
+ }
21
+ function splitStoragePath(path) {
22
+ const parts = path.split('/');
23
+ if (parts.length > exports.MAX_STORAGE_DEPTH ||
24
+ new TextEncoder().encode(path).byteLength > 4096) {
25
+ throw new Error(`Path exceeds the ${exports.MAX_STORAGE_DEPTH}-level or 4096-byte limit`);
26
+ }
27
+ return parts.map(normalizeStorageName);
28
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ /** Maximum consumer reach. This declaration never grants resource permissions. */
3
+ export declare const storageScopesSchema: z.ZodRecord<z.ZodUUID, z.ZodEnum<{
4
+ viewer: "viewer";
5
+ editor: "editor";
6
+ admin: "admin";
7
+ }>>;
8
+ export type StorageScopes = z.infer<typeof storageScopesSchema>;
9
+ export declare function readStorageScopes(manifest: unknown): StorageScopes;
10
+ /** Catalog discovery and workspace provisioning are not delegated to consumers. */
11
+ export declare function storageScopeAllows({ scopes, method, pathname, }: {
12
+ scopes: StorageScopes;
13
+ method: string;
14
+ pathname: string;
15
+ }): boolean;
package/dist/scope.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storageScopesSchema = void 0;
4
+ exports.readStorageScopes = readStorageScopes;
5
+ exports.storageScopeAllows = storageScopeAllows;
6
+ const zod_1 = require("zod");
7
+ /** Maximum consumer reach. This declaration never grants resource permissions. */
8
+ exports.storageScopesSchema = zod_1.z
9
+ .record(zod_1.z.uuid(), zod_1.z.enum(['viewer', 'editor', 'admin']))
10
+ .refine((value) => Object.keys(value).length <= 64, 'At most 64 workspace bindings');
11
+ function readStorageScopes(manifest) {
12
+ const parsed = zod_1.z
13
+ .object({
14
+ capabilities: zod_1.z
15
+ .object({ storage: exports.storageScopesSchema.optional() })
16
+ .optional(),
17
+ })
18
+ .safeParse(manifest);
19
+ return parsed.success ? (parsed.data.capabilities?.storage ?? {}) : {};
20
+ }
21
+ /** Catalog discovery and workspace provisioning are not delegated to consumers. */
22
+ function storageScopeAllows({ scopes, method, pathname, }) {
23
+ const match = /^\/api\/storage\/workspaces\/([0-9a-f-]{36})(\/.*)?$/.exec(pathname);
24
+ if (!match)
25
+ return false;
26
+ const role = scopes[match[1]];
27
+ if (!role)
28
+ return false;
29
+ const suffix = match[2] ?? '';
30
+ const upper = method.toUpperCase();
31
+ if (suffix === '/owner' || (!suffix && upper !== 'GET'))
32
+ return false;
33
+ if (suffix.endsWith('/access'))
34
+ return role === 'admin';
35
+ if (suffix.endsWith('/purge') ||
36
+ (/\/versions\/[0-9a-f-]+$/.test(suffix) && upper === 'DELETE') ||
37
+ suffix.endsWith('/retry'))
38
+ return role === 'admin';
39
+ if (upper === 'GET' || (upper === 'POST' && suffix.endsWith('/download')))
40
+ return true;
41
+ return role === 'editor' || role === 'admin';
42
+ }
@@ -0,0 +1,34 @@
1
+ export type StorageUploadSource = Blob | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
2
+ export interface StorageTransferOptions {
3
+ signal?: AbortSignal;
4
+ onProgress?: (uploadedBytes: number) => void;
5
+ fetch?: typeof fetch;
6
+ /** Multiples of 256 KiB, between 256 KiB and 32 MiB. Defaults to 8 MiB. */
7
+ chunkBytes?: number;
8
+ /** Use one request for a small Blob. Large/streaming inputs remain chunked. */
9
+ method?: 'simple' | 'resumable';
10
+ }
11
+ export interface StorageTransferResult {
12
+ /**
13
+ * CRC32C of the whole source as this call read it from byte zero, in GCS's
14
+ * encoding. Pass it to upload completion so the server can compare it with
15
+ * the stored object.
16
+ */
17
+ crc32c: string;
18
+ }
19
+ export declare class StorageTransferError extends Error {
20
+ readonly status?: number | undefined;
21
+ constructor(message: string, status?: number | undefined);
22
+ }
23
+ /**
24
+ * Resume a session with the original source, starting at byte zero. The
25
+ * acknowledged prefix is read again (and discarded with bounded memory) so the
26
+ * returned CRC32C covers the whole object, not just the bytes this call sent.
27
+ * Session URLs are bearer credentials: never persist them in logs or telemetry.
28
+ */
29
+ export declare function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...options }: StorageTransferOptions & {
30
+ uploadUrl: string;
31
+ source: StorageUploadSource;
32
+ size?: number;
33
+ maxBytes: number;
34
+ }): Promise<StorageTransferResult>;