@stone-js/resources 0.8.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 +42 -0
- package/dist/Resource.d.ts +75 -0
- package/dist/declarations.d.ts +36 -0
- package/dist/defineResource.d.ts +17 -0
- package/dist/helpers.d.ts +44 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +191 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Stone Foundation
|
|
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,42 @@
|
|
|
1
|
+
# Stone.js · Resources
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@stone-js/resources)
|
|
4
|
+
[](https://github.com/stone-foundation/stone-js-framework/actions/workflows/ci.yml)
|
|
5
|
+
[](https://sonarcloud.io/summary/new_code?id=stone-foundation_stone-js-framework)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://conventionalcommits.org)
|
|
8
|
+
|
|
9
|
+
> Framework-agnostic API resources for Stone.js. Shape what your domain exposes — sparse fieldsets, conditional fields, includes and envelopes — decoupled from controllers, the same on backend and frontend.
|
|
10
|
+
|
|
11
|
+
Part of **[Stone.js](https://stonejs.dev)**, the reference implementation of the
|
|
12
|
+
[Continuum Architecture](https://evens-stone.github.io/continuum-manifesto/manifesto): write your
|
|
13
|
+
domain once, and the context (runtime, protocol, caller) applies to it at run time.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @stone-js/resources
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { defineResource, only } from '@stone-js/resources'
|
|
25
|
+
|
|
26
|
+
// Shape what your domain exposes: hide internals, support sparse fieldsets.
|
|
27
|
+
export const TaskResource = defineResource((task) => ({
|
|
28
|
+
id: task.id,
|
|
29
|
+
title: task.title,
|
|
30
|
+
createdAt: task.createdAt
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
// only(...) / except(...) drive sparse fieldsets from the request.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
Full documentation: **[stonejs.dev/docs/extensions/resources](https://stonejs.dev/docs/extensions/resources)**.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
[MIT](https://opensource.org/licenses/MIT) © Evens Pierre ("Mr. Stone") and the Stone.js contributors.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { IResource, ResourceContext, ResourceEnvelope, ResourceOutput } from './declarations';
|
|
2
|
+
/**
|
|
3
|
+
* Base API resource — the declarative way to shape what your domain exposes.
|
|
4
|
+
*
|
|
5
|
+
* Extend it and implement {@link Resource.toArray} to map a model to its public shape. Everything
|
|
6
|
+
* else (sparse fieldsets, dropping conditional fields, collections, envelopes) is handled for you.
|
|
7
|
+
* A resource is decoupled from controllers and platform-agnostic: the same resource shapes data on
|
|
8
|
+
* the backend and on the frontend.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* class UserResource extends Resource<User> {
|
|
13
|
+
* toArray (user: User, ctx: ResourceContext) {
|
|
14
|
+
* return {
|
|
15
|
+
* id: user.id,
|
|
16
|
+
* name: user.name,
|
|
17
|
+
* email: this.when(ctx.self === true, user.email),
|
|
18
|
+
* posts: this.whenIncluded(ctx, 'posts', () => postResource.collection(user.posts))
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare abstract class Resource<Model = unknown, Output extends ResourceOutput = ResourceOutput> implements IResource<Model, Output> {
|
|
25
|
+
/**
|
|
26
|
+
* Map a model to its public shape (before field filtering).
|
|
27
|
+
*
|
|
28
|
+
* @param model - The domain model.
|
|
29
|
+
* @param context - The resource context.
|
|
30
|
+
* @returns The public shape.
|
|
31
|
+
*/
|
|
32
|
+
abstract toArray(model: Model, context: ResourceContext): Output;
|
|
33
|
+
/**
|
|
34
|
+
* Transform one model, applying the requested sparse fieldset and dropping undefined fields.
|
|
35
|
+
*
|
|
36
|
+
* @param model - The domain model.
|
|
37
|
+
* @param context - The resource context.
|
|
38
|
+
* @returns The filtered public shape.
|
|
39
|
+
*/
|
|
40
|
+
item(model: Model, context?: ResourceContext): Partial<Output>;
|
|
41
|
+
/**
|
|
42
|
+
* Transform a collection of models.
|
|
43
|
+
*
|
|
44
|
+
* @param models - The domain models.
|
|
45
|
+
* @param context - The resource context.
|
|
46
|
+
* @returns The transformed collection.
|
|
47
|
+
*/
|
|
48
|
+
collection(models: Model[], context?: ResourceContext): Array<Partial<Output>>;
|
|
49
|
+
/**
|
|
50
|
+
* Wrap a model or a collection in a `{ data, meta }` envelope.
|
|
51
|
+
*
|
|
52
|
+
* @param models - A model or a collection.
|
|
53
|
+
* @param context - The resource context.
|
|
54
|
+
* @param meta - Optional metadata (pagination, counts, …).
|
|
55
|
+
* @returns The envelope.
|
|
56
|
+
*/
|
|
57
|
+
response(models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>): ResourceEnvelope<Partial<Output> | Array<Partial<Output>>>;
|
|
58
|
+
/**
|
|
59
|
+
* Include a value only when `condition` is truthy (otherwise the field is dropped).
|
|
60
|
+
*
|
|
61
|
+
* @param condition - Whether to include the value.
|
|
62
|
+
* @param value - The value, or a lazy factory (only evaluated when included).
|
|
63
|
+
* @returns The value, or `undefined`.
|
|
64
|
+
*/
|
|
65
|
+
protected when<T>(condition: boolean, value: T | (() => T)): T | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Include a value only when the relation was requested via `context.include`.
|
|
68
|
+
*
|
|
69
|
+
* @param context - The resource context.
|
|
70
|
+
* @param name - The relation name.
|
|
71
|
+
* @param value - The value, or a lazy factory (only evaluated when included).
|
|
72
|
+
* @returns The value, or `undefined`.
|
|
73
|
+
*/
|
|
74
|
+
protected whenIncluded<T>(context: ResourceContext, name: string, value: T | (() => T)): T | undefined;
|
|
75
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The context that shapes a transformation: which fields the client asked for, which relations to
|
|
3
|
+
* include, and any extra data (the current user, the event, …) a resource may consult.
|
|
4
|
+
*
|
|
5
|
+
* It is intentionally open so resources can read whatever they need while staying agnostic.
|
|
6
|
+
*/
|
|
7
|
+
export interface ResourceContext {
|
|
8
|
+
/** Requested sparse fieldset — when set, the output is limited to these top-level keys. */
|
|
9
|
+
fields?: string[];
|
|
10
|
+
/** Requested relations to embed. */
|
|
11
|
+
include?: string[];
|
|
12
|
+
/** Anything else a resource needs (e.g. the authenticated principal). */
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/** A plain, serialisable output object. */
|
|
16
|
+
export type ResourceOutput = Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* A `{ data, meta }` envelope around a transformed item or collection.
|
|
19
|
+
*/
|
|
20
|
+
export interface ResourceEnvelope<T> {
|
|
21
|
+
data: T;
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The resource contract: transform a model (or a collection) into its public representation.
|
|
26
|
+
*/
|
|
27
|
+
export interface IResource<Model = unknown, Output extends ResourceOutput = ResourceOutput> {
|
|
28
|
+
/** Transform one model into its public shape (before field filtering). */
|
|
29
|
+
toArray: (model: Model, context: ResourceContext) => Output;
|
|
30
|
+
/** Transform one model, applying sparse fieldsets and dropping undefined fields. */
|
|
31
|
+
item: (model: Model, context?: ResourceContext) => Partial<Output>;
|
|
32
|
+
/** Transform a collection. */
|
|
33
|
+
collection: (models: Model[], context?: ResourceContext) => Array<Partial<Output>>;
|
|
34
|
+
/** Wrap a model or collection in a `{ data, meta }` envelope. */
|
|
35
|
+
response: (models: Model | Model[], context?: ResourceContext, meta?: Record<string, unknown>) => ResourceEnvelope<Partial<Output> | Array<Partial<Output>>>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Resource } from './Resource';
|
|
2
|
+
import { ResourceContext, ResourceOutput } from './declarations';
|
|
3
|
+
/**
|
|
4
|
+
* The imperative/functional way to define a resource — a plain transform function instead of a
|
|
5
|
+
* class. Returns a full {@link Resource} (so you still get `item`/`collection`/`response` and
|
|
6
|
+
* sparse fieldsets for free).
|
|
7
|
+
*
|
|
8
|
+
* @param transform - Maps a model to its public shape.
|
|
9
|
+
* @returns A resource.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const userResource = defineResource<User>((user) => ({ id: user.id, name: user.name }))
|
|
14
|
+
* userResource.collection(users, { fields: ['id'] })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function defineResource<Model = unknown, Output extends ResourceOutput = ResourceOutput>(transform: (model: Model, context: ResourceContext) => Output): Resource<Model, Output>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ResourceContext, ResourceOutput } from './declarations';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a copy of `object` without any `undefined` values (so conditional fields simply vanish).
|
|
4
|
+
*
|
|
5
|
+
* @param object - The object to clean.
|
|
6
|
+
* @returns A copy without undefined values.
|
|
7
|
+
*/
|
|
8
|
+
export declare function stripUndefined<T extends ResourceOutput>(object: T): Partial<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Keeps only the given keys of an object (ignoring keys that are absent).
|
|
11
|
+
*
|
|
12
|
+
* @param object - The source object.
|
|
13
|
+
* @param keys - The keys to keep.
|
|
14
|
+
* @returns A new object with only those keys.
|
|
15
|
+
*/
|
|
16
|
+
export declare function only<T extends ResourceOutput>(object: T, keys: string[]): Partial<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns a copy of an object without the given keys.
|
|
19
|
+
*
|
|
20
|
+
* @param object - The source object.
|
|
21
|
+
* @param keys - The keys to drop.
|
|
22
|
+
* @returns A new object without those keys.
|
|
23
|
+
*/
|
|
24
|
+
export declare function except<T extends ResourceOutput>(object: T, keys: string[]): Partial<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Applies a sparse fieldset to an output: strips undefined, then narrows to the requested fields
|
|
27
|
+
* (when any were requested).
|
|
28
|
+
*
|
|
29
|
+
* @param output - The transformed output.
|
|
30
|
+
* @param fields - The requested fields (optional).
|
|
31
|
+
* @returns The filtered output.
|
|
32
|
+
*/
|
|
33
|
+
export declare function applyFields<T extends ResourceOutput>(output: T, fields?: string[]): Partial<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Builds a {@link ResourceContext} from an incoming event's `fields` and `include` query
|
|
36
|
+
* parameters (comma-separated). Agnostic: the event only needs a `get(key)` method.
|
|
37
|
+
*
|
|
38
|
+
* @param event - Anything with `get(key)` (an `IncomingHttpEvent`, a URL search wrapper, …).
|
|
39
|
+
* @param extra - Extra context to merge in.
|
|
40
|
+
* @returns The resource context.
|
|
41
|
+
*/
|
|
42
|
+
export declare function contextFromEvent(event: {
|
|
43
|
+
get: <T>(key: string, fallback?: T) => T;
|
|
44
|
+
}, extra?: ResourceContext): ResourceContext;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a copy of `object` without any `undefined` values (so conditional fields simply vanish).
|
|
3
|
+
*
|
|
4
|
+
* @param object - The object to clean.
|
|
5
|
+
* @returns A copy without undefined values.
|
|
6
|
+
*/
|
|
7
|
+
function stripUndefined(object) {
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const key of Object.keys(object)) {
|
|
10
|
+
if (object[key] !== undefined) {
|
|
11
|
+
out[key] = object[key];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Keeps only the given keys of an object (ignoring keys that are absent).
|
|
18
|
+
*
|
|
19
|
+
* @param object - The source object.
|
|
20
|
+
* @param keys - The keys to keep.
|
|
21
|
+
* @returns A new object with only those keys.
|
|
22
|
+
*/
|
|
23
|
+
function only(object, keys) {
|
|
24
|
+
const set = new Set(keys);
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const key of Object.keys(object)) {
|
|
27
|
+
if (set.has(key)) {
|
|
28
|
+
out[key] = object[key];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns a copy of an object without the given keys.
|
|
35
|
+
*
|
|
36
|
+
* @param object - The source object.
|
|
37
|
+
* @param keys - The keys to drop.
|
|
38
|
+
* @returns A new object without those keys.
|
|
39
|
+
*/
|
|
40
|
+
function except(object, keys) {
|
|
41
|
+
const set = new Set(keys);
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const key of Object.keys(object)) {
|
|
44
|
+
if (!set.has(key)) {
|
|
45
|
+
out[key] = object[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Applies a sparse fieldset to an output: strips undefined, then narrows to the requested fields
|
|
52
|
+
* (when any were requested).
|
|
53
|
+
*
|
|
54
|
+
* @param output - The transformed output.
|
|
55
|
+
* @param fields - The requested fields (optional).
|
|
56
|
+
* @returns The filtered output.
|
|
57
|
+
*/
|
|
58
|
+
function applyFields(output, fields) {
|
|
59
|
+
const clean = stripUndefined(output);
|
|
60
|
+
return fields !== undefined && fields.length > 0 ? only(clean, fields) : clean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds a {@link ResourceContext} from an incoming event's `fields` and `include` query
|
|
64
|
+
* parameters (comma-separated). Agnostic: the event only needs a `get(key)` method.
|
|
65
|
+
*
|
|
66
|
+
* @param event - Anything with `get(key)` (an `IncomingHttpEvent`, a URL search wrapper, …).
|
|
67
|
+
* @param extra - Extra context to merge in.
|
|
68
|
+
* @returns The resource context.
|
|
69
|
+
*/
|
|
70
|
+
function contextFromEvent(event, extra = {}) {
|
|
71
|
+
return {
|
|
72
|
+
...extra,
|
|
73
|
+
fields: splitCsv(event.get('fields', '')),
|
|
74
|
+
include: splitCsv(event.get('include', ''))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Splits a comma-separated string into a trimmed, non-empty list (or `undefined` when empty).
|
|
79
|
+
*
|
|
80
|
+
* @param value - The CSV string.
|
|
81
|
+
* @returns The list, or `undefined`.
|
|
82
|
+
*/
|
|
83
|
+
function splitCsv(value) {
|
|
84
|
+
const parts = value.split(',').map((part) => part.trim()).filter((part) => part.length > 0);
|
|
85
|
+
return parts.length > 0 ? parts : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Base API resource — the declarative way to shape what your domain exposes.
|
|
90
|
+
*
|
|
91
|
+
* Extend it and implement {@link Resource.toArray} to map a model to its public shape. Everything
|
|
92
|
+
* else (sparse fieldsets, dropping conditional fields, collections, envelopes) is handled for you.
|
|
93
|
+
* A resource is decoupled from controllers and platform-agnostic: the same resource shapes data on
|
|
94
|
+
* the backend and on the frontend.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* class UserResource extends Resource<User> {
|
|
99
|
+
* toArray (user: User, ctx: ResourceContext) {
|
|
100
|
+
* return {
|
|
101
|
+
* id: user.id,
|
|
102
|
+
* name: user.name,
|
|
103
|
+
* email: this.when(ctx.self === true, user.email),
|
|
104
|
+
* posts: this.whenIncluded(ctx, 'posts', () => postResource.collection(user.posts))
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
class Resource {
|
|
111
|
+
/**
|
|
112
|
+
* Transform one model, applying the requested sparse fieldset and dropping undefined fields.
|
|
113
|
+
*
|
|
114
|
+
* @param model - The domain model.
|
|
115
|
+
* @param context - The resource context.
|
|
116
|
+
* @returns The filtered public shape.
|
|
117
|
+
*/
|
|
118
|
+
item(model, context = {}) {
|
|
119
|
+
return applyFields(this.toArray(model, context), context.fields);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Transform a collection of models.
|
|
123
|
+
*
|
|
124
|
+
* @param models - The domain models.
|
|
125
|
+
* @param context - The resource context.
|
|
126
|
+
* @returns The transformed collection.
|
|
127
|
+
*/
|
|
128
|
+
collection(models, context = {}) {
|
|
129
|
+
return models.map((model) => this.item(model, context));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Wrap a model or a collection in a `{ data, meta }` envelope.
|
|
133
|
+
*
|
|
134
|
+
* @param models - A model or a collection.
|
|
135
|
+
* @param context - The resource context.
|
|
136
|
+
* @param meta - Optional metadata (pagination, counts, …).
|
|
137
|
+
* @returns The envelope.
|
|
138
|
+
*/
|
|
139
|
+
response(models, context = {}, meta) {
|
|
140
|
+
const data = Array.isArray(models) ? this.collection(models, context) : this.item(models, context);
|
|
141
|
+
return meta === undefined ? { data } : { data, meta };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Include a value only when `condition` is truthy (otherwise the field is dropped).
|
|
145
|
+
*
|
|
146
|
+
* @param condition - Whether to include the value.
|
|
147
|
+
* @param value - The value, or a lazy factory (only evaluated when included).
|
|
148
|
+
* @returns The value, or `undefined`.
|
|
149
|
+
*/
|
|
150
|
+
when(condition, value) {
|
|
151
|
+
if (!condition) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
return typeof value === 'function' ? value() : value;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Include a value only when the relation was requested via `context.include`.
|
|
158
|
+
*
|
|
159
|
+
* @param context - The resource context.
|
|
160
|
+
* @param name - The relation name.
|
|
161
|
+
* @param value - The value, or a lazy factory (only evaluated when included).
|
|
162
|
+
* @returns The value, or `undefined`.
|
|
163
|
+
*/
|
|
164
|
+
whenIncluded(context, name, value) {
|
|
165
|
+
return this.when(context.include?.includes(name) === true, value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The imperative/functional way to define a resource — a plain transform function instead of a
|
|
171
|
+
* class. Returns a full {@link Resource} (so you still get `item`/`collection`/`response` and
|
|
172
|
+
* sparse fieldsets for free).
|
|
173
|
+
*
|
|
174
|
+
* @param transform - Maps a model to its public shape.
|
|
175
|
+
* @returns A resource.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const userResource = defineResource<User>((user) => ({ id: user.id, name: user.name }))
|
|
180
|
+
* userResource.collection(users, { fields: ['id'] })
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
function defineResource(transform) {
|
|
184
|
+
return new class extends Resource {
|
|
185
|
+
toArray(model, context) {
|
|
186
|
+
return transform(model, context);
|
|
187
|
+
}
|
|
188
|
+
}();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { Resource, applyFields, contextFromEvent, defineResource, except, only, stripUndefined };
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stone-js/resources",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Framework-agnostic API resources for Stone.js. Shape what your domain exposes — sparse fieldsets, conditional fields, includes and envelopes — decoupled from controllers, the same on backend and frontend.",
|
|
5
|
+
"author": "Mr. Stone <evensstone@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/stone-foundation/stone-js-framework.git",
|
|
10
|
+
"directory": "stone-js-resources"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://stonejs.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/stone-foundation/stone-js-framework/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"StoneJS",
|
|
18
|
+
"resources",
|
|
19
|
+
"transformer",
|
|
20
|
+
"serializer",
|
|
21
|
+
"api",
|
|
22
|
+
"fieldsets",
|
|
23
|
+
"presenter",
|
|
24
|
+
"isomorphic"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"/dist"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.17.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@commitlint/cli": "^19.8.1",
|
|
43
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
44
|
+
"@rollup/plugin-commonjs": "^28.0.6",
|
|
45
|
+
"@rollup/plugin-multi-entry": "^6.0.1",
|
|
46
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
47
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
48
|
+
"@types/node": "^24.0.7",
|
|
49
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
50
|
+
"husky": "^9.1.7",
|
|
51
|
+
"rimraf": "^6.0.1",
|
|
52
|
+
"rollup": "^4.44.1",
|
|
53
|
+
"rollup-plugin-node-externals": "^8.0.1",
|
|
54
|
+
"ts-standard": "^12.0.2",
|
|
55
|
+
"tslib": "^2.8.1",
|
|
56
|
+
"typedoc": "^0.28.6",
|
|
57
|
+
"typedoc-plugin-markdown": "^4.7.0",
|
|
58
|
+
"typescript": "^5.6.3",
|
|
59
|
+
"vitest": "^3.2.4"
|
|
60
|
+
},
|
|
61
|
+
"ts-standard": {
|
|
62
|
+
"globals": [
|
|
63
|
+
"it",
|
|
64
|
+
"test",
|
|
65
|
+
"vi",
|
|
66
|
+
"expect",
|
|
67
|
+
"describe",
|
|
68
|
+
"beforeEach"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"lint": "ts-standard src",
|
|
73
|
+
"lint:fix": "ts-standard --fix src tests",
|
|
74
|
+
"predoc": "rimraf docs",
|
|
75
|
+
"doc": "typedoc",
|
|
76
|
+
"clean": "rimraf dist",
|
|
77
|
+
"build": "rollup -c",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:cvg": "npm run test -- --coverage",
|
|
80
|
+
"test:text": "npm run test:cvg -- --coverage.reporter=text",
|
|
81
|
+
"test:html": "npm run test:cvg -- --coverage.reporter=html",
|
|
82
|
+
"test:clover": "npm run test:cvg -- --coverage.reporter=clover"
|
|
83
|
+
}
|
|
84
|
+
}
|