@arki/journal 0.0.0-stage → 0.1.1

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 (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +541 -2
  3. package/dist/api.d.ts +336 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +2 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/command.d.ts +27 -0
  8. package/dist/command.d.ts.map +1 -0
  9. package/dist/command.js +157 -0
  10. package/dist/command.js.map +1 -0
  11. package/dist/debug.d.ts +7 -0
  12. package/dist/debug.d.ts.map +1 -0
  13. package/dist/debug.js +8 -0
  14. package/dist/debug.js.map +1 -0
  15. package/dist/dot.d.ts +102 -0
  16. package/dist/dot.d.ts.map +1 -0
  17. package/dist/dot.js +159 -0
  18. package/dist/dot.js.map +1 -0
  19. package/dist/entity.d.ts +56 -0
  20. package/dist/entity.d.ts.map +1 -0
  21. package/dist/entity.js +174 -0
  22. package/dist/entity.js.map +1 -0
  23. package/dist/errors.d.ts +47 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +112 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/event.d.ts +9 -0
  28. package/dist/event.d.ts.map +1 -0
  29. package/dist/event.js +19 -0
  30. package/dist/event.js.map +1 -0
  31. package/dist/fold.d.ts +29 -0
  32. package/dist/fold.d.ts.map +1 -0
  33. package/dist/fold.js +49 -0
  34. package/dist/fold.js.map +1 -0
  35. package/dist/handlers.d.ts +26 -0
  36. package/dist/handlers.d.ts.map +1 -0
  37. package/dist/handlers.js +130 -0
  38. package/dist/handlers.js.map +1 -0
  39. package/dist/history.d.ts +49 -0
  40. package/dist/history.d.ts.map +1 -0
  41. package/dist/history.js +259 -0
  42. package/dist/history.js.map +1 -0
  43. package/dist/hooks.d.ts +14 -0
  44. package/dist/hooks.d.ts.map +1 -0
  45. package/dist/hooks.js +67 -0
  46. package/dist/hooks.js.map +1 -0
  47. package/dist/id.d.ts +18 -0
  48. package/dist/id.d.ts.map +1 -0
  49. package/dist/id.js +41 -0
  50. package/dist/id.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +5 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/journal.d.ts +51 -0
  56. package/dist/journal.d.ts.map +1 -0
  57. package/dist/journal.js +181 -0
  58. package/dist/journal.js.map +1 -0
  59. package/dist/listen.d.ts +37 -0
  60. package/dist/listen.d.ts.map +1 -0
  61. package/dist/listen.js +90 -0
  62. package/dist/listen.js.map +1 -0
  63. package/dist/orpc.d.ts +21 -0
  64. package/dist/orpc.d.ts.map +1 -0
  65. package/dist/orpc.js +57 -0
  66. package/dist/orpc.js.map +1 -0
  67. package/dist/pg.d.ts +537 -0
  68. package/dist/pg.d.ts.map +1 -0
  69. package/dist/pg.js +80 -0
  70. package/dist/pg.js.map +1 -0
  71. package/dist/storage.d.ts +52 -0
  72. package/dist/storage.d.ts.map +1 -0
  73. package/dist/storage.js +221 -0
  74. package/dist/storage.js.map +1 -0
  75. package/dist/testing.d.ts +19 -0
  76. package/dist/testing.d.ts.map +1 -0
  77. package/dist/testing.js +23 -0
  78. package/dist/testing.js.map +1 -0
  79. package/dist/transaction-guard.d.ts +2 -0
  80. package/dist/transaction-guard.d.ts.map +1 -0
  81. package/dist/transaction-guard.js +38 -0
  82. package/dist/transaction-guard.js.map +1 -0
  83. package/dist/types.d.ts +414 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +2 -0
  86. package/dist/types.js.map +1 -0
  87. package/dist/worker.d.ts +109 -0
  88. package/dist/worker.d.ts.map +1 -0
  89. package/dist/worker.js +458 -0
  90. package/dist/worker.js.map +1 -0
  91. package/dist/write.d.ts +39 -0
  92. package/dist/write.d.ts.map +1 -0
  93. package/dist/write.js +287 -0
  94. package/dist/write.js.map +1 -0
  95. package/docs/agent-guide.md +324 -0
  96. package/llms.txt +40 -0
  97. package/package.json +99 -4
  98. package/src/api.ts +465 -0
  99. package/src/command.ts +242 -0
  100. package/src/debug.ts +8 -0
  101. package/src/dot.ts +296 -0
  102. package/src/entity.ts +377 -0
  103. package/src/errors.ts +134 -0
  104. package/src/event.ts +26 -0
  105. package/src/fold.ts +97 -0
  106. package/src/handlers.ts +262 -0
  107. package/src/history.ts +390 -0
  108. package/src/hooks.ts +85 -0
  109. package/src/id.ts +54 -0
  110. package/src/index.ts +76 -0
  111. package/src/journal.ts +256 -0
  112. package/src/listen.ts +109 -0
  113. package/src/orpc.ts +59 -0
  114. package/src/pg.ts +107 -0
  115. package/src/storage.ts +277 -0
  116. package/src/test-support.ts +82 -0
  117. package/src/testing.ts +23 -0
  118. package/src/transaction-guard.ts +40 -0
  119. package/src/types.ts +439 -0
  120. package/src/worker.ts +609 -0
  121. package/src/write.ts +434 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The entry point: `initJournal`, the definition helpers it returns, and
3
+ * `createJournal`, which checks the handlers, builds each part with its
4
+ * dependencies and wires the public methods.
5
+ * May import every other file. No implementation file may import this one.
6
+ */
7
+ import { getTableColumns, getTableName, is, lt } from 'drizzle-orm';
8
+ import { getTableConfig, PgColumn } from 'drizzle-orm/pg-core';
9
+ import { z } from 'zod';
10
+ import { createCommandRunner } from './command.js';
11
+ import { defineEntity } from './entity.js';
12
+ import { JournalError } from './errors.js';
13
+ import { defineEvent } from './event.js';
14
+ import { bindEffect, bindTap, defineProjection, effectFor, tapFor } from './handlers.js';
15
+ import { createHistory } from './history.js';
16
+ import { createHookPoint } from './hooks.js';
17
+ import { createAdmin, createCatchUp, createDrain, createStrandedJobs, createTapDispatch, createWorkers, } from './worker.js';
18
+ import { createWriter } from './write.js';
19
+ const emptyMeta = z.object({});
20
+ function initFor() {
21
+ function init(options) {
22
+ return options?.meta === undefined
23
+ ? createInit(emptyMeta)
24
+ : createInit(options.meta);
25
+ }
26
+ return init;
27
+ }
28
+ /**
29
+ * `initJournal({ meta })` types `write`'s `tx`, `events`' `{ tx }` and
30
+ * projection handlers with the structural `DbHandle`. `initJournal.tx<AppTx>()
31
+ * .create({ meta })` uses the app's own Drizzle transaction type instead, so
32
+ * repositories typed against it fit. `initJournal.db<AppDb>()` names the
33
+ * app's root database type: effects get it as `ctx.db`, and `drain` and
34
+ * `worker` then need it. Both are type-only steps and chain in either order.
35
+ */
36
+ export const initJournal = Object.assign(initFor(), {
37
+ tx: () => ({
38
+ create: initFor(),
39
+ db: () => ({ create: initFor() }),
40
+ }),
41
+ db: () => ({
42
+ create: initFor(),
43
+ tx: () => ({ create: initFor() }),
44
+ }),
45
+ });
46
+ /** A table with a column called `id`. The check is real; the guard narrows to `IdTable`. */
47
+ function hasIdColumn(table) {
48
+ const columns = getTableColumns(table);
49
+ return is(columns.id, PgColumn);
50
+ }
51
+ function createInit(metaSchema) {
52
+ function entity(kind, table, options) {
53
+ if (options !== undefined) {
54
+ return defineEntity(kind, table, options.key);
55
+ }
56
+ if (!hasIdColumn(table)) {
57
+ throw new JournalError('BAD_DEFINITION', 'entity', kind, `journal: entity "${kind}": table "${getTableName(table)}" has no "id" column; pass { key: 'columnName' }`);
58
+ }
59
+ return defineEntity(kind, table, 'id');
60
+ }
61
+ return {
62
+ event: defineEvent,
63
+ entity,
64
+ command: (name, input, handler) => ({ name, input, handler }),
65
+ projection: (name, entity, on, opts) => defineProjection(name, entity, on, opts),
66
+ effect: effectFor(),
67
+ tap: tapFor(),
68
+ compose: config => {
69
+ const effects = (config.effects ?? []).map(effect => bindEffect(effect, config.services ?? {}));
70
+ const taps = (config.taps ?? []).map(tap => bindTap(tap, config.services ?? {}));
71
+ return createJournal(metaSchema, config.tables, config.projections ?? [], effects, taps);
72
+ },
73
+ };
74
+ }
75
+ /** Throws when two handlers of one kind share a name: the second would silently replace the first. */
76
+ function assertUniqueNames(kind, handlers) {
77
+ const seen = new Set();
78
+ for (const { name } of handlers) {
79
+ if (seen.has(name))
80
+ throw new JournalError('BAD_DEFINITION', kind, name, `journal: duplicate ${kind} name "${name}"`);
81
+ seen.add(name);
82
+ }
83
+ }
84
+ function createJournal(metaSchema, tables, projections, effects, taps = []) {
85
+ assertUniqueNames('tap', taps);
86
+ const eventsSchema = getTableConfig(tables.events).schema;
87
+ const wakeChannel = eventsSchema === undefined ? 'journal_wake' : `${eventsSchema}_journal_wake`;
88
+ assertUniqueNames('effect', effects);
89
+ assertUniqueNames('projection', projections);
90
+ const entitiesByKind = new Map();
91
+ for (const handler of [...projections, ...effects]) {
92
+ for (const entity of handler.entities)
93
+ entitiesByKind.set(entity.kind, entity);
94
+ }
95
+ // `journal.drain`, `journal.wake` and `journal.run` are read from the journal object on each
96
+ // call, so the worker loop, `run`, a bound runner and an effect's `ctx.run` always use the methods the object holds.
97
+ const history = createHistory({ metaSchema, tables });
98
+ const inlineProjections = projections.filter(projection => projection.lane === 'inline');
99
+ const asyncProjections = projections.filter(projection => projection.lane === 'async');
100
+ const { write, adopt, writeWithValidatedMeta } = createWriter({
101
+ metaSchema,
102
+ tables,
103
+ projections: inlineProjections,
104
+ asyncProjections,
105
+ effects,
106
+ history,
107
+ wakeChannel,
108
+ });
109
+ const commandHooks = createHookPoint('command');
110
+ const effectHooks = createHookPoint('effect');
111
+ const run = createCommandRunner({
112
+ metaSchema,
113
+ writeWithValidatedMeta,
114
+ wake: () => journal.wake(),
115
+ hooks: commandHooks,
116
+ tables,
117
+ });
118
+ const drain = createDrain({
119
+ tables,
120
+ hooks: effectHooks,
121
+ effects,
122
+ history,
123
+ runOn: (db, cause) => (command, input, meta, call) => run(db, command, input, meta, call, cause),
124
+ });
125
+ const catching = createCatchUp({ tables, projections: asyncProjections, history });
126
+ const dispatch = createTapDispatch({ tables, taps, history });
127
+ const workers = createWorkers({
128
+ drain: (db, opts) => journal.drain(db, opts),
129
+ catchUp: (db, opts) => journal.catchUp(db, opts),
130
+ dispatch,
131
+ tapNames: taps.map(tap => tap.name),
132
+ });
133
+ const strandedJobs = createStrandedJobs({ tables, effectNames: effects.map(effect => effect.name) });
134
+ const admin = createAdmin({ tables, wake: () => journal.wake() });
135
+ const journal = {
136
+ write: (tx, entity, id, input) => write(tx, entity, id, input),
137
+ adopt: (tx, entity, id, input) => adopt(tx, entity, id, input),
138
+ run: (source, command, input, meta, opts) => run(source, command, input, meta, opts),
139
+ bind: (source, meta, opts) => (command, input, call) => journal.run(source, command, input, meta, { ...opts, ...call }),
140
+ replay: (db, entity, id, opts) => history.replay(db, entity, id, opts),
141
+ verify: (db, entity, id) => history.verify(db, entity, id),
142
+ repair: (db, entity, id) => history.repair(db, entity, id),
143
+ forget: (db, entity, id) => history.forget(db, entity, id),
144
+ pruneReceipts: async (db, opts) => {
145
+ const gone = await db
146
+ .delete(tables.receipts)
147
+ .where(lt(tables.receipts.recordedAt, opts.before))
148
+ .returning({ key: tables.receipts.key });
149
+ return gone.length;
150
+ },
151
+ rerun: (db, projection, opts) => {
152
+ if (projection.lane === 'async')
153
+ throw new JournalError('BAD_DEFINITION', 'projection', projection.name, `journal: projection "${projection.name}" is async; use reset and catchUp instead of rerun`);
154
+ return history.rerun(db, projection, opts);
155
+ },
156
+ catchUp: (db, opts) => catching.catchUp(db, opts),
157
+ reset: (db, projection, clear) => catching.reset(db, projection, clear),
158
+ cursors: db => catching.cursors(db),
159
+ drain: (db, opts) => drain(db, opts),
160
+ worker: (db, opts) => workers.worker(db, opts),
161
+ wake: () => workers.wakeAll(),
162
+ strandedJobs: db => strandedJobs(db),
163
+ jobs: (db, opts) => admin.jobs(db, opts),
164
+ retry: (db, jobId) => admin.retry(db, jobId),
165
+ discard: (db, jobId) => admin.discard(db, jobId),
166
+ wakeChannel,
167
+ hooks: { command: commandHooks.point, effect: effectHooks.point },
168
+ catalog: {
169
+ entities: [...entitiesByKind.values()].map(entity => ({
170
+ kind: entity.kind,
171
+ table: getTableName(entity.table),
172
+ events: [...entity.events.keys()],
173
+ })),
174
+ projections: projections.map(projection => projection.name),
175
+ effects: effects.map(effect => effect.name),
176
+ taps: taps.map(tap => tap.name),
177
+ },
178
+ };
179
+ return journal;
180
+ }
181
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `listenOnPool`: the `WakeListener` for a `pg` pool. It borrows one client from
3
+ * the pool for as long as the worker runs, `LISTEN`s on the channel, and forwards
4
+ * each payload. A dropped connection is released and replaced after `retryMs`;
5
+ * notifications sent while it was gone are lost, which taps accept and polling
6
+ * covers for jobs. Typed by shape, so this file imports no driver.
7
+ * Leaf file: may import `debug` and `types` only.
8
+ */
9
+ import type { WakeListener } from './types.js';
10
+ /**
11
+ * Let the process exit while this timer waits. Node and Bun return a timer object
12
+ * with `unref`; browsers, Deno's web API and Hermes return a number, which never
13
+ * holds a process open, so there is nothing to do.
14
+ */
15
+ export declare function unrefTimer(timer: unknown): void;
16
+ /** The part of a `pg` pool client a listener uses. */
17
+ export type ListenClient = {
18
+ query(text: string): Promise<unknown>;
19
+ on(event: 'notification', listener: (message: {
20
+ readonly channel: string;
21
+ readonly payload?: string;
22
+ }) => void): unknown;
23
+ on(event: 'error', listener: (error: Error) => void): unknown;
24
+ release(error?: Error | boolean): void;
25
+ };
26
+ /** The part of a `pg` pool a listener uses: `new pg.Pool(...)`, or a Drizzle node-postgres database's `$client`. */
27
+ export type ListenPool = {
28
+ connect(): Promise<ListenClient>;
29
+ };
30
+ /**
31
+ * Hear `channel` on a dedicated client of `pool`: `journal.worker(db, { listen: listenOnPool(db.$client, journal.wakeChannel) })`.
32
+ * The first connection is made when the worker starts; if it fails, `start()` rejects.
33
+ */
34
+ export declare function listenOnPool(pool: ListenPool, channel: string, opts?: {
35
+ readonly retryMs?: number;
36
+ }): WakeListener;
37
+ //# sourceMappingURL=listen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listen.d.ts","sourceRoot":"","sources":["../src/listen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAG/C;AAED,sDAAsD;AACtD,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,EAAE,CACA,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GACnF,OAAO,CAAC;IACX,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF,oHAAoH;AACpH,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CAAA;CAAE,CAAC;AAE9D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAqElH"}
package/dist/listen.js ADDED
@@ -0,0 +1,90 @@
1
+ import { debugWorker } from './debug.js';
2
+ /**
3
+ * Let the process exit while this timer waits. Node and Bun return a timer object
4
+ * with `unref`; browsers, Deno's web API and Hermes return a number, which never
5
+ * holds a process open, so there is nothing to do.
6
+ */
7
+ export function unrefTimer(timer) {
8
+ if (typeof timer === 'object' && timer !== null && 'unref' in timer && typeof timer.unref === 'function')
9
+ timer.unref();
10
+ }
11
+ /**
12
+ * Hear `channel` on a dedicated client of `pool`: `journal.worker(db, { listen: listenOnPool(db.$client, journal.wakeChannel) })`.
13
+ * The first connection is made when the worker starts; if it fails, `start()` rejects.
14
+ */
15
+ export function listenOnPool(pool, channel, opts) {
16
+ const retryMs = opts?.retryMs ?? 1000;
17
+ const quoted = `"${channel.replaceAll('"', '""')}"`;
18
+ return async (onMessage) => {
19
+ let stopped = false;
20
+ let current;
21
+ let timer;
22
+ const connect = async () => {
23
+ const client = await pool.connect();
24
+ let released = false;
25
+ const drop = (error) => {
26
+ if (released)
27
+ return;
28
+ released = true;
29
+ if (current === client)
30
+ current = undefined;
31
+ client.release(error ?? true);
32
+ };
33
+ client.on('notification', message => {
34
+ if (message.channel === channel && message.payload !== undefined)
35
+ onMessage(message.payload);
36
+ });
37
+ client.on('error', error => {
38
+ debugWorker.debug('listen {channel}: connection lost: {lost}', { channel, lost: error.message });
39
+ drop(error);
40
+ schedule();
41
+ });
42
+ try {
43
+ await client.query(`LISTEN ${quoted}`);
44
+ }
45
+ catch (error) {
46
+ drop(error instanceof Error ? error : new Error(String(error)));
47
+ throw error;
48
+ }
49
+ if (stopped) {
50
+ drop();
51
+ return;
52
+ }
53
+ current = client;
54
+ debugWorker.debug('listen {channel}: connected', { channel });
55
+ };
56
+ const schedule = () => {
57
+ if (stopped || timer !== undefined)
58
+ return;
59
+ timer = setTimeout(() => {
60
+ timer = undefined;
61
+ connect().catch((error) => {
62
+ debugWorker.debug('listen {channel}: reconnect failed', {
63
+ channel,
64
+ error: error instanceof Error ? error.message : String(error),
65
+ });
66
+ schedule();
67
+ });
68
+ }, retryMs);
69
+ unrefTimer(timer);
70
+ };
71
+ await connect();
72
+ return async () => {
73
+ stopped = true;
74
+ if (timer !== undefined)
75
+ clearTimeout(timer);
76
+ const client = current;
77
+ current = undefined;
78
+ if (client === undefined)
79
+ return;
80
+ try {
81
+ await client.query(`UNLISTEN ${quoted}`);
82
+ client.release();
83
+ }
84
+ catch (error) {
85
+ client.release(error instanceof Error ? error : true);
86
+ }
87
+ };
88
+ };
89
+ }
90
+ //# sourceMappingURL=listen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listen.js","sourceRoot":"","sources":["../src/listen.ts"],"names":[],"mappings":"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"}
package/dist/orpc.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * - A `CommandError` becomes the `ORPCError` with the same code and message.
3
+ * - A `JournalError` with code `CONFLICT` becomes an `ORPCError('CONFLICT')`, a 409 the client can retry.
4
+ * - Anything else is returned unchanged: the app decides what it means.
5
+ *
6
+ * The original error is NOT attached as `cause`. The journal's message names the entity kind and
7
+ * id, and a host may log or serialize a cause. A host that wants the original logs it itself,
8
+ * in a middleware placed inside this one.
9
+ */
10
+ export declare function toORPCError(error: unknown): unknown;
11
+ /**
12
+ * The middleware form of `toORPCError`. Add it once to the app's procedure
13
+ * builders, and no procedure needs a try/catch around a command.
14
+ *
15
+ * Order: put it INSIDE the app's own error middleware, so after it in the
16
+ * chain: `os.use(appErrors).use(commandErrors())`. A rejected rule is then
17
+ * already an `ORPCError` when the app's middleware sees it, and passes. Placed
18
+ * before it, the app's middleware sees a plain error first and answers 500.
19
+ */
20
+ export declare function commandErrors(): import("@orpc/server").DecoratedMiddleware<Record<never, never>, Record<never, never>, unknown, any, any, Record<never, never>>;
21
+ //# sourceMappingURL=orpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orpc.d.ts","sourceRoot":"","sources":["../src/orpc.ts"],"names":[],"mappings":"AAsBA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,oIAQ5B"}
package/dist/orpc.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `@orpc/server` adapter: what an oRPC client sees when a command fails.
3
+ *
4
+ * - `toORPCError(error)` maps one error.
5
+ * - `commandErrors()` is the middleware that maps whatever a procedure throws.
6
+ *
7
+ * `@orpc/server` is an optional peer. Import this module only in an app that
8
+ * serves oRPC. May import `errors` only; this is the one file that may import
9
+ * `@orpc/server`.
10
+ */
11
+ import { ORPCError, os } from '@orpc/server';
12
+ import { CommandError, isJournalError } from './errors.js';
13
+ /**
14
+ * Another request changed the same entity first, or the client's `expectedVersion`
15
+ * is behind. Nothing was written. Repeating the same request as it stands fails
16
+ * again when a version was given, so the client reloads first. The journal's own
17
+ * message names the entity kind and id; the client does not get those.
18
+ */
19
+ const CONFLICT_MESSAGE = 'This was changed by another request. Reload the current state and try again.';
20
+ /**
21
+ * - A `CommandError` becomes the `ORPCError` with the same code and message.
22
+ * - A `JournalError` with code `CONFLICT` becomes an `ORPCError('CONFLICT')`, a 409 the client can retry.
23
+ * - Anything else is returned unchanged: the app decides what it means.
24
+ *
25
+ * The original error is NOT attached as `cause`. The journal's message names the entity kind and
26
+ * id, and a host may log or serialize a cause. A host that wants the original logs it itself,
27
+ * in a middleware placed inside this one.
28
+ */
29
+ export function toORPCError(error) {
30
+ if (error instanceof CommandError) {
31
+ return new ORPCError(error.code, { message: error.message });
32
+ }
33
+ if (isJournalError(error, 'CONFLICT')) {
34
+ return new ORPCError('CONFLICT', { message: CONFLICT_MESSAGE });
35
+ }
36
+ return error;
37
+ }
38
+ /**
39
+ * The middleware form of `toORPCError`. Add it once to the app's procedure
40
+ * builders, and no procedure needs a try/catch around a command.
41
+ *
42
+ * Order: put it INSIDE the app's own error middleware, so after it in the
43
+ * chain: `os.use(appErrors).use(commandErrors())`. A rejected rule is then
44
+ * already an `ORPCError` when the app's middleware sees it, and passes. Placed
45
+ * before it, the app's middleware sees a plain error first and answers 500.
46
+ */
47
+ export function commandErrors() {
48
+ return os.middleware(async ({ next }) => {
49
+ try {
50
+ return await next();
51
+ }
52
+ catch (error) {
53
+ throw toORPCError(error);
54
+ }
55
+ });
56
+ }
57
+ //# sourceMappingURL=orpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orpc.js","sourceRoot":"","sources":["../src/orpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,8EAA8E,CAAC;AAExG;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;QAClC,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}