@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.
@@ -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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"}
@@ -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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"}
@@ -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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"}