@classytic/repo-core 0.2.0 → 0.3.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/CHANGELOG.md +120 -0
- package/README.md +28 -7
- package/dist/errors/contract.d.mts +37 -0
- package/dist/errors/contract.mjs +75 -0
- package/dist/errors/index.d.mts +3 -2
- package/dist/errors/index.mjs +3 -1
- package/dist/errors/types.d.mts +113 -8
- package/dist/errors/types.mjs +29 -0
- package/dist/pagination/canonical.d.mts +35 -0
- package/dist/pagination/canonical.mjs +32 -0
- package/dist/pagination/index.d.mts +3 -2
- package/dist/pagination/index.mjs +2 -1
- package/dist/pagination/types.d.mts +65 -1
- package/dist/repository/base.mjs +21 -0
- package/dist/schema/field-rules.d.mts +19 -8
- package/dist/schema/field-rules.mjs +29 -9
- package/dist/schema/generator.d.mts +72 -0
- package/dist/schema/generator.mjs +16 -0
- package/dist/schema/index.d.mts +2 -1
- package/dist/schema/index.mjs +2 -1
- package/dist/schema/types.d.mts +56 -3
- package/dist/tenant/index.d.mts +3 -0
- package/dist/tenant/index.mjs +2 -0
- package/dist/tenant/resolve.d.mts +27 -0
- package/dist/tenant/resolve.mjs +69 -0
- package/dist/tenant/types.d.mts +142 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,126 @@ All notable changes to `@classytic/repo-core` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-04-29
|
|
8
|
+
|
|
9
|
+
### Added — Aggregate pagination shapes
|
|
10
|
+
|
|
11
|
+
- `AggregatePaginationResultCore<TDoc>` and `AggregatePaginationResult<TDoc, TExtra>` join `Offset*` / `Keyset*` as the third pagination shape every kit reports. Mirrors offset (page / total / pages / hasNext / hasPrev) with `method: 'aggregate'` discriminant. Mongokit's existing local `AggregatePaginationResult` (3.10.x) becomes redundant — to be deleted in mongokit 4.0.
|
|
12
|
+
- `AnyPaginationResult<TDoc, TExtra>` — union over the three result shapes. Use as the input type to anything that converts repo results into HTTP envelopes.
|
|
13
|
+
|
|
14
|
+
### Added — HTTP wire envelopes
|
|
15
|
+
|
|
16
|
+
The repository result shapes (`OffsetPaginationResult`, etc.) carry the `method` discriminant, so the corresponding HTTP wire envelope is just `{ success: true } & Result`. Adding the literal here closes the **server/client envelope mismatch** — arc's HTTP server was emitting flattened paginated responses without the `method` field while arc-next's typed responses required it.
|
|
17
|
+
|
|
18
|
+
- `OffsetPaginationResponse<TDoc, TExtra>` = `{ success: true } & OffsetPaginationResult<TDoc, TExtra>`
|
|
19
|
+
- `KeysetPaginationResponse<TDoc, TExtra>` = same for keyset
|
|
20
|
+
- `AggregatePaginationResponse<TDoc, TExtra>` = same for aggregate
|
|
21
|
+
- `BareListResponse<TDoc>` = `{ success: true; docs: TDoc[] }` for endpoints that don't paginate
|
|
22
|
+
- `PaginatedResponse<TDoc, TExtra>` = union over all four. The discriminated-union contract is `success: true` literal first, `method` second — typed clients (arc-next, SDKs) narrow with `if (res.success && 'method' in res && res.method === 'offset')`.
|
|
23
|
+
|
|
24
|
+
### Added — `toCanonicalList()` runtime normalizer
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { toCanonicalList } from '@classytic/repo-core/pagination';
|
|
28
|
+
|
|
29
|
+
const result = await userRepo.getAll(query);
|
|
30
|
+
reply.send(toCanonicalList(result)); // → PaginatedResponse<User>
|
|
31
|
+
|
|
32
|
+
reply.send(toCanonicalList([u1, u2])); // → BareListResponse<User>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The single point where an internal `Result` becomes an external `Response`. Three overloads route bare arrays / paginated results to the right wire shape; `TExtra` fields (mongokit's `warning?: string`, etc.) flow through.
|
|
36
|
+
|
|
37
|
+
**Subtle behavior**: `success: true` is stamped *after* the spread, so a stale `success: false` accidentally present on the input cannot override the literal — paginated success path is always `success: true`. Tested.
|
|
38
|
+
|
|
39
|
+
### Added — `isPaginatedResult()` type guard
|
|
40
|
+
|
|
41
|
+
Branches on the `method` discriminant rather than `Array.isArray`, so an empty paginated result still routes through the paginated branch. Used internally by `toCanonicalList`; exported for consumers writing custom envelope logic.
|
|
42
|
+
|
|
43
|
+
### Test delta
|
|
44
|
+
|
|
45
|
+
230 → 303 tests across 0.3.0. New coverage includes `tests/unit/pagination/canonical.test.ts` and the type-level coverage extensions in `result-types.test.ts` (35 tests landed with the aggregate / wire-envelope / `toCanonicalList` work), plus `tests/unit/repository/base-plugin-validation.test.ts` (6 tests for the `assertValidPlugin` guard).
|
|
46
|
+
|
|
47
|
+
### Added — `SchemaGenerator<TModel>` interface in `/schema`
|
|
48
|
+
|
|
49
|
+
Canonical contract for repository kits' CRUD-schema generators. Mongokit's `buildCrudSchemasFromModel` and sqlitekit's `buildCrudSchemasFromTable` (and any future kit's equivalent) `satisfies SchemaGenerator<TKitModel>` at the call site, so arc's `MongooseAdapter.schemaGenerator` / `DrizzleAdapter.schemaGenerator` accept them by structural typing — no glue, no inheritance, no inline function signatures duplicated in every adapter.
|
|
50
|
+
|
|
51
|
+
- `SchemaGenerator<TModel = unknown>` — `(model, options?, context?) => CrudSchemas | Record<string, unknown>`.
|
|
52
|
+
- `SchemaGeneratorContext` — resource-level context threaded at boot (`idField`, `resourceName`).
|
|
53
|
+
- `isSchemaGenerator(value)` — runtime predicate (arity 1-3 functions). Conservative — doesn't invoke.
|
|
54
|
+
|
|
55
|
+
Each kit ships a compile-time conformance check (same playbook as mongokit's `RepositoryLike` conformance gate):
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const _conformance: SchemaGenerator<Model<unknown>> = buildCrudSchemasFromModel;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Drift surfaces in the kit's typecheck immediately, before any consumer sees it.
|
|
62
|
+
|
|
63
|
+
10 new tests in `tests/unit/schema/generator.test.ts`. Total repo-core: 293 → 303.
|
|
64
|
+
|
|
65
|
+
### Added — `errors` module: canonical wire + throwable error contract
|
|
66
|
+
|
|
67
|
+
`@classytic/repo-core/errors` is now the single source of truth for error contracts across the org. Two complementary shapes:
|
|
68
|
+
|
|
69
|
+
- **`HttpError extends Error`** — the *throwable* shape. Plain `Error` with `status`, optional `code`, `meta`, `validationErrors`, `duplicate`. Kits classify their driver-specific errors into this shape; framework layers (arc) catch and serialize. Existing `HttpError` extended in 0.3 with `code?: string` and `meta?: Record<string, unknown>` (mongokit had these locally pre-3.12).
|
|
70
|
+
- **`ErrorContract`** — the *wire* shape (RFC 7807 / Stripe-style). What gets serialized to JSON responses, dead-letter records, audit trails, inter-service envelopes. Flat top-level `code` / `message` / `status` matches the org-wide `{ success, ... }` envelope convention.
|
|
71
|
+
- **`ErrorDetail`** — single field-scoped error (path / code / message). `ErrorContract.details` is `ReadonlyArray<ErrorDetail>`.
|
|
72
|
+
- **`ERROR_CODES` + `ErrorCode`** — canonical lowercase + snake_case codes (`'validation_error'`, `'not_found'`, `'conflict'`, `'unauthorized'`, `'forbidden'`, `'rate_limited'`, `'idempotency_conflict'`, `'precondition_failed'`, `'internal_error'`, `'service_unavailable'`, `'timeout'`). Domain packages extend hierarchically (`'order.validation.missing_line'`).
|
|
73
|
+
- **`toErrorContract(error)`** — converts any `Error` / `HttpError` / non-`Error` value to the canonical wire `ErrorContract`. `code` cascade: explicit `error.code` → status-derived → `'internal_error'`. Flattens mongokit-shaped `validationErrors` and `duplicate.fields` into the canonical `details[]` array.
|
|
74
|
+
- **`statusToErrorCode(status)`** — well-known HTTP status → canonical code. Conservative mapping; unknown statuses fall through to `'internal_error'` so domain handlers explicitly opt in.
|
|
75
|
+
|
|
76
|
+
Consumed by mongokit (drops local `HttpError`), arc (`ArcError implements HttpError` with `status` getter), and any future kit / service. Relocated from `@classytic/primitives/errors` (which had `ErrorContract` + `ERROR_CODES` but not the throwable contract) — same playbook as the pagination, tenant, and events relocations: errors are infrastructure-shaped, not domain primitives.
|
|
77
|
+
|
|
78
|
+
14 new tests in `tests/unit/errors/contract.test.ts`. Total repo-core: 279 → 293.
|
|
79
|
+
|
|
80
|
+
### Added — `tenant` subpath (canonical home for tenant scope contract)
|
|
81
|
+
|
|
82
|
+
New subpath `@classytic/repo-core/tenant` ships:
|
|
83
|
+
- `TenantConfig` — static config (`strategy`, `enabled`, `tenantField`, `fieldType`, `ref`, `contextKey`, `required`, `resolve`).
|
|
84
|
+
- `TenantStrategy = 'field' | 'none' | 'custom'`, `TenantFieldType = 'objectId' | 'string'`.
|
|
85
|
+
- `ResolvedTenantConfig` — the resolved-with-defaults shape returned by `resolveTenantConfig`.
|
|
86
|
+
- `DEFAULT_TENANT_CONFIG` — sensible org-wide defaults (`tenantField: 'organizationId'`, `fieldType: 'objectId'`, `ref: 'organization'`, `required: true`).
|
|
87
|
+
- `resolveTenantConfig(config?)` — normaliser; validates `'custom'` strategy requires `resolve`.
|
|
88
|
+
|
|
89
|
+
Relocated from `@classytic/primitives/tenant` (which has been removed in primitives 0.3 cleanup). Tenant scope is **infrastructure-shaped** — describes how queries get scoped, not a domain primitive like Money or Address. Repo-core is its proper home: it sits next to `context`, `filter`, `hooks`, `schema`, `cache` — every other repository contract — and lets mongokit / sqlitekit / future kits consume it through the existing `@classytic/repo-core` peer dep without pulling primitives just for one type.
|
|
90
|
+
|
|
91
|
+
**Custom tenancy escape hatch** unchanged: `strategy: 'custom'` + `resolve: (ctx) => filterShape` covers multi-field composites, region+partner shards, hash-derived filters, anything that doesn't fit `field === id`.
|
|
92
|
+
|
|
93
|
+
14 new tests in `tests/unit/tenant/resolve.test.ts` (ported from primitives' suite). Total repo-core: 265 → 279 tests.
|
|
94
|
+
|
|
95
|
+
### Added — schema-builder vocabulary
|
|
96
|
+
|
|
97
|
+
- **`SchemaBuilderOptions.excludeFields`** — global field exclusion. Fields listed here are dropped from create / update / response schemas in one place. Equivalent to setting `create.omitFields`, `update.omitFields`, AND `response.omitFields` to the same list. Use for fields that should never appear in any HTTP-facing schema.
|
|
98
|
+
- **`SchemaBuilderOptions.response`** with `omitFields?: string[]` — response-schema overrides. Drops extra fields from the response shape without marking them globally hidden.
|
|
99
|
+
- **`CrudSchemas.response?: JsonSchema`** — optional response-shape schema. Includes server-set fields (`createdAt`, `updatedAt`, `_id`, immutable / readonly / systemManaged) since those ARE returned to clients. Only `fieldRules[field].hidden: true` strips automatically. Set `additionalProperties: true` so virtuals / computed fields pass through.
|
|
100
|
+
- **`FieldRule.hidden?: boolean`** — strips the field from the response shape. Distinct from `systemManaged` (request-body concern). Use for passwords, secrets, internal scoring.
|
|
101
|
+
- **`collectFieldsToOmit(options, 'response')`** — third purpose alongside `'create'` / `'update'`. Implements the response policy (only `hidden` + `excludeFields` + `response.omitFields`).
|
|
102
|
+
|
|
103
|
+
These are the contracts mongokit 3.12 implements, arc 2.12's MongooseAdapter consumes, and any future kit (sqlitekit, prismakit) inherits for free.
|
|
104
|
+
|
|
105
|
+
### Hardened — `RepositoryBase` plugin-shape validation
|
|
106
|
+
|
|
107
|
+
`RepositoryBase.use()` and the constructor's plugin loop now reject malformed plugin entries up front via `assertValidPlugin()`. The motivating field bug: `new Repository(Model, ['organizationId'], opts)` — passing a tenant-field string array where the constructor expected `plugins[]` — used to crash deep in the call site with `TypeError: plugin.apply is not a function`, cascade-failing every test that booted the app. The validator now throws a single descriptive `TypeError` at construction with the offending index and a hint about the common `tenantField`-in-the-wrong-slot mistake:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
[repo-core] Repository "Foo": plugin at index 0 has wrong type.
|
|
111
|
+
Expected a function or { name, apply(repo) } object — got string 'organizationId'.
|
|
112
|
+
Common cause: `new Repository(Model, [tenantField], opts)` — second argument must be a plugins array.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Lock-in: `tests/unit/repository/base-plugin-validation.test.ts` (6 cases covering string/null/object-without-apply/function/object/post-construction `use()` paths).
|
|
116
|
+
|
|
117
|
+
### Migration — mongokit 4.0, arc 2.12, arc-next 0.6
|
|
118
|
+
|
|
119
|
+
Three downstream changes drop their local copies and import directly:
|
|
120
|
+
|
|
121
|
+
1. **mongokit 4.0** — deletes its local `AggregatePaginationResult` declaration and `PaginationResult` union; consumers that imported them from `@classytic/mongokit` must switch to `@classytic/repo-core/pagination`. (Breaking.)
|
|
122
|
+
2. **arc 2.12** — `fastifyAdapter` calls `toCanonicalList()` once instead of inline-flattening offset and falling through keyset/aggregate as nested `data`. Closes a real wire-envelope-mismatch bug.
|
|
123
|
+
3. **arc-next 0.6** — adds `@classytic/repo-core` as peer dep, deletes its local `OffsetPaginationResponse` / `KeysetPaginationResponse` / `AggregatePaginationResponse` / `PaginatedResponse` types. Server and client now share one declaration — the `method` field asymmetry is impossible by construction.
|
|
124
|
+
|
|
125
|
+
No breaking changes inside `@classytic/repo-core` itself — purely additive.
|
|
126
|
+
|
|
7
127
|
## [0.2.0] - 2026-04-22
|
|
8
128
|
|
|
9
129
|
### Added — Update IR (portable write-side primitive)
|
package/README.md
CHANGED
|
@@ -28,14 +28,34 @@ import { and, eq, gte, in_, like, buildTenantScope, matchFilter } from '@classyt
|
|
|
28
28
|
// URL → ParsedQuery grammar. Backend frameworks (Express/Arc/Fastify) parse req.query here.
|
|
29
29
|
import { parseUrl } from '@classytic/repo-core/query-parser';
|
|
30
30
|
|
|
31
|
-
// Pagination primitives — cursor codec, keyset helpers, offset math
|
|
32
|
-
|
|
31
|
+
// Pagination primitives — cursor codec, keyset helpers, offset math, the canonical
|
|
32
|
+
// result types (`OffsetPaginationResult`, `KeysetPaginationResult`,
|
|
33
|
+
// `AggregatePaginationResult`, `PaginationResult`) and the wire helper
|
|
34
|
+
// `toCanonicalList()`. Single source of truth — primitives' duplicate dropped,
|
|
35
|
+
// mongokit/sqlitekit re-export from here.
|
|
36
|
+
import { encodeCursor, decodeCursor, validateKeysetSort, toCanonicalList } from '@classytic/repo-core/pagination';
|
|
37
|
+
import type { OffsetPaginationResult, KeysetPaginationResult, AggregatePaginationResult, PaginationResult } from '@classytic/repo-core/pagination';
|
|
38
|
+
|
|
39
|
+
// Tenant config — the canonical `TenantConfig`, `TenantStrategy`, `TenantFieldType`,
|
|
40
|
+
// `resolveTenantConfig`, `DEFAULT_TENANT_CONFIG`, `ResolvedTenantConfig`. Kits'
|
|
41
|
+
// `MultiTenantOptions extends Pick<TenantConfig, ...>`.
|
|
42
|
+
import { resolveTenantConfig, DEFAULT_TENANT_CONFIG } from '@classytic/repo-core/tenant';
|
|
43
|
+
import type { TenantConfig, ResolvedTenantConfig } from '@classytic/repo-core/tenant';
|
|
33
44
|
|
|
34
45
|
// Cache plumbing — the CacheAdapter interface every kit's cachePlugin writes against.
|
|
35
46
|
import { type CacheAdapter, stableStringify, createMemoryCacheAdapter } from '@classytic/repo-core/cache';
|
|
36
47
|
|
|
37
|
-
//
|
|
38
|
-
|
|
48
|
+
// Error contracts — `HttpError` throwable + `ErrorContract` wire shape +
|
|
49
|
+
// `ErrorDetail` + `ErrorCode` + `ERROR_CODES` + `toErrorContract()` +
|
|
50
|
+
// `statusToErrorCode()`. Single source of truth — primitives' errors module dropped,
|
|
51
|
+
// mongokit's local `HttpError` dropped, `ArcError implements HttpError`.
|
|
52
|
+
import { toErrorContract, statusToErrorCode, ERROR_CODES, createError, conservativeMongoIsDuplicateKey } from '@classytic/repo-core/errors';
|
|
53
|
+
import type { HttpError, ErrorContract, ErrorDetail, ErrorCode } from '@classytic/repo-core/errors';
|
|
54
|
+
|
|
55
|
+
// Schema generator interface — kits ship `SchemaGenerator<TModel>` + the
|
|
56
|
+
// compile-time conformance assertion; arc adapters are typed against it.
|
|
57
|
+
import type { SchemaGenerator, SchemaGeneratorContext } from '@classytic/repo-core/schema';
|
|
58
|
+
import { isSchemaGenerator } from '@classytic/repo-core/schema';
|
|
39
59
|
|
|
40
60
|
// Operation registry (for arc-level policy dispatch + doc generation).
|
|
41
61
|
import { CORE_OP_REGISTRY, describe } from '@classytic/repo-core/operations';
|
|
@@ -141,11 +161,12 @@ Default `TExtra` is `Record<string, never>` — `OffsetPaginationResult<User>` b
|
|
|
141
161
|
|
|
142
162
|
## Status
|
|
143
163
|
|
|
144
|
-
**v0.
|
|
164
|
+
**v0.3.0 — canonical contracts release.** Pagination types + wire envelope, tenant config, error contracts, and the `SchemaGenerator<TModel>` interface relocated from primitives / mongokit / arc to single sources of truth here.
|
|
145
165
|
|
|
146
166
|
Consumed by:
|
|
147
|
-
- `@classytic/mongokit` ≥ 3.
|
|
148
|
-
- `@classytic/sqlitekit`
|
|
167
|
+
- `@classytic/mongokit` ≥ 3.12 — `Repository extends RepositoryBase`; hook engine, plugin-order validator, `HOOK_PRIORITY` sourced from repo-core. Pagination + `HttpError` types now flow from repo-core (mongokit's local copies dropped). `MultiTenantOptions extends Pick<TenantConfig, ...>`. `buildCrudSchemasFromModel` ships a compile-time `SchemaGenerator<TModel>` conformance assertion. Mongokit's own `QueryParser` remains standalone.
|
|
168
|
+
- `@classytic/sqlitekit` ≥ 0.2 — `SqliteRepository extends RepositoryBase`; Filter IR compiled to Drizzle / raw SQL natively. `MultiTenantOptions extends Pick<TenantConfig, ...>`. `buildCrudSchemasFromTable` ships the same `SchemaGenerator` conformance assertion.
|
|
169
|
+
- `@classytic/arc` ≥ 2.12 — adapters typed against `SchemaGenerator<TModel>`; `ArcError implements HttpError`; pagination wire envelope (`method` discriminant) emitted via `toCanonicalList()` with `reply.sendList()`.
|
|
149
170
|
|
|
150
171
|
See [INFRA.md](./INFRA.md) for the architectural principles, subpath map, build/tooling decisions, and the roadmap for pgkit / prismakit.
|
|
151
172
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ErrorCode, ErrorContract } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/contract.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Map an HTTP status code to the canonical {@link ErrorCode}. Used as a
|
|
6
|
+
* fallback when a thrown `HttpError` lacks an explicit `code` field.
|
|
7
|
+
*
|
|
8
|
+
* The mapping is conservative: only well-known status codes get a
|
|
9
|
+
* canonical code; unmapped statuses (406, 422, 410, ...) flow through as
|
|
10
|
+
* `'internal_error'` because there's no portable HTTP-Spec mapping
|
|
11
|
+
* everyone agrees on. Domain handlers should set `code` explicitly for
|
|
12
|
+
* any non-default status.
|
|
13
|
+
*/
|
|
14
|
+
declare function statusToErrorCode(status: number): ErrorCode;
|
|
15
|
+
/**
|
|
16
|
+
* Convert a throwable {@link HttpError} (or any `Error` with a `status`
|
|
17
|
+
* field) into the canonical {@link ErrorContract} wire shape.
|
|
18
|
+
*
|
|
19
|
+
* `code` cascade:
|
|
20
|
+
* 1. `error.code` (explicit machine code on the throwable) — preferred.
|
|
21
|
+
* 2. {@link statusToErrorCode}(`error.status`) — derived from status.
|
|
22
|
+
* 3. `'internal_error'` — fallback for plain `Error` without status.
|
|
23
|
+
*
|
|
24
|
+
* `validationErrors` (mongokit-shaped throwable field) is mapped into the
|
|
25
|
+
* canonical `details` array so wire consumers see one shape regardless
|
|
26
|
+
* of which kit threw the error. Each `validationErrors[i]` becomes an
|
|
27
|
+
* `ErrorDetail` with `code: validator`, `message: error`, `path` left
|
|
28
|
+
* unset (kits that have field paths set them in their own ErrorDetail
|
|
29
|
+
* mapping).
|
|
30
|
+
*
|
|
31
|
+
* `duplicate.fields` is similarly flattened into `details` with the
|
|
32
|
+
* duplicate-key code so unique-constraint failures look uniform on the
|
|
33
|
+
* wire.
|
|
34
|
+
*/
|
|
35
|
+
declare function toErrorContract(error: unknown): ErrorContract;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { statusToErrorCode, toErrorContract };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ERROR_CODES } from "./types.mjs";
|
|
2
|
+
//#region src/errors/contract.ts
|
|
3
|
+
/**
|
|
4
|
+
* Map an HTTP status code to the canonical {@link ErrorCode}. Used as a
|
|
5
|
+
* fallback when a thrown `HttpError` lacks an explicit `code` field.
|
|
6
|
+
*
|
|
7
|
+
* The mapping is conservative: only well-known status codes get a
|
|
8
|
+
* canonical code; unmapped statuses (406, 422, 410, ...) flow through as
|
|
9
|
+
* `'internal_error'` because there's no portable HTTP-Spec mapping
|
|
10
|
+
* everyone agrees on. Domain handlers should set `code` explicitly for
|
|
11
|
+
* any non-default status.
|
|
12
|
+
*/
|
|
13
|
+
function statusToErrorCode(status) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case 400: return ERROR_CODES.VALIDATION;
|
|
16
|
+
case 401: return ERROR_CODES.UNAUTHORIZED;
|
|
17
|
+
case 403: return ERROR_CODES.FORBIDDEN;
|
|
18
|
+
case 404: return ERROR_CODES.NOT_FOUND;
|
|
19
|
+
case 409: return ERROR_CODES.CONFLICT;
|
|
20
|
+
case 412: return ERROR_CODES.PRECONDITION_FAILED;
|
|
21
|
+
case 429: return ERROR_CODES.RATE_LIMITED;
|
|
22
|
+
case 503: return ERROR_CODES.UNAVAILABLE;
|
|
23
|
+
case 504: return ERROR_CODES.TIMEOUT;
|
|
24
|
+
default: return ERROR_CODES.INTERNAL;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert a throwable {@link HttpError} (or any `Error` with a `status`
|
|
29
|
+
* field) into the canonical {@link ErrorContract} wire shape.
|
|
30
|
+
*
|
|
31
|
+
* `code` cascade:
|
|
32
|
+
* 1. `error.code` (explicit machine code on the throwable) — preferred.
|
|
33
|
+
* 2. {@link statusToErrorCode}(`error.status`) — derived from status.
|
|
34
|
+
* 3. `'internal_error'` — fallback for plain `Error` without status.
|
|
35
|
+
*
|
|
36
|
+
* `validationErrors` (mongokit-shaped throwable field) is mapped into the
|
|
37
|
+
* canonical `details` array so wire consumers see one shape regardless
|
|
38
|
+
* of which kit threw the error. Each `validationErrors[i]` becomes an
|
|
39
|
+
* `ErrorDetail` with `code: validator`, `message: error`, `path` left
|
|
40
|
+
* unset (kits that have field paths set them in their own ErrorDetail
|
|
41
|
+
* mapping).
|
|
42
|
+
*
|
|
43
|
+
* `duplicate.fields` is similarly flattened into `details` with the
|
|
44
|
+
* duplicate-key code so unique-constraint failures look uniform on the
|
|
45
|
+
* wire.
|
|
46
|
+
*/
|
|
47
|
+
function toErrorContract(error) {
|
|
48
|
+
if (!(error instanceof Error)) return {
|
|
49
|
+
code: "internal_error",
|
|
50
|
+
message: typeof error === "string" ? error : "Internal error",
|
|
51
|
+
status: 500
|
|
52
|
+
};
|
|
53
|
+
const e = error;
|
|
54
|
+
const status = typeof e.status === "number" ? e.status : 500;
|
|
55
|
+
const contract = {
|
|
56
|
+
code: e.code ?? statusToErrorCode(status),
|
|
57
|
+
message: e.message || "Internal error",
|
|
58
|
+
status
|
|
59
|
+
};
|
|
60
|
+
const details = [];
|
|
61
|
+
if (Array.isArray(e.validationErrors)) for (const v of e.validationErrors) details.push({
|
|
62
|
+
code: v.validator,
|
|
63
|
+
message: v.error
|
|
64
|
+
});
|
|
65
|
+
if (e.duplicate?.fields?.length) for (const field of e.duplicate.fields) details.push({
|
|
66
|
+
path: field,
|
|
67
|
+
code: "duplicate_key",
|
|
68
|
+
message: `Duplicate value for "${field}"`
|
|
69
|
+
});
|
|
70
|
+
if (details.length > 0) contract.details = details;
|
|
71
|
+
if (e.meta) contract.meta = e.meta;
|
|
72
|
+
return contract;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { statusToErrorCode, toErrorContract };
|
package/dist/errors/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DuplicateKeyMeta, HttpError, ValidationErrorMeta } from "./types.mjs";
|
|
1
|
+
import { DuplicateKeyMeta, ERROR_CODES, ErrorCode, ErrorContract, ErrorDetail, HttpError, ValidationErrorMeta } from "./types.mjs";
|
|
2
|
+
import { statusToErrorCode, toErrorContract } from "./contract.mjs";
|
|
2
3
|
import { createError, isHttpError } from "./create-error.mjs";
|
|
3
4
|
import { IsDuplicateKeyErrorFn, ToDuplicateKeyHttpErrorOptions, conservativeMongoIsDuplicateKey, toDuplicateKeyHttpError } from "./duplicate-key.mjs";
|
|
4
|
-
export { type DuplicateKeyMeta, type HttpError, type IsDuplicateKeyErrorFn, type ToDuplicateKeyHttpErrorOptions, type ValidationErrorMeta, conservativeMongoIsDuplicateKey, createError, isHttpError, toDuplicateKeyHttpError };
|
|
5
|
+
export { type DuplicateKeyMeta, ERROR_CODES, type ErrorCode, type ErrorContract, type ErrorDetail, type HttpError, type IsDuplicateKeyErrorFn, type ToDuplicateKeyHttpErrorOptions, type ValidationErrorMeta, conservativeMongoIsDuplicateKey, createError, isHttpError, statusToErrorCode, toDuplicateKeyHttpError, toErrorContract };
|
package/dist/errors/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ERROR_CODES } from "./types.mjs";
|
|
2
|
+
import { statusToErrorCode, toErrorContract } from "./contract.mjs";
|
|
1
3
|
import { createError, isHttpError } from "./create-error.mjs";
|
|
2
4
|
import { conservativeMongoIsDuplicateKey, toDuplicateKeyHttpError } from "./duplicate-key.mjs";
|
|
3
|
-
export { conservativeMongoIsDuplicateKey, createError, isHttpError, toDuplicateKeyHttpError };
|
|
5
|
+
export { ERROR_CODES, conservativeMongoIsDuplicateKey, createError, isHttpError, statusToErrorCode, toDuplicateKeyHttpError, toErrorContract };
|
package/dist/errors/types.d.mts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
//#region src/errors/types.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* HTTP-shaped error
|
|
3
|
+
* HTTP-shaped error contracts used across every driver kit, integrator,
|
|
4
|
+
* and HTTP-emitting service in the org.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* **`@classytic/repo-core/errors` is the canonical home for the wire +
|
|
7
|
+
* throwable error contract.** Two complementary shapes live here:
|
|
8
|
+
*
|
|
9
|
+
* - {@link HttpError} — the *throwable* shape. Plain `Error` with
|
|
10
|
+
* `status` and optional structured fields. Kits classify their
|
|
11
|
+
* driver-specific errors into this shape at the boundary; framework
|
|
12
|
+
* layers (arc) catch and serialize.
|
|
13
|
+
* - {@link ErrorContract} — the *wire* shape (RFC 7807 / Stripe-style).
|
|
14
|
+
* What gets serialized into JSON responses, queue dead-letter records,
|
|
15
|
+
* audit logs, and inter-service error envelopes.
|
|
16
|
+
*
|
|
17
|
+
* Throwable classes (arc's `ArcError` family, future `BillingError` etc.)
|
|
18
|
+
* `implements HttpError` and serialize to `ErrorContract` for the wire.
|
|
19
|
+
* One contract, one canonical home, every package follows the same shape.
|
|
20
|
+
*
|
|
21
|
+
* **Custom-domain escape hatch.** {@link ErrorCode} is a documented union
|
|
22
|
+
* of canonical codes; `code: string` accepts ANY string so domain packages
|
|
23
|
+
* can extend (`'order.validation.missing_line'`, `'payment.gateway.timeout'`).
|
|
24
|
+
* The canonical codes cover cross-cutting concerns; domain extensions
|
|
25
|
+
* hierarchically narrow.
|
|
10
26
|
*/
|
|
11
27
|
/** Structured metadata for duplicate-key (unique-constraint) errors. */
|
|
12
28
|
interface DuplicateKeyMeta {
|
|
@@ -24,14 +40,103 @@ interface ValidationErrorMeta {
|
|
|
24
40
|
validator: string;
|
|
25
41
|
error: string;
|
|
26
42
|
}
|
|
27
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* HTTP-shaped error — the throwable envelope every repository error and
|
|
45
|
+
* arc handler resolves to.
|
|
46
|
+
*
|
|
47
|
+
* Note: `code` and `meta` are optional but every long-lived production
|
|
48
|
+
* handler should populate them. `code` lets clients switch on
|
|
49
|
+
* machine-readable identifiers without grepping `message`; `meta` carries
|
|
50
|
+
* structured diagnostics safe for logs.
|
|
51
|
+
*/
|
|
28
52
|
interface HttpError extends Error {
|
|
29
53
|
/** HTTP status code (400, 404, 409, 500, ...). */
|
|
30
54
|
status: number;
|
|
55
|
+
/**
|
|
56
|
+
* Stable machine-readable error code. Use one of {@link ErrorCode} for
|
|
57
|
+
* cross-cutting cases or extend hierarchically with a domain prefix
|
|
58
|
+
* (`'order.validation.missing_line'`, `'payment.gateway.timeout'`).
|
|
59
|
+
* Hosts switch on this in catch blocks instead of regex-matching
|
|
60
|
+
* `message`.
|
|
61
|
+
*/
|
|
62
|
+
code?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Free-form structured metadata for diagnostics. Pairs with `code` so
|
|
65
|
+
* hosts can render a clearer message in their own UI without parsing
|
|
66
|
+
* `message`. Safe for logs (don't include PII).
|
|
67
|
+
*/
|
|
68
|
+
meta?: Record<string, unknown>;
|
|
31
69
|
/** Structured validation failures when `status` is 400. */
|
|
32
70
|
validationErrors?: ValidationErrorMeta[];
|
|
33
71
|
/** Structured duplicate-key metadata when `status` is 409. */
|
|
34
72
|
duplicate?: DuplicateKeyMeta;
|
|
35
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Standard error contract — a framework-agnostic JSON shape that maps
|
|
76
|
+
* cleanly to HTTP responses, worker failure logs, and inter-service
|
|
77
|
+
* errors. Loosely matches RFC 7807 (`application/problem+json`); shape
|
|
78
|
+
* matches Stripe / Shopify / Slack API conventions.
|
|
79
|
+
*
|
|
80
|
+
* Packages throw `HttpError` instances. Hosts (HTTP adapters, workers)
|
|
81
|
+
* serialize those errors into this shape on the wire via
|
|
82
|
+
* {@link toErrorContract}. Wire shape is FLAT (top-level `code` /
|
|
83
|
+
* `message` / `status`), not nested under `{ error: { ... } }` — matches
|
|
84
|
+
* the existing org-wide envelope convention. Hosts that need a
|
|
85
|
+
* Stripe-style nested envelope wrap once at the edge.
|
|
86
|
+
*/
|
|
87
|
+
interface ErrorContract {
|
|
88
|
+
/** Machine-readable, hierarchical code — e.g. `'order.validation.missing_line'`. */
|
|
89
|
+
code: string;
|
|
90
|
+
/** Human-readable, safe-for-client message. */
|
|
91
|
+
message: string;
|
|
92
|
+
/** Suggested HTTP status code — hosts may override. */
|
|
93
|
+
status?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Field-scoped structured details. Populated for validation failures
|
|
96
|
+
* (one entry per offending field) or domain errors that map to multiple
|
|
97
|
+
* sub-codes. Distinct from `HttpError.validationErrors` (which is a
|
|
98
|
+
* mongokit-shaped throwable field) — the wire form is canonical.
|
|
99
|
+
*/
|
|
100
|
+
details?: readonly ErrorDetail[];
|
|
101
|
+
/** Correlation / trace identifier for support lookups. */
|
|
102
|
+
correlationId?: string;
|
|
103
|
+
/** Non-PII metadata (safe to log, safe to return to clients). */
|
|
104
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
105
|
+
}
|
|
106
|
+
/** A single field-scoped error detail. */
|
|
107
|
+
interface ErrorDetail {
|
|
108
|
+
/** Dot-path pointer to the offending field, e.g. `'lines.0.quantity'`. */
|
|
109
|
+
path?: string;
|
|
110
|
+
code: string;
|
|
111
|
+
message: string;
|
|
112
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Cross-cutting error codes used across the org. Every canonical code is
|
|
116
|
+
* lowercase + snake_case to match RFC 7807, Stripe, and Shopify
|
|
117
|
+
* conventions. Domain packages add their own hierarchical codes
|
|
118
|
+
* (`'order.validation.*'`, `'payment.gateway.*'`); these cover the
|
|
119
|
+
* universal cases every HTTP-emitting layer needs.
|
|
120
|
+
*
|
|
121
|
+
* **Arc compatibility note.** Arc's `ArcError` hierarchy historically
|
|
122
|
+
* uses UPPER_SNAKE codes (`'NOT_FOUND'`, `'VALIDATION_ERROR'`) for
|
|
123
|
+
* back-compat with hosts that switch on those values. New code should
|
|
124
|
+
* prefer the canonical lowercase codes; arc keeps emitting its UPPER_SNAKE
|
|
125
|
+
* codes on the wire so existing client switches keep working.
|
|
126
|
+
*/
|
|
127
|
+
declare const ERROR_CODES: {
|
|
128
|
+
readonly VALIDATION: "validation_error";
|
|
129
|
+
readonly NOT_FOUND: "not_found";
|
|
130
|
+
readonly CONFLICT: "conflict";
|
|
131
|
+
readonly UNAUTHORIZED: "unauthorized";
|
|
132
|
+
readonly FORBIDDEN: "forbidden";
|
|
133
|
+
readonly RATE_LIMITED: "rate_limited";
|
|
134
|
+
readonly IDEMPOTENCY_CONFLICT: "idempotency_conflict";
|
|
135
|
+
readonly PRECONDITION_FAILED: "precondition_failed";
|
|
136
|
+
readonly INTERNAL: "internal_error";
|
|
137
|
+
readonly UNAVAILABLE: "service_unavailable";
|
|
138
|
+
readonly TIMEOUT: "timeout";
|
|
139
|
+
};
|
|
140
|
+
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
36
141
|
//#endregion
|
|
37
|
-
export { DuplicateKeyMeta, HttpError, ValidationErrorMeta };
|
|
142
|
+
export { DuplicateKeyMeta, ERROR_CODES, ErrorCode, ErrorContract, ErrorDetail, HttpError, ValidationErrorMeta };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/errors/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cross-cutting error codes used across the org. Every canonical code is
|
|
4
|
+
* lowercase + snake_case to match RFC 7807, Stripe, and Shopify
|
|
5
|
+
* conventions. Domain packages add their own hierarchical codes
|
|
6
|
+
* (`'order.validation.*'`, `'payment.gateway.*'`); these cover the
|
|
7
|
+
* universal cases every HTTP-emitting layer needs.
|
|
8
|
+
*
|
|
9
|
+
* **Arc compatibility note.** Arc's `ArcError` hierarchy historically
|
|
10
|
+
* uses UPPER_SNAKE codes (`'NOT_FOUND'`, `'VALIDATION_ERROR'`) for
|
|
11
|
+
* back-compat with hosts that switch on those values. New code should
|
|
12
|
+
* prefer the canonical lowercase codes; arc keeps emitting its UPPER_SNAKE
|
|
13
|
+
* codes on the wire so existing client switches keep working.
|
|
14
|
+
*/
|
|
15
|
+
const ERROR_CODES = {
|
|
16
|
+
VALIDATION: "validation_error",
|
|
17
|
+
NOT_FOUND: "not_found",
|
|
18
|
+
CONFLICT: "conflict",
|
|
19
|
+
UNAUTHORIZED: "unauthorized",
|
|
20
|
+
FORBIDDEN: "forbidden",
|
|
21
|
+
RATE_LIMITED: "rate_limited",
|
|
22
|
+
IDEMPOTENCY_CONFLICT: "idempotency_conflict",
|
|
23
|
+
PRECONDITION_FAILED: "precondition_failed",
|
|
24
|
+
INTERNAL: "internal_error",
|
|
25
|
+
UNAVAILABLE: "service_unavailable",
|
|
26
|
+
TIMEOUT: "timeout"
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ERROR_CODES };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AnyPaginationResult, BareListResponse, PaginatedResponse } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/pagination/canonical.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Type guard: is this value a paginated result envelope (vs a bare array
|
|
6
|
+
* or some other shape)?
|
|
7
|
+
*
|
|
8
|
+
* Checks for the `method` discriminant rather than `Array.isArray` so a
|
|
9
|
+
* paginated result whose `docs` field happens to contain zero items still
|
|
10
|
+
* routes through the paginated branch.
|
|
11
|
+
*
|
|
12
|
+
* Accepts `unknown` (rather than `T[] | AnyPaginationResult<T>`) so wire-
|
|
13
|
+
* boundary callers can guard arbitrary inputs without pre-narrowing — the
|
|
14
|
+
* arc / arc-next response pipeline routinely sees `{ docs: unknown[] }`
|
|
15
|
+
* shapes that are neither a bare array nor a paginated result, and forcing
|
|
16
|
+
* those callers to cast first defeats the guard's purpose.
|
|
17
|
+
*/
|
|
18
|
+
declare function isPaginatedResult<TDoc>(input: unknown): input is AnyPaginationResult<TDoc>;
|
|
19
|
+
/**
|
|
20
|
+
* Normalise a list-shaped value into the canonical wire envelope.
|
|
21
|
+
*
|
|
22
|
+
* Overloads keep the return type tight:
|
|
23
|
+
* - bare array → {@link BareListResponse}
|
|
24
|
+
* - paginated → {@link PaginatedResponse} (preserves method discriminant)
|
|
25
|
+
*
|
|
26
|
+
* The mutable-array overload widens to `TDoc[]` because that's the most
|
|
27
|
+
* common server input (kit results return `TDoc[]` for `docs`); the
|
|
28
|
+
* readonly overload covers callers passing `readonly TDoc[]`.
|
|
29
|
+
*/
|
|
30
|
+
declare function toCanonicalList<TDoc>(input: TDoc[]): BareListResponse<TDoc>;
|
|
31
|
+
declare function toCanonicalList<TDoc>(input: readonly TDoc[]): BareListResponse<TDoc>;
|
|
32
|
+
declare function toCanonicalList<TDoc, TExtra extends Record<string, unknown>>(input: AnyPaginationResult<TDoc, TExtra>): PaginatedResponse<TDoc, TExtra>;
|
|
33
|
+
declare function toCanonicalList<TDoc>(input: readonly TDoc[] | AnyPaginationResult<TDoc>): PaginatedResponse<TDoc>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { isPaginatedResult, toCanonicalList };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/pagination/canonical.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type guard: is this value a paginated result envelope (vs a bare array
|
|
4
|
+
* or some other shape)?
|
|
5
|
+
*
|
|
6
|
+
* Checks for the `method` discriminant rather than `Array.isArray` so a
|
|
7
|
+
* paginated result whose `docs` field happens to contain zero items still
|
|
8
|
+
* routes through the paginated branch.
|
|
9
|
+
*
|
|
10
|
+
* Accepts `unknown` (rather than `T[] | AnyPaginationResult<T>`) so wire-
|
|
11
|
+
* boundary callers can guard arbitrary inputs without pre-narrowing — the
|
|
12
|
+
* arc / arc-next response pipeline routinely sees `{ docs: unknown[] }`
|
|
13
|
+
* shapes that are neither a bare array nor a paginated result, and forcing
|
|
14
|
+
* those callers to cast first defeats the guard's purpose.
|
|
15
|
+
*/
|
|
16
|
+
function isPaginatedResult(input) {
|
|
17
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return false;
|
|
18
|
+
const method = input.method;
|
|
19
|
+
return method === "offset" || method === "keyset" || method === "aggregate";
|
|
20
|
+
}
|
|
21
|
+
function toCanonicalList(input) {
|
|
22
|
+
if (isPaginatedResult(input)) return {
|
|
23
|
+
...input,
|
|
24
|
+
success: true
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
docs: [...input]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { isPaginatedResult, toCanonicalList };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CursorPayload, DecodedCursor, KeysetPaginationResult, KeysetPaginationResultCore, OffsetPaginationResult, OffsetPaginationResultCore, PaginationConfig, SortDirection, SortSpec, ValueType } from "./types.mjs";
|
|
1
|
+
import { AggregatePaginationResponse, AggregatePaginationResult, AggregatePaginationResultCore, AnyPaginationResult, BareListResponse, CursorPayload, DecodedCursor, KeysetPaginationResponse, KeysetPaginationResult, KeysetPaginationResultCore, OffsetPaginationResponse, OffsetPaginationResult, OffsetPaginationResultCore, PaginatedResponse, PaginationConfig, SortDirection, SortSpec, ValueType } from "./types.mjs";
|
|
2
|
+
import { isPaginatedResult, toCanonicalList } from "./canonical.mjs";
|
|
2
3
|
import { decodeCursor, encodeCursor, validateCursorSort, validateCursorVersion } from "./cursor.mjs";
|
|
3
4
|
import { getPrimaryField, invertSort, normalizeSort, validateKeysetSort } from "./keyset.mjs";
|
|
4
5
|
import { calculateSkip, calculateTotalPages, shouldWarnDeepPagination, validateLimit, validatePage } from "./offset.mjs";
|
|
5
|
-
export { type CursorPayload, type DecodedCursor, type KeysetPaginationResult, type KeysetPaginationResultCore, type OffsetPaginationResult, type OffsetPaginationResultCore, type PaginationConfig, type SortDirection, type SortSpec, type ValueType, calculateSkip, calculateTotalPages, decodeCursor, encodeCursor, getPrimaryField, invertSort, normalizeSort, shouldWarnDeepPagination, validateCursorSort, validateCursorVersion, validateKeysetSort, validateLimit, validatePage };
|
|
6
|
+
export { type AggregatePaginationResponse, type AggregatePaginationResult, type AggregatePaginationResultCore, type AnyPaginationResult, type BareListResponse, type CursorPayload, type DecodedCursor, type KeysetPaginationResponse, type KeysetPaginationResult, type KeysetPaginationResultCore, type OffsetPaginationResponse, type OffsetPaginationResult, type OffsetPaginationResultCore, type PaginatedResponse, type PaginationConfig, type SortDirection, type SortSpec, type ValueType, calculateSkip, calculateTotalPages, decodeCursor, encodeCursor, getPrimaryField, invertSort, isPaginatedResult, normalizeSort, shouldWarnDeepPagination, toCanonicalList, validateCursorSort, validateCursorVersion, validateKeysetSort, validateLimit, validatePage };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { isPaginatedResult, toCanonicalList } from "./canonical.mjs";
|
|
1
2
|
import { decodeCursor, encodeCursor, validateCursorSort, validateCursorVersion } from "./cursor.mjs";
|
|
2
3
|
import { getPrimaryField, invertSort, normalizeSort, validateKeysetSort } from "./keyset.mjs";
|
|
3
4
|
import { calculateSkip, calculateTotalPages, shouldWarnDeepPagination, validateLimit, validatePage } from "./offset.mjs";
|
|
4
|
-
export { calculateSkip, calculateTotalPages, decodeCursor, encodeCursor, getPrimaryField, invertSort, normalizeSort, shouldWarnDeepPagination, validateCursorSort, validateCursorVersion, validateKeysetSort, validateLimit, validatePage };
|
|
5
|
+
export { calculateSkip, calculateTotalPages, decodeCursor, encodeCursor, getPrimaryField, invertSort, isPaginatedResult, normalizeSort, shouldWarnDeepPagination, toCanonicalList, validateCursorSort, validateCursorVersion, validateKeysetSort, validateLimit, validatePage };
|