@molecule/api-resource-comment 1.1.4 → 1.2.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/README.md +50 -18
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -8
- package/dist/numbering.d.ts +39 -0
- package/dist/numbering.d.ts.map +1 -0
- package/dist/numbering.js +84 -0
- package/dist/numbers.d.ts +22 -3
- package/dist/numbers.d.ts.map +1 -1
- package/dist/numbers.js +28 -63
- package/dist/service.d.ts +28 -6
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +41 -6
- package/package.json +1 -1
- package/src/__setup__/comment_numbers.sql +4 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-19T07:54:32.119Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-resource-comment
|
|
@@ -16,7 +16,8 @@ replies, pagination, and ownership-based authorization.
|
|
|
16
16
|
**Tables this package owns: `comments` and `comment_numbers`.** A comment row is found by its
|
|
17
17
|
`comments.id` (a UUID); `comment_numbers` gives it the sequential integer many clients address it
|
|
18
18
|
by, and the join between the two is `comment_numbers.commentId` = `comments.id` (declared FK,
|
|
19
|
-
`ON DELETE CASCADE`) — one row per
|
|
19
|
+
`ON DELETE CASCADE`) — one row per comment created through this package, never a mapping table
|
|
20
|
+
of your own.
|
|
20
21
|
|
|
21
22
|
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
22
23
|
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
@@ -227,7 +228,9 @@ function update(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
|
|
|
227
228
|
#### `numberComment(commentId)`
|
|
228
229
|
|
|
229
230
|
The comment's number, allocating the next one when it has none. Numbers are
|
|
230
|
-
unique and increase.
|
|
231
|
+
unique and increase. Use it for a comment that was inserted into `comments`
|
|
232
|
+
without `createComment()` — every comment created through this package is
|
|
233
|
+
numbered already.
|
|
231
234
|
|
|
232
235
|
```typescript
|
|
233
236
|
function numberComment(commentId: string): Promise<number>
|
|
@@ -241,6 +244,10 @@ function numberComment(commentId: string): Promise<number>
|
|
|
241
244
|
|
|
242
245
|
Creates a comment and its number together: both are written, or neither.
|
|
243
246
|
|
|
247
|
+
This is `createComment()` under the name an integer-id API reads best — it
|
|
248
|
+
takes the same arguments and returns the same `NumberedComment`. Address the
|
|
249
|
+
result by `number` and read it back with `getCommentByNumber(number)`.
|
|
250
|
+
|
|
244
251
|
```typescript
|
|
245
252
|
function createNumberedComment(
|
|
246
253
|
resourceType: string,
|
|
@@ -259,7 +266,9 @@ function createNumberedComment(
|
|
|
259
266
|
|
|
260
267
|
#### `getCommentByNumber(number)`
|
|
261
268
|
|
|
262
|
-
The comment a number belongs to
|
|
269
|
+
The comment a number belongs to — the lookup behind an integer-addressed
|
|
270
|
+
route such as `DELETE /comments/:id`: find the comment here, then pass its
|
|
271
|
+
`id` to `deleteComment()`/`updateComment()`.
|
|
263
272
|
|
|
264
273
|
```typescript
|
|
265
274
|
function getCommentByNumber(number: number): Promise<NumberedComment | null>
|
|
@@ -272,7 +281,11 @@ function getCommentByNumber(number: number): Promise<NumberedComment | null>
|
|
|
272
281
|
#### `commentNumbers(commentIds)`
|
|
273
282
|
|
|
274
283
|
The numbers of the given comments, for listing them with their numbers.
|
|
275
|
-
|
|
284
|
+
|
|
285
|
+
Every comment created through this package is numbered, so a comment missing
|
|
286
|
+
from the map was inserted directly into `comments` (or predates numbering by
|
|
287
|
+
default); give it one with `numberComment(id)`. Never read a missing entry as
|
|
288
|
+
`0` — `0` is not a comment id, and an integer id of `0` cannot be found again.
|
|
276
289
|
|
|
277
290
|
```typescript
|
|
278
291
|
function commentNumbers(commentIds: readonly string[]): Promise<Map<string, number>>
|
|
@@ -284,7 +297,18 @@ function commentNumbers(commentIds: readonly string[]): Promise<Map<string, numb
|
|
|
284
297
|
|
|
285
298
|
#### `createComment(resourceType, resourceId, userId, data)`
|
|
286
299
|
|
|
287
|
-
Creates a new comment on a resource.
|
|
300
|
+
Creates a new comment on a resource, with its sequential number.
|
|
301
|
+
|
|
302
|
+
The comment row and its `comment_numbers` row are written together: the
|
|
303
|
+
returned comment carries `number` (1, 2, 3, …) beside its UUID `id`. An API
|
|
304
|
+
whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
305
|
+
`DELETE /comments/:id`) returns `number` as that id and reads it back with
|
|
306
|
+
`getCommentByNumber(number)` — never hand-build an id from the UUID.
|
|
307
|
+
`createNumberedComment()` is this same create under the name that path reads
|
|
308
|
+
best; `numberComment(id)` numbers a row that was inserted without this
|
|
309
|
+
function.
|
|
310
|
+
|
|
311
|
+
Both `__setup__` SQL files are required (`comments`, `comment_numbers`).
|
|
288
312
|
|
|
289
313
|
```typescript
|
|
290
314
|
function createComment(
|
|
@@ -292,7 +316,7 @@ function createComment(
|
|
|
292
316
|
resourceId: string,
|
|
293
317
|
userId: string,
|
|
294
318
|
data: CreateCommentInput,
|
|
295
|
-
): Promise<
|
|
319
|
+
): Promise<NumberedComment>
|
|
296
320
|
```
|
|
297
321
|
|
|
298
322
|
- `resourceType` — The type of resource being commented on.
|
|
@@ -300,13 +324,17 @@ function createComment(
|
|
|
300
324
|
- `userId` — The ID of the commenting user.
|
|
301
325
|
- `data` — The comment creation input.
|
|
302
326
|
|
|
303
|
-
**Returns:** The created comment
|
|
327
|
+
**Returns:** The created comment with its sequential `number`.
|
|
304
328
|
|
|
305
329
|
#### `getCommentsByResource(resourceType, resourceId, options)`
|
|
306
330
|
|
|
307
331
|
Retrieves paginated comments for a resource, ordered by creation date descending.
|
|
308
332
|
Only returns top-level comments (no replies).
|
|
309
333
|
|
|
334
|
+
The rows carry each comment's UUID `id`; for an API that addresses comments by an
|
|
335
|
+
integer, map a page's ids with `commentNumbers()` — every comment `createComment()`
|
|
336
|
+
wrote is in that map, and a missing entry (a raw insert) is never `0`.
|
|
337
|
+
|
|
310
338
|
```typescript
|
|
311
339
|
function getCommentsByResource(
|
|
312
340
|
resourceType: string,
|
|
@@ -329,7 +357,7 @@ Retrieves a single comment by ID.
|
|
|
329
357
|
function getCommentById(commentId: string): Promise<Comment | null>
|
|
330
358
|
```
|
|
331
359
|
|
|
332
|
-
- `commentId` — The comment
|
|
360
|
+
- `commentId` — The comment's UUID. For the sequential integer an integer-addressed API uses, call `getCommentByNumber(number)` instead.
|
|
333
361
|
|
|
334
362
|
**Returns:** The comment or `null` if not found.
|
|
335
363
|
|
|
@@ -345,7 +373,7 @@ function updateComment(
|
|
|
345
373
|
): Promise<Comment | null>
|
|
346
374
|
```
|
|
347
375
|
|
|
348
|
-
- `commentId` — The comment
|
|
376
|
+
- `commentId` — The comment's UUID. For an integer number, resolve it with `getCommentByNumber(number)` first.
|
|
349
377
|
- `userId` — The requesting user's ID (must match comment owner).
|
|
350
378
|
- `data` — The update input.
|
|
351
379
|
|
|
@@ -359,7 +387,7 @@ Deletes a comment. Only the comment owner can delete.
|
|
|
359
387
|
function deleteComment(commentId: string, userId: string): Promise<boolean>
|
|
360
388
|
```
|
|
361
389
|
|
|
362
|
-
- `commentId` — The comment
|
|
390
|
+
- `commentId` — The comment's UUID. To delete by the integer number an API like RealWorld exposes (`DELETE /comments/:id`), look the row up with `getCommentByNumber(number)` and pass its `id`.
|
|
363
391
|
- `userId` — The requesting user's ID (must match comment owner).
|
|
364
392
|
|
|
365
393
|
**Returns:** `true` if deleted, `false` if not found or unauthorized.
|
|
@@ -501,13 +529,17 @@ Peer dependencies:
|
|
|
501
529
|
- **A scaffold that lists this package already has these tables** — its `__setup__` SQL is
|
|
502
530
|
copied into the app at create time and replayed on every boot, so do not copy it into
|
|
503
531
|
`api/migrations/` yourself; only a package added after the scaffold needs that.
|
|
504
|
-
`src/__setup__/comments.sql`
|
|
505
|
-
|
|
506
|
-
first).
|
|
507
|
-
- **
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
532
|
+
Both SQL files this package ships — `src/__setup__/comments.sql` and
|
|
533
|
+
`src/__setup__/comment_numbers.sql` — must exist in the target database before use
|
|
534
|
+
(scaffolded apps apply both automatically; existing apps must apply both first).
|
|
535
|
+
- **Every comment is numbered — numbering is the default create path.** `createComment()`
|
|
536
|
+
writes the comment and its sequential `number` (1, 2, 3, …) together and returns both;
|
|
537
|
+
`createNumberedComment()` is the same create under the name an integer-id API reads best.
|
|
538
|
+
For an API whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
539
|
+
`DELETE /comments/:id`), return `number` as the id and read it back with
|
|
540
|
+
`getCommentByNumber(n)` — never hand-build an id from the UUID, and never read a missing
|
|
541
|
+
`commentNumbers(ids)` entry as `0` (a row inserted without `createComment()` has no number;
|
|
542
|
+
`numberComment(id)` gives it one). Never build your own id-mapping table.
|
|
511
543
|
- **Reads are PUBLIC by default.** `list`, `read`, `replies`, and `count` ship
|
|
512
544
|
with no auth middleware (comment threads on public content). If the
|
|
513
545
|
commented resources are private in your app, add an authorizer that checks
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* **Tables this package owns: `comments` and `comment_numbers`.** A comment row is found by its
|
|
8
8
|
* `comments.id` (a UUID); `comment_numbers` gives it the sequential integer many clients address it
|
|
9
9
|
* by, and the join between the two is `comment_numbers.commentId` = `comments.id` (declared FK,
|
|
10
|
-
* `ON DELETE CASCADE`) — one row per
|
|
10
|
+
* `ON DELETE CASCADE`) — one row per comment created through this package, never a mapping table
|
|
11
|
+
* of your own.
|
|
11
12
|
*
|
|
12
13
|
* @module
|
|
13
14
|
* @example
|
|
@@ -33,13 +34,17 @@
|
|
|
33
34
|
* - **A scaffold that lists this package already has these tables** — its `__setup__` SQL is
|
|
34
35
|
* copied into the app at create time and replayed on every boot, so do not copy it into
|
|
35
36
|
* `api/migrations/` yourself; only a package added after the scaffold needs that.
|
|
36
|
-
* `src/__setup__/comments.sql`
|
|
37
|
-
*
|
|
38
|
-
* first).
|
|
39
|
-
* - **
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
37
|
+
* Both SQL files this package ships — `src/__setup__/comments.sql` and
|
|
38
|
+
* `src/__setup__/comment_numbers.sql` — must exist in the target database before use
|
|
39
|
+
* (scaffolded apps apply both automatically; existing apps must apply both first).
|
|
40
|
+
* - **Every comment is numbered — numbering is the default create path.** `createComment()`
|
|
41
|
+
* writes the comment and its sequential `number` (1, 2, 3, …) together and returns both;
|
|
42
|
+
* `createNumberedComment()` is the same create under the name an integer-id API reads best.
|
|
43
|
+
* For an API whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
44
|
+
* `DELETE /comments/:id`), return `number` as the id and read it back with
|
|
45
|
+
* `getCommentByNumber(n)` — never hand-build an id from the UUID, and never read a missing
|
|
46
|
+
* `commentNumbers(ids)` entry as `0` (a row inserted without `createComment()` has no number;
|
|
47
|
+
* `numberComment(id)` gives it one). Never build your own id-mapping table.
|
|
43
48
|
* - **Reads are PUBLIC by default.** `list`, `read`, `replies`, and `count` ship
|
|
44
49
|
* with no auth middleware (comment threads on public content). If the
|
|
45
50
|
* commented resources are private in your app, add an authorizer that checks
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* **Tables this package owns: `comments` and `comment_numbers`.** A comment row is found by its
|
|
8
8
|
* `comments.id` (a UUID); `comment_numbers` gives it the sequential integer many clients address it
|
|
9
9
|
* by, and the join between the two is `comment_numbers.commentId` = `comments.id` (declared FK,
|
|
10
|
-
* `ON DELETE CASCADE`) — one row per
|
|
10
|
+
* `ON DELETE CASCADE`) — one row per comment created through this package, never a mapping table
|
|
11
|
+
* of your own.
|
|
11
12
|
*
|
|
12
13
|
* @module
|
|
13
14
|
* @example
|
|
@@ -33,13 +34,17 @@
|
|
|
33
34
|
* - **A scaffold that lists this package already has these tables** — its `__setup__` SQL is
|
|
34
35
|
* copied into the app at create time and replayed on every boot, so do not copy it into
|
|
35
36
|
* `api/migrations/` yourself; only a package added after the scaffold needs that.
|
|
36
|
-
* `src/__setup__/comments.sql`
|
|
37
|
-
*
|
|
38
|
-
* first).
|
|
39
|
-
* - **
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
37
|
+
* Both SQL files this package ships — `src/__setup__/comments.sql` and
|
|
38
|
+
* `src/__setup__/comment_numbers.sql` — must exist in the target database before use
|
|
39
|
+
* (scaffolded apps apply both automatically; existing apps must apply both first).
|
|
40
|
+
* - **Every comment is numbered — numbering is the default create path.** `createComment()`
|
|
41
|
+
* writes the comment and its sequential `number` (1, 2, 3, …) together and returns both;
|
|
42
|
+
* `createNumberedComment()` is the same create under the name an integer-id API reads best.
|
|
43
|
+
* For an API whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
44
|
+
* `DELETE /comments/:id`), return `number` as the id and read it back with
|
|
45
|
+
* `getCommentByNumber(n)` — never hand-build an id from the UUID, and never read a missing
|
|
46
|
+
* `commentNumbers(ids)` entry as `0` (a row inserted without `createComment()` has no number;
|
|
47
|
+
* `numberComment(id)` gives it one). Never build your own id-mapping table.
|
|
43
48
|
* - **Reads are PUBLIC by default.** `list`, `read`, `replies`, and `count` ship
|
|
44
49
|
* with no auth middleware (comment threads on public content). If the
|
|
45
50
|
* commented resources are private in your app, add an authorizer that checks
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number allocation for comments — the `comment_numbers` rows behind
|
|
3
|
+
* `NumberedComment.number`.
|
|
4
|
+
*
|
|
5
|
+
* Internal to this package: `createComment()` calls `allocateCommentNumber()`
|
|
6
|
+
* so every comment it writes has a number, and `numbers.ts` re-exposes the
|
|
7
|
+
* public surface (`numberComment`, `commentNumbers`, …). The allocation needs
|
|
8
|
+
* no interactive transaction — the unique key on `number` is what makes a
|
|
9
|
+
* concurrent writer lose, and the next attempt reads the new highest number —
|
|
10
|
+
* so it also works on a database bond without transactions (Cloudflare D1).
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The comment's number, allocating the next one when it has none. Numbers are
|
|
16
|
+
* unique and increase. A concurrent writer that took the number first fails
|
|
17
|
+
* the insert on the unique key, and the next attempt reads the highest number
|
|
18
|
+
* again. Inside a caller's transaction the work runs once — a failed statement
|
|
19
|
+
* can poison it (Postgres) — and the caller decides.
|
|
20
|
+
*
|
|
21
|
+
* @param commentId - The comment's UUID.
|
|
22
|
+
* @returns Its sequential number.
|
|
23
|
+
* @throws {Error} When no number could be allocated after several attempts.
|
|
24
|
+
*/
|
|
25
|
+
export declare function allocateCommentNumber(commentId: string): Promise<number>;
|
|
26
|
+
/**
|
|
27
|
+
* The numbers of the given comments, in one query.
|
|
28
|
+
*
|
|
29
|
+
* Every comment this package creates has a number, so a comment missing from
|
|
30
|
+
* the map was inserted into `comments` without `createComment()` (a raw insert,
|
|
31
|
+
* or a row from before this package numbered by default). Such a row has NO
|
|
32
|
+
* number — `numberComment(id)` gives it one. Never read a missing entry as `0`:
|
|
33
|
+
* `0` is not a comment id.
|
|
34
|
+
*
|
|
35
|
+
* @param commentIds - The comments' UUIDs.
|
|
36
|
+
* @returns Each numbered comment's UUID mapped to its number.
|
|
37
|
+
*/
|
|
38
|
+
export declare function numbersForComments(commentIds: readonly string[]): Promise<Map<string, number>>;
|
|
39
|
+
//# sourceMappingURL=numbering.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numbering.d.ts","sourceRoot":"","sources":["../src/numbering.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqCH;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW9E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAM9B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number allocation for comments — the `comment_numbers` rows behind
|
|
3
|
+
* `NumberedComment.number`.
|
|
4
|
+
*
|
|
5
|
+
* Internal to this package: `createComment()` calls `allocateCommentNumber()`
|
|
6
|
+
* so every comment it writes has a number, and `numbers.ts` re-exposes the
|
|
7
|
+
* public surface (`numberComment`, `commentNumbers`, …). The allocation needs
|
|
8
|
+
* no interactive transaction — the unique key on `number` is what makes a
|
|
9
|
+
* concurrent writer lose, and the next attempt reads the new highest number —
|
|
10
|
+
* so it also works on a database bond without transactions (Cloudflare D1).
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { create as dbCreate, findMany, findOne, getActiveTransaction } from '@molecule/api-database';
|
|
15
|
+
/** The table the numbers live in. */
|
|
16
|
+
const NUMBERS = 'comment_numbers';
|
|
17
|
+
/** How many times allocation is redone when a concurrent writer took the number first. */
|
|
18
|
+
const MAX_ATTEMPTS = 5;
|
|
19
|
+
/**
|
|
20
|
+
* Allocates the next number for a comment that has none. Each call is one
|
|
21
|
+
* read and, when the comment is unnumbered, one insert.
|
|
22
|
+
*
|
|
23
|
+
* @param commentId - The comment's UUID.
|
|
24
|
+
* @returns Its number.
|
|
25
|
+
*/
|
|
26
|
+
async function allocate(commentId) {
|
|
27
|
+
const existing = await findOne(NUMBERS, [
|
|
28
|
+
{ field: 'commentId', operator: '=', value: commentId },
|
|
29
|
+
]);
|
|
30
|
+
if (existing)
|
|
31
|
+
return Number(existing.number);
|
|
32
|
+
const [last] = await findMany(NUMBERS, {
|
|
33
|
+
orderBy: [{ field: 'number', direction: 'desc' }],
|
|
34
|
+
limit: 1,
|
|
35
|
+
});
|
|
36
|
+
const number = (last ? Number(last.number) : 0) + 1;
|
|
37
|
+
await dbCreate(NUMBERS, { number, commentId });
|
|
38
|
+
return number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The comment's number, allocating the next one when it has none. Numbers are
|
|
42
|
+
* unique and increase. A concurrent writer that took the number first fails
|
|
43
|
+
* the insert on the unique key, and the next attempt reads the highest number
|
|
44
|
+
* again. Inside a caller's transaction the work runs once — a failed statement
|
|
45
|
+
* can poison it (Postgres) — and the caller decides.
|
|
46
|
+
*
|
|
47
|
+
* @param commentId - The comment's UUID.
|
|
48
|
+
* @returns Its sequential number.
|
|
49
|
+
* @throws {Error} When no number could be allocated after several attempts.
|
|
50
|
+
*/
|
|
51
|
+
export async function allocateCommentNumber(commentId) {
|
|
52
|
+
if (getActiveTransaction())
|
|
53
|
+
return allocate(commentId);
|
|
54
|
+
for (let attempt = 1;; attempt++) {
|
|
55
|
+
try {
|
|
56
|
+
return await allocate(commentId);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
60
|
+
throw new Error(`Could not number comment ${commentId}`, { cause: error });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The numbers of the given comments, in one query.
|
|
67
|
+
*
|
|
68
|
+
* Every comment this package creates has a number, so a comment missing from
|
|
69
|
+
* the map was inserted into `comments` without `createComment()` (a raw insert,
|
|
70
|
+
* or a row from before this package numbered by default). Such a row has NO
|
|
71
|
+
* number — `numberComment(id)` gives it one. Never read a missing entry as `0`:
|
|
72
|
+
* `0` is not a comment id.
|
|
73
|
+
*
|
|
74
|
+
* @param commentIds - The comments' UUIDs.
|
|
75
|
+
* @returns Each numbered comment's UUID mapped to its number.
|
|
76
|
+
*/
|
|
77
|
+
export async function numbersForComments(commentIds) {
|
|
78
|
+
if (commentIds.length === 0)
|
|
79
|
+
return new Map();
|
|
80
|
+
const rows = await findMany(NUMBERS, {
|
|
81
|
+
where: [{ field: 'commentId', operator: 'in', value: [...commentIds] }],
|
|
82
|
+
});
|
|
83
|
+
return new Map(rows.map((r) => [r.commentId, Number(r.number)]));
|
|
84
|
+
}
|
package/dist/numbers.d.ts
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* API specs). The comment keeps its UUID; the number lives in
|
|
5
5
|
* `comment_numbers` and is allocated once, at most one per comment.
|
|
6
6
|
*
|
|
7
|
+
* **Numbering is the default create path:** `createComment()` writes the
|
|
8
|
+
* comment and its number together, so every comment this package creates is
|
|
9
|
+
* numbered, and a numbered API never has to remember a second call.
|
|
10
|
+
* `createNumberedComment()` is the same create, kept as the name that path
|
|
11
|
+
* reads best. `numberComment(id)` numbers a row that was inserted without it
|
|
12
|
+
* (a raw insert, or a row from before numbering was the default).
|
|
13
|
+
*
|
|
7
14
|
* @module
|
|
8
15
|
*/
|
|
9
16
|
import type { Comment, CreateCommentInput } from './types.js';
|
|
@@ -14,7 +21,9 @@ export type NumberedComment = Comment & {
|
|
|
14
21
|
};
|
|
15
22
|
/**
|
|
16
23
|
* The comment's number, allocating the next one when it has none. Numbers are
|
|
17
|
-
* unique and increase.
|
|
24
|
+
* unique and increase. Use it for a comment that was inserted into `comments`
|
|
25
|
+
* without `createComment()` — every comment created through this package is
|
|
26
|
+
* numbered already.
|
|
18
27
|
*
|
|
19
28
|
* @param commentId - The comment's UUID.
|
|
20
29
|
* @returns Its sequential number.
|
|
@@ -24,6 +33,10 @@ export declare function numberComment(commentId: string): Promise<number>;
|
|
|
24
33
|
/**
|
|
25
34
|
* Creates a comment and its number together: both are written, or neither.
|
|
26
35
|
*
|
|
36
|
+
* This is `createComment()` under the name an integer-id API reads best — it
|
|
37
|
+
* takes the same arguments and returns the same `NumberedComment`. Address the
|
|
38
|
+
* result by `number` and read it back with `getCommentByNumber(number)`.
|
|
39
|
+
*
|
|
27
40
|
* @param resourceType - The type of resource being commented on.
|
|
28
41
|
* @param resourceId - The ID of the resource being commented on.
|
|
29
42
|
* @param userId - The ID of the commenting user.
|
|
@@ -32,7 +45,9 @@ export declare function numberComment(commentId: string): Promise<number>;
|
|
|
32
45
|
*/
|
|
33
46
|
export declare function createNumberedComment(resourceType: string, resourceId: string, userId: string, data: CreateCommentInput): Promise<NumberedComment>;
|
|
34
47
|
/**
|
|
35
|
-
* The comment a number belongs to
|
|
48
|
+
* The comment a number belongs to — the lookup behind an integer-addressed
|
|
49
|
+
* route such as `DELETE /comments/:id`: find the comment here, then pass its
|
|
50
|
+
* `id` to `deleteComment()`/`updateComment()`.
|
|
36
51
|
*
|
|
37
52
|
* @param number - The comment's sequential number.
|
|
38
53
|
* @returns The comment with its number, or `null` when no comment has it.
|
|
@@ -40,7 +55,11 @@ export declare function createNumberedComment(resourceType: string, resourceId:
|
|
|
40
55
|
export declare function getCommentByNumber(number: number): Promise<NumberedComment | null>;
|
|
41
56
|
/**
|
|
42
57
|
* The numbers of the given comments, for listing them with their numbers.
|
|
43
|
-
*
|
|
58
|
+
*
|
|
59
|
+
* Every comment created through this package is numbered, so a comment missing
|
|
60
|
+
* from the map was inserted directly into `comments` (or predates numbering by
|
|
61
|
+
* default); give it one with `numberComment(id)`. Never read a missing entry as
|
|
62
|
+
* `0` — `0` is not a comment id, and an integer id of `0` cannot be found again.
|
|
44
63
|
*
|
|
45
64
|
* @param commentIds - The comments' UUIDs.
|
|
46
65
|
* @returns Each numbered comment's UUID mapped to its number.
|
package/dist/numbers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numbers.d.ts","sourceRoot":"","sources":["../src/numbers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"numbers.d.ts","sourceRoot":"","sources":["../src/numbers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAK7D,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAQD;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,eAAe,CAAC,CAE1B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAQxF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAEhG"}
|
package/dist/numbers.js
CHANGED
|
@@ -4,73 +4,40 @@
|
|
|
4
4
|
* API specs). The comment keeps its UUID; the number lives in
|
|
5
5
|
* `comment_numbers` and is allocated once, at most one per comment.
|
|
6
6
|
*
|
|
7
|
+
* **Numbering is the default create path:** `createComment()` writes the
|
|
8
|
+
* comment and its number together, so every comment this package creates is
|
|
9
|
+
* numbered, and a numbered API never has to remember a second call.
|
|
10
|
+
* `createNumberedComment()` is the same create, kept as the name that path
|
|
11
|
+
* reads best. `numberComment(id)` numbers a row that was inserted without it
|
|
12
|
+
* (a raw insert, or a row from before numbering was the default).
|
|
13
|
+
*
|
|
7
14
|
* @module
|
|
8
15
|
*/
|
|
9
|
-
import {
|
|
16
|
+
import { findOne } from '@molecule/api-database';
|
|
17
|
+
import { allocateCommentNumber, numbersForComments } from './numbering.js';
|
|
10
18
|
import { createComment, getCommentById } from './service.js';
|
|
19
|
+
/** The table the numbers live in. */
|
|
11
20
|
const NUMBERS = 'comment_numbers';
|
|
12
|
-
/** How many times the work is redone when a concurrent writer took the number first. */
|
|
13
|
-
const MAX_ATTEMPTS = 5;
|
|
14
|
-
/**
|
|
15
|
-
* Runs `work` in its own transaction, again when it fails: a concurrent
|
|
16
|
-
* writer that took the same number fails the unique key, and the next
|
|
17
|
-
* attempt allocates the next number. Inside a caller's transaction a failed
|
|
18
|
-
* statement can poison it (Postgres), so there the work runs once and the
|
|
19
|
-
* caller decides.
|
|
20
|
-
*
|
|
21
|
-
* @param what - What is being numbered, for the error.
|
|
22
|
-
* @param work - The allocation.
|
|
23
|
-
* @returns What `work` resolves to.
|
|
24
|
-
*/
|
|
25
|
-
async function withNumberRetry(what, work) {
|
|
26
|
-
if (getActiveTransaction())
|
|
27
|
-
return work();
|
|
28
|
-
for (let attempt = 1;; attempt++) {
|
|
29
|
-
try {
|
|
30
|
-
return await withTransaction(work);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
if (attempt >= MAX_ATTEMPTS)
|
|
34
|
-
throw new Error(`Could not number ${what}`, { cause: error });
|
|
35
|
-
// Rolled back; a concurrent writer most likely took the number (its
|
|
36
|
-
// unique key failed). The next attempt reads the new highest number.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Allocates the next number for a comment that has none (no transaction of its own).
|
|
42
|
-
*
|
|
43
|
-
* @param commentId - The comment's UUID.
|
|
44
|
-
* @returns Its number.
|
|
45
|
-
*/
|
|
46
|
-
async function allocate(commentId) {
|
|
47
|
-
const existing = await findOne(NUMBERS, [
|
|
48
|
-
{ field: 'commentId', operator: '=', value: commentId },
|
|
49
|
-
]);
|
|
50
|
-
if (existing)
|
|
51
|
-
return Number(existing.number);
|
|
52
|
-
const [last] = await findMany(NUMBERS, {
|
|
53
|
-
orderBy: [{ field: 'number', direction: 'desc' }],
|
|
54
|
-
limit: 1,
|
|
55
|
-
});
|
|
56
|
-
const number = (last ? Number(last.number) : 0) + 1;
|
|
57
|
-
await dbCreate(NUMBERS, { number, commentId });
|
|
58
|
-
return number;
|
|
59
|
-
}
|
|
60
21
|
/**
|
|
61
22
|
* The comment's number, allocating the next one when it has none. Numbers are
|
|
62
|
-
* unique and increase.
|
|
23
|
+
* unique and increase. Use it for a comment that was inserted into `comments`
|
|
24
|
+
* without `createComment()` — every comment created through this package is
|
|
25
|
+
* numbered already.
|
|
63
26
|
*
|
|
64
27
|
* @param commentId - The comment's UUID.
|
|
65
28
|
* @returns Its sequential number.
|
|
66
29
|
* @throws {Error} When no number could be allocated after several attempts.
|
|
67
30
|
*/
|
|
68
31
|
export async function numberComment(commentId) {
|
|
69
|
-
return
|
|
32
|
+
return allocateCommentNumber(commentId);
|
|
70
33
|
}
|
|
71
34
|
/**
|
|
72
35
|
* Creates a comment and its number together: both are written, or neither.
|
|
73
36
|
*
|
|
37
|
+
* This is `createComment()` under the name an integer-id API reads best — it
|
|
38
|
+
* takes the same arguments and returns the same `NumberedComment`. Address the
|
|
39
|
+
* result by `number` and read it back with `getCommentByNumber(number)`.
|
|
40
|
+
*
|
|
74
41
|
* @param resourceType - The type of resource being commented on.
|
|
75
42
|
* @param resourceId - The ID of the resource being commented on.
|
|
76
43
|
* @param userId - The ID of the commenting user.
|
|
@@ -78,13 +45,12 @@ export async function numberComment(commentId) {
|
|
|
78
45
|
* @returns The created comment with its number.
|
|
79
46
|
*/
|
|
80
47
|
export async function createNumberedComment(resourceType, resourceId, userId, data) {
|
|
81
|
-
return
|
|
82
|
-
const comment = await createComment(resourceType, resourceId, userId, data);
|
|
83
|
-
return { ...comment, number: await allocate(comment.id) };
|
|
84
|
-
});
|
|
48
|
+
return createComment(resourceType, resourceId, userId, data);
|
|
85
49
|
}
|
|
86
50
|
/**
|
|
87
|
-
* The comment a number belongs to
|
|
51
|
+
* The comment a number belongs to — the lookup behind an integer-addressed
|
|
52
|
+
* route such as `DELETE /comments/:id`: find the comment here, then pass its
|
|
53
|
+
* `id` to `deleteComment()`/`updateComment()`.
|
|
88
54
|
*
|
|
89
55
|
* @param number - The comment's sequential number.
|
|
90
56
|
* @returns The comment with its number, or `null` when no comment has it.
|
|
@@ -102,16 +68,15 @@ export async function getCommentByNumber(number) {
|
|
|
102
68
|
}
|
|
103
69
|
/**
|
|
104
70
|
* The numbers of the given comments, for listing them with their numbers.
|
|
105
|
-
*
|
|
71
|
+
*
|
|
72
|
+
* Every comment created through this package is numbered, so a comment missing
|
|
73
|
+
* from the map was inserted directly into `comments` (or predates numbering by
|
|
74
|
+
* default); give it one with `numberComment(id)`. Never read a missing entry as
|
|
75
|
+
* `0` — `0` is not a comment id, and an integer id of `0` cannot be found again.
|
|
106
76
|
*
|
|
107
77
|
* @param commentIds - The comments' UUIDs.
|
|
108
78
|
* @returns Each numbered comment's UUID mapped to its number.
|
|
109
79
|
*/
|
|
110
80
|
export async function commentNumbers(commentIds) {
|
|
111
|
-
|
|
112
|
-
return new Map();
|
|
113
|
-
const rows = await findMany(NUMBERS, {
|
|
114
|
-
where: [{ field: 'commentId', operator: 'in', value: [...commentIds] }],
|
|
115
|
-
});
|
|
116
|
-
return new Map(rows.map((r) => [r.commentId, Number(r.number)]));
|
|
81
|
+
return numbersForComments(commentIds);
|
|
117
82
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -6,21 +6,39 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import type { NumberedComment } from './numbers.js';
|
|
9
10
|
import type { Comment, CreateCommentInput, PaginatedResult, PaginationOptions, UpdateCommentInput } from './types.js';
|
|
10
11
|
/**
|
|
11
|
-
* Creates a new comment on a resource.
|
|
12
|
+
* Creates a new comment on a resource, with its sequential number.
|
|
13
|
+
*
|
|
14
|
+
* The comment row and its `comment_numbers` row are written together: the
|
|
15
|
+
* returned comment carries `number` (1, 2, 3, …) beside its UUID `id`. An API
|
|
16
|
+
* whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
17
|
+
* `DELETE /comments/:id`) returns `number` as that id and reads it back with
|
|
18
|
+
* `getCommentByNumber(number)` — never hand-build an id from the UUID.
|
|
19
|
+
* `createNumberedComment()` is this same create under the name that path reads
|
|
20
|
+
* best; `numberComment(id)` numbers a row that was inserted without this
|
|
21
|
+
* function.
|
|
22
|
+
*
|
|
23
|
+
* Both `__setup__` SQL files are required (`comments`, `comment_numbers`).
|
|
12
24
|
*
|
|
13
25
|
* @param resourceType - The type of resource being commented on.
|
|
14
26
|
* @param resourceId - The ID of the resource being commented on.
|
|
15
27
|
* @param userId - The ID of the commenting user.
|
|
16
28
|
* @param data - The comment creation input.
|
|
17
|
-
* @returns The created comment
|
|
29
|
+
* @returns The created comment with its sequential `number`.
|
|
30
|
+
* @throws {Error} When the comment could not be numbered; the comment row is
|
|
31
|
+
* removed again so the caller is not told a create failed while it exists.
|
|
18
32
|
*/
|
|
19
|
-
export declare function createComment(resourceType: string, resourceId: string, userId: string, data: CreateCommentInput): Promise<
|
|
33
|
+
export declare function createComment(resourceType: string, resourceId: string, userId: string, data: CreateCommentInput): Promise<NumberedComment>;
|
|
20
34
|
/**
|
|
21
35
|
* Retrieves paginated comments for a resource, ordered by creation date descending.
|
|
22
36
|
* Only returns top-level comments (no replies).
|
|
23
37
|
*
|
|
38
|
+
* The rows carry each comment's UUID `id`; for an API that addresses comments by an
|
|
39
|
+
* integer, map a page's ids with `commentNumbers()` — every comment `createComment()`
|
|
40
|
+
* wrote is in that map, and a missing entry (a raw insert) is never `0`.
|
|
41
|
+
*
|
|
24
42
|
* @param resourceType - The type of resource.
|
|
25
43
|
* @param resourceId - The ID of the resource.
|
|
26
44
|
* @param options - Pagination options.
|
|
@@ -30,14 +48,16 @@ export declare function getCommentsByResource(resourceType: string, resourceId:
|
|
|
30
48
|
/**
|
|
31
49
|
* Retrieves a single comment by ID.
|
|
32
50
|
*
|
|
33
|
-
* @param commentId - The comment
|
|
51
|
+
* @param commentId - The comment's UUID. For the sequential integer an
|
|
52
|
+
* integer-addressed API uses, call `getCommentByNumber(number)` instead.
|
|
34
53
|
* @returns The comment or `null` if not found.
|
|
35
54
|
*/
|
|
36
55
|
export declare function getCommentById(commentId: string): Promise<Comment | null>;
|
|
37
56
|
/**
|
|
38
57
|
* Updates a comment. Only the comment owner can update.
|
|
39
58
|
*
|
|
40
|
-
* @param commentId - The comment
|
|
59
|
+
* @param commentId - The comment's UUID. For an integer number, resolve it with
|
|
60
|
+
* `getCommentByNumber(number)` first.
|
|
41
61
|
* @param userId - The requesting user's ID (must match comment owner).
|
|
42
62
|
* @param data - The update input.
|
|
43
63
|
* @returns The updated comment or `null` if not found or unauthorized.
|
|
@@ -46,7 +66,9 @@ export declare function updateComment(commentId: string, userId: string, data: U
|
|
|
46
66
|
/**
|
|
47
67
|
* Deletes a comment. Only the comment owner can delete.
|
|
48
68
|
*
|
|
49
|
-
* @param commentId - The comment
|
|
69
|
+
* @param commentId - The comment's UUID. To delete by the integer number an
|
|
70
|
+
* API like RealWorld exposes (`DELETE /comments/:id`), look the row up with
|
|
71
|
+
* `getCommentByNumber(number)` and pass its `id`.
|
|
50
72
|
* @param userId - The requesting user's ID (must match comment owner).
|
|
51
73
|
* @returns `true` if deleted, `false` if not found or unauthorized.
|
|
52
74
|
*/
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EACV,OAAO,EACP,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAInB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAqBnC;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAE/E;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAczB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMvF;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAiBnC;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK/F"}
|
package/dist/service.js
CHANGED
|
@@ -7,15 +7,29 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { count, create as dbCreate, deleteById, findById, findMany, updateById, } from '@molecule/api-database';
|
|
10
|
+
import { allocateCommentNumber } from './numbering.js';
|
|
10
11
|
const TABLE = 'comments';
|
|
11
12
|
/**
|
|
12
|
-
* Creates a new comment on a resource.
|
|
13
|
+
* Creates a new comment on a resource, with its sequential number.
|
|
14
|
+
*
|
|
15
|
+
* The comment row and its `comment_numbers` row are written together: the
|
|
16
|
+
* returned comment carries `number` (1, 2, 3, …) beside its UUID `id`. An API
|
|
17
|
+
* whose clients address a comment by an integer (RealWorld's `comment.id`,
|
|
18
|
+
* `DELETE /comments/:id`) returns `number` as that id and reads it back with
|
|
19
|
+
* `getCommentByNumber(number)` — never hand-build an id from the UUID.
|
|
20
|
+
* `createNumberedComment()` is this same create under the name that path reads
|
|
21
|
+
* best; `numberComment(id)` numbers a row that was inserted without this
|
|
22
|
+
* function.
|
|
23
|
+
*
|
|
24
|
+
* Both `__setup__` SQL files are required (`comments`, `comment_numbers`).
|
|
13
25
|
*
|
|
14
26
|
* @param resourceType - The type of resource being commented on.
|
|
15
27
|
* @param resourceId - The ID of the resource being commented on.
|
|
16
28
|
* @param userId - The ID of the commenting user.
|
|
17
29
|
* @param data - The comment creation input.
|
|
18
|
-
* @returns The created comment
|
|
30
|
+
* @returns The created comment with its sequential `number`.
|
|
31
|
+
* @throws {Error} When the comment could not be numbered; the comment row is
|
|
32
|
+
* removed again so the caller is not told a create failed while it exists.
|
|
19
33
|
*/
|
|
20
34
|
export async function createComment(resourceType, resourceId, userId, data) {
|
|
21
35
|
const result = await dbCreate(TABLE, {
|
|
@@ -26,12 +40,29 @@ export async function createComment(resourceType, resourceId, userId, data) {
|
|
|
26
40
|
body: data.body,
|
|
27
41
|
editedAt: null,
|
|
28
42
|
});
|
|
29
|
-
|
|
43
|
+
const comment = result.data;
|
|
44
|
+
try {
|
|
45
|
+
return { ...comment, number: await allocateCommentNumber(comment.id) };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// The caller is told the create failed, so the row must not stay behind.
|
|
49
|
+
try {
|
|
50
|
+
await deleteById(TABLE, comment.id);
|
|
51
|
+
}
|
|
52
|
+
catch (cleanupError) {
|
|
53
|
+
throw new AggregateError([error, cleanupError], `Could not number comment ${comment.id}, and could not remove the unnumbered row`, { cause: cleanupError });
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
30
57
|
}
|
|
31
58
|
/**
|
|
32
59
|
* Retrieves paginated comments for a resource, ordered by creation date descending.
|
|
33
60
|
* Only returns top-level comments (no replies).
|
|
34
61
|
*
|
|
62
|
+
* The rows carry each comment's UUID `id`; for an API that addresses comments by an
|
|
63
|
+
* integer, map a page's ids with `commentNumbers()` — every comment `createComment()`
|
|
64
|
+
* wrote is in that map, and a missing entry (a raw insert) is never `0`.
|
|
65
|
+
*
|
|
35
66
|
* @param resourceType - The type of resource.
|
|
36
67
|
* @param resourceId - The ID of the resource.
|
|
37
68
|
* @param options - Pagination options.
|
|
@@ -59,7 +90,8 @@ export async function getCommentsByResource(resourceType, resourceId, options =
|
|
|
59
90
|
/**
|
|
60
91
|
* Retrieves a single comment by ID.
|
|
61
92
|
*
|
|
62
|
-
* @param commentId - The comment
|
|
93
|
+
* @param commentId - The comment's UUID. For the sequential integer an
|
|
94
|
+
* integer-addressed API uses, call `getCommentByNumber(number)` instead.
|
|
63
95
|
* @returns The comment or `null` if not found.
|
|
64
96
|
*/
|
|
65
97
|
export async function getCommentById(commentId) {
|
|
@@ -68,7 +100,8 @@ export async function getCommentById(commentId) {
|
|
|
68
100
|
/**
|
|
69
101
|
* Updates a comment. Only the comment owner can update.
|
|
70
102
|
*
|
|
71
|
-
* @param commentId - The comment
|
|
103
|
+
* @param commentId - The comment's UUID. For an integer number, resolve it with
|
|
104
|
+
* `getCommentByNumber(number)` first.
|
|
72
105
|
* @param userId - The requesting user's ID (must match comment owner).
|
|
73
106
|
* @param data - The update input.
|
|
74
107
|
* @returns The updated comment or `null` if not found or unauthorized.
|
|
@@ -89,7 +122,9 @@ export async function updateComment(commentId, userId, data) {
|
|
|
89
122
|
/**
|
|
90
123
|
* Deletes a comment. Only the comment owner can delete.
|
|
91
124
|
*
|
|
92
|
-
* @param commentId - The comment
|
|
125
|
+
* @param commentId - The comment's UUID. To delete by the integer number an
|
|
126
|
+
* API like RealWorld exposes (`DELETE /comments/:id`), look the row up with
|
|
127
|
+
* `getCommentByNumber(number)` and pass its `id`.
|
|
93
128
|
* @param userId - The requesting user's ID (must match comment owner).
|
|
94
129
|
* @returns `true` if deleted, `false` if not found or unauthorized.
|
|
95
130
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
-- Sequential integer numbers for comments, for APIs whose clients address a
|
|
2
|
-
-- comment by an integer id (RealWorld's `comment.id` is an integer).
|
|
3
|
-
--
|
|
4
|
-
--
|
|
2
|
+
-- comment by an integer id (RealWorld's `comment.id` is an integer).
|
|
3
|
+
-- createComment() / createNumberedComment() write this row with the comment;
|
|
4
|
+
-- numberComment(id) numbers a row that was inserted without them. The comment
|
|
5
|
+
-- keeps its UUID either way.
|
|
5
6
|
CREATE TABLE IF NOT EXISTS "comment_numbers" (
|
|
6
7
|
"number" INTEGER PRIMARY KEY,
|
|
7
8
|
"commentId" UUID NOT NULL UNIQUE REFERENCES "comments"("id") ON DELETE CASCADE
|