@nexo-alpha/data 0.0.0-stage → 0.1.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/dist/client.d.ts +71 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +192 -0
- package/dist/client.js.map +1 -0
- package/dist/fields.d.ts +115 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +127 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/resource.d.ts +119 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +289 -0
- package/dist/resource.js.map +1 -0
- package/dist/store.d.ts +68 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +132 -0
- package/dist/store.js.map +1 -0
- package/package.json +45 -3
- package/README.md +0 -3
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexo-alpha/data — resource() factory.
|
|
3
|
+
*
|
|
4
|
+
* Generates five CRUD `NexoApi` objects from a `Fields` map and wires them
|
|
5
|
+
* to a `ResourceStore`. The Hapi adapter picks up the resulting module
|
|
6
|
+
* unchanged — no modifications to `@nexo-alpha/hapi` required.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { resource, string, email, number } from "@nexo-alpha/data";
|
|
11
|
+
*
|
|
12
|
+
* export const User = resource("User", {
|
|
13
|
+
* name: string({ min: 1 }),
|
|
14
|
+
* email: email(),
|
|
15
|
+
* age: number().optional(),
|
|
16
|
+
* }, { auth: { scopes: ["users:read", "users:write"] } });
|
|
17
|
+
*
|
|
18
|
+
* app.module(User.module());
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { type NexoApi, type NexoModule } from "@nexo-alpha/core";
|
|
22
|
+
import { type Fields, type Infer } from "./fields.js";
|
|
23
|
+
import { type Entity, type ListQuery, type Page, type ResourceStore } from "./store.js";
|
|
24
|
+
export interface ResourceEmitter {
|
|
25
|
+
emit(event: string, payload: unknown): void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export type Operation = "list" | "get" | "create" | "update" | "delete";
|
|
28
|
+
export interface OperationAuth {
|
|
29
|
+
readonly scopes?: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
export interface ResourceOptions<T> {
|
|
32
|
+
/**
|
|
33
|
+
* URL path for the collection. Defaults to `"/<plural-kebab-name>"`.
|
|
34
|
+
* Override when the default pluralisation is wrong:
|
|
35
|
+
* `{ path: "/people" }`.
|
|
36
|
+
*/
|
|
37
|
+
readonly path?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Default auth applied to all operations. Individual operations can
|
|
40
|
+
* override this via `operationAuth`.
|
|
41
|
+
*/
|
|
42
|
+
readonly auth?: {
|
|
43
|
+
readonly scopes?: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Per-operation scope overrides. If omitted, falls back to the top-level
|
|
47
|
+
* `auth.scopes`.
|
|
48
|
+
*/
|
|
49
|
+
readonly operationAuth?: Partial<Record<Operation, OperationAuth>>;
|
|
50
|
+
/**
|
|
51
|
+
* Expose only a subset of the five CRUD operations.
|
|
52
|
+
* Default: all five.
|
|
53
|
+
*/
|
|
54
|
+
readonly operations?: readonly Operation[];
|
|
55
|
+
/**
|
|
56
|
+
* Provide an alternative store (Drizzle, Prisma, etc.).
|
|
57
|
+
* Default: `memoryStore()`.
|
|
58
|
+
*/
|
|
59
|
+
readonly store?: ResourceStore<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Event emitter for domain events (`user.created`, …).
|
|
62
|
+
* Default: no-op — safe to wire to `@nexo-alpha/events` later.
|
|
63
|
+
*/
|
|
64
|
+
readonly events?: ResourceEmitter;
|
|
65
|
+
}
|
|
66
|
+
export interface ResourceFieldDetail {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly type: string;
|
|
69
|
+
readonly required: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface ResourceDescription {
|
|
72
|
+
readonly model: string;
|
|
73
|
+
readonly path: string;
|
|
74
|
+
readonly fields: readonly string[];
|
|
75
|
+
readonly fieldDetails?: readonly ResourceFieldDetail[];
|
|
76
|
+
readonly operations: readonly Operation[];
|
|
77
|
+
readonly scopes: readonly string[];
|
|
78
|
+
readonly events: readonly string[];
|
|
79
|
+
readonly store: string;
|
|
80
|
+
}
|
|
81
|
+
export interface Resource<F extends Fields> {
|
|
82
|
+
/** Resource name, e.g. `"User"`. */
|
|
83
|
+
readonly name: string;
|
|
84
|
+
/** Collection path, e.g. `"/users"`. */
|
|
85
|
+
readonly path: string;
|
|
86
|
+
/** The field descriptor map passed to `resource()`. */
|
|
87
|
+
readonly fields: F;
|
|
88
|
+
/** Raw store access — the only part that changes per database adapter. */
|
|
89
|
+
readonly store: ResourceStore<Infer<F>>;
|
|
90
|
+
list(query?: ListQuery<Infer<F>>): Promise<Page<Entity<Infer<F>>>>;
|
|
91
|
+
get(id: string): Promise<Entity<Infer<F>>>;
|
|
92
|
+
create(data: Infer<F>): Promise<Entity<Infer<F>>>;
|
|
93
|
+
update(id: string, patch: Partial<Infer<F>>): Promise<Entity<Infer<F>>>;
|
|
94
|
+
delete(id: string): Promise<void>;
|
|
95
|
+
/** Returns the five (or fewer) `NexoApi` objects registered by `module()`. */
|
|
96
|
+
apis(): readonly NexoApi[];
|
|
97
|
+
/** Returns a `NexoModule` ready to be passed to `app.module()`. */
|
|
98
|
+
module(): NexoModule;
|
|
99
|
+
/** Powers `nexo inspect <Resource>`. */
|
|
100
|
+
describe(): ResourceDescription;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Declares a CRUD resource.
|
|
104
|
+
*
|
|
105
|
+
* @param name PascalCase model name (`"User"`, `"BlogPost"`)
|
|
106
|
+
* @param fields Field descriptor map built with `string()`, `email()`, etc.
|
|
107
|
+
* @param options Optional: path override, auth scopes, per-op auth, store
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* export const User = resource("User", {
|
|
112
|
+
* name: string({ min: 1 }),
|
|
113
|
+
* email: email(),
|
|
114
|
+
* age: number().optional(),
|
|
115
|
+
* }, { auth: { scopes: ["users:read", "users:write"] } });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function resource<F extends Fields>(name: string, fields: F, options?: ResourceOptions<Infer<F>>): Resource<F>;
|
|
119
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
package/dist/resource.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexo-alpha/data — resource() factory.
|
|
3
|
+
*
|
|
4
|
+
* Generates five CRUD `NexoApi` objects from a `Fields` map and wires them
|
|
5
|
+
* to a `ResourceStore`. The Hapi adapter picks up the resulting module
|
|
6
|
+
* unchanged — no modifications to `@nexo-alpha/hapi` required.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { resource, string, email, number } from "@nexo-alpha/data";
|
|
11
|
+
*
|
|
12
|
+
* export const User = resource("User", {
|
|
13
|
+
* name: string({ min: 1 }),
|
|
14
|
+
* email: email(),
|
|
15
|
+
* age: number().optional(),
|
|
16
|
+
* }, { auth: { scopes: ["users:read", "users:write"] } });
|
|
17
|
+
*
|
|
18
|
+
* app.module(User.module());
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { NexoHttpError, httpResponse } from "@nexo-alpha/core";
|
|
22
|
+
import { fieldsToProperties, requiredFieldNames, allOptional } from "./fields.js";
|
|
23
|
+
import { memoryStore } from "./store.js";
|
|
24
|
+
const NOOP_EMITTER = { emit: () => undefined };
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Utility: simple pluralisation + kebab-case
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function toKebab(name) {
|
|
29
|
+
return name
|
|
30
|
+
.replace(/([A-Z])/g, (m, ch, i) => (i > 0 ? "-" : "") + ch.toLowerCase())
|
|
31
|
+
.replace(/^-/, "");
|
|
32
|
+
}
|
|
33
|
+
function pluralise(word) {
|
|
34
|
+
if (word.endsWith("y") && !/[aeiou]y$/.test(word))
|
|
35
|
+
return word.slice(0, -1) + "ies";
|
|
36
|
+
if (word.endsWith("s") ||
|
|
37
|
+
word.endsWith("x") ||
|
|
38
|
+
word.endsWith("z") ||
|
|
39
|
+
word.endsWith("ch") ||
|
|
40
|
+
word.endsWith("sh")) {
|
|
41
|
+
return word + "es";
|
|
42
|
+
}
|
|
43
|
+
return word + "s";
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// API builder helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Builds the body `NexoFieldSchema` for CREATE (all required fields). */
|
|
49
|
+
function bodySchema(fields) {
|
|
50
|
+
// Use Field.required (the user-facing flag) not schema.required (which is
|
|
51
|
+
// always true inside the NexoFieldSchema descriptor itself).
|
|
52
|
+
const reqNames = requiredFieldNames(fields);
|
|
53
|
+
return {
|
|
54
|
+
type: "object",
|
|
55
|
+
...(reqNames.length > 0 ? { required: reqNames } : {}),
|
|
56
|
+
properties: fieldsToProperties(fields)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Builds the body `NexoFieldSchema` for PATCH (all fields optional). */
|
|
60
|
+
function patchBodySchema(fields) {
|
|
61
|
+
return {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: fieldsToProperties(allOptional(fields))
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns the spread-safe auth fragment for a NexoApi literal.
|
|
68
|
+
* Returns `{}` (no auth key) when scopes is empty, so `exactOptionalPropertyTypes`
|
|
69
|
+
* is satisfied — spreading `{}` omits the key entirely.
|
|
70
|
+
*/
|
|
71
|
+
function authSpread(op, topScopes, overrides) {
|
|
72
|
+
const scopes = overrides[op]?.scopes ?? topScopes;
|
|
73
|
+
if (scopes.length === 0)
|
|
74
|
+
return {};
|
|
75
|
+
return { auth: { required: true, scopes } };
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Main factory
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
/**
|
|
81
|
+
* Declares a CRUD resource.
|
|
82
|
+
*
|
|
83
|
+
* @param name PascalCase model name (`"User"`, `"BlogPost"`)
|
|
84
|
+
* @param fields Field descriptor map built with `string()`, `email()`, etc.
|
|
85
|
+
* @param options Optional: path override, auth scopes, per-op auth, store
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* export const User = resource("User", {
|
|
90
|
+
* name: string({ min: 1 }),
|
|
91
|
+
* email: email(),
|
|
92
|
+
* age: number().optional(),
|
|
93
|
+
* }, { auth: { scopes: ["users:read", "users:write"] } });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function resource(name, fields, options = {}) {
|
|
97
|
+
const collectionPath = options.path ?? `/${pluralise(toKebab(name))}`;
|
|
98
|
+
const itemPath = `${collectionPath}/:id`;
|
|
99
|
+
const topScopes = options.auth?.scopes ?? [];
|
|
100
|
+
const opAuth = options.operationAuth ?? {};
|
|
101
|
+
const enabledOps = options.operations ?? ["list", "get", "create", "update", "delete"];
|
|
102
|
+
const store = options.store ??
|
|
103
|
+
memoryStore();
|
|
104
|
+
const emitter = options.events ?? NOOP_EMITTER;
|
|
105
|
+
// Derive event names once
|
|
106
|
+
const lower = name.toLowerCase();
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// API handlers
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
const listApi = {
|
|
111
|
+
name: `${lower}s.list`,
|
|
112
|
+
method: "GET",
|
|
113
|
+
path: collectionPath,
|
|
114
|
+
description: `List ${name} records`,
|
|
115
|
+
schema: {
|
|
116
|
+
query: {
|
|
117
|
+
limit: { type: "number", integer: true, min: 1, max: 200 },
|
|
118
|
+
cursor: { type: "string" },
|
|
119
|
+
sort: { type: "string" }
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
...authSpread("list", topScopes, opAuth),
|
|
123
|
+
handler: async (ctx) => {
|
|
124
|
+
const q = ctx.query;
|
|
125
|
+
// Build query omitting keys that are absent (exactOptionalPropertyTypes)
|
|
126
|
+
const query = {
|
|
127
|
+
...(q["limit"] !== undefined ? { limit: Number(q["limit"]) } : {}),
|
|
128
|
+
...(typeof q["cursor"] === "string" ? { cursor: q["cursor"] } : {}),
|
|
129
|
+
...(typeof q["sort"] === "string" ? { sort: q["sort"] } : {})
|
|
130
|
+
};
|
|
131
|
+
return store.list(query);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const getApi = {
|
|
135
|
+
name: `${lower}s.get`,
|
|
136
|
+
method: "GET",
|
|
137
|
+
path: itemPath,
|
|
138
|
+
description: `Get a single ${name} by id`,
|
|
139
|
+
schema: { params: { id: { type: "string", required: true } } },
|
|
140
|
+
...authSpread("get", topScopes, opAuth),
|
|
141
|
+
handler: async (ctx) => {
|
|
142
|
+
const id = ctx.params["id"];
|
|
143
|
+
if (!id)
|
|
144
|
+
throw new NexoHttpError(400, "MISSING_ID", "id param is required.");
|
|
145
|
+
const entity = await store.get(id);
|
|
146
|
+
if (entity === null) {
|
|
147
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
148
|
+
}
|
|
149
|
+
return entity;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const createApi = {
|
|
153
|
+
name: `${lower}s.create`,
|
|
154
|
+
method: "POST",
|
|
155
|
+
path: collectionPath,
|
|
156
|
+
description: `Create a ${name}`,
|
|
157
|
+
schema: { body: bodySchema(fields) },
|
|
158
|
+
...authSpread("create", topScopes, opAuth),
|
|
159
|
+
handler: async (ctx) => {
|
|
160
|
+
const data = ctx.payload;
|
|
161
|
+
const entity = await store.create(data);
|
|
162
|
+
await emitter.emit(`${lower}.created`, entity);
|
|
163
|
+
return httpResponse(201, entity);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const updateApi = {
|
|
167
|
+
name: `${lower}s.update`,
|
|
168
|
+
method: "PATCH",
|
|
169
|
+
path: itemPath,
|
|
170
|
+
description: `Partially update a ${name}`,
|
|
171
|
+
schema: {
|
|
172
|
+
params: { id: { type: "string", required: true } },
|
|
173
|
+
body: patchBodySchema(fields)
|
|
174
|
+
},
|
|
175
|
+
...authSpread("update", topScopes, opAuth),
|
|
176
|
+
handler: async (ctx) => {
|
|
177
|
+
const id = ctx.params["id"];
|
|
178
|
+
if (!id)
|
|
179
|
+
throw new NexoHttpError(400, "MISSING_ID", "id param is required.");
|
|
180
|
+
const patch = ctx.payload;
|
|
181
|
+
const entity = await store.update(id, patch);
|
|
182
|
+
if (entity === null) {
|
|
183
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
184
|
+
}
|
|
185
|
+
await emitter.emit(`${lower}.updated`, entity);
|
|
186
|
+
return entity;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const deleteApi = {
|
|
190
|
+
name: `${lower}s.delete`,
|
|
191
|
+
method: "DELETE",
|
|
192
|
+
path: itemPath,
|
|
193
|
+
description: `Delete a ${name}`,
|
|
194
|
+
schema: { params: { id: { type: "string", required: true } } },
|
|
195
|
+
...authSpread("delete", topScopes, opAuth),
|
|
196
|
+
handler: async (ctx) => {
|
|
197
|
+
const id = ctx.params["id"];
|
|
198
|
+
if (!id)
|
|
199
|
+
throw new NexoHttpError(400, "MISSING_ID", "id param is required.");
|
|
200
|
+
const deleted = await store.delete(id);
|
|
201
|
+
if (!deleted) {
|
|
202
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
203
|
+
}
|
|
204
|
+
await emitter.emit(`${lower}.deleted`, { id });
|
|
205
|
+
return httpResponse(204);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const ALL_APIS = {
|
|
209
|
+
list: listApi,
|
|
210
|
+
get: getApi,
|
|
211
|
+
create: createApi,
|
|
212
|
+
update: updateApi,
|
|
213
|
+
delete: deleteApi
|
|
214
|
+
};
|
|
215
|
+
// -------------------------------------------------------------------------
|
|
216
|
+
// Public Resource object
|
|
217
|
+
// -------------------------------------------------------------------------
|
|
218
|
+
const self = {
|
|
219
|
+
name,
|
|
220
|
+
path: collectionPath,
|
|
221
|
+
fields,
|
|
222
|
+
store,
|
|
223
|
+
// High-level helpers
|
|
224
|
+
list: (query) => store.list(query ?? {}),
|
|
225
|
+
get: async (id) => {
|
|
226
|
+
const e = await store.get(id);
|
|
227
|
+
if (e === null)
|
|
228
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
229
|
+
return e;
|
|
230
|
+
},
|
|
231
|
+
create: async (data) => {
|
|
232
|
+
const entity = await store.create(data);
|
|
233
|
+
await emitter.emit(`${lower}.created`, entity);
|
|
234
|
+
return entity;
|
|
235
|
+
},
|
|
236
|
+
update: async (id, patch) => {
|
|
237
|
+
const e = await store.update(id, patch);
|
|
238
|
+
if (e === null)
|
|
239
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
240
|
+
await emitter.emit(`${lower}.updated`, e);
|
|
241
|
+
return e;
|
|
242
|
+
},
|
|
243
|
+
delete: async (id) => {
|
|
244
|
+
const deleted = await store.delete(id);
|
|
245
|
+
if (!deleted)
|
|
246
|
+
throw new NexoHttpError(404, `${name.toUpperCase()}_NOT_FOUND`, `${name} "${id}" not found.`);
|
|
247
|
+
await emitter.emit(`${lower}.deleted`, { id });
|
|
248
|
+
},
|
|
249
|
+
apis() {
|
|
250
|
+
return enabledOps.map((op) => ALL_APIS[op]);
|
|
251
|
+
},
|
|
252
|
+
module() {
|
|
253
|
+
return {
|
|
254
|
+
name: `${lower}s`,
|
|
255
|
+
description: `CRUD module for ${name}`,
|
|
256
|
+
apis: self.apis(),
|
|
257
|
+
describe: () => self.describe(),
|
|
258
|
+
resource: self
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
describe() {
|
|
262
|
+
const scopes = [...new Set(topScopes)];
|
|
263
|
+
const fieldDetails = [
|
|
264
|
+
{ name: "id", type: "string", required: true },
|
|
265
|
+
...Object.entries(fields).map(([fieldName, f]) => ({
|
|
266
|
+
name: fieldName,
|
|
267
|
+
type: f.schema.type,
|
|
268
|
+
required: f.required
|
|
269
|
+
})),
|
|
270
|
+
{ name: "createdAt", type: "string", required: true },
|
|
271
|
+
{ name: "updatedAt", type: "string", required: true }
|
|
272
|
+
];
|
|
273
|
+
return {
|
|
274
|
+
model: name,
|
|
275
|
+
path: collectionPath,
|
|
276
|
+
fields: ["id", ...Object.keys(fields), "createdAt", "updatedAt"],
|
|
277
|
+
fieldDetails,
|
|
278
|
+
operations: enabledOps,
|
|
279
|
+
scopes,
|
|
280
|
+
events: enabledOps
|
|
281
|
+
.filter((op) => op !== "list" && op !== "get")
|
|
282
|
+
.map((op) => `${lower}.${op === "create" ? "created" : op === "update" ? "updated" : "deleted"}`),
|
|
283
|
+
store: options.store !== undefined ? "custom" : "memory"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
return self;
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexo-alpha/data — storage interface + in-memory implementation.
|
|
3
|
+
*
|
|
4
|
+
* `ResourceStore<T>` is the only surface that changes when swapping databases.
|
|
5
|
+
* `memoryStore()` ships as the default; Drizzle/Prisma/SQLite adapters will
|
|
6
|
+
* implement the same interface and can derive table definitions from `fields`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Flat query params for list operations.
|
|
10
|
+
*
|
|
11
|
+
* Wire format on the HTTP layer:
|
|
12
|
+
* `GET /users?sort=name:asc&limit=20&cursor=<opaque>&where[role]=admin`
|
|
13
|
+
*/
|
|
14
|
+
export interface ListQuery<T> {
|
|
15
|
+
/** Equality filter — every supplied key must match the stored value. */
|
|
16
|
+
readonly where?: Partial<T>;
|
|
17
|
+
/**
|
|
18
|
+
* `"fieldName:asc"` or `"fieldName:desc"`. The field must be a key of `T`.
|
|
19
|
+
* The store is free to ignore an unrecognised field and fall back to
|
|
20
|
+
* insertion order.
|
|
21
|
+
*/
|
|
22
|
+
readonly sort?: string;
|
|
23
|
+
/** Maximum items to return. Default / max enforced by the store. */
|
|
24
|
+
readonly limit?: number;
|
|
25
|
+
/** Opaque continuation token returned by a previous call. */
|
|
26
|
+
readonly cursor?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A page of results returned by `ResourceStore.list`. */
|
|
29
|
+
export interface Page<E> {
|
|
30
|
+
readonly items: readonly E[];
|
|
31
|
+
/** Present when there are more results to fetch. Pass to the next call. */
|
|
32
|
+
readonly nextCursor?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Every persisted record has `id`, `createdAt`, and `updatedAt` added. */
|
|
35
|
+
export type Entity<T> = T & {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly createdAt: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The minimal persistence contract that resource handlers depend on.
|
|
42
|
+
* Swap out the implementation per database without touching a single handler.
|
|
43
|
+
*/
|
|
44
|
+
export interface ResourceStore<T> {
|
|
45
|
+
list(query: ListQuery<T>): Promise<Page<Entity<T>>>;
|
|
46
|
+
get(id: string): Promise<Entity<T> | null>;
|
|
47
|
+
create(data: T): Promise<Entity<T>>;
|
|
48
|
+
update(id: string, patch: Partial<T>): Promise<Entity<T> | null>;
|
|
49
|
+
delete(id: string): Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a fully in-memory `ResourceStore<T>`.
|
|
53
|
+
*
|
|
54
|
+
* - IDs are time-ordered 26-char strings (ULID-style, no external dep).
|
|
55
|
+
* - `list()` supports equality `where` filter, `sort`, `limit`, and
|
|
56
|
+
* cursor-based pagination.
|
|
57
|
+
* - Insertion order is preserved when no `sort` is specified.
|
|
58
|
+
* - All returned objects are deep-cloned, so mutations never affect the store.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const store = memoryStore<User>();
|
|
63
|
+
* const user = await store.create({ name: "Ada", email: "ada@example.com" });
|
|
64
|
+
* const page = await store.list({ where: { name: "Ada" }, limit: 10 });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function memoryStore<T extends Record<string, unknown>>(): ResourceStore<T>;
|
|
68
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;;;;GAKG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0DAA0D;AAC1D,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAMD,2EAA2E;AAC3E,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAMF;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC;AAqCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,CAyFjF"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexo-alpha/data — storage interface + in-memory implementation.
|
|
3
|
+
*
|
|
4
|
+
* `ResourceStore<T>` is the only surface that changes when swapping databases.
|
|
5
|
+
* `memoryStore()` ships as the default; Drizzle/Prisma/SQLite adapters will
|
|
6
|
+
* implement the same interface and can derive table definitions from `fields`.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// ULID-like ID generation (no external dependency)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Crockford Base-32 alphabet — no ambiguous characters.
|
|
12
|
+
const B32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
13
|
+
/**
|
|
14
|
+
* Generates a time-ordered, URL-safe, 26-character ID in the same spirit as
|
|
15
|
+
* ULID — monotonically increasing within a millisecond, lexicographically
|
|
16
|
+
* sortable, no external dependency required.
|
|
17
|
+
*/
|
|
18
|
+
function generateId() {
|
|
19
|
+
const ts = Date.now();
|
|
20
|
+
// 10 chars for timestamp (48-bit ms epoch in base-32)
|
|
21
|
+
let tsPart = "";
|
|
22
|
+
let t = ts;
|
|
23
|
+
for (let i = 0; i < 10; i++) {
|
|
24
|
+
tsPart = (B32[t % 32] ?? "0") + tsPart;
|
|
25
|
+
t = Math.floor(t / 32);
|
|
26
|
+
}
|
|
27
|
+
// 16 chars of random
|
|
28
|
+
let randPart = "";
|
|
29
|
+
for (let i = 0; i < 16; i++) {
|
|
30
|
+
randPart += B32[Math.floor(Math.random() * 32)] ?? "0";
|
|
31
|
+
}
|
|
32
|
+
return tsPart + randPart;
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// In-memory implementation
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a fully in-memory `ResourceStore<T>`.
|
|
40
|
+
*
|
|
41
|
+
* - IDs are time-ordered 26-char strings (ULID-style, no external dep).
|
|
42
|
+
* - `list()` supports equality `where` filter, `sort`, `limit`, and
|
|
43
|
+
* cursor-based pagination.
|
|
44
|
+
* - Insertion order is preserved when no `sort` is specified.
|
|
45
|
+
* - All returned objects are deep-cloned, so mutations never affect the store.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const store = memoryStore<User>();
|
|
50
|
+
* const user = await store.create({ name: "Ada", email: "ada@example.com" });
|
|
51
|
+
* const page = await store.list({ where: { name: "Ada" }, limit: 10 });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function memoryStore() {
|
|
55
|
+
// Ordered map: id → entity
|
|
56
|
+
const rows = new Map();
|
|
57
|
+
function clone(value) {
|
|
58
|
+
return JSON.parse(JSON.stringify(value));
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
async list(query) {
|
|
62
|
+
let items = [...rows.values()];
|
|
63
|
+
// --- filter ---
|
|
64
|
+
if (query.where !== undefined) {
|
|
65
|
+
const where = query.where;
|
|
66
|
+
items = items.filter((item) => Object.entries(where).every(([k, v]) => v === undefined || item[k] === v));
|
|
67
|
+
}
|
|
68
|
+
// --- sort ---
|
|
69
|
+
if (query.sort !== undefined) {
|
|
70
|
+
const colonIdx = query.sort.lastIndexOf(":");
|
|
71
|
+
const field = colonIdx !== -1 ? query.sort.slice(0, colonIdx) : query.sort;
|
|
72
|
+
const dir = colonIdx !== -1 ? query.sort.slice(colonIdx + 1) : "asc";
|
|
73
|
+
const asc = dir !== "desc";
|
|
74
|
+
items = [...items].sort((a, b) => {
|
|
75
|
+
const av = a[field];
|
|
76
|
+
const bv = b[field];
|
|
77
|
+
if (av === bv)
|
|
78
|
+
return 0;
|
|
79
|
+
if (av === undefined || av === null)
|
|
80
|
+
return 1;
|
|
81
|
+
if (bv === undefined || bv === null)
|
|
82
|
+
return -1;
|
|
83
|
+
const as_ = typeof av === "string" ? av : JSON.stringify(av);
|
|
84
|
+
const bs_ = typeof bv === "string" ? bv : JSON.stringify(bv);
|
|
85
|
+
return (as_ < bs_ ? -1 : as_ > bs_ ? 1 : 0) * (asc ? 1 : -1);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// --- cursor pagination ---
|
|
89
|
+
const limit = Math.min(query.limit ?? DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE);
|
|
90
|
+
let startIdx = 0;
|
|
91
|
+
if (query.cursor !== undefined) {
|
|
92
|
+
const cursorIdx = items.findIndex((item) => item.id === query.cursor);
|
|
93
|
+
if (cursorIdx !== -1)
|
|
94
|
+
startIdx = cursorIdx + 1;
|
|
95
|
+
}
|
|
96
|
+
const page = items.slice(startIdx, startIdx + limit);
|
|
97
|
+
const nextCursor = startIdx + limit < items.length ? items[startIdx + limit - 1]?.id : undefined;
|
|
98
|
+
return {
|
|
99
|
+
items: page.map(clone),
|
|
100
|
+
...(nextCursor !== undefined ? { nextCursor } : {})
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
async get(id) {
|
|
104
|
+
const row = rows.get(id);
|
|
105
|
+
return row !== undefined ? clone(row) : null;
|
|
106
|
+
},
|
|
107
|
+
async create(data) {
|
|
108
|
+
const now = new Date().toISOString();
|
|
109
|
+
const entity = { ...clone(data), id: generateId(), createdAt: now, updatedAt: now };
|
|
110
|
+
rows.set(entity.id, entity);
|
|
111
|
+
return clone(entity);
|
|
112
|
+
},
|
|
113
|
+
async update(id, patch) {
|
|
114
|
+
const existing = rows.get(id);
|
|
115
|
+
if (existing === undefined)
|
|
116
|
+
return null;
|
|
117
|
+
const updated = {
|
|
118
|
+
...existing,
|
|
119
|
+
...clone(patch),
|
|
120
|
+
id: existing.id,
|
|
121
|
+
createdAt: existing.createdAt,
|
|
122
|
+
updatedAt: new Date().toISOString()
|
|
123
|
+
};
|
|
124
|
+
rows.set(id, updated);
|
|
125
|
+
return clone(updated);
|
|
126
|
+
},
|
|
127
|
+
async delete(id) {
|
|
128
|
+
return rows.delete(id);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|