@danceroutine/tango-resources 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/context/RequestContext.d.ts +23 -4
- package/dist/filters/FilterSet.d.ts +59 -4
- package/dist/filters/index.d.ts +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.js +515 -205
- package/dist/index.js.map +1 -1
- package/dist/pagination/CursorPaginationInput.d.ts +7 -0
- package/dist/pagination/OffsetPaginationInput.d.ts +7 -0
- package/dist/pagination/PaginatedResponse.d.ts +8 -2
- package/dist/pagination/Paginator.d.ts +5 -3
- package/dist/pagination/index.d.ts +5 -3
- package/dist/paginators/CursorPaginator.d.ts +32 -6
- package/dist/paginators/OffsetPaginator.d.ts +30 -7
- package/dist/resource/OpenAPIDescription.d.ts +21 -0
- package/dist/resource/ResourceModelLike.d.ts +16 -0
- package/dist/resource/index.d.ts +5 -0
- package/dist/serializer/ModelSerializer.d.ts +47 -0
- package/dist/serializer/Serializer.d.ts +52 -0
- package/dist/serializer/index.d.ts +5 -0
- package/dist/view/APIView.d.ts +26 -0
- package/dist/view/GenericAPIView.d.ts +57 -0
- package/dist/view/generics/CreateAPIView.d.ts +10 -0
- package/dist/view/generics/ListAPIView.d.ts +10 -0
- package/dist/view/generics/ListCreateAPIView.d.ts +11 -0
- package/dist/view/generics/RetrieveAPIView.d.ts +10 -0
- package/dist/view/generics/RetrieveDestroyAPIView.d.ts +11 -0
- package/dist/view/generics/RetrieveUpdateAPIView.d.ts +12 -0
- package/dist/view/generics/RetrieveUpdateDestroyAPIView.d.ts +13 -0
- package/dist/view/generics/index.d.ts +10 -0
- package/dist/view/index.d.ts +8 -0
- package/dist/view/index.js +3 -0
- package/dist/view/mixins/CreateModelMixin.d.ts +11 -0
- package/dist/view/mixins/DestroyModelMixin.d.ts +11 -0
- package/dist/view/mixins/ListModelMixin.d.ts +11 -0
- package/dist/view/mixins/RetrieveModelMixin.d.ts +11 -0
- package/dist/view/mixins/UpdateModelMixin.d.ts +12 -0
- package/dist/view/mixins/index.d.ts +8 -0
- package/dist/view-BNGEURL_.js +547 -0
- package/dist/view-BNGEURL_.js.map +1 -0
- package/dist/viewset/ModelViewSet.d.ts +91 -45
- package/dist/viewset/index.d.ts +2 -1
- package/package.json +75 -69
- package/dist/domain/index.d.ts +0 -8
- package/dist/pagination/PaginationInput.d.ts +0 -7
- package/dist/viewset/ModelViewSet.js +0 -143
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pedro Del Moral Lopez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @danceroutine/tango-resources
|
|
2
|
+
|
|
3
|
+
`@danceroutine/tango-resources` provides Tango's API-layer primitives.
|
|
4
|
+
|
|
5
|
+
The resources package turns model-backed data access into HTTP behavior. It gives application code a consistent way to express CRUD endpoints, custom API views, filtering, ordering, search, pagination, and serializer-backed request and response contracts while leaving request lifecycle ownership to adapters such as Express and Next.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @danceroutine/tango-resources
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You will usually pair this package with `@danceroutine/tango-schema` and `@danceroutine/tango-orm`.
|
|
14
|
+
|
|
15
|
+
## What the package does inside Tango
|
|
16
|
+
|
|
17
|
+
The resource layer centers on four roles:
|
|
18
|
+
|
|
19
|
+
- `APIView` and the generic API view classes for endpoints that are not full CRUD resources
|
|
20
|
+
- `Serializer` and `ModelSerializer` for Zod-backed input validation, output representation, and resource-scoped normalization
|
|
21
|
+
- `ModelViewSet` for CRUD APIs backed by a Tango serializer
|
|
22
|
+
- filtering and pagination primitives that keep collection behavior consistent
|
|
23
|
+
|
|
24
|
+
Model lifecycle hooks remain part of the persistence story through `@danceroutine/tango-schema`. A serializer shapes the resource contract. A model hook shapes the record lifecycle.
|
|
25
|
+
|
|
26
|
+
Request query input reaches the resource layer through `TangoRequest.queryParams`, which exposes `TangoQueryParams` from `@danceroutine/tango-core`. That keeps filtering, search, ordering, and pagination behavior framework-agnostic while giving application code a public query helper it can reuse outside resources.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
import '@danceroutine/tango-orm/runtime';
|
|
33
|
+
import { FilterSet, ModelSerializer, ModelViewSet } from '@danceroutine/tango-resources';
|
|
34
|
+
import { Model, t } from '@danceroutine/tango-schema';
|
|
35
|
+
|
|
36
|
+
const TodoReadSchema = z.object({
|
|
37
|
+
id: z.number(),
|
|
38
|
+
title: z.string(),
|
|
39
|
+
completed: z.coerce.boolean(),
|
|
40
|
+
createdAt: z.string(),
|
|
41
|
+
updatedAt: z.string(),
|
|
42
|
+
});
|
|
43
|
+
const TodoCreateSchema = z.object({
|
|
44
|
+
title: z.string(),
|
|
45
|
+
completed: z.boolean().optional(),
|
|
46
|
+
});
|
|
47
|
+
const TodoUpdateSchema = TodoCreateSchema.partial();
|
|
48
|
+
|
|
49
|
+
type Todo = z.output<typeof TodoReadSchema>;
|
|
50
|
+
|
|
51
|
+
const TodoModel = Model({
|
|
52
|
+
namespace: 'app',
|
|
53
|
+
name: 'Todo',
|
|
54
|
+
schema: TodoReadSchema.extend({
|
|
55
|
+
id: t.primaryKey(z.number().int()),
|
|
56
|
+
title: z.string(),
|
|
57
|
+
completed: t.default(z.coerce.boolean(), 'false'),
|
|
58
|
+
}),
|
|
59
|
+
hooks: {
|
|
60
|
+
async beforeCreate({ data }) {
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...data,
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
async beforeUpdate({ patch }) {
|
|
70
|
+
return {
|
|
71
|
+
...patch,
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
class TodoSerializer extends ModelSerializer<
|
|
79
|
+
Todo,
|
|
80
|
+
typeof TodoCreateSchema,
|
|
81
|
+
typeof TodoUpdateSchema,
|
|
82
|
+
typeof TodoReadSchema
|
|
83
|
+
> {
|
|
84
|
+
static readonly model = TodoModel;
|
|
85
|
+
static readonly createSchema = TodoCreateSchema;
|
|
86
|
+
static readonly updateSchema = TodoUpdateSchema;
|
|
87
|
+
static readonly outputSchema = TodoReadSchema;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class TodoViewSet extends ModelViewSet<Todo, typeof TodoSerializer> {
|
|
91
|
+
constructor() {
|
|
92
|
+
super({
|
|
93
|
+
serializer: TodoSerializer,
|
|
94
|
+
filters: FilterSet.define<Todo>({
|
|
95
|
+
fields: { completed: true },
|
|
96
|
+
}),
|
|
97
|
+
orderingFields: ['id', 'title'],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Adapters wire those resource classes to host routes through helpers such as `ExpressAdapter.registerViewSet(...)` and `NextAdapter.adaptViewSet(...)`.
|
|
104
|
+
|
|
105
|
+
## Where logic belongs
|
|
106
|
+
|
|
107
|
+
Use a serializer for:
|
|
108
|
+
|
|
109
|
+
- create and update input validation
|
|
110
|
+
- output representation
|
|
111
|
+
- request-scoped normalization
|
|
112
|
+
- resource-specific transformation that belongs to the HTTP contract
|
|
113
|
+
|
|
114
|
+
Use model hooks for:
|
|
115
|
+
|
|
116
|
+
- timestamp stamping
|
|
117
|
+
- slug generation that must apply for every write path
|
|
118
|
+
- persistence defaults and normalization that belong to the record itself
|
|
119
|
+
- side effects that should run no matter which caller writes through `Model.objects`
|
|
120
|
+
|
|
121
|
+
Use the resource or viewset for:
|
|
122
|
+
|
|
123
|
+
- routing behavior
|
|
124
|
+
- filtering, search, and pagination policy
|
|
125
|
+
- custom actions and endpoint orchestration
|
|
126
|
+
|
|
127
|
+
## Public API
|
|
128
|
+
|
|
129
|
+
The root export includes:
|
|
130
|
+
|
|
131
|
+
- `RequestContext`
|
|
132
|
+
- `Serializer` and `ModelSerializer`
|
|
133
|
+
- `FilterSet`
|
|
134
|
+
- `OffsetPaginator`, `CursorPaginator`, and pagination contracts
|
|
135
|
+
- `ModelViewSet`
|
|
136
|
+
- `APIView`, `GenericAPIView`, and the generic CRUD-oriented view classes and mixins
|
|
137
|
+
|
|
138
|
+
Most applications start with `ModelSerializer`, `ModelViewSet`, `FilterSet`, and one paginator. The generic view stack becomes useful when an endpoint is narrower than a full CRUD resource, and `APIView` stays available for fully custom request handling.
|
|
139
|
+
|
|
140
|
+
## Import style
|
|
141
|
+
|
|
142
|
+
The package supports both root imports and domain-style imports:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { APIView, FilterSet, ModelSerializer, ModelViewSet, OffsetPaginator } from '@danceroutine/tango-resources';
|
|
146
|
+
import { context, filters, pagination, serializer, view, viewset } from '@danceroutine/tango-resources';
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Available subpaths include `context`, `filters`, `pagination`, `paginators`, `serializer`, `viewset`, `view`, and `domain`.
|
|
150
|
+
|
|
151
|
+
## Developer workflow
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pnpm --filter @danceroutine/tango-resources build
|
|
155
|
+
pnpm --filter @danceroutine/tango-resources typecheck
|
|
156
|
+
pnpm --filter @danceroutine/tango-resources test
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Bugs and support
|
|
160
|
+
|
|
161
|
+
- Documentation: <https://tangowebframework.dev>
|
|
162
|
+
- Serializers topic: <https://tangowebframework.dev/topics/serializers>
|
|
163
|
+
- Model lifecycle hooks topic: <https://tangowebframework.dev/topics/model-lifecycle-hooks>
|
|
164
|
+
- Resources topic: <https://tangowebframework.dev/topics/resources-and-viewsets>
|
|
165
|
+
- API reference: <https://tangowebframework.dev/reference/resources-api>
|
|
166
|
+
- Issue tracker: <https://github.com/danceroutine/tango/issues>
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TangoRequest } from '@danceroutine/tango-core';
|
|
1
2
|
/**
|
|
2
3
|
* Default user shape for RequestContext.
|
|
3
4
|
* Consumers can provide their own user type via the TUser generic parameter.
|
|
@@ -11,17 +12,35 @@ export interface BaseUser {
|
|
|
11
12
|
* Generic over the user type so consumers can plug in their own auth infrastructure.
|
|
12
13
|
*/
|
|
13
14
|
export declare class RequestContext<TUser = BaseUser> {
|
|
14
|
-
readonly request:
|
|
15
|
+
readonly request: TangoRequest;
|
|
15
16
|
user: TUser | null;
|
|
16
17
|
params: Record<string, string>;
|
|
17
18
|
static readonly BRAND: "tango.resources.request_context";
|
|
18
|
-
private state;
|
|
19
19
|
readonly __tangoBrand: typeof RequestContext.BRAND;
|
|
20
|
-
|
|
20
|
+
private state;
|
|
21
|
+
constructor(request: TangoRequest, user?: TUser | null, params?: Record<string, string>);
|
|
22
|
+
/**
|
|
23
|
+
* Narrow an unknown value to `RequestContext`.
|
|
24
|
+
*/
|
|
21
25
|
static isRequestContext<TUser = BaseUser>(value: unknown): value is RequestContext<TUser>;
|
|
26
|
+
/**
|
|
27
|
+
* Construct a context with optional user payload.
|
|
28
|
+
*/
|
|
29
|
+
static create<TUser = BaseUser>(request: Request, user?: TUser | null): RequestContext<TUser>;
|
|
30
|
+
/**
|
|
31
|
+
* Store arbitrary per-request state for downstream middleware/handlers.
|
|
32
|
+
*/
|
|
22
33
|
setState<T>(key: string | symbol, value: T): void;
|
|
34
|
+
/**
|
|
35
|
+
* Retrieve previously stored request state.
|
|
36
|
+
*/
|
|
23
37
|
getState<T>(key: string | symbol): T | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Check whether a state key has been set.
|
|
40
|
+
*/
|
|
24
41
|
hasState(key: string | symbol): boolean;
|
|
25
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Clone the context, including route params and request-local state.
|
|
44
|
+
*/
|
|
26
45
|
clone(): RequestContext<TUser>;
|
|
27
46
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { TangoQueryParams } from '@danceroutine/tango-core';
|
|
2
|
+
import type { FilterInput, FilterValue, LookupType } from '@danceroutine/tango-orm';
|
|
2
3
|
import { InternalFilterType } from './internal/InternalFilterType';
|
|
3
4
|
import type { RangeOperator } from './RangeOperator';
|
|
4
5
|
/**
|
|
@@ -22,12 +23,66 @@ export type FilterResolver<T> = {
|
|
|
22
23
|
type: typeof InternalFilterType.CUSTOM;
|
|
23
24
|
apply: (value: string | string[] | undefined) => FilterInput<T> | undefined;
|
|
24
25
|
};
|
|
26
|
+
export type FilterLookup = LookupType;
|
|
27
|
+
export type FilterValueParser = (raw: string | string[]) => FilterValue | FilterValue[] | undefined;
|
|
28
|
+
export type FieldFilterDeclaration = true | readonly FilterLookup[] | {
|
|
29
|
+
lookups?: readonly FilterLookup[];
|
|
30
|
+
param?: string;
|
|
31
|
+
parse?: FilterValueParser;
|
|
32
|
+
};
|
|
33
|
+
export type AliasFilterDeclaration<T extends Record<string, unknown>> = FilterResolver<T> | {
|
|
34
|
+
field: keyof T;
|
|
35
|
+
lookup?: FilterLookup;
|
|
36
|
+
parse?: FilterValueParser;
|
|
37
|
+
} | {
|
|
38
|
+
fields: readonly (keyof T)[];
|
|
39
|
+
lookup?: FilterLookup;
|
|
40
|
+
parse?: FilterValueParser;
|
|
41
|
+
};
|
|
42
|
+
export interface FilterSetDefineConfig<T extends Record<string, unknown>> {
|
|
43
|
+
fields?: Partial<Record<keyof T, FieldFilterDeclaration>>;
|
|
44
|
+
aliases?: Record<string, AliasFilterDeclaration<T>>;
|
|
45
|
+
parsers?: Partial<Record<keyof T, FilterValueParser>>;
|
|
46
|
+
all?: '__all__';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Declarative query-param to filter translation.
|
|
50
|
+
*
|
|
51
|
+
* A `FilterSet` lets viewsets expose safe, explicit filtering behavior
|
|
52
|
+
* without leaking raw ORM filter syntax to request handlers.
|
|
53
|
+
*/
|
|
25
54
|
export declare class FilterSet<T extends Record<string, unknown>> {
|
|
26
|
-
private spec;
|
|
55
|
+
private readonly spec;
|
|
56
|
+
private readonly allowAllParams;
|
|
27
57
|
static readonly BRAND: "tango.resources.filter_set";
|
|
28
58
|
readonly __tangoBrand: typeof FilterSet.BRAND;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve matching query parameters into ORM filter inputs.
|
|
61
|
+
*/
|
|
62
|
+
constructor(spec: Record<string, FilterResolver<T>>, allowAllParams?: boolean);
|
|
63
|
+
/**
|
|
64
|
+
* Build a filter set from Django-style field declarations.
|
|
65
|
+
*/
|
|
66
|
+
static define<T extends Record<string, unknown>>(config: FilterSetDefineConfig<T>): FilterSet<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Narrow an unknown value to `FilterSet`.
|
|
69
|
+
*/
|
|
29
70
|
static isFilterSet<T extends Record<string, unknown>>(value: unknown): value is FilterSet<T>;
|
|
30
|
-
|
|
31
|
-
|
|
71
|
+
private static normalizeDefineConfig;
|
|
72
|
+
private static addFieldDeclaration;
|
|
73
|
+
private static isLookupArray;
|
|
74
|
+
private static normalizeAliasDeclaration;
|
|
75
|
+
private static isFilterResolverDeclaration;
|
|
76
|
+
private static createMultiFieldResolver;
|
|
77
|
+
private static createLookupResolver;
|
|
78
|
+
private static resolveLookupFilter;
|
|
79
|
+
private static resolveLookupParam;
|
|
80
|
+
private static resolveParserValue;
|
|
81
|
+
private static toScalarString;
|
|
82
|
+
/**
|
|
83
|
+
* Apply all configured resolvers against query params.
|
|
84
|
+
*/
|
|
85
|
+
apply(params: TangoQueryParams): FilterInput<T>[];
|
|
86
|
+
private buildAllResolver;
|
|
32
87
|
private resolveFilter;
|
|
33
88
|
}
|
package/dist/filters/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type { FilterType } from './FilterType';
|
|
5
5
|
export type { RangeOperator } from './RangeOperator';
|
|
6
|
-
export type { FilterResolver } from './FilterSet';
|
|
6
|
+
export type { AliasFilterDeclaration, FieldFilterDeclaration, FilterLookup, FilterResolver, FilterSetDefineConfig, FilterValueParser, } from './FilterSet';
|
|
7
7
|
export { FilterSet } from './FilterSet';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,12 +6,18 @@ export * as context from './context/index';
|
|
|
6
6
|
export * as filters from './filters/index';
|
|
7
7
|
export * as pagination from './pagination/index';
|
|
8
8
|
export * as paginators from './paginators/index';
|
|
9
|
+
export * as resource from './resource/index';
|
|
10
|
+
export * as serializer from './serializer/index';
|
|
9
11
|
export * as viewset from './viewset/index';
|
|
10
|
-
export * as
|
|
12
|
+
export * as view from './view/index';
|
|
11
13
|
export { RequestContext } from './context/index';
|
|
12
14
|
export type { BaseUser } from './context/index';
|
|
13
|
-
export { FilterSet, type FilterResolver } from './filters/index';
|
|
15
|
+
export { FilterSet, type AliasFilterDeclaration, type FieldFilterDeclaration, type FilterLookup, type FilterResolver, type FilterSetDefineConfig, type FilterValueParser, } from './filters/index';
|
|
14
16
|
export type { FilterType, RangeOperator } from './filters/index';
|
|
15
|
-
export { CursorPaginator, OffsetPaginator,
|
|
17
|
+
export { CursorPaginator, OffsetPaginator, CursorPaginationInput, OffsetPaginationInput, type Page, type BasePaginatedResponse, type CursorPaginatedResponse, type OffsetPaginatedResponse, type PaginatedResponse, type Paginator, } from './pagination/index';
|
|
18
|
+
export { Serializer, ModelSerializer, type SerializerClass, type AnySerializerClass, type SerializerCreateInput, type SerializerUpdateInput, type SerializerOutput, type SerializerSchema, type ModelSerializerClass, type AnyModelSerializerClass, } from './serializer/index';
|
|
16
19
|
export { ModelViewSet } from './viewset/index';
|
|
17
|
-
export type { ModelViewSetConfig } from './viewset/index';
|
|
20
|
+
export type { ModelViewSetOpenAPIDescription, ModelViewSetConfig, ViewSetActionDescriptor, ViewSetActionMethod, ViewSetActionScope, ResolvedViewSetActionDescriptor, } from './viewset/index';
|
|
21
|
+
export { APIView, GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, ListAPIView, CreateAPIView, RetrieveAPIView, ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView, } from './view/index';
|
|
22
|
+
export type { APIViewMethod, GenericAPIViewConfig, GenericAPIViewOpenAPIDescription } from './view/index';
|
|
23
|
+
export type { ResourceModelFieldMetadata, ResourceModelLike, ResourceModelMetadata } from './resource/index';
|