Skip to content

Commit 1d7cc0b

Browse files
committed
feat(api): add comprehensive versioning support with multiple strategies and lifecycle management
- Introduced versioning strategies including urlPath, header, queryParam, and dateBased. - Defined version lifecycle states: preview, current, supported, deprecated, and retired. - Implemented VersionDefinitionSchema for detailed version metadata including release, deprecation, and migration guide. - Created VersioningConfigSchema for API version management configuration. - Added VersionNegotiationResponseSchema for handling version negotiation responses. - Established default versioning configuration for ObjectStack API. - Added unit tests for versioning schemas and negotiation logic to ensure correctness. - Expanded REST API routes to include view management, workflow, realtime, notifications, AI, i18n, analytics, hub management, and automation.
1 parent e39e080 commit 1d7cc0b

5 files changed

Lines changed: 1442 additions & 3 deletions

File tree

packages/spec/src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* - Batch operations
99
* - Metadata caching
1010
* - Hub Management APIs
11+
* - HttpDispatcher routing
12+
* - API versioning
1113
*/
1214

1315
export * from './contract.zod';
@@ -27,6 +29,7 @@ export * from './hub.zod';
2729
export * from './registry.zod';
2830
export * from './documentation.zod';
2931
export * from './analytics.zod';
32+
export * from './versioning.zod';
3033

3134
// Legacy interface export (deprecated)
3235
// export type { IObjectStackProtocol } from './protocol';

packages/spec/src/api/plugin-rest-api.test.ts

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import {
1414
DEFAULT_DATA_CRUD_ROUTES,
1515
DEFAULT_BATCH_ROUTES,
1616
DEFAULT_PERMISSION_ROUTES,
17+
DEFAULT_VIEW_ROUTES,
18+
DEFAULT_WORKFLOW_ROUTES,
19+
DEFAULT_REALTIME_ROUTES,
20+
DEFAULT_NOTIFICATION_ROUTES,
21+
DEFAULT_AI_ROUTES,
22+
DEFAULT_I18N_ROUTES,
23+
DEFAULT_ANALYTICS_ROUTES,
24+
DEFAULT_HUB_ROUTES,
25+
DEFAULT_AUTOMATION_ROUTES,
1726
getDefaultRouteRegistrations,
1827
} from './plugin-rest-api.zod';
1928

@@ -510,15 +519,157 @@ describe('plugin-rest-api.zod', () => {
510519
expect(DEFAULT_PERMISSION_ROUTES.endpoints).toHaveLength(3);
511520
});
512521

513-
it('should return all default registrations', () => {
522+
it('should validate DEFAULT_VIEW_ROUTES', () => {
523+
expect(DEFAULT_VIEW_ROUTES.prefix).toBe('/api/v1/ui');
524+
expect(DEFAULT_VIEW_ROUTES.service).toBe('ui');
525+
expect(DEFAULT_VIEW_ROUTES.category).toBe('ui');
526+
expect(DEFAULT_VIEW_ROUTES.methods).toContain('listViews');
527+
expect(DEFAULT_VIEW_ROUTES.methods).toContain('getView');
528+
expect(DEFAULT_VIEW_ROUTES.methods).toContain('createView');
529+
expect(DEFAULT_VIEW_ROUTES.methods).toContain('updateView');
530+
expect(DEFAULT_VIEW_ROUTES.methods).toContain('deleteView');
531+
expect(DEFAULT_VIEW_ROUTES.endpoints).toHaveLength(5);
532+
});
533+
534+
it('should validate DEFAULT_WORKFLOW_ROUTES', () => {
535+
expect(DEFAULT_WORKFLOW_ROUTES.prefix).toBe('/api/v1/workflow');
536+
expect(DEFAULT_WORKFLOW_ROUTES.service).toBe('workflow');
537+
expect(DEFAULT_WORKFLOW_ROUTES.category).toBe('workflow');
538+
expect(DEFAULT_WORKFLOW_ROUTES.methods).toContain('getWorkflowConfig');
539+
expect(DEFAULT_WORKFLOW_ROUTES.methods).toContain('getWorkflowState');
540+
expect(DEFAULT_WORKFLOW_ROUTES.methods).toContain('workflowTransition');
541+
expect(DEFAULT_WORKFLOW_ROUTES.methods).toContain('workflowApprove');
542+
expect(DEFAULT_WORKFLOW_ROUTES.methods).toContain('workflowReject');
543+
expect(DEFAULT_WORKFLOW_ROUTES.endpoints).toHaveLength(5);
544+
});
545+
546+
it('should validate DEFAULT_REALTIME_ROUTES', () => {
547+
expect(DEFAULT_REALTIME_ROUTES.prefix).toBe('/api/v1/realtime');
548+
expect(DEFAULT_REALTIME_ROUTES.service).toBe('realtime');
549+
expect(DEFAULT_REALTIME_ROUTES.category).toBe('realtime');
550+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('realtimeConnect');
551+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('realtimeDisconnect');
552+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('realtimeSubscribe');
553+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('realtimeUnsubscribe');
554+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('setPresence');
555+
expect(DEFAULT_REALTIME_ROUTES.methods).toContain('getPresence');
556+
expect(DEFAULT_REALTIME_ROUTES.endpoints).toHaveLength(6);
557+
});
558+
559+
it('should validate DEFAULT_NOTIFICATION_ROUTES', () => {
560+
expect(DEFAULT_NOTIFICATION_ROUTES.prefix).toBe('/api/v1/notifications');
561+
expect(DEFAULT_NOTIFICATION_ROUTES.service).toBe('notification');
562+
expect(DEFAULT_NOTIFICATION_ROUTES.category).toBe('notification');
563+
expect(DEFAULT_NOTIFICATION_ROUTES.methods).toContain('registerDevice');
564+
expect(DEFAULT_NOTIFICATION_ROUTES.methods).toContain('listNotifications');
565+
expect(DEFAULT_NOTIFICATION_ROUTES.methods).toContain('markNotificationsRead');
566+
expect(DEFAULT_NOTIFICATION_ROUTES.methods).toContain('markAllNotificationsRead');
567+
expect(DEFAULT_NOTIFICATION_ROUTES.endpoints).toHaveLength(7);
568+
});
569+
570+
it('should validate DEFAULT_AI_ROUTES', () => {
571+
expect(DEFAULT_AI_ROUTES.prefix).toBe('/api/v1/ai');
572+
expect(DEFAULT_AI_ROUTES.service).toBe('ai');
573+
expect(DEFAULT_AI_ROUTES.category).toBe('ai');
574+
expect(DEFAULT_AI_ROUTES.methods).toContain('aiNlq');
575+
expect(DEFAULT_AI_ROUTES.methods).toContain('aiChat');
576+
expect(DEFAULT_AI_ROUTES.methods).toContain('aiSuggest');
577+
expect(DEFAULT_AI_ROUTES.methods).toContain('aiInsights');
578+
expect(DEFAULT_AI_ROUTES.endpoints).toHaveLength(4);
579+
// AI endpoints should have extended timeouts
580+
const chatEndpoint = DEFAULT_AI_ROUTES.endpoints?.find(e => e.handler === 'aiChat');
581+
expect(chatEndpoint?.timeout).toBe(60000);
582+
});
583+
584+
it('should validate DEFAULT_I18N_ROUTES', () => {
585+
expect(DEFAULT_I18N_ROUTES.prefix).toBe('/api/v1/i18n');
586+
expect(DEFAULT_I18N_ROUTES.service).toBe('i18n');
587+
expect(DEFAULT_I18N_ROUTES.category).toBe('i18n');
588+
expect(DEFAULT_I18N_ROUTES.methods).toContain('getLocales');
589+
expect(DEFAULT_I18N_ROUTES.methods).toContain('getTranslations');
590+
expect(DEFAULT_I18N_ROUTES.methods).toContain('getFieldLabels');
591+
expect(DEFAULT_I18N_ROUTES.endpoints).toHaveLength(3);
592+
// i18n endpoints should be cacheable
593+
DEFAULT_I18N_ROUTES.endpoints?.forEach(endpoint => {
594+
expect(endpoint.cacheable).toBe(true);
595+
});
596+
});
597+
598+
it('should validate DEFAULT_ANALYTICS_ROUTES', () => {
599+
expect(DEFAULT_ANALYTICS_ROUTES.prefix).toBe('/api/v1/analytics');
600+
expect(DEFAULT_ANALYTICS_ROUTES.service).toBe('analytics');
601+
expect(DEFAULT_ANALYTICS_ROUTES.category).toBe('analytics');
602+
expect(DEFAULT_ANALYTICS_ROUTES.methods).toContain('analyticsQuery');
603+
expect(DEFAULT_ANALYTICS_ROUTES.methods).toContain('getAnalyticsMeta');
604+
expect(DEFAULT_ANALYTICS_ROUTES.endpoints).toHaveLength(2);
605+
// Analytics query should have extended timeout
606+
const queryEndpoint = DEFAULT_ANALYTICS_ROUTES.endpoints?.find(e => e.handler === 'analyticsQuery');
607+
expect(queryEndpoint?.timeout).toBe(120000);
608+
});
609+
610+
it('should validate DEFAULT_HUB_ROUTES', () => {
611+
expect(DEFAULT_HUB_ROUTES.prefix).toBe('/api/v1/hub');
612+
expect(DEFAULT_HUB_ROUTES.service).toBe('hub');
613+
expect(DEFAULT_HUB_ROUTES.category).toBe('hub');
614+
expect(DEFAULT_HUB_ROUTES.methods).toContain('listSpaces');
615+
expect(DEFAULT_HUB_ROUTES.methods).toContain('createSpace');
616+
expect(DEFAULT_HUB_ROUTES.methods).toContain('installPlugin');
617+
expect(DEFAULT_HUB_ROUTES.methods).toContain('listPackages');
618+
expect(DEFAULT_HUB_ROUTES.methods).toContain('installPackage');
619+
expect(DEFAULT_HUB_ROUTES.methods).toContain('uninstallPackage');
620+
expect(DEFAULT_HUB_ROUTES.methods).toContain('enablePackage');
621+
expect(DEFAULT_HUB_ROUTES.methods).toContain('disablePackage');
622+
expect(DEFAULT_HUB_ROUTES.endpoints).toHaveLength(9);
623+
});
624+
625+
it('should validate DEFAULT_AUTOMATION_ROUTES', () => {
626+
expect(DEFAULT_AUTOMATION_ROUTES.prefix).toBe('/api/v1/automation');
627+
expect(DEFAULT_AUTOMATION_ROUTES.service).toBe('automation');
628+
expect(DEFAULT_AUTOMATION_ROUTES.category).toBe('automation');
629+
expect(DEFAULT_AUTOMATION_ROUTES.methods).toContain('triggerAutomation');
630+
expect(DEFAULT_AUTOMATION_ROUTES.endpoints).toHaveLength(1);
631+
// Automation trigger should have extended timeout
632+
expect(DEFAULT_AUTOMATION_ROUTES.endpoints?.[0].timeout).toBe(120000);
633+
});
634+
635+
it('should return all 14 default registrations', () => {
514636
const registrations = getDefaultRouteRegistrations();
515637

516-
expect(registrations).toHaveLength(5);
638+
expect(registrations).toHaveLength(14);
517639
expect(registrations[0]).toBe(DEFAULT_DISCOVERY_ROUTES);
518640
expect(registrations[1]).toBe(DEFAULT_METADATA_ROUTES);
519641
expect(registrations[2]).toBe(DEFAULT_DATA_CRUD_ROUTES);
520642
expect(registrations[3]).toBe(DEFAULT_BATCH_ROUTES);
521643
expect(registrations[4]).toBe(DEFAULT_PERMISSION_ROUTES);
644+
expect(registrations[5]).toBe(DEFAULT_VIEW_ROUTES);
645+
expect(registrations[6]).toBe(DEFAULT_WORKFLOW_ROUTES);
646+
expect(registrations[7]).toBe(DEFAULT_REALTIME_ROUTES);
647+
expect(registrations[8]).toBe(DEFAULT_NOTIFICATION_ROUTES);
648+
expect(registrations[9]).toBe(DEFAULT_AI_ROUTES);
649+
expect(registrations[10]).toBe(DEFAULT_I18N_ROUTES);
650+
expect(registrations[11]).toBe(DEFAULT_ANALYTICS_ROUTES);
651+
expect(registrations[12]).toBe(DEFAULT_HUB_ROUTES);
652+
expect(registrations[13]).toBe(DEFAULT_AUTOMATION_ROUTES);
653+
});
654+
655+
it('should cover all protocol categories', () => {
656+
const registrations = getDefaultRouteRegistrations();
657+
const categories = registrations.map(r => r.category);
658+
659+
expect(categories).toContain('discovery');
660+
expect(categories).toContain('metadata');
661+
expect(categories).toContain('data');
662+
expect(categories).toContain('batch');
663+
expect(categories).toContain('permission');
664+
expect(categories).toContain('ui');
665+
expect(categories).toContain('workflow');
666+
expect(categories).toContain('realtime');
667+
expect(categories).toContain('notification');
668+
expect(categories).toContain('ai');
669+
expect(categories).toContain('i18n');
670+
expect(categories).toContain('analytics');
671+
expect(categories).toContain('hub');
672+
expect(categories).toContain('automation');
522673
});
523674
});
524675

0 commit comments

Comments
 (0)