@serve.zone/interfaces 17.4.0 → 17.5.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.
package/changelog.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-07-23 - 17.5.0
4
+
5
+ ### Features
6
+
7
+ - add portable named filesystem and object-storage App Store request contracts
8
+ - add backend-neutral storage capability advertisements and resolved binding lifecycle contracts
9
+ - keep physical backend configuration and credential values outside portable and resolved public storage DTOs
10
+
3
11
  ## 2026-07-23 - 17.4.0
4
12
 
5
13
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '17.4.0',
6
+ version: '17.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -1,9 +1,72 @@
1
1
  import type { IServiceSecretFile } from '../data/service.js';
2
2
  import type { IServiceDomainRoute, IServicePublicPortMapping, IServiceTargetPort } from '../data/serviceports.js';
3
+ import type { IStorageCapacityRequest, IStorageClassRequirement, TFilesystemStorageAccessMode, TObjectStorageAccessMode, TObjectStorageDelivery, TStorageReclaimPolicy, TStorageResourceKind, TStorageSnapshotMode } from '../platform/storage.js';
3
4
  export type TAppStorePlatformRequirement = 'mongodb' | 's3' | 'clickhouse' | 'valkey' | 'mariadb';
5
+ export interface IAppStorePlatformRequirements {
6
+ mongodb?: boolean;
7
+ /**
8
+ * @deprecated `s3: true` is normalized to one legacy object-storage binding.
9
+ * New templates use a named objectStorage request.
10
+ */
11
+ s3?: boolean;
12
+ clickhouse?: boolean;
13
+ valkey?: boolean;
14
+ mariadb?: boolean;
15
+ }
4
16
  export type TAppStoreSourceType = 'inline' | 'repoManifest' | 'dockerImage';
5
17
  export type TAppStoreTrackingMode = 'tag' | 'digest';
6
18
  export type TAppStoreUpgradeStrategy = 'semver' | 'branch' | 'dockerDigest';
19
+ export declare const appStoreStorageFeatureIds: {
20
+ readonly bindingsV1: "storage.bindings.v1";
21
+ readonly filesystemV1: "storage.filesystem.v1";
22
+ readonly objectStorageV1: "storage.object-storage.v1";
23
+ readonly objectStorageSecretFileV1: "storage.object-storage.secret-file.v1";
24
+ };
25
+ export type TAppStoreStorageFeatureId = typeof appStoreStorageFeatureIds[keyof typeof appStoreStorageFeatureIds];
26
+ export type TAppStoreStoragePurpose = 'runtime' | 'database' | 'registry' | 'backup';
27
+ /**
28
+ * A template-local logical class. It states portable policy requirements and
29
+ * preferences; fulfillment adapters select their own compatible policy class.
30
+ */
31
+ export interface IAppStoreStorageClass {
32
+ kind: TStorageResourceKind;
33
+ purpose: TAppStoreStoragePurpose;
34
+ required?: IStorageClassRequirement;
35
+ preferred?: IStorageClassRequirement;
36
+ }
37
+ export interface IAppStoreStorageRequestBase {
38
+ /** Stable identity used across upgrades, migrations, and restores. */
39
+ id: string;
40
+ kind: TStorageResourceKind;
41
+ /** Key in the containing version config's storageClasses record. */
42
+ storageClass: string;
43
+ capacity?: IStorageCapacityRequest;
44
+ reclaimPolicy: TStorageReclaimPolicy;
45
+ }
46
+ export interface IAppStoreFilesystemProtection {
47
+ backup?: 'required';
48
+ snapshots?: Exclude<TStorageSnapshotMode, 'none'>;
49
+ consistency?: 'crashConsistent' | 'applicationConsistent';
50
+ }
51
+ export interface IAppStoreFilesystemStorageRequest extends IAppStoreStorageRequestBase {
52
+ kind: 'filesystem';
53
+ mountPath: string;
54
+ accessMode: TFilesystemStorageAccessMode;
55
+ protection?: IAppStoreFilesystemProtection;
56
+ }
57
+ export interface IAppStoreObjectStorageProtection {
58
+ backup?: 'required';
59
+ versioning?: 'required';
60
+ /** Minimum provider-enforced retention duration. */
61
+ retentionDays?: number;
62
+ }
63
+ export interface IAppStoreObjectStorageRequest extends IAppStoreStorageRequestBase {
64
+ kind: 'objectStorage';
65
+ accessMode: TObjectStorageAccessMode;
66
+ delivery: TObjectStorageDelivery;
67
+ protection?: IAppStoreObjectStorageProtection;
68
+ }
69
+ export type TAppStoreStorageRequest = IAppStoreFilesystemStorageRequest | IAppStoreObjectStorageRequest;
7
70
  export interface IAppStoreInlineSource {
8
71
  type: 'inline';
9
72
  }
@@ -119,9 +182,21 @@ export interface IAppStoreVersionConfig {
119
182
  /** Edge/coretraffic public TCP/UDP exposure, distinct from Docker publishedPorts. */
120
183
  publicPortMappings?: IServicePublicPortMapping[];
121
184
  envVars?: IAppStoreEnvVar[];
185
+ /**
186
+ * @deprecated Legacy volume syntax. New templates use storageClasses and
187
+ * storageRequests. Resolver normalization must reject legacy physical driver
188
+ * options that cannot be represented portably.
189
+ */
122
190
  volumes?: TAppStoreVolumeSpec[];
191
+ /**
192
+ * Template-local logical policy classes. Keys are stable within the
193
+ * template; they are not Onebox or Cloudly operator class names.
194
+ */
195
+ storageClasses?: Record<string, IAppStoreStorageClass>;
196
+ /** Stable named filesystem and managed object-storage requests. */
197
+ storageRequests?: TAppStoreStorageRequest[];
123
198
  publishedPorts?: IAppStorePublishedPort[];
124
- platformRequirements?: Partial<Record<TAppStorePlatformRequirement, boolean>>;
199
+ platformRequirements?: IAppStorePlatformRequirements;
125
200
  minOneboxVersion?: string;
126
201
  minCloudlyVersion?: string;
127
202
  appStoreVersion?: string;
@@ -1,2 +1,7 @@
1
- export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9hcHBzdG9yZS90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
1
+ export const appStoreStorageFeatureIds = {
2
+ bindingsV1: 'storage.bindings.v1',
3
+ filesystemV1: 'storage.filesystem.v1',
4
+ objectStorageV1: 'storage.object-storage.v1',
5
+ objectStorageSecretFileV1: 'storage.object-storage.secret-file.v1',
6
+ };
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9hcHBzdG9yZS90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFpQ0EsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUc7SUFDdkMsVUFBVSxFQUFFLHFCQUFxQjtJQUNqQyxZQUFZLEVBQUUsdUJBQXVCO0lBQ3JDLGVBQWUsRUFBRSwyQkFBMkI7SUFDNUMseUJBQXlCLEVBQUUsdUNBQXVDO0NBQzFELENBQUMifQ==
@@ -8,6 +8,8 @@ import * as objectstorage from './objectstorage.js';
8
8
  import * as pushnotification from './pushnotification.js';
9
9
  import * as sip from './sip.js';
10
10
  import * as sms from './sms.js';
11
+ import * as storage from './storage.js';
11
12
  import * as types from './types.js';
12
- export { ai, backup, database, email, letter, logging, objectstorage, pushnotification, sip, sms, types, };
13
+ export { ai, backup, database, email, letter, logging, objectstorage, pushnotification, sip, sms, storage, types, };
14
+ export * from './storage.js';
13
15
  export * from './types.js';
@@ -8,7 +8,9 @@ import * as objectstorage from './objectstorage.js';
8
8
  import * as pushnotification from './pushnotification.js';
9
9
  import * as sip from './sip.js';
10
10
  import * as sms from './sms.js';
11
+ import * as storage from './storage.js';
11
12
  import * as types from './types.js';
12
- export { ai, backup, database, email, letter, logging, objectstorage, pushnotification, sip, sms, types, };
13
+ export { ai, backup, database, email, letter, logging, objectstorage, pushnotification, sip, sms, storage, types, };
14
+ export * from './storage.js';
13
15
  export * from './types.js';
14
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9wbGF0Zm9ybS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUM5QixPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssUUFBUSxNQUFNLGVBQWUsQ0FBQztBQUMxQyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUNwQyxPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssYUFBYSxNQUFNLG9CQUFvQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUMxRCxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUVwQyxPQUFPLEVBQ0wsRUFBRSxFQUNGLE1BQU0sRUFDTixRQUFRLEVBQ1IsS0FBSyxFQUNMLE1BQU0sRUFDTixPQUFPLEVBQ1AsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixHQUFHLEVBQ0gsR0FBRyxFQUNILEtBQUssR0FDTixDQUFDO0FBRUYsY0FBYyxZQUFZLENBQUMifQ==
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9wbGF0Zm9ybS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUM5QixPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssUUFBUSxNQUFNLGVBQWUsQ0FBQztBQUMxQyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUNwQyxPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssYUFBYSxNQUFNLG9CQUFvQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxnQkFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUMxRCxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUVwQyxPQUFPLEVBQ0wsRUFBRSxFQUNGLE1BQU0sRUFDTixRQUFRLEVBQ1IsS0FBSyxFQUNMLE1BQU0sRUFDTixPQUFPLEVBQ1AsYUFBYSxFQUNiLGdCQUFnQixFQUNoQixHQUFHLEVBQ0gsR0FBRyxFQUNILE9BQU8sRUFDUCxLQUFLLEdBQ04sQ0FBQztBQUVGLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsWUFBWSxDQUFDIn0=
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Backend-neutral storage vocabulary shared by App Store manifests, control
3
+ * planes, and fulfillment adapters. Physical providers and runtime attachment
4
+ * details intentionally do not belong in these contracts.
5
+ */
6
+ export type TStorageResourceKind = 'filesystem' | 'objectStorage';
7
+ /**
8
+ * Compatibility bridge for the existing generic platform capability. New
9
+ * filesystem requests intentionally have no generic IPlatformBinding mapping.
10
+ */
11
+ export declare const storageKindPlatformCapabilityIds: {
12
+ readonly objectStorage: "objectstorage";
13
+ };
14
+ export type TFilesystemStorageAccessMode = 'ReadWriteOnce' | 'ReadOnlyMany' | 'ReadWriteMany';
15
+ export type TObjectStorageAccessMode = 'readOnly' | 'readWrite';
16
+ export type TStoragePerformanceTier = 'standard' | 'highIops' | 'capacity';
17
+ export type TStorageDurability = 'ephemeral' | 'persistent';
18
+ export type TStorageTopology = 'singleNode' | 'multiNode';
19
+ export type TStorageSnapshotMode = 'none' | 'portable' | 'native';
20
+ export type TStorageReclaimPolicy = 'retain' | 'delete';
21
+ /**
22
+ * A positive base-2 quantity written as an integer followed by KiB, MiB, GiB,
23
+ * or TiB, for example `20GiB`. Parsers must reject every other representation.
24
+ */
25
+ export type TStorageCapacityQuantity = string;
26
+ export interface IStorageCapacityRequest {
27
+ request: TStorageCapacityQuantity;
28
+ /**
29
+ * A hard upper bound. A request with a limit can only be fulfilled by a
30
+ * class whose granted capabilities include hardQuota.
31
+ */
32
+ limit?: TStorageCapacityQuantity;
33
+ }
34
+ /**
35
+ * App-facing requirements describe policy, never a concrete provider class.
36
+ * Values in a `required` block are hard constraints; values in `preferred`
37
+ * influence selection but may not weaken required constraints.
38
+ */
39
+ export interface IStorageClassRequirement {
40
+ performanceTier?: TStoragePerformanceTier;
41
+ durability?: TStorageDurability;
42
+ topology?: TStorageTopology;
43
+ hardQuota?: true;
44
+ snapshots?: Exclude<TStorageSnapshotMode, 'none'>;
45
+ backup?: true;
46
+ encryptedInTransit?: true;
47
+ }
48
+ export interface IStorageGrantedCapabilities {
49
+ performanceTier: TStoragePerformanceTier;
50
+ durability: TStorageDurability;
51
+ topology: TStorageTopology;
52
+ hardQuota: boolean;
53
+ snapshots: TStorageSnapshotMode;
54
+ backup: boolean;
55
+ encryptedInTransit: boolean;
56
+ }
57
+ export interface IStorageClassCapabilitiesBase {
58
+ /** Opaque operator-defined policy class ID, not a physical backend name. */
59
+ classId: string;
60
+ /** Changes whenever matching behavior or guarantees change. */
61
+ revision: string;
62
+ kind: TStorageResourceKind;
63
+ performanceTiers: TStoragePerformanceTier[];
64
+ durabilities: TStorageDurability[];
65
+ topologies: TStorageTopology[];
66
+ hardQuota: boolean;
67
+ snapshotModes: TStorageSnapshotMode[];
68
+ backup: boolean;
69
+ encryptedInTransit: boolean;
70
+ }
71
+ export interface IFilesystemStorageClassCapabilities extends IStorageClassCapabilitiesBase {
72
+ kind: 'filesystem';
73
+ accessModes: TFilesystemStorageAccessMode[];
74
+ }
75
+ export interface IObjectStorageClassCapabilities extends IStorageClassCapabilitiesBase {
76
+ kind: 'objectStorage';
77
+ accessModes: TObjectStorageAccessMode[];
78
+ versioning: boolean;
79
+ retention: boolean;
80
+ }
81
+ export type TStorageClassCapabilities = IFilesystemStorageClassCapabilities | IObjectStorageClassCapabilities;
82
+ /**
83
+ * Fulfillment adapters advertise only portable guarantees. Provider names,
84
+ * addresses, mount options, principals, and credential values are private.
85
+ */
86
+ export interface IStorageCapabilityAdvertisement {
87
+ schemaVersion: 1;
88
+ featureIds: string[];
89
+ classes: TStorageClassCapabilities[];
90
+ }
91
+ export interface IObjectStorageEnvironmentDelivery {
92
+ type: 'environment';
93
+ /**
94
+ * Explicit target keys prevent two named bindings from silently overwriting
95
+ * each other. Every required field must have a distinct environment key.
96
+ */
97
+ keys: {
98
+ endpoint: string;
99
+ bucket: string;
100
+ region: string;
101
+ accessKeyId: string;
102
+ secretAccessKey: string;
103
+ sessionToken?: string;
104
+ };
105
+ }
106
+ export interface IObjectStorageSecretFileDelivery {
107
+ type: 'secretFile';
108
+ /** Canonical absolute path below /run/secrets/. */
109
+ targetPath: string;
110
+ /**
111
+ * JSON object with endpoint, bucket, region, accessKeyId,
112
+ * secretAccessKey, and optional sessionToken fields.
113
+ */
114
+ format: 'servezone-object-storage-v1';
115
+ }
116
+ export type TObjectStorageDelivery = IObjectStorageEnvironmentDelivery | IObjectStorageSecretFileDelivery;
117
+ export type TResolvedStorageBindingStatus = 'requested' | 'provisioning' | 'ready' | 'degraded' | 'failed' | 'releasing' | 'retained' | 'released';
118
+ export type TStorageBindingFailureCode = 'unsupportedCapability' | 'policyUnavailable' | 'provisionFailed' | 'attachmentFailed' | 'quotaExceeded' | 'credentialsUnavailable' | 'backendUnavailable' | 'migrationRequired';
119
+ export interface IStorageBindingFailure {
120
+ code: TStorageBindingFailureCode;
121
+ message: string;
122
+ retryable: boolean;
123
+ observedAt: number;
124
+ }
125
+ export interface IResolvedStoragePolicyRef {
126
+ /** Opaque operator-defined policy class ID selected for this request. */
127
+ classId: string;
128
+ revision: string;
129
+ }
130
+ export interface IResolvedStorageCapacity {
131
+ requested?: TStorageCapacityQuantity;
132
+ granted?: TStorageCapacityQuantity;
133
+ limit?: TStorageCapacityQuantity;
134
+ }
135
+ export interface IResolvedStorageBindingBase {
136
+ schemaVersion: 1;
137
+ /** Stable binding/allocation identity. */
138
+ id: string;
139
+ serviceId: string;
140
+ /** Stable App Store storageRequests[].id. */
141
+ requestId: string;
142
+ /**
143
+ * Canonical digest of the normalized request. Adapters use it to distinguish
144
+ * an idempotent reconciliation from a migration.
145
+ */
146
+ requestDigest: string;
147
+ kind: TStorageResourceKind;
148
+ generation: number;
149
+ observedGeneration: number;
150
+ status: TResolvedStorageBindingStatus;
151
+ policy: IResolvedStoragePolicyRef;
152
+ capabilities: IStorageGrantedCapabilities;
153
+ /** Opaque resource identity; never a host path, export, or provider URL. */
154
+ resourceRef: string;
155
+ capacity?: IResolvedStorageCapacity;
156
+ failure?: IStorageBindingFailure;
157
+ createdAt?: number;
158
+ updatedAt?: number;
159
+ }
160
+ export interface IResolvedFilesystemStorageBinding extends IResolvedStorageBindingBase {
161
+ kind: 'filesystem';
162
+ mountPath: string;
163
+ accessMode: TFilesystemStorageAccessMode;
164
+ }
165
+ export interface IStorageObjectCredentialsRef {
166
+ secretBundleId: string;
167
+ accessKeyIdKey: string;
168
+ secretAccessKeyKey: string;
169
+ sessionTokenKey?: string;
170
+ }
171
+ export interface IResolvedObjectStorageConnection {
172
+ endpoint: string;
173
+ bucket: string;
174
+ region: string;
175
+ }
176
+ export interface IResolvedObjectStorageBinding extends IResolvedStorageBindingBase {
177
+ kind: 'objectStorage';
178
+ accessMode: TObjectStorageAccessMode;
179
+ connection: IResolvedObjectStorageConnection;
180
+ credentials: IStorageObjectCredentialsRef;
181
+ delivery: TObjectStorageDelivery;
182
+ versioning: boolean;
183
+ retentionDays?: number;
184
+ }
185
+ export type TResolvedStorageBinding = IResolvedFilesystemStorageBinding | IResolvedObjectStorageBinding;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Compatibility bridge for the existing generic platform capability. New
3
+ * filesystem requests intentionally have no generic IPlatformBinding mapping.
4
+ */
5
+ export const storageKindPlatformCapabilityIds = {
6
+ objectStorage: 'objectstorage',
7
+ };
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3BsYXRmb3JtL3N0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBTUE7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZ0NBQWdDLEdBQUc7SUFDOUMsYUFBYSxFQUFFLGVBQWU7Q0FDdEIsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "17.4.0",
3
+ "version": "17.5.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
package/readme.md CHANGED
@@ -30,6 +30,96 @@ import { appstore, data, platform, platformservice, requests } from '@serve.zone
30
30
 
31
31
  This package intentionally has no service implementation logic. It is a stable vocabulary for services that need to agree on payload shape, method names, and response types.
32
32
 
33
+ ## Portable Storage Contracts
34
+
35
+ App Store templates can declare logical, template-local `storageClasses` and
36
+ stable named `storageRequests`. The same manifest is fulfilled by Onebox or
37
+ Cloudly without exposing a physical provider:
38
+
39
+ ```typescript
40
+ const storageConfig: appstore.IAppStoreVersionConfig = {
41
+ image: 'example/database:1.0.0',
42
+ port: 5432,
43
+ storageClasses: {
44
+ databaseFast: {
45
+ kind: 'filesystem',
46
+ purpose: 'database',
47
+ required: {
48
+ performanceTier: 'highIops',
49
+ durability: 'persistent',
50
+ hardQuota: true,
51
+ snapshots: 'native',
52
+ encryptedInTransit: true,
53
+ },
54
+ },
55
+ backupCapacity: {
56
+ kind: 'objectStorage',
57
+ purpose: 'backup',
58
+ required: {
59
+ performanceTier: 'capacity',
60
+ durability: 'persistent',
61
+ hardQuota: true,
62
+ encryptedInTransit: true,
63
+ },
64
+ },
65
+ },
66
+ storageRequests: [
67
+ {
68
+ id: 'database-data',
69
+ kind: 'filesystem',
70
+ storageClass: 'databaseFast',
71
+ mountPath: '/var/lib/example',
72
+ accessMode: 'ReadWriteOnce',
73
+ capacity: { request: '20GiB', limit: '40GiB' },
74
+ reclaimPolicy: 'retain',
75
+ protection: { backup: 'required', snapshots: 'native' },
76
+ },
77
+ {
78
+ id: 'backup-archive',
79
+ kind: 'objectStorage',
80
+ storageClass: 'backupCapacity',
81
+ accessMode: 'readWrite',
82
+ capacity: { request: '100GiB', limit: '1TiB' },
83
+ reclaimPolicy: 'retain',
84
+ delivery: {
85
+ type: 'secretFile',
86
+ targetPath: '/run/secrets/backup-archive.json',
87
+ format: 'servezone-object-storage-v1',
88
+ },
89
+ protection: { versioning: 'required', retentionDays: 30 },
90
+ },
91
+ ],
92
+ requiresFeatures: [
93
+ appstore.appStoreStorageFeatureIds.bindingsV1,
94
+ appstore.appStoreStorageFeatureIds.filesystemV1,
95
+ appstore.appStoreStorageFeatureIds.objectStorageV1,
96
+ appstore.appStoreStorageFeatureIds.objectStorageSecretFileV1,
97
+ ],
98
+ };
99
+ ```
100
+
101
+ Capacity quantities are positive integers followed by `KiB`, `MiB`, `GiB`, or
102
+ `TiB`. Storage request IDs survive upgrades and restores. Logical class keys
103
+ express requirements and preferences only; Onebox and Cloudly map them to
104
+ operator policy independently.
105
+
106
+ An app may declare multiple `objectStorage` requests. Each request resolves to
107
+ its own endpoint, bucket, and credential reference. Environment delivery uses
108
+ an explicit key map and secret-file delivery uses a unique target path, so two
109
+ bindings cannot share credential destinations accidentally.
110
+
111
+ `platform.storage` contains separate capability advertisements and resolved
112
+ binding/status contracts. Resolved object-storage bindings expose connection
113
+ metadata plus credential references, never credential values. Filesystem
114
+ bindings expose the container mount and access mode, never a host path.
115
+
116
+ Portable manifests and resolved bindings intentionally have no fields for
117
+ Synology, NFS, Kerberos, Corestore, Kubernetes, Docker drivers, servers,
118
+ exports, mount options, provider credential values, or local fallback paths. Runtimes
119
+ must reject unknown manifest fields and unsupported required feature IDs before
120
+ provisioning. Legacy `volumes` and `platformRequirements.s3` remain deprecated
121
+ inputs for strict resolver normalization only.
122
+
33
123
  ## Data Contracts
34
124
 
35
125
  Use `data` when you need object shapes that are persisted, exchanged between services, or exposed through the Cloudly API.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '17.4.0',
6
+ version: '17.5.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -4,12 +4,103 @@ import type {
4
4
  IServicePublicPortMapping,
5
5
  IServiceTargetPort,
6
6
  } from '../data/serviceports.js';
7
+ import type {
8
+ IStorageCapacityRequest,
9
+ IStorageClassRequirement,
10
+ TFilesystemStorageAccessMode,
11
+ TObjectStorageAccessMode,
12
+ TObjectStorageDelivery,
13
+ TStorageReclaimPolicy,
14
+ TStorageResourceKind,
15
+ TStorageSnapshotMode,
16
+ } from '../platform/storage.js';
7
17
 
8
18
  export type TAppStorePlatformRequirement = 'mongodb' | 's3' | 'clickhouse' | 'valkey' | 'mariadb';
19
+ export interface IAppStorePlatformRequirements {
20
+ mongodb?: boolean;
21
+ /**
22
+ * @deprecated `s3: true` is normalized to one legacy object-storage binding.
23
+ * New templates use a named objectStorage request.
24
+ */
25
+ s3?: boolean;
26
+ clickhouse?: boolean;
27
+ valkey?: boolean;
28
+ mariadb?: boolean;
29
+ }
9
30
  export type TAppStoreSourceType = 'inline' | 'repoManifest' | 'dockerImage';
10
31
  export type TAppStoreTrackingMode = 'tag' | 'digest';
11
32
  export type TAppStoreUpgradeStrategy = 'semver' | 'branch' | 'dockerDigest';
12
33
 
34
+ export const appStoreStorageFeatureIds = {
35
+ bindingsV1: 'storage.bindings.v1',
36
+ filesystemV1: 'storage.filesystem.v1',
37
+ objectStorageV1: 'storage.object-storage.v1',
38
+ objectStorageSecretFileV1: 'storage.object-storage.secret-file.v1',
39
+ } as const;
40
+
41
+ export type TAppStoreStorageFeatureId =
42
+ typeof appStoreStorageFeatureIds[keyof typeof appStoreStorageFeatureIds];
43
+
44
+ export type TAppStoreStoragePurpose =
45
+ | 'runtime'
46
+ | 'database'
47
+ | 'registry'
48
+ | 'backup';
49
+
50
+ /**
51
+ * A template-local logical class. It states portable policy requirements and
52
+ * preferences; fulfillment adapters select their own compatible policy class.
53
+ */
54
+ export interface IAppStoreStorageClass {
55
+ kind: TStorageResourceKind;
56
+ purpose: TAppStoreStoragePurpose;
57
+ required?: IStorageClassRequirement;
58
+ preferred?: IStorageClassRequirement;
59
+ }
60
+
61
+ export interface IAppStoreStorageRequestBase {
62
+ /** Stable identity used across upgrades, migrations, and restores. */
63
+ id: string;
64
+ kind: TStorageResourceKind;
65
+ /** Key in the containing version config's storageClasses record. */
66
+ storageClass: string;
67
+ capacity?: IStorageCapacityRequest;
68
+ reclaimPolicy: TStorageReclaimPolicy;
69
+ }
70
+
71
+ export interface IAppStoreFilesystemProtection {
72
+ backup?: 'required';
73
+ snapshots?: Exclude<TStorageSnapshotMode, 'none'>;
74
+ consistency?: 'crashConsistent' | 'applicationConsistent';
75
+ }
76
+
77
+ export interface IAppStoreFilesystemStorageRequest
78
+ extends IAppStoreStorageRequestBase {
79
+ kind: 'filesystem';
80
+ mountPath: string;
81
+ accessMode: TFilesystemStorageAccessMode;
82
+ protection?: IAppStoreFilesystemProtection;
83
+ }
84
+
85
+ export interface IAppStoreObjectStorageProtection {
86
+ backup?: 'required';
87
+ versioning?: 'required';
88
+ /** Minimum provider-enforced retention duration. */
89
+ retentionDays?: number;
90
+ }
91
+
92
+ export interface IAppStoreObjectStorageRequest
93
+ extends IAppStoreStorageRequestBase {
94
+ kind: 'objectStorage';
95
+ accessMode: TObjectStorageAccessMode;
96
+ delivery: TObjectStorageDelivery;
97
+ protection?: IAppStoreObjectStorageProtection;
98
+ }
99
+
100
+ export type TAppStoreStorageRequest =
101
+ | IAppStoreFilesystemStorageRequest
102
+ | IAppStoreObjectStorageRequest;
103
+
13
104
  export interface IAppStoreInlineSource {
14
105
  type: 'inline';
15
106
  }
@@ -140,9 +231,21 @@ export interface IAppStoreVersionConfig {
140
231
  /** Edge/coretraffic public TCP/UDP exposure, distinct from Docker publishedPorts. */
141
232
  publicPortMappings?: IServicePublicPortMapping[];
142
233
  envVars?: IAppStoreEnvVar[];
234
+ /**
235
+ * @deprecated Legacy volume syntax. New templates use storageClasses and
236
+ * storageRequests. Resolver normalization must reject legacy physical driver
237
+ * options that cannot be represented portably.
238
+ */
143
239
  volumes?: TAppStoreVolumeSpec[];
240
+ /**
241
+ * Template-local logical policy classes. Keys are stable within the
242
+ * template; they are not Onebox or Cloudly operator class names.
243
+ */
244
+ storageClasses?: Record<string, IAppStoreStorageClass>;
245
+ /** Stable named filesystem and managed object-storage requests. */
246
+ storageRequests?: TAppStoreStorageRequest[];
144
247
  publishedPorts?: IAppStorePublishedPort[];
145
- platformRequirements?: Partial<Record<TAppStorePlatformRequirement, boolean>>;
248
+ platformRequirements?: IAppStorePlatformRequirements;
146
249
  minOneboxVersion?: string;
147
250
  minCloudlyVersion?: string;
148
251
  appStoreVersion?: string;
@@ -8,6 +8,7 @@ import * as objectstorage from './objectstorage.js';
8
8
  import * as pushnotification from './pushnotification.js';
9
9
  import * as sip from './sip.js';
10
10
  import * as sms from './sms.js';
11
+ import * as storage from './storage.js';
11
12
  import * as types from './types.js';
12
13
 
13
14
  export {
@@ -21,7 +22,9 @@ export {
21
22
  pushnotification,
22
23
  sip,
23
24
  sms,
25
+ storage,
24
26
  types,
25
27
  };
26
28
 
29
+ export * from './storage.js';
27
30
  export * from './types.js';
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Backend-neutral storage vocabulary shared by App Store manifests, control
3
+ * planes, and fulfillment adapters. Physical providers and runtime attachment
4
+ * details intentionally do not belong in these contracts.
5
+ */
6
+ export type TStorageResourceKind = 'filesystem' | 'objectStorage';
7
+ /**
8
+ * Compatibility bridge for the existing generic platform capability. New
9
+ * filesystem requests intentionally have no generic IPlatformBinding mapping.
10
+ */
11
+ export const storageKindPlatformCapabilityIds = {
12
+ objectStorage: 'objectstorage',
13
+ } as const;
14
+ export type TFilesystemStorageAccessMode =
15
+ | 'ReadWriteOnce'
16
+ | 'ReadOnlyMany'
17
+ | 'ReadWriteMany';
18
+ export type TObjectStorageAccessMode = 'readOnly' | 'readWrite';
19
+ export type TStoragePerformanceTier = 'standard' | 'highIops' | 'capacity';
20
+ export type TStorageDurability = 'ephemeral' | 'persistent';
21
+ export type TStorageTopology = 'singleNode' | 'multiNode';
22
+ export type TStorageSnapshotMode = 'none' | 'portable' | 'native';
23
+ export type TStorageReclaimPolicy = 'retain' | 'delete';
24
+
25
+ /**
26
+ * A positive base-2 quantity written as an integer followed by KiB, MiB, GiB,
27
+ * or TiB, for example `20GiB`. Parsers must reject every other representation.
28
+ */
29
+ export type TStorageCapacityQuantity = string;
30
+
31
+ export interface IStorageCapacityRequest {
32
+ request: TStorageCapacityQuantity;
33
+ /**
34
+ * A hard upper bound. A request with a limit can only be fulfilled by a
35
+ * class whose granted capabilities include hardQuota.
36
+ */
37
+ limit?: TStorageCapacityQuantity;
38
+ }
39
+
40
+ /**
41
+ * App-facing requirements describe policy, never a concrete provider class.
42
+ * Values in a `required` block are hard constraints; values in `preferred`
43
+ * influence selection but may not weaken required constraints.
44
+ */
45
+ export interface IStorageClassRequirement {
46
+ performanceTier?: TStoragePerformanceTier;
47
+ durability?: TStorageDurability;
48
+ topology?: TStorageTopology;
49
+ hardQuota?: true;
50
+ snapshots?: Exclude<TStorageSnapshotMode, 'none'>;
51
+ backup?: true;
52
+ encryptedInTransit?: true;
53
+ }
54
+
55
+ export interface IStorageGrantedCapabilities {
56
+ performanceTier: TStoragePerformanceTier;
57
+ durability: TStorageDurability;
58
+ topology: TStorageTopology;
59
+ hardQuota: boolean;
60
+ snapshots: TStorageSnapshotMode;
61
+ backup: boolean;
62
+ encryptedInTransit: boolean;
63
+ }
64
+
65
+ export interface IStorageClassCapabilitiesBase {
66
+ /** Opaque operator-defined policy class ID, not a physical backend name. */
67
+ classId: string;
68
+ /** Changes whenever matching behavior or guarantees change. */
69
+ revision: string;
70
+ kind: TStorageResourceKind;
71
+ performanceTiers: TStoragePerformanceTier[];
72
+ durabilities: TStorageDurability[];
73
+ topologies: TStorageTopology[];
74
+ hardQuota: boolean;
75
+ snapshotModes: TStorageSnapshotMode[];
76
+ backup: boolean;
77
+ encryptedInTransit: boolean;
78
+ }
79
+
80
+ export interface IFilesystemStorageClassCapabilities
81
+ extends IStorageClassCapabilitiesBase {
82
+ kind: 'filesystem';
83
+ accessModes: TFilesystemStorageAccessMode[];
84
+ }
85
+
86
+ export interface IObjectStorageClassCapabilities
87
+ extends IStorageClassCapabilitiesBase {
88
+ kind: 'objectStorage';
89
+ accessModes: TObjectStorageAccessMode[];
90
+ versioning: boolean;
91
+ retention: boolean;
92
+ }
93
+
94
+ export type TStorageClassCapabilities =
95
+ | IFilesystemStorageClassCapabilities
96
+ | IObjectStorageClassCapabilities;
97
+
98
+ /**
99
+ * Fulfillment adapters advertise only portable guarantees. Provider names,
100
+ * addresses, mount options, principals, and credential values are private.
101
+ */
102
+ export interface IStorageCapabilityAdvertisement {
103
+ schemaVersion: 1;
104
+ featureIds: string[];
105
+ classes: TStorageClassCapabilities[];
106
+ }
107
+
108
+ export interface IObjectStorageEnvironmentDelivery {
109
+ type: 'environment';
110
+ /**
111
+ * Explicit target keys prevent two named bindings from silently overwriting
112
+ * each other. Every required field must have a distinct environment key.
113
+ */
114
+ keys: {
115
+ endpoint: string;
116
+ bucket: string;
117
+ region: string;
118
+ accessKeyId: string;
119
+ secretAccessKey: string;
120
+ sessionToken?: string;
121
+ };
122
+ }
123
+
124
+ export interface IObjectStorageSecretFileDelivery {
125
+ type: 'secretFile';
126
+ /** Canonical absolute path below /run/secrets/. */
127
+ targetPath: string;
128
+ /**
129
+ * JSON object with endpoint, bucket, region, accessKeyId,
130
+ * secretAccessKey, and optional sessionToken fields.
131
+ */
132
+ format: 'servezone-object-storage-v1';
133
+ }
134
+
135
+ export type TObjectStorageDelivery =
136
+ | IObjectStorageEnvironmentDelivery
137
+ | IObjectStorageSecretFileDelivery;
138
+
139
+ export type TResolvedStorageBindingStatus =
140
+ | 'requested'
141
+ | 'provisioning'
142
+ | 'ready'
143
+ | 'degraded'
144
+ | 'failed'
145
+ | 'releasing'
146
+ | 'retained'
147
+ | 'released';
148
+
149
+ export type TStorageBindingFailureCode =
150
+ | 'unsupportedCapability'
151
+ | 'policyUnavailable'
152
+ | 'provisionFailed'
153
+ | 'attachmentFailed'
154
+ | 'quotaExceeded'
155
+ | 'credentialsUnavailable'
156
+ | 'backendUnavailable'
157
+ | 'migrationRequired';
158
+
159
+ export interface IStorageBindingFailure {
160
+ code: TStorageBindingFailureCode;
161
+ message: string;
162
+ retryable: boolean;
163
+ observedAt: number;
164
+ }
165
+
166
+ export interface IResolvedStoragePolicyRef {
167
+ /** Opaque operator-defined policy class ID selected for this request. */
168
+ classId: string;
169
+ revision: string;
170
+ }
171
+
172
+ export interface IResolvedStorageCapacity {
173
+ requested?: TStorageCapacityQuantity;
174
+ granted?: TStorageCapacityQuantity;
175
+ limit?: TStorageCapacityQuantity;
176
+ }
177
+
178
+ export interface IResolvedStorageBindingBase {
179
+ schemaVersion: 1;
180
+ /** Stable binding/allocation identity. */
181
+ id: string;
182
+ serviceId: string;
183
+ /** Stable App Store storageRequests[].id. */
184
+ requestId: string;
185
+ /**
186
+ * Canonical digest of the normalized request. Adapters use it to distinguish
187
+ * an idempotent reconciliation from a migration.
188
+ */
189
+ requestDigest: string;
190
+ kind: TStorageResourceKind;
191
+ generation: number;
192
+ observedGeneration: number;
193
+ status: TResolvedStorageBindingStatus;
194
+ policy: IResolvedStoragePolicyRef;
195
+ capabilities: IStorageGrantedCapabilities;
196
+ /** Opaque resource identity; never a host path, export, or provider URL. */
197
+ resourceRef: string;
198
+ capacity?: IResolvedStorageCapacity;
199
+ failure?: IStorageBindingFailure;
200
+ createdAt?: number;
201
+ updatedAt?: number;
202
+ }
203
+
204
+ export interface IResolvedFilesystemStorageBinding
205
+ extends IResolvedStorageBindingBase {
206
+ kind: 'filesystem';
207
+ mountPath: string;
208
+ accessMode: TFilesystemStorageAccessMode;
209
+ }
210
+
211
+ export interface IStorageObjectCredentialsRef {
212
+ secretBundleId: string;
213
+ accessKeyIdKey: string;
214
+ secretAccessKeyKey: string;
215
+ sessionTokenKey?: string;
216
+ }
217
+
218
+ export interface IResolvedObjectStorageConnection {
219
+ endpoint: string;
220
+ bucket: string;
221
+ region: string;
222
+ }
223
+
224
+ export interface IResolvedObjectStorageBinding
225
+ extends IResolvedStorageBindingBase {
226
+ kind: 'objectStorage';
227
+ accessMode: TObjectStorageAccessMode;
228
+ connection: IResolvedObjectStorageConnection;
229
+ credentials: IStorageObjectCredentialsRef;
230
+ delivery: TObjectStorageDelivery;
231
+ versioning: boolean;
232
+ retentionDays?: number;
233
+ }
234
+
235
+ export type TResolvedStorageBinding =
236
+ | IResolvedFilesystemStorageBinding
237
+ | IResolvedObjectStorageBinding;