@oxyhq/contracts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +26 -0
- package/dist/cjs/userResponse.js +157 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/userResponse.js +152 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/userResponse.d.ts +993 -0
- package/package.json +84 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @oxyhq/contracts — single source of truth for API request/response contracts.
|
|
4
|
+
*
|
|
5
|
+
* Zod schemas plus their inferred types, shared by the backend (`@oxyhq/api`)
|
|
6
|
+
* and the client SDKs (`@oxyhq/core`, `@oxyhq/auth`, `@oxyhq/services`). The
|
|
7
|
+
* producer validates its output and every consumer validates its input against
|
|
8
|
+
* exactly the same definitions, so the wire shape cannot drift.
|
|
9
|
+
*
|
|
10
|
+
* Platform-agnostic — zod is the only runtime dependency. No react/react-native/
|
|
11
|
+
* expo, no `require()` in the ESM build.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.safeParseContract = exports.resolveUserId = exports.deviceSessionsResponseSchema = exports.deviceSessionAccountSchema = exports.currentUserResponseSchema = exports.refreshAllResponseSchema = exports.refreshAllAccountSchema = exports.userResponseSchema = exports.userNameSchema = void 0;
|
|
15
|
+
var userResponse_1 = require("./userResponse");
|
|
16
|
+
// Schemas
|
|
17
|
+
Object.defineProperty(exports, "userNameSchema", { enumerable: true, get: function () { return userResponse_1.userNameSchema; } });
|
|
18
|
+
Object.defineProperty(exports, "userResponseSchema", { enumerable: true, get: function () { return userResponse_1.userResponseSchema; } });
|
|
19
|
+
Object.defineProperty(exports, "refreshAllAccountSchema", { enumerable: true, get: function () { return userResponse_1.refreshAllAccountSchema; } });
|
|
20
|
+
Object.defineProperty(exports, "refreshAllResponseSchema", { enumerable: true, get: function () { return userResponse_1.refreshAllResponseSchema; } });
|
|
21
|
+
Object.defineProperty(exports, "currentUserResponseSchema", { enumerable: true, get: function () { return userResponse_1.currentUserResponseSchema; } });
|
|
22
|
+
Object.defineProperty(exports, "deviceSessionAccountSchema", { enumerable: true, get: function () { return userResponse_1.deviceSessionAccountSchema; } });
|
|
23
|
+
Object.defineProperty(exports, "deviceSessionsResponseSchema", { enumerable: true, get: function () { return userResponse_1.deviceSessionsResponseSchema; } });
|
|
24
|
+
// Helpers
|
|
25
|
+
Object.defineProperty(exports, "resolveUserId", { enumerable: true, get: function () { return userResponse_1.resolveUserId; } });
|
|
26
|
+
Object.defineProperty(exports, "safeParseContract", { enumerable: true, get: function () { return userResponse_1.safeParseContract; } });
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical API user-response contracts.
|
|
4
|
+
*
|
|
5
|
+
* SINGLE SOURCE OF TRUTH for the wire shape of every user object the API emits
|
|
6
|
+
* and every consumer (the auth app, services, accounts) parses. The API
|
|
7
|
+
* validates its OUTPUT against these schemas; web/RN consumers validate their
|
|
8
|
+
* INPUT against the same schemas. Because there is exactly one definition, the
|
|
9
|
+
* producer and the consumers cannot drift — the class of bugs that motivated
|
|
10
|
+
* this module (the auth app's local Zod schema requiring `name` to be a plain
|
|
11
|
+
* string, dropping every account that had a structured name) is impossible.
|
|
12
|
+
*
|
|
13
|
+
* This package (`@oxyhq/contracts`) is the dedicated, zero-dependency home for
|
|
14
|
+
* these contracts so the backend (`@oxyhq/api`) and the client SDKs
|
|
15
|
+
* (`@oxyhq/core`, `@oxyhq/auth`, `@oxyhq/services`) can all depend on it without
|
|
16
|
+
* the backend having to depend on a client SDK to obtain its schemas.
|
|
17
|
+
*
|
|
18
|
+
* Faithful to the producers:
|
|
19
|
+
* - `packages/api/src/utils/userTransform.ts` `formatUserResponse` — the
|
|
20
|
+
* canonical serialization used by `/auth/refresh-all`, device sessions, etc.
|
|
21
|
+
* Emits `id` (NOT `_id`), forwards `username` verbatim (may be absent), and
|
|
22
|
+
* emits `name` as the structured `{ first, last, full }` subdocument.
|
|
23
|
+
* - `packages/api/src/models/User.ts` — `NameSchema` (`first`/`last` default
|
|
24
|
+
* `''`; `full` is a Mongoose VIRTUAL) and the `displayName` virtual. Because
|
|
25
|
+
* virtuals are only present when a query uses `.lean({ virtuals: true })` (or
|
|
26
|
+
* a hydrated doc), `name.full` and `displayName` MUST be treated as OPTIONAL.
|
|
27
|
+
* - The `/auth/refresh-all` handler in `packages/api/src/routes/auth.ts`, whose
|
|
28
|
+
* per-slot `authuser` is `number | null` (null = legacy un-suffixed `oxy_rt`
|
|
29
|
+
* cookie slot).
|
|
30
|
+
*
|
|
31
|
+
* Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
|
|
32
|
+
* `require()`).
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.deviceSessionsResponseSchema = exports.deviceSessionAccountSchema = exports.currentUserResponseSchema = exports.refreshAllResponseSchema = exports.refreshAllAccountSchema = exports.userResponseSchema = exports.userNameSchema = void 0;
|
|
36
|
+
exports.resolveUserId = resolveUserId;
|
|
37
|
+
exports.safeParseContract = safeParseContract;
|
|
38
|
+
const zod_1 = require("zod");
|
|
39
|
+
/**
|
|
40
|
+
* Structured human name subdocument. Mirrors `User.name` (`NameSchema`).
|
|
41
|
+
*
|
|
42
|
+
* - `first` / `last` default to `''` in Mongo, so they are optional on the wire.
|
|
43
|
+
* - `full` is a Mongoose virtual — ABSENT unless the query materialised virtuals.
|
|
44
|
+
*
|
|
45
|
+
* `.passthrough()` is intentional: it tolerates additive name fields without a
|
|
46
|
+
* coordinated contract bump, while the three known keys stay strongly typed.
|
|
47
|
+
*/
|
|
48
|
+
exports.userNameSchema = zod_1.z
|
|
49
|
+
.object({
|
|
50
|
+
first: zod_1.z.string().optional(),
|
|
51
|
+
last: zod_1.z.string().optional(),
|
|
52
|
+
full: zod_1.z.string().optional(),
|
|
53
|
+
})
|
|
54
|
+
.passthrough();
|
|
55
|
+
/**
|
|
56
|
+
* The canonical user object emitted by `formatUserResponse`.
|
|
57
|
+
*
|
|
58
|
+
* Only `id` is guaranteed present (it is the early-return guard in
|
|
59
|
+
* `formatUserResponse`). Every other field is forwarded verbatim from the user
|
|
60
|
+
* document and may be absent depending on the query's `.select(...)`/`.lean()`
|
|
61
|
+
* projection — so all are optional/nullable to match reality. Both `id` and
|
|
62
|
+
* `_id` are accepted because RAW-document responses (e.g. `GET /users/me`,
|
|
63
|
+
* which does NOT pass through `formatUserResponse`) carry `_id` instead of `id`;
|
|
64
|
+
* resolve the identifier with {@link resolveUserId}.
|
|
65
|
+
*
|
|
66
|
+
* `.passthrough()` keeps the large tail of profile fields
|
|
67
|
+
* (`privacySettings`, `locations`, `links`, `linksMetadata`, `bio`,
|
|
68
|
+
* `description`, `language`, `verified`, timestamps, …) available to callers
|
|
69
|
+
* that need them without enumerating every nested shape here — the load-bearing
|
|
70
|
+
* identity/display fields are the ones we pin precisely.
|
|
71
|
+
*/
|
|
72
|
+
exports.userResponseSchema = zod_1.z
|
|
73
|
+
.object({
|
|
74
|
+
/** MongoDB ObjectId as a string. Present on `formatUserResponse` output. */
|
|
75
|
+
id: zod_1.z.string().optional(),
|
|
76
|
+
/** Raw-document id (e.g. `GET /users/me`). Present when `id` is not. */
|
|
77
|
+
_id: zod_1.z.string().optional(),
|
|
78
|
+
publicKey: zod_1.z.string().optional(),
|
|
79
|
+
username: zod_1.z.string().optional(),
|
|
80
|
+
email: zod_1.z.string().optional(),
|
|
81
|
+
/** Avatar file id (string) or null. */
|
|
82
|
+
avatar: zod_1.z.string().nullable().optional(),
|
|
83
|
+
/** Named Bloom color preset (e.g. `"blue"`) or null. */
|
|
84
|
+
color: zod_1.z.string().nullable().optional(),
|
|
85
|
+
name: exports.userNameSchema.optional(),
|
|
86
|
+
/** Server `displayName` virtual (`username || truncatedKey`). Optional. */
|
|
87
|
+
displayName: zod_1.z.string().optional(),
|
|
88
|
+
verified: zod_1.z.boolean().optional(),
|
|
89
|
+
language: zod_1.z.string().optional(),
|
|
90
|
+
})
|
|
91
|
+
.passthrough();
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the canonical user id from a {@link UserResponse}, accepting either
|
|
94
|
+
* the `formatUserResponse` `id` field or the raw-document `_id` field.
|
|
95
|
+
*/
|
|
96
|
+
function resolveUserId(user) {
|
|
97
|
+
return user.id ?? user._id;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One rotated account entry from `POST /auth/refresh-all`.
|
|
101
|
+
*
|
|
102
|
+
* `authuser` is the device-local slot index (`0..N-1`). The server emits
|
|
103
|
+
* `authuser: null` for the legacy un-suffixed `oxy_rt` cookie slot — accept null
|
|
104
|
+
* so a browser holding only a legacy cookie is NOT dropped from the account
|
|
105
|
+
* chooser. `user` is the canonical {@link userResponseSchema} shape (the handler
|
|
106
|
+
* projects a whitelist and runs it through `formatUserResponse`).
|
|
107
|
+
*/
|
|
108
|
+
exports.refreshAllAccountSchema = zod_1.z.object({
|
|
109
|
+
authuser: zod_1.z.number().int().nonnegative().nullable(),
|
|
110
|
+
accessToken: zod_1.z.string(),
|
|
111
|
+
expiresAt: zod_1.z.string(),
|
|
112
|
+
sessionId: zod_1.z.string(),
|
|
113
|
+
user: exports.userResponseSchema,
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* Wire shape of `POST /auth/refresh-all`: every valid device-local account,
|
|
117
|
+
* sorted by `authuser` ascending. An empty `accounts` array means "no signed-in
|
|
118
|
+
* accounts on this device" — the IdP must show the sign-in form. A 404 means the
|
|
119
|
+
* endpoint is not deployed and the caller falls back to single-account
|
|
120
|
+
* `/auth/refresh`.
|
|
121
|
+
*/
|
|
122
|
+
exports.refreshAllResponseSchema = zod_1.z.object({
|
|
123
|
+
accounts: zod_1.z.array(exports.refreshAllAccountSchema),
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Wire shape of `GET /users/me` — the API success envelope (`{ data: <user> }`)
|
|
127
|
+
* wrapping the RAW Mongo user document. It does NOT pass through
|
|
128
|
+
* `formatUserResponse`, so the id field is `_id` (resolve via
|
|
129
|
+
* {@link resolveUserId}) and virtuals (`name.full`, `displayName`) may be
|
|
130
|
+
* present when the query materialised them.
|
|
131
|
+
*/
|
|
132
|
+
exports.currentUserResponseSchema = zod_1.z.object({
|
|
133
|
+
data: exports.userResponseSchema,
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* One entry of `GET /session/device/sessions/:sessionId` — the deduplicated
|
|
137
|
+
* accounts signed in on this physical device (one per user, most recent
|
|
138
|
+
* session). Backs the multi-account chooser. The embedded user mirrors
|
|
139
|
+
* `formatUserResponse`; it is nullable on slots that lost their user document.
|
|
140
|
+
*/
|
|
141
|
+
exports.deviceSessionAccountSchema = zod_1.z.object({
|
|
142
|
+
sessionId: zod_1.z.string(),
|
|
143
|
+
isCurrent: zod_1.z.boolean().optional(),
|
|
144
|
+
user: exports.userResponseSchema.nullable().optional(),
|
|
145
|
+
});
|
|
146
|
+
/** Wire shape of `GET /session/device/sessions/:sessionId` (an array). */
|
|
147
|
+
exports.deviceSessionsResponseSchema = zod_1.z.array(exports.deviceSessionAccountSchema);
|
|
148
|
+
/**
|
|
149
|
+
* Safely parse a value against a contract schema. Returns the parsed (typed)
|
|
150
|
+
* value, or `null` when validation fails — the same ergonomics the auth app's
|
|
151
|
+
* local `safeParse` provided, now sourced from the contracts package so the
|
|
152
|
+
* parse helper and the schemas live together.
|
|
153
|
+
*/
|
|
154
|
+
function safeParseContract(schema, data) {
|
|
155
|
+
const result = schema.safeParse(data);
|
|
156
|
+
return result.success ? result.data : null;
|
|
157
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/contracts — single source of truth for API request/response contracts.
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas plus their inferred types, shared by the backend (`@oxyhq/api`)
|
|
5
|
+
* and the client SDKs (`@oxyhq/core`, `@oxyhq/auth`, `@oxyhq/services`). The
|
|
6
|
+
* producer validates its output and every consumer validates its input against
|
|
7
|
+
* exactly the same definitions, so the wire shape cannot drift.
|
|
8
|
+
*
|
|
9
|
+
* Platform-agnostic — zod is the only runtime dependency. No react/react-native/
|
|
10
|
+
* expo, no `require()` in the ESM build.
|
|
11
|
+
*/
|
|
12
|
+
export {
|
|
13
|
+
// Schemas
|
|
14
|
+
userNameSchema, userResponseSchema, refreshAllAccountSchema, refreshAllResponseSchema, currentUserResponseSchema, deviceSessionAccountSchema, deviceSessionsResponseSchema,
|
|
15
|
+
// Helpers
|
|
16
|
+
resolveUserId, safeParseContract, } from './userResponse.js';
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical API user-response contracts.
|
|
3
|
+
*
|
|
4
|
+
* SINGLE SOURCE OF TRUTH for the wire shape of every user object the API emits
|
|
5
|
+
* and every consumer (the auth app, services, accounts) parses. The API
|
|
6
|
+
* validates its OUTPUT against these schemas; web/RN consumers validate their
|
|
7
|
+
* INPUT against the same schemas. Because there is exactly one definition, the
|
|
8
|
+
* producer and the consumers cannot drift — the class of bugs that motivated
|
|
9
|
+
* this module (the auth app's local Zod schema requiring `name` to be a plain
|
|
10
|
+
* string, dropping every account that had a structured name) is impossible.
|
|
11
|
+
*
|
|
12
|
+
* This package (`@oxyhq/contracts`) is the dedicated, zero-dependency home for
|
|
13
|
+
* these contracts so the backend (`@oxyhq/api`) and the client SDKs
|
|
14
|
+
* (`@oxyhq/core`, `@oxyhq/auth`, `@oxyhq/services`) can all depend on it without
|
|
15
|
+
* the backend having to depend on a client SDK to obtain its schemas.
|
|
16
|
+
*
|
|
17
|
+
* Faithful to the producers:
|
|
18
|
+
* - `packages/api/src/utils/userTransform.ts` `formatUserResponse` — the
|
|
19
|
+
* canonical serialization used by `/auth/refresh-all`, device sessions, etc.
|
|
20
|
+
* Emits `id` (NOT `_id`), forwards `username` verbatim (may be absent), and
|
|
21
|
+
* emits `name` as the structured `{ first, last, full }` subdocument.
|
|
22
|
+
* - `packages/api/src/models/User.ts` — `NameSchema` (`first`/`last` default
|
|
23
|
+
* `''`; `full` is a Mongoose VIRTUAL) and the `displayName` virtual. Because
|
|
24
|
+
* virtuals are only present when a query uses `.lean({ virtuals: true })` (or
|
|
25
|
+
* a hydrated doc), `name.full` and `displayName` MUST be treated as OPTIONAL.
|
|
26
|
+
* - The `/auth/refresh-all` handler in `packages/api/src/routes/auth.ts`, whose
|
|
27
|
+
* per-slot `authuser` is `number | null` (null = legacy un-suffixed `oxy_rt`
|
|
28
|
+
* cookie slot).
|
|
29
|
+
*
|
|
30
|
+
* Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
|
|
31
|
+
* `require()`).
|
|
32
|
+
*/
|
|
33
|
+
import { z } from 'zod';
|
|
34
|
+
/**
|
|
35
|
+
* Structured human name subdocument. Mirrors `User.name` (`NameSchema`).
|
|
36
|
+
*
|
|
37
|
+
* - `first` / `last` default to `''` in Mongo, so they are optional on the wire.
|
|
38
|
+
* - `full` is a Mongoose virtual — ABSENT unless the query materialised virtuals.
|
|
39
|
+
*
|
|
40
|
+
* `.passthrough()` is intentional: it tolerates additive name fields without a
|
|
41
|
+
* coordinated contract bump, while the three known keys stay strongly typed.
|
|
42
|
+
*/
|
|
43
|
+
export const userNameSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
first: z.string().optional(),
|
|
46
|
+
last: z.string().optional(),
|
|
47
|
+
full: z.string().optional(),
|
|
48
|
+
})
|
|
49
|
+
.passthrough();
|
|
50
|
+
/**
|
|
51
|
+
* The canonical user object emitted by `formatUserResponse`.
|
|
52
|
+
*
|
|
53
|
+
* Only `id` is guaranteed present (it is the early-return guard in
|
|
54
|
+
* `formatUserResponse`). Every other field is forwarded verbatim from the user
|
|
55
|
+
* document and may be absent depending on the query's `.select(...)`/`.lean()`
|
|
56
|
+
* projection — so all are optional/nullable to match reality. Both `id` and
|
|
57
|
+
* `_id` are accepted because RAW-document responses (e.g. `GET /users/me`,
|
|
58
|
+
* which does NOT pass through `formatUserResponse`) carry `_id` instead of `id`;
|
|
59
|
+
* resolve the identifier with {@link resolveUserId}.
|
|
60
|
+
*
|
|
61
|
+
* `.passthrough()` keeps the large tail of profile fields
|
|
62
|
+
* (`privacySettings`, `locations`, `links`, `linksMetadata`, `bio`,
|
|
63
|
+
* `description`, `language`, `verified`, timestamps, …) available to callers
|
|
64
|
+
* that need them without enumerating every nested shape here — the load-bearing
|
|
65
|
+
* identity/display fields are the ones we pin precisely.
|
|
66
|
+
*/
|
|
67
|
+
export const userResponseSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
/** MongoDB ObjectId as a string. Present on `formatUserResponse` output. */
|
|
70
|
+
id: z.string().optional(),
|
|
71
|
+
/** Raw-document id (e.g. `GET /users/me`). Present when `id` is not. */
|
|
72
|
+
_id: z.string().optional(),
|
|
73
|
+
publicKey: z.string().optional(),
|
|
74
|
+
username: z.string().optional(),
|
|
75
|
+
email: z.string().optional(),
|
|
76
|
+
/** Avatar file id (string) or null. */
|
|
77
|
+
avatar: z.string().nullable().optional(),
|
|
78
|
+
/** Named Bloom color preset (e.g. `"blue"`) or null. */
|
|
79
|
+
color: z.string().nullable().optional(),
|
|
80
|
+
name: userNameSchema.optional(),
|
|
81
|
+
/** Server `displayName` virtual (`username || truncatedKey`). Optional. */
|
|
82
|
+
displayName: z.string().optional(),
|
|
83
|
+
verified: z.boolean().optional(),
|
|
84
|
+
language: z.string().optional(),
|
|
85
|
+
})
|
|
86
|
+
.passthrough();
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the canonical user id from a {@link UserResponse}, accepting either
|
|
89
|
+
* the `formatUserResponse` `id` field or the raw-document `_id` field.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveUserId(user) {
|
|
92
|
+
return user.id ?? user._id;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* One rotated account entry from `POST /auth/refresh-all`.
|
|
96
|
+
*
|
|
97
|
+
* `authuser` is the device-local slot index (`0..N-1`). The server emits
|
|
98
|
+
* `authuser: null` for the legacy un-suffixed `oxy_rt` cookie slot — accept null
|
|
99
|
+
* so a browser holding only a legacy cookie is NOT dropped from the account
|
|
100
|
+
* chooser. `user` is the canonical {@link userResponseSchema} shape (the handler
|
|
101
|
+
* projects a whitelist and runs it through `formatUserResponse`).
|
|
102
|
+
*/
|
|
103
|
+
export const refreshAllAccountSchema = z.object({
|
|
104
|
+
authuser: z.number().int().nonnegative().nullable(),
|
|
105
|
+
accessToken: z.string(),
|
|
106
|
+
expiresAt: z.string(),
|
|
107
|
+
sessionId: z.string(),
|
|
108
|
+
user: userResponseSchema,
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Wire shape of `POST /auth/refresh-all`: every valid device-local account,
|
|
112
|
+
* sorted by `authuser` ascending. An empty `accounts` array means "no signed-in
|
|
113
|
+
* accounts on this device" — the IdP must show the sign-in form. A 404 means the
|
|
114
|
+
* endpoint is not deployed and the caller falls back to single-account
|
|
115
|
+
* `/auth/refresh`.
|
|
116
|
+
*/
|
|
117
|
+
export const refreshAllResponseSchema = z.object({
|
|
118
|
+
accounts: z.array(refreshAllAccountSchema),
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* Wire shape of `GET /users/me` — the API success envelope (`{ data: <user> }`)
|
|
122
|
+
* wrapping the RAW Mongo user document. It does NOT pass through
|
|
123
|
+
* `formatUserResponse`, so the id field is `_id` (resolve via
|
|
124
|
+
* {@link resolveUserId}) and virtuals (`name.full`, `displayName`) may be
|
|
125
|
+
* present when the query materialised them.
|
|
126
|
+
*/
|
|
127
|
+
export const currentUserResponseSchema = z.object({
|
|
128
|
+
data: userResponseSchema,
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* One entry of `GET /session/device/sessions/:sessionId` — the deduplicated
|
|
132
|
+
* accounts signed in on this physical device (one per user, most recent
|
|
133
|
+
* session). Backs the multi-account chooser. The embedded user mirrors
|
|
134
|
+
* `formatUserResponse`; it is nullable on slots that lost their user document.
|
|
135
|
+
*/
|
|
136
|
+
export const deviceSessionAccountSchema = z.object({
|
|
137
|
+
sessionId: z.string(),
|
|
138
|
+
isCurrent: z.boolean().optional(),
|
|
139
|
+
user: userResponseSchema.nullable().optional(),
|
|
140
|
+
});
|
|
141
|
+
/** Wire shape of `GET /session/device/sessions/:sessionId` (an array). */
|
|
142
|
+
export const deviceSessionsResponseSchema = z.array(deviceSessionAccountSchema);
|
|
143
|
+
/**
|
|
144
|
+
* Safely parse a value against a contract schema. Returns the parsed (typed)
|
|
145
|
+
* value, or `null` when validation fails — the same ergonomics the auth app's
|
|
146
|
+
* local `safeParse` provided, now sourced from the contracts package so the
|
|
147
|
+
* parse helper and the schemas live together.
|
|
148
|
+
*/
|
|
149
|
+
export function safeParseContract(schema, data) {
|
|
150
|
+
const result = schema.safeParse(data);
|
|
151
|
+
return result.success ? result.data : null;
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/contracts — single source of truth for API request/response contracts.
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas plus their inferred types, shared by the backend (`@oxyhq/api`)
|
|
5
|
+
* and the client SDKs (`@oxyhq/core`, `@oxyhq/auth`, `@oxyhq/services`). The
|
|
6
|
+
* producer validates its output and every consumer validates its input against
|
|
7
|
+
* exactly the same definitions, so the wire shape cannot drift.
|
|
8
|
+
*
|
|
9
|
+
* Platform-agnostic — zod is the only runtime dependency. No react/react-native/
|
|
10
|
+
* expo, no `require()` in the ESM build.
|
|
11
|
+
*/
|
|
12
|
+
export { userNameSchema, userResponseSchema, refreshAllAccountSchema, refreshAllResponseSchema, currentUserResponseSchema, deviceSessionAccountSchema, deviceSessionsResponseSchema, resolveUserId, safeParseContract, } from './userResponse';
|
|
13
|
+
export type { UserNameResponse, UserResponse, RefreshAllAccountResponse, RefreshAllResponseContract, CurrentUserResponseContract, DeviceSessionAccountResponse, DeviceSessionsResponseContract, } from './userResponse';
|