@owlmeans/resource 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +53 -379
  2. package/package.json +5 -4
  3. package/tsconfig.json +5 -9
package/README.md CHANGED
@@ -1,420 +1,94 @@
1
1
  # @owlmeans/resource
2
2
 
3
- A comprehensive data persistence and resource management library for OwlMeans Common applications. This package provides abstract interfaces and implementations for handling database operations, resource management, and data access patterns across different storage backends.
3
+ Abstract CRUD interface and types for all data persistence in OwlMeans applications.
4
4
 
5
5
  ## Overview
6
6
 
7
- The `@owlmeans/resource` package is a foundational data access library in the OwlMeans Common ecosystem that provides:
8
-
9
- - **Database Abstraction**: Generic interfaces for different database backends
10
- - **CRUD Operations**: Complete Create, Read, Update, Delete functionality
11
- - **Resource Management**: Advanced resource lifecycle management
12
- - **Query System**: Flexible query and filtering capabilities
13
- - **Pagination Support**: Built-in pagination and sorting functionality
14
- - **Validation Integration**: Schema validation with JSON Schema support
15
- - **Caching & TTL**: Time-to-live and caching mechanisms
16
- - **Multi-Database Support**: Handle multiple database configurations
7
+ - `Resource<T>` interface: `get`, `load`, `list`, `save`, `create`, `update`, `delete`, `pick`
8
+ - `ResourceRecord` base type (`{ id?: string }`) that all stored records extend
9
+ - `ResourceMaker<R, T>` factory signature used by `makeMongoResource`, `createStateResource`, etc.
10
+ - `ListResult<T>`, `ListOptions`, `ListPager` for paginated queries
11
+ - Error classes: `ResourceError`, `UnknownRecordError`, `RecordExists`
17
12
 
18
13
  ## Installation
19
14
 
20
15
  ```bash
21
- npm install @owlmeans/resource
22
- ```
23
-
24
- ## Core Concepts
25
-
26
- ### Resource Interface
27
-
28
- The `Resource<T>` interface provides the primary data access API for working with records that extend `ResourceRecord`.
29
-
30
- ### Database Service
31
-
32
- The `ResourceDbService` interface provides an abstraction for database-specific implementations, allowing different storage backends (MongoDB, Redis, etc.) to be used interchangeably.
33
-
34
- ### Resource Records
35
-
36
- All data entities extend `ResourceRecord`, which provides a basic structure with an optional `id` field.
37
-
38
- ### List Operations
39
-
40
- Advanced listing functionality with criteria-based filtering, pagination, and sorting capabilities.
41
-
42
- ## API Reference
43
-
44
- ### Types
45
-
46
- #### `Resource<T extends ResourceRecord>`
47
- Primary interface for resource operations.
48
-
49
- ```typescript
50
- interface Resource<T extends ResourceRecord> extends BasicResource {
51
- get: <Type extends T>(id: string, field?: Getter, opts?: LifecycleOptions) => Promise<Type>
52
- load: <Type extends T>(id: string, field?: Getter, opts?: LifecycleOptions) => Promise<Type | null>
53
- list: <Type extends T>(criteria?: ListOptions | ListCriteria, opts?: ListOptions) => Promise<ListResult<Type>>
54
- save: <Type extends T>(record: Partial<Type>, opts?: Getter) => Promise<Type>
55
- create: <Type extends T>(record: Partial<Type>, opts?: LifecycleOptions) => Promise<Type>
56
- update: <Type extends T>(record: Partial<Type>, opts?: Getter) => Promise<Type>
57
- delete: <Type extends T>(id: string | T, opts?: Getter) => Promise<Type | null>
58
- pick: <Type extends T>(id: string | T, opts?: Getter) => Promise<Type>
59
- }
60
- ```
61
-
62
- #### `ResourceRecord`
63
- Base interface for all resource entities.
64
-
65
- ```typescript
66
- interface ResourceRecord {
67
- id?: string
68
- }
69
- ```
70
-
71
- #### `ListOptions`
72
- Configuration for list operations.
73
-
74
- ```typescript
75
- interface ListOptions {
76
- pager?: ListPager
77
- criteria?: ListCriteria
78
- }
79
- ```
80
-
81
- #### `ListPager`
82
- Pagination and sorting configuration.
83
-
84
- ```typescript
85
- interface ListPager {
86
- sort?: ListSort[]
87
- page?: number
88
- size?: number
89
- total?: number
90
- }
91
- ```
92
-
93
- #### `ListResult<T>`
94
- Result structure for list operations.
95
-
96
- ```typescript
97
- interface ListResult<T extends ResourceRecord> {
98
- items: T[]
99
- pager?: ListPager
100
- }
101
- ```
102
-
103
- #### `DbConfig`
104
- Database configuration structure.
105
-
106
- ```typescript
107
- interface DbConfig<P extends {} = {}> {
108
- service: string
109
- alias?: string
110
- host: string | string[]
111
- port?: number
112
- user?: string
113
- secret?: string
114
- schema?: string
115
- resourcePrefix?: string
116
- entitySensitive?: boolean
117
- serviceSensitive?: boolean
118
- encryptionKey?: string
119
- meta?: P
120
- }
121
- ```
122
-
123
- ### Factory Functions
124
-
125
- #### `createDbService<Db, Client, Service>(alias: string, override: Partial<Service>, init?: InitMethod<Service>): Service`
126
- Creates a database service with the specified configuration.
127
-
128
- **Parameters:**
129
- - `alias`: Service alias for registration
130
- - `override`: Partial service implementation
131
- - `init`: Optional initialization method
132
-
133
- **Returns:** Configured database service
134
-
135
- ### Helper Functions
136
-
137
- #### `createListSchema<T>(schema: JSONSchemaType<T>): JSONSchemaType<ListResult<T>>`
138
- Creates a JSON Schema for list results based on a record schema.
139
-
140
- #### `prepareListOptions(defPageSize?: number, criteria?: ListOptions | ListCriteria, opts?: ListOptions): ListOptions`
141
- Prepares and normalizes list options with default pagination.
142
-
143
- #### `filterObject<T>(obj: T, keep?: string[]): T`
144
- Filters an object, removing null/undefined values except for specified keys.
145
-
146
- ### CRUD Operations
147
-
148
- #### `get(id: string, field?: Getter, opts?: LifecycleOptions): Promise<T>`
149
- Retrieves a record by ID. Throws `UnknownRecordError` if not found.
150
-
151
- #### `load(id: string, field?: Getter, opts?: LifecycleOptions): Promise<T | null>`
152
- Loads a record by ID. Returns `null` if not found.
153
-
154
- #### `save(record: Partial<T>, opts?: Getter): Promise<T>`
155
- Saves a record (creates if new, updates if exists).
156
-
157
- #### `create(record: Partial<T>, opts?: LifecycleOptions): Promise<T>`
158
- Creates a new record. Throws `RecordExists` if record already exists.
159
-
160
- #### `update(record: Partial<T>, opts?: Getter): Promise<T>`
161
- Updates an existing record. Throws `UnknownRecordError` if not found.
162
-
163
- #### `delete(id: string | T, opts?: Getter): Promise<T | null>`
164
- Deletes a record by ID or record object.
165
-
166
- #### `pick(id: string | T, opts?: Getter): Promise<T>`
167
- Retrieves a record, throwing `UnknownRecordError` if not found.
168
-
169
- #### `list(criteria?: ListOptions | ListCriteria, opts?: ListOptions): Promise<ListResult<T>>`
170
- Lists records with optional filtering, pagination, and sorting.
171
-
172
- ### Error Types
173
-
174
- The package provides a comprehensive error hierarchy for resource operations:
175
-
176
- #### `ResourceError`
177
- Base error class for all resource-related errors.
178
-
179
- #### `UnknownRecordError`
180
- Error thrown when a requested record is not found.
181
-
182
- ```typescript
183
- class UnknownRecordError extends ResourceError {
184
- constructor(id: string)
185
- get id(): string // Extract the record ID from the error
186
- }
16
+ bun add @owlmeans/resource
187
17
  ```
188
18
 
189
- #### `MisshapedRecord`
190
- Error for malformed or invalid record data.
191
-
192
- #### `RecordExists`
193
- Error thrown when attempting to create a record that already exists.
194
-
195
- #### `RecordUpdateFailed`
196
- Error for failed update operations.
197
-
198
- #### `UnsupportedArgumentError`
199
- Error for unsupported method arguments.
200
-
201
- #### `UnsupportedMethodError`
202
- Error for unsupported operations.
203
-
204
- ## Usage Examples
205
-
206
- ### Basic Resource Operations
207
-
208
- ```typescript
209
- interface User extends ResourceRecord {
210
- id?: string
211
- name: string
212
- email: string
213
- createdAt?: Date
214
- }
215
-
216
- // Create a new user
217
- const newUser: User = await userResource.create({
218
- name: 'John Doe',
219
- email: 'john@example.com'
220
- })
19
+ ## Usage
221
20
 
222
- // Get a user by ID
223
- const user = await userResource.get('user123')
224
-
225
- // Update a user
226
- const updatedUser = await userResource.update({
227
- id: 'user123',
228
- name: 'John Smith'
229
- })
230
-
231
- // Delete a user
232
- await userResource.delete('user123')
233
- ```
234
-
235
- ### List Operations with Pagination
21
+ Define a resource maker following the standard factory pattern:
236
22
 
237
23
  ```typescript
238
- // Basic list
239
- const users = await userResource.list()
240
-
241
- // List with pagination
242
- const pagedUsers = await userResource.list({
243
- pager: {
244
- page: 0,
245
- size: 10,
246
- sort: [['name', true], 'createdAt'] // Sort by name asc, then createdAt
247
- }
248
- })
249
-
250
- // List with criteria
251
- const filteredUsers = await userResource.list({
252
- criteria: {
253
- status: 'active',
254
- role: ['admin', 'moderator']
255
- },
256
- pager: { size: 20 }
257
- })
258
- ```
259
-
260
- ### Error Handling
24
+ import type { ResourceMaker, ResourceRecord } from '@owlmeans/resource'
25
+ import { makeMongoResource } from '@owlmeans/mongo-resource'
261
26
 
262
- ```typescript
263
- import { UnknownRecordError, RecordExists } from '@owlmeans/resource'
264
-
265
- try {
266
- const user = await userResource.get('nonexistent')
267
- } catch (error) {
268
- if (error instanceof UnknownRecordError) {
269
- console.log(`User not found: ${error.id}`)
270
- }
27
+ interface ProjectRecord extends ResourceRecord {
28
+ entityId: string
29
+ alias: string
30
+ createdAt: Date
271
31
  }
272
32
 
273
- try {
274
- await userResource.create({ id: 'existing', name: 'Test' })
275
- } catch (error) {
276
- if (error instanceof RecordExists) {
277
- console.log('User already exists')
278
- }
33
+ export const makeProjectResource: ResourceMaker<ProjectRecord> = (dbAlias, serviceAlias) => {
34
+ return makeMongoResource<ProjectRecord>('project', dbAlias, serviceAlias, makeProjectResource)
279
35
  }
280
36
  ```
281
37
 
282
- ### Database Configuration
38
+ Use resource methods in a handler:
283
39
 
284
40
  ```typescript
285
- import { createBasicContext } from '@owlmeans/context'
286
-
287
- const context = createBasicContext({
288
- dbs: [
289
- {
290
- service: 'mongo-db',
291
- alias: 'primary',
292
- host: 'localhost',
293
- port: 27017,
294
- schema: 'myapp',
295
- user: 'dbuser',
296
- secret: 'dbpass',
297
- resourcePrefix: 'app_',
298
- entitySensitive: true
299
- },
300
- {
301
- service: 'redis-db',
302
- alias: 'cache',
303
- host: 'localhost',
304
- port: 6379
305
- }
306
- ]
307
- })
308
- ```
309
-
310
- ### Custom Resource Implementation
41
+ import { UnknownRecordError } from '@owlmeans/resource'
42
+ import type { ListResult } from '@owlmeans/resource'
311
43
 
312
- ```typescript
313
- import { Resource, ResourceRecord } from '@owlmeans/resource'
44
+ // Get or throw
45
+ const project = await ctx.project().get(projectId)
314
46
 
315
- interface Product extends ResourceRecord {
316
- name: string
317
- price: number
318
- category: string
319
- }
320
-
321
- class ProductResource implements Resource<Product> {
322
- // Implement all required methods
323
- async get(id: string): Promise<Product> {
324
- // Custom implementation
325
- }
326
-
327
- async list(criteria?: ListOptions): Promise<ListResult<Product>> {
328
- // Custom implementation with business logic
329
- }
330
-
331
- // ... other methods
332
- }
333
- ```
334
-
335
- ### TTL and Caching
336
-
337
- ```typescript
338
- // Load with TTL
339
- const user = await userResource.load('user123', {
340
- ttl: 300 // Cache for 5 minutes
341
- })
47
+ // Load (returns null if not found)
48
+ const story = await ctx.story().load(storyId)
342
49
 
343
- // Load with expiration date
344
- const user = await userResource.load('user123', {
345
- ttl: new Date(Date.now() + 300000) // Expire in 5 minutes
50
+ // List with criteria and pagination
51
+ const result: ListResult<ProjectRecord> = await ctx.project().list({
52
+ criteria: { entityId: req.auth!.entityId },
53
+ pager: { page: 0, size: 20 }
346
54
  })
347
55
  ```
348
56
 
349
- ## Advanced Features
57
+ ## API
350
58
 
351
- ### Multi-Database Support
59
+ ### `Resource<T>` methods
352
60
 
353
- The package supports multiple database configurations within a single application:
61
+ - `get(id, field?, opts?)` — fetch by ID; throws `UnknownRecordError` if missing
62
+ - `load(id, field?, opts?)` — fetch by ID; returns `null` if missing
63
+ - `list(criteria?, opts?)` — paginated list; returns `ListResult<T>`
64
+ - `create(record, opts?)` — insert; throws `RecordExists` if already exists
65
+ - `save(record, opts?)` — upsert by ID
66
+ - `update(record, opts?)` — update; throws `UnknownRecordError` if missing
67
+ - `delete(id, opts?)` — remove; returns the deleted record or `null`
68
+ - `pick(id, opts?)` — like `get` but optimized for existence checks
354
69
 
355
- ```typescript
356
- // Different databases for different resource types
357
- const userResource = makeUserResource('primary-db')
358
- const cacheResource = makeCacheResource('redis-db')
359
- const analyticsResource = makeAnalyticsResource('analytics-db')
360
- ```
361
-
362
- ### Schema Validation
363
-
364
- Integration with JSON Schema for record validation:
365
-
366
- ```typescript
367
- import { createListSchema } from '@owlmeans/resource'
368
-
369
- const userSchema: JSONSchemaType<User> = {
370
- type: 'object',
371
- properties: {
372
- id: { type: 'string', nullable: true },
373
- name: { type: 'string' },
374
- email: { type: 'string', format: 'email' }
375
- },
376
- required: ['name', 'email'],
377
- additionalProperties: false
378
- }
379
-
380
- const userListSchema = createListSchema(userSchema)
381
- ```
382
-
383
- ### Resource Locking
384
-
385
- Support for record locking mechanisms:
70
+ ### `ResourceMaker<R, T>`
386
71
 
387
72
  ```typescript
388
- interface ResourceLocker<T extends ResourceRecord> {
389
- lock: (record: Partial<T>, fields?: string[]) => Promise<T>
390
- unlock: (record: Partial<T>, fields?: string[]) => Promise<T>
73
+ interface ResourceMaker<R extends ResourceRecord, T extends Resource<R> = Resource<R>> {
74
+ (dbAlias?: string, serviceAlias?: string): T
391
75
  }
392
76
  ```
393
77
 
394
- ## Integration with OwlMeans Ecosystem
395
-
396
- The `@owlmeans/resource` package integrates with:
397
-
398
- - **@owlmeans/context**: Service registration and configuration management
399
- - **@owlmeans/error**: Resilient error handling system
400
- - **@owlmeans/mongo-resource**: MongoDB-specific implementation
401
- - **@owlmeans/redis-resource**: Redis-specific implementation
402
- - **@owlmeans/client-resource**: Client-side resource management
403
- - **@owlmeans/static-resource**: Static file resource management
78
+ ### Helpers
404
79
 
405
- ## Performance Considerations
80
+ - `prepareListOptions(defPageSize, criteria?, opts?)` — normalize list criteria + pager
81
+ - `filterObject(obj, keep?)` — strip null/undefined fields from a record before saving
406
82
 
407
- - Use `load()` instead of `get()` when records might not exist
408
- - Implement appropriate pagination for large datasets
409
- - Use TTL for frequently accessed but rarely changed data
410
- - Consider database-specific optimizations for complex queries
411
- - Implement proper indexing strategies for list criteria
83
+ ### Error Classes
412
84
 
413
- ## Security Features
85
+ - `ResourceError` — base resource error
86
+ - `UnknownRecordError` — record not found (has `.id` getter)
87
+ - `RecordExists` — duplicate record on `create`
88
+ - `MisshapedRecord` — invalid record structure
414
89
 
415
- - Entity-sensitive configurations for multi-tenant applications
416
- - Service-sensitive configurations for microservice architectures
417
- - Encryption key support for sensitive data
418
- - Input validation through schema integration
90
+ ## Related Packages
419
91
 
420
- Fixes #32.
92
+ - [`@owlmeans/mongo-resource`](../mongo-resource) — MongoDB implementation
93
+ - [`@owlmeans/redis-resource`](../redis-resource) — Redis implementation
94
+ - [`@owlmeans/state`](../state) — in-memory implementation for client state
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/resource",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,17 +22,18 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@noble/hashes": "^1.5.0",
25
- "@owlmeans/context": "^0.1.2",
26
- "@owlmeans/error": "^0.1.2",
25
+ "@owlmeans/context": "^0.1.4",
26
+ "@owlmeans/error": "^0.1.4",
27
27
  "@scure/base": "^1.1.9"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "ajv": "*"
31
31
  },
32
32
  "devDependencies": {
33
+ "@owlmeans/dep-config": "workspace:*",
33
34
  "nodemon": "^3.1.11",
34
35
  "npm-check": "^6.0.1",
35
- "typescript": "^5.8.3"
36
+ "typescript": "^6.0.2"
36
37
  },
37
38
  "publishConfig": {
38
39
  "access": "public"
package/tsconfig.json CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json"
4
4
  ],
5
5
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
6
+ "rootDir": "./src/",
7
+ "outDir": "./build/"
8
8
  },
9
- "exclude": [
10
- "./dist/**/*",
11
- "./build/**/*",
12
- "./*.ts"
13
- ]
14
- }
9
+ "exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
10
+ }