Skip to content

Commit d718d18

Browse files
Copilothotlong
andcommitted
Task 1.2: Merge scoped-storage.zod.ts into object-storage.zod.ts
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 036f08a commit d718d18

4 files changed

Lines changed: 167 additions & 83 deletions

File tree

packages/spec/src/system/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export * from './manifest.zod';
2525
export * from './plugin.zod';
2626
export * from './plugin-capability.zod';
2727
export * from './context.zod';
28-
export * from './scoped-storage.zod';
2928
export * from './datasource.zod';
3029

3130
// Driver Protocol
@@ -38,7 +37,7 @@ export * from './driver/postgres.zod';
3837
// Data Engine Protocol
3938
export * from './data-engine.zod';
4039

41-
// Object Storage Protocol
40+
// Object Storage Protocol (includes scoped storage functionality)
4241
export * from './object-storage.zod';
4342

4443
// Cache Protocol

packages/spec/src/system/object-storage.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { describe, it, expect } from 'vitest';
22
import {
3+
StorageScopeSchema,
4+
FileMetadataSchema,
35
StorageProviderSchema,
46
StorageAclSchema,
57
StorageClassSchema,
@@ -13,6 +15,8 @@ import {
1315
BucketConfigSchema,
1416
StorageConnectionSchema,
1517
ObjectStorageConfigSchema,
18+
type StorageScope,
19+
type FileMetadata,
1620
type StorageProvider,
1721
type StorageAcl,
1822
type StorageClass,
@@ -27,6 +31,54 @@ import {
2731
type ObjectStorageConfig,
2832
} from './object-storage.zod';
2933

34+
describe('StorageScopeSchema', () => {
35+
it('should accept valid storage scopes', () => {
36+
expect(() => StorageScopeSchema.parse('global')).not.toThrow();
37+
expect(() => StorageScopeSchema.parse('tenant')).not.toThrow();
38+
expect(() => StorageScopeSchema.parse('user')).not.toThrow();
39+
expect(() => StorageScopeSchema.parse('session')).not.toThrow();
40+
expect(() => StorageScopeSchema.parse('temp')).not.toThrow();
41+
expect(() => StorageScopeSchema.parse('cache')).not.toThrow();
42+
expect(() => StorageScopeSchema.parse('data')).not.toThrow();
43+
expect(() => StorageScopeSchema.parse('logs')).not.toThrow();
44+
expect(() => StorageScopeSchema.parse('config')).not.toThrow();
45+
expect(() => StorageScopeSchema.parse('public')).not.toThrow();
46+
});
47+
48+
it('should reject invalid storage scopes', () => {
49+
expect(() => StorageScopeSchema.parse('invalid')).toThrow();
50+
});
51+
});
52+
53+
describe('FileMetadataSchema', () => {
54+
it('should accept valid file metadata', () => {
55+
const metadata: FileMetadata = {
56+
path: '/uploads/file.txt',
57+
name: 'file.txt',
58+
size: 1024,
59+
mimeType: 'text/plain',
60+
lastModified: '2024-01-15T10:30:00.000Z',
61+
created: '2024-01-15T10:00:00.000Z',
62+
etag: '"abc123"',
63+
};
64+
65+
expect(() => FileMetadataSchema.parse(metadata)).not.toThrow();
66+
});
67+
68+
it('should accept metadata without optional fields', () => {
69+
const metadata: FileMetadata = {
70+
path: '/uploads/file.txt',
71+
name: 'file.txt',
72+
size: 1024,
73+
mimeType: 'text/plain',
74+
lastModified: '2024-01-15T10:30:00.000Z',
75+
created: '2024-01-15T10:00:00.000Z',
76+
};
77+
78+
expect(() => FileMetadataSchema.parse(metadata)).not.toThrow();
79+
});
80+
});
81+
3082
describe('StorageProviderSchema', () => {
3183
it('should accept valid storage providers', () => {
3284
expect(() => StorageProviderSchema.parse('s3')).not.toThrow();
@@ -611,6 +663,53 @@ describe('ObjectStorageConfigSchema', () => {
611663
expect(storage.provider).toBe('s3');
612664
expect(storage.enabled).toBe(true); // default
613665
expect(storage.buckets).toHaveLength(0); // default
666+
expect(storage.scope).toBe('global'); // default
667+
});
668+
669+
it('should accept storage config with scope', () => {
670+
const storage = ObjectStorageConfigSchema.parse({
671+
name: 'tenant_storage',
672+
label: 'Tenant Storage',
673+
provider: 's3',
674+
scope: 'tenant',
675+
connection: {
676+
accessKeyId: 'key',
677+
secretAccessKey: 'secret',
678+
},
679+
});
680+
681+
expect(storage.scope).toBe('tenant');
682+
});
683+
684+
it('should accept storage config with location and quota', () => {
685+
const storage = ObjectStorageConfigSchema.parse({
686+
name: 'local_storage',
687+
label: 'Local Storage',
688+
provider: 'local',
689+
scope: 'temp',
690+
connection: {},
691+
location: '/var/tmp/storage',
692+
quota: 10737418240, // 10GB
693+
});
694+
695+
expect(storage.location).toBe('/var/tmp/storage');
696+
expect(storage.quota).toBe(10737418240);
697+
});
698+
699+
it('should accept storage config with options', () => {
700+
const storage = ObjectStorageConfigSchema.parse({
701+
name: 'custom_storage',
702+
label: 'Custom Storage',
703+
provider: 'minio',
704+
connection: {},
705+
options: {
706+
endpoint: 'http://localhost:9000',
707+
pathStyle: true,
708+
},
709+
});
710+
711+
expect(storage.options?.endpoint).toBe('http://localhost:9000');
712+
expect(storage.options?.pathStyle).toBe(true);
614713
});
615714

616715
it('should accept full storage config with buckets', () => {

packages/spec/src/system/object-storage.zod.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import { SystemIdentifierSchema } from '../shared/identifiers.zod';
44
/**
55
* Object Storage Protocol
66
*
7-
* Defines schemas for object storage systems (S3, Azure Blob, GCS, MinIO)
8-
* that provide persistent file storage capabilities for ObjectStack applications.
9-
*
10-
* This protocol supports:
7+
* Unified storage protocol that combines:
8+
* - Object storage systems (S3, Azure Blob, GCS, MinIO)
9+
* - Scoped storage configuration (temp, cache, data, logs, config, public)
1110
* - Multi-cloud storage providers
1211
* - Bucket/container configuration
1312
* - Access control and permissions
@@ -16,6 +15,45 @@ import { SystemIdentifierSchema } from '../shared/identifiers.zod';
1615
* - Multipart uploads for large files
1716
*/
1817

18+
// ============================================================================
19+
// Storage Scope Protocol (formerly from scoped-storage.zod.ts)
20+
// ============================================================================
21+
22+
/**
23+
* Storage Scope Enum
24+
* Defines the lifecycle and persistence guarantee of the storage area.
25+
*/
26+
export const StorageScopeSchema = z.enum([
27+
'global', // Global application-wide storage
28+
'tenant', // Tenant-scoped storage (multi-tenant apps)
29+
'user', // User-scoped storage
30+
'session', // Session-scoped storage (ephemeral)
31+
'temp', // Ephemeral, cleared on restart
32+
'cache', // Ephemeral, survives restarts, cleared on LRU/Expiration
33+
'data', // Persistent, backed up
34+
'logs', // Append-only, rotated
35+
'config', // Read-heavy, versioned
36+
'public' // Publicly accessible static assets
37+
]).describe('Storage scope classification');
38+
39+
export type StorageScope = z.infer<typeof StorageScopeSchema>;
40+
41+
/**
42+
* File Metadata Schema
43+
* Standardized file attribute structure
44+
*/
45+
export const FileMetadataSchema = z.object({
46+
path: z.string().describe('File path'),
47+
name: z.string().describe('File name'),
48+
size: z.number().int().describe('File size in bytes'),
49+
mimeType: z.string().describe('MIME type'),
50+
lastModified: z.string().datetime().describe('Last modified timestamp'),
51+
created: z.string().datetime().describe('Creation timestamp'),
52+
etag: z.string().optional().describe('Entity tag'),
53+
});
54+
55+
export type FileMetadata = z.infer<typeof FileMetadataSchema>;
56+
1957
// ============================================================================
2058
// Enums
2159
// ============================================================================
@@ -403,6 +441,7 @@ export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
403441
* name: 'production_storage',
404442
* label: 'Production File Storage',
405443
* provider: 's3',
444+
* scope: 'global',
406445
* connection: {
407446
* accessKeyId: '${AWS_ACCESS_KEY_ID}',
408447
* secretAccessKey: '${AWS_SECRET_ACCESS_KEY}',
@@ -424,9 +463,33 @@ export const ObjectStorageConfigSchema = z.object({
424463
name: SystemIdentifierSchema.describe('Storage configuration identifier'),
425464
label: z.string().describe('Display label'),
426465
provider: StorageProviderSchema.describe('Primary storage provider'),
466+
467+
/**
468+
* Storage scope
469+
* Defines the lifecycle and access pattern for this storage
470+
*/
471+
scope: StorageScopeSchema.optional().default('global').describe('Storage scope'),
472+
427473
connection: StorageConnectionSchema.describe('Connection credentials'),
428474
buckets: z.array(BucketConfigSchema).default([]).describe('Configured buckets'),
429475
defaultBucket: z.string().optional().describe('Default bucket name for operations'),
476+
477+
/**
478+
* Base path or location
479+
* For local/scoped storage configurations
480+
*/
481+
location: z.string().optional().describe('Root path (local) or base location'),
482+
483+
/**
484+
* Storage quota in bytes
485+
*/
486+
quota: z.number().int().positive().optional().describe('Max size in bytes'),
487+
488+
/**
489+
* Provider-specific options
490+
*/
491+
options: z.record(z.any()).optional().describe('Provider-specific configuration options'),
492+
430493
enabled: z.boolean().default(true).describe('Enable this storage configuration'),
431494
description: z.string().optional().describe('Configuration description'),
432495
});

packages/spec/src/system/scoped-storage.zod.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)