refactor: front end code to unified manager api

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-06-20 18:56:35 -04:00
parent 053c272fe4
commit c4cb1512d2
21 changed files with 1484 additions and 927 deletions
+28 -20
View File
@@ -1,27 +1,37 @@
/**
* Collection type definitions
*/
import type { ListFilter, ListSort, SourceSelector } from './common';
import type {
ServiceIdentifier,
CollectionIdentifier,
ListFilter,
ListSort
} from './common';
/**
* Collection information
*/
export interface CollectionInterface {
export interface CollectionInterface<T = CollectionPropertiesInterface> {
'@type': string;
version: number;
provider: string;
service: string | number;
collection: string | number | null;
identifier: string | number;
collection: CollectionIdentifier | null;
identifier: CollectionIdentifier;
signature?: string | null;
created?: string | null;
modified?: string | null;
properties: CollectionPropertiesInterface;
properties: T;
}
export interface CollectionModelInterface extends Omit<CollectionInterface<CollectionPropertiesInterface>, '@type' | 'version' | 'properties'> {
properties: CollectionPropertiesModelInterface;
}
export type CollectionContentTypes = 'individual' | 'organization' | 'group';
export interface CollectionBaseProperties {
'@type': string;
version: number;
}
export interface CollectionImmutableProperties extends CollectionBaseProperties {
@@ -38,11 +48,13 @@ export interface CollectionMutableProperties extends CollectionBaseProperties {
export interface CollectionPropertiesInterface extends CollectionMutableProperties, CollectionImmutableProperties {}
export interface CollectionPropertiesModelInterface extends Omit<CollectionPropertiesInterface, '@type'> {}
/**
* Collection list
*/
export interface CollectionListRequest {
sources?: SourceSelector;
sources?: ServiceIdentifier[] | CollectionIdentifier[];
filter?: ListFilter;
sort?: ListSort;
}
@@ -59,18 +71,18 @@ export interface CollectionListResponse {
* Collection fetch
*/
export interface CollectionFetchRequest {
provider: string;
service: string | number;
collection: string | number;
targets: CollectionIdentifier[];
}
export interface CollectionFetchResponse extends CollectionInterface {}
export interface CollectionFetchResponse {
[identifier: CollectionIdentifier]: CollectionInterface;
}
/**
* Collection extant
*/
export interface CollectionExtantRequest {
sources: SourceSelector;
targets: CollectionIdentifier[];
}
export interface CollectionExtantResponse {
@@ -87,7 +99,6 @@ export interface CollectionExtantResponse {
export interface CollectionCreateRequest {
provider: string;
service: string | number;
collection?: string | number | null; // Parent Collection Identifier
properties: CollectionMutableProperties;
}
@@ -97,9 +108,7 @@ export interface CollectionCreateResponse extends CollectionInterface {}
* Collection modify
*/
export interface CollectionUpdateRequest {
provider: string;
service: string | number;
identifier: string | number;
target: CollectionIdentifier;
properties: CollectionMutableProperties;
}
@@ -109,14 +118,13 @@ export interface CollectionUpdateResponse extends CollectionInterface {}
* Collection delete
*/
export interface CollectionDeleteRequest {
provider: string;
service: string | number;
identifier: string | number;
target: CollectionIdentifier;
options?: {
force?: boolean; // Whether to force delete even if collection is not empty
};
}
export interface CollectionDeleteResponse {
success: boolean;
disposition: 'deleted' | 'moved';
mutation?: CollectionInterface | null; // If moved, the new location of the collection
}
+45 -23
View File
@@ -44,34 +44,56 @@ export interface ApiErrorResponse {
export type ApiResponse<T = any> = ApiSuccessResponse<T> | ApiErrorResponse;
/**
* Selector for targeting specific providers, services, collections, or entities in list or extant operations.
*
* Example usage:
* {
* "provider1": true, // Select all services/collections/entities under provider1
* "provider2": {
* "serviceA": true, // Select all collections/entities under serviceA of provider2
* "serviceB": {
* "collectionX": true, // Select all entities under collectionX of serviceB of provider2
* "collectionY": [1, 2, 3] // Select entities with identifiers 1, 2, and 3 under collectionY of serviceB of provider2
* }
* }
* }
* Stream control start line
*/
export type SourceSelector = {
[provider: string]: boolean | ServiceSelector;
};
export interface ApiStreamStartResponse {
type: 'control';
status: 'start';
version: number;
transaction: string;
}
export type ServiceSelector = {
[service: string]: boolean | CollectionSelector;
};
/**
* Stream control end line
*/
export interface ApiStreamEndResponse {
type: 'control';
status: 'end';
total: number;
}
export type CollectionSelector = {
[collection: string | number]: boolean | EntitySelector;
};
/**
* Stream error line
*/
export interface ApiStreamErrorResponse {
type: 'error';
message: string;
}
export type EntitySelector = (string | number)[];
export interface ApiStreamDataResponse<T = any> {
type: 'data';
data: T;
}
/**
* Shared stream control lines
*/
export type ApiStreamResponse<T = any> =
| ApiStreamStartResponse
| ApiStreamEndResponse
| ApiStreamErrorResponse
| ApiStreamDataResponse<T>;
/**
* Identifiers for targeting specific providers, services, collections, or entities in list or extant operations.
*
* Operations accept flat arrays of colon-separated identifier strings, e.g.
* ["carddav:account1:contacts", "carddav:account1:contacts:1001"].
*/
export type ProviderIdentifier = `${string}`;
export type ServiceIdentifier = `${string}:${string}`;
export type CollectionIdentifier = `${string}:${string}:${string | number}`;
export type EntityIdentifier = `${string}:${string}:${string}:${string | number}`;
/**
* Filter comparison for list operations
+94 -56
View File
@@ -1,64 +1,85 @@
/**
* Entity type definitions
*/
import type { ListFilter, ListRange, ListSort, SourceSelector } from './common';
import type {
CollectionIdentifier,
EntityIdentifier,
ListFilter,
ListRange,
ListSort,
} from './common';
import type { GroupInterface } from './group';
import type { IndividualInterface } from './individual';
import type { OrganizationInterface } from './organization';
export type EntityPropertiesInterface = IndividualInterface | OrganizationInterface | GroupInterface;
/**
* Entity definition
*/
export interface EntityInterface<T = IndividualInterface | OrganizationInterface | GroupInterface> {
export interface EntityInterface<T = EntityPropertiesInterface> {
'@type': string;
version: number;
provider: string;
service: string;
collection: string | number;
identifier: string | number;
collection: CollectionIdentifier;
identifier: EntityIdentifier;
signature: string | null;
created: string | null;
modified: string | null;
properties: T;
}
export interface EntityModelInterface extends Omit<EntityInterface<EntityPropertiesInterface>, '@type' | 'version'> {}
/**
* Entity list
* Entity list bulk
*/
export interface EntityListRequest {
sources?: SourceSelector;
export interface EntityListBulkRequest {
sources?: CollectionIdentifier[];
filter?: ListFilter;
sort?: ListSort;
range?: ListRange;
}
export interface EntityListResponse {
export interface EntityListBulkResponse {
[providerId: string]: {
[serviceId: string]: {
[collectionId: string]: {
[identifier: string]: EntityInterface<IndividualInterface | OrganizationInterface | GroupInterface>;
[identifier: string]: EntityInterface;
};
};
};
}
/**
* Entity list stream
*/
export interface EntityListStreamRequest {
sources?: CollectionIdentifier[];
filter?: ListFilter;
sort?: ListSort;
range?: ListRange;
}
export interface EntityListStreamResponse extends EntityInterface {}
/**
* Entity fetch
*/
export interface EntityFetchRequest {
provider: string;
service: string | number;
collection: string | number;
identifiers: (string | number)[];
targets: EntityIdentifier[];
}
export interface EntityFetchResponse {
[identifier: string]: EntityInterface<IndividualInterface | OrganizationInterface | GroupInterface>;
[identifier: string]: EntityInterface;
}
/**
* Entity extant
*/
export interface EntityExtantRequest {
sources: SourceSelector;
targets: EntityIdentifier[];
}
export interface EntityExtantResponse {
@@ -71,50 +92,13 @@ export interface EntityExtantResponse {
};
}
/**
* Entity create
*/
export interface EntityCreateRequest<T = IndividualInterface | OrganizationInterface | GroupInterface> {
provider: string;
service: string | number;
collection: string | number;
properties: T;
}
export interface EntityCreateResponse<T = IndividualInterface | OrganizationInterface | GroupInterface> extends EntityInterface<T> {}
/**
* Entity update
*/
export interface EntityUpdateRequest<T = IndividualInterface | OrganizationInterface | GroupInterface> {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
properties: T;
}
export interface EntityUpdateResponse<T = IndividualInterface | OrganizationInterface | GroupInterface> extends EntityInterface<T> {}
/**
* Entity delete
*/
export interface EntityDeleteRequest {
provider: string;
service: string | number;
collection: string | number;
identifier: string | number;
}
export interface EntityDeleteResponse {
success: boolean;
}
/**
* Entity delta
*/
export interface EntityDeltaRequest {
sources: SourceSelector;
// Each target is provider:service:collection, or provider:service:collection:signature
// to request a delta relative to a known signature (the signature is the entity slot).
targets: (CollectionIdentifier | EntityIdentifier)[];
}
export interface EntityDeltaResponse {
@@ -128,4 +112,58 @@ export interface EntityDeltaResponse {
};
};
};
}
}
/**
* Entity create
*/
export interface EntityCreateRequest<T = EntityPropertiesInterface> {
target: CollectionIdentifier;
properties: T;
options?: Record<string, any>;
}
export interface EntityCreateResponse<T = EntityPropertiesInterface> extends EntityInterface<T> {}
/**
* Entity update
*/
export interface EntityUpdateRequest<T = EntityPropertiesInterface> {
target: EntityIdentifier;
properties: T;
}
export interface EntityUpdateResponse<T = EntityPropertiesInterface> extends EntityInterface<T> {}
/**
* Entity delete
*/
export interface EntityDeleteRequest {
targets: EntityIdentifier[];
}
export interface EntityDeleteResponse {
[targetIdentifier: EntityIdentifier]: {
disposition: 'deleted' | 'moved' | 'error';
destination: CollectionIdentifier | null;
mutation: EntityIdentifier | null;
error?: string;
};
}
/**
* Entity move
*/
export interface EntityMoveRequest {
target: CollectionIdentifier;
sources: EntityIdentifier[];
}
export interface EntityMoveResponse {
[sourceIdentifier: EntityIdentifier]: {
disposition: 'moved' | 'error';
destination: CollectionIdentifier | null;
mutation: EntityIdentifier | null;
error?: string;
};
}
+9 -6
View File
@@ -1,7 +1,7 @@
/**
* Provider type definitions
*/
import type { SourceSelector } from "./common";
import type { ProviderIdentifier } from "./common";
/**
* Provider capabilities
@@ -23,27 +23,30 @@ export interface ProviderCapabilitiesInterface {
*/
export interface ProviderInterface {
'@type': string;
version: number;
identifier: string;
label: string;
capabilities: ProviderCapabilitiesInterface;
}
export interface ProviderModelInterface extends Omit<ProviderInterface, '@type' | 'version'> {}
/**
* Provider list
*/
export interface ProviderListRequest {
sources?: SourceSelector;
targets?: ProviderIdentifier[];
}
export interface ProviderListResponse {
[identifier: string]: ProviderInterface;
[identifier: ProviderIdentifier]: ProviderInterface;
}
/**
* Provider fetch
*/
export interface ProviderFetchRequest {
identifier: string;
target: ProviderIdentifier;
}
export interface ProviderFetchResponse extends ProviderInterface {}
@@ -52,9 +55,9 @@ export interface ProviderFetchResponse extends ProviderInterface {}
* Provider extant
*/
export interface ProviderExtantRequest {
sources: SourceSelector;
targets: ProviderIdentifier[];
}
export interface ProviderExtantResponse {
[identifier: string]: boolean;
[identifier: ProviderIdentifier]: boolean;
}
+31 -4
View File
@@ -1,7 +1,12 @@
/**
* Service type definitions
*/
import type { SourceSelector, ListFilterComparisonOperator } from './common';
import type { Identity } from '@/models/identity';
import type {
ServiceIdentifier,
CollectionIdentifier,
ListFilterComparisonOperator
} from './common';
/**
* Service capabilities
@@ -37,6 +42,7 @@ export interface ServiceCapabilitiesInterface {
*/
export interface ServiceInterface {
'@type': string;
version: number;
provider: string;
identifier: string | number | null;
label: string | null;
@@ -47,11 +53,18 @@ export interface ServiceInterface {
auxiliary?: Record<string, any>; // Provider-specific extension data
}
export interface ServiceModelInterface extends Omit<{
[K in keyof ServiceInterface]-?: Exclude<ServiceInterface[K], undefined>;
}, '@type' | 'version' | 'location' | 'identity'> {
location: Location | null;
identity: Identity | null;
}
/**
* Service list
*/
export interface ServiceListRequest {
sources?: SourceSelector;
targets?: ServiceIdentifier[] | CollectionIdentifier[];
}
export interface ServiceListResponse {
@@ -74,7 +87,7 @@ export interface ServiceFetchResponse extends ServiceInterface {}
* Service extant
*/
export interface ServiceExtantRequest {
sources: SourceSelector;
targets: ServiceIdentifier[];
}
export interface ServiceExtantResponse {
@@ -99,6 +112,7 @@ export interface ServiceCreateResponse extends ServiceInterface {}
export interface ServiceUpdateRequest {
provider: string;
identifier: string | number;
delta?: boolean; // If true, 'data' contains only fields to update (partial update). If false or omitted, 'data' is a full replacement.
data: Partial<ServiceInterface>;
}
@@ -125,7 +139,20 @@ export interface ServiceDiscoverRequest {
}
export interface ServiceDiscoverResponse {
[provider: string]: ServiceLocation; // Uses existing ServiceLocation discriminated union
provider: string;
location: ServiceLocation;
}
export interface ProviderDiscoveryStatus {
provider: string;
status: 'pending' | 'discovering' | 'success' | 'failed';
location?: ServiceLocation;
metadata?: {
host?: string;
port?: number;
protocol?: string;
};
error?: string;
}
/**