@payez/next-mvp 4.4.6 → 4.4.7

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 (51) hide show
  1. package/dist/api-handlers/account/change-password.js +28 -6
  2. package/dist/auth/better-auth.d.ts +44 -14
  3. package/dist/auth/better-auth.js +92 -7
  4. package/dist/client/better-auth-client.d.ts +101 -956
  5. package/dist/config/env.d.ts +0 -2
  6. package/dist/config/env.js +0 -2
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +8 -1
  9. package/dist/lib/idp-client-config.js +8 -1
  10. package/dist/lib/never-in-response.d.ts +30 -0
  11. package/dist/lib/never-in-response.js +71 -0
  12. package/dist/lib/safe-error-response.d.ts +6 -6
  13. package/dist/lib/safe-error-response.js +6 -6
  14. package/dist/lib/two-factor-verify-failure.d.ts +36 -3
  15. package/dist/lib/two-factor-verify-failure.js +79 -2
  16. package/dist/middleware/create-middleware.d.ts +2 -0
  17. package/dist/middleware/create-middleware.js +36 -0
  18. package/dist/pages/login/page.js +5 -0
  19. package/dist/pages/verify-code/page.js +4 -2
  20. package/dist/routes/auth/settings.js +4 -1
  21. package/dist/server/auth.d.ts +36 -294
  22. package/dist/server/auth.js +22 -25
  23. package/dist/vibe/client.d.ts +132 -16
  24. package/dist/vibe/client.js +288 -82
  25. package/dist/vibe/document-unwrap.d.ts +50 -0
  26. package/dist/vibe/document-unwrap.js +78 -0
  27. package/dist/vibe/errors.d.ts +17 -0
  28. package/dist/vibe/errors.js +21 -1
  29. package/dist/vibe/generic.d.ts +2 -49
  30. package/dist/vibe/generic.js +17 -78
  31. package/dist/vibe/index.d.ts +1 -1
  32. package/dist/vibe/index.js +2 -1
  33. package/package.json +12 -2
  34. package/src/api-handlers/account/change-password.ts +31 -6
  35. package/src/auth/better-auth.ts +93 -8
  36. package/src/config/env.ts +0 -2
  37. package/src/index.ts +4 -0
  38. package/src/lib/idp-client-config.ts +8 -1
  39. package/src/lib/never-in-response.ts +64 -0
  40. package/src/lib/safe-error-response.ts +6 -6
  41. package/src/lib/two-factor-verify-failure.ts +86 -5
  42. package/src/middleware/create-middleware.ts +42 -0
  43. package/src/pages/login/page.tsx +4 -0
  44. package/src/pages/verify-code/page.tsx +4 -2
  45. package/src/routes/auth/settings.ts +5 -1
  46. package/src/server/auth.ts +23 -29
  47. package/src/vibe/client.ts +378 -91
  48. package/src/vibe/document-unwrap.ts +103 -0
  49. package/src/vibe/errors.ts +24 -0
  50. package/src/vibe/generic.ts +8 -100
  51. package/src/vibe/index.ts +1 -0
@@ -24,300 +24,42 @@ export type IdpTokenResult = {
24
24
  terminal: false;
25
25
  sessionData: SessionData;
26
26
  };
27
- export declare function getAuthInstance(): Promise<import("better-auth/types").Auth<{
28
- baseURL: string;
29
- secret: string;
30
- socialProviders: Record<string, import("../auth/better-auth").BetterAuthSocialProvider>;
31
- trustedOrigins: string[];
32
- secondaryStorage: {
33
- get: (key: string) => Promise<string | null>;
34
- set: (key: string, value: string, ttl?: number) => Promise<void>;
35
- delete: (key: string) => Promise<void>;
36
- };
37
- session: {
38
- cookieCache: {
39
- enabled: false;
40
- maxAge: number;
41
- refreshCache: false;
42
- };
43
- };
44
- advanced: {
45
- cookiePrefix: string;
46
- cookies: {
47
- session_token: {
48
- name: string;
49
- };
50
- };
51
- };
52
- plugins: [{
53
- id: "next-cookies";
54
- hooks: {
55
- before: {
56
- matcher(ctx: import("@better-auth/core").HookEndpointContext): boolean;
57
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
58
- }[];
59
- after: {
60
- matcher(ctx: import("@better-auth/core").HookEndpointContext): true;
61
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
62
- }[];
63
- };
64
- }, ...(import("@better-auth/core").BetterAuthPlugin | {
65
- id: "magic-link";
66
- endpoints: {
67
- signInMagicLink: import("better-call").StrictEndpoint<"/sign-in/magic-link", {
68
- method: "POST";
69
- requireHeaders: true;
70
- body: import("zod").ZodObject<{
71
- email: import("zod").ZodEmail;
72
- name: import("zod").ZodOptional<import("zod").ZodString>;
73
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
74
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
75
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
76
- metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
77
- }, import("better-auth/*").$strip>;
78
- metadata: {
79
- openapi: {
80
- operationId: string;
81
- description: string;
82
- responses: {
83
- 200: {
84
- description: string;
85
- content: {
86
- "application/json": {
87
- schema: {
88
- type: "object";
89
- properties: {
90
- status: {
91
- type: string;
92
- };
93
- };
94
- };
95
- };
96
- };
97
- };
98
- };
99
- };
100
- };
101
- }, {
102
- status: boolean;
103
- }>;
104
- magicLinkVerify: import("better-call").StrictEndpoint<"/magic-link/verify", {
105
- method: "GET";
106
- query: import("zod").ZodObject<{
107
- token: import("zod").ZodString;
108
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
109
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
110
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
111
- }, import("better-auth/*").$strip>;
112
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>)[];
113
- requireHeaders: true;
114
- metadata: {
115
- openapi: {
116
- operationId: string;
117
- description: string;
118
- responses: {
119
- 200: {
120
- description: string;
121
- content: {
122
- "application/json": {
123
- schema: {
124
- type: "object";
125
- properties: {
126
- session: {
127
- $ref: string;
128
- };
129
- user: {
130
- $ref: string;
131
- };
132
- };
133
- };
134
- };
135
- };
136
- };
137
- };
138
- };
139
- };
140
- }, {
141
- token: string;
142
- user: {
143
- id: string;
144
- createdAt: Date;
145
- updatedAt: Date;
146
- email: string;
147
- emailVerified: boolean;
148
- name: string;
149
- image?: string | null | undefined;
150
- };
151
- }>;
152
- };
153
- rateLimit: {
154
- pathMatcher(path: string): boolean;
155
- window: number;
156
- max: number;
157
- }[];
158
- options: import("better-auth/plugins/magic-link").MagicLinkOptions;
159
- } | {
160
- id: "generic-oauth";
161
- init: (ctx: import("@better-auth/core").AuthContext) => {
162
- context: {
163
- socialProviders: import("better-auth/*").OAuthProvider<Record<string, any>, Partial<import("better-auth/*").ProviderOptions<any>>>[];
164
- };
165
- };
166
- endpoints: {
167
- signInWithOAuth2: import("better-call").StrictEndpoint<"/sign-in/oauth2", {
168
- method: "POST";
169
- body: import("zod").ZodObject<{
170
- providerId: import("zod").ZodString;
171
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
172
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
173
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
174
- disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
175
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
176
- requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
177
- additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
178
- }, import("better-auth/*").$strip>;
179
- metadata: {
180
- openapi: {
181
- description: string;
182
- responses: {
183
- 200: {
184
- description: string;
185
- content: {
186
- "application/json": {
187
- schema: {
188
- type: "object";
189
- properties: {
190
- url: {
191
- type: string;
192
- };
193
- redirect: {
194
- type: string;
195
- };
196
- };
197
- };
198
- };
199
- };
200
- };
201
- };
202
- };
203
- };
204
- }, {
205
- url: string;
206
- redirect: boolean;
207
- }>;
208
- oAuth2Callback: import("better-call").StrictEndpoint<"/oauth2/callback/:providerId", {
209
- method: "GET";
210
- query: import("zod").ZodObject<{
211
- code: import("zod").ZodOptional<import("zod").ZodString>;
212
- error: import("zod").ZodOptional<import("zod").ZodString>;
213
- error_description: import("zod").ZodOptional<import("zod").ZodString>;
214
- state: import("zod").ZodOptional<import("zod").ZodString>;
215
- iss: import("zod").ZodOptional<import("zod").ZodString>;
216
- }, import("better-auth/*").$strip>;
217
- metadata: {
218
- allowedMediaTypes: string[];
219
- openapi: {
220
- description: string;
221
- responses: {
222
- 200: {
223
- description: string;
224
- content: {
225
- "application/json": {
226
- schema: {
227
- type: "object";
228
- properties: {
229
- url: {
230
- type: string;
231
- };
232
- };
233
- };
234
- };
235
- };
236
- };
237
- };
238
- };
239
- scope: "server";
240
- };
241
- }, void>;
242
- oAuth2LinkAccount: import("better-call").StrictEndpoint<"/oauth2/link", {
243
- method: "POST";
244
- body: import("zod").ZodObject<{
245
- providerId: import("zod").ZodString;
246
- callbackURL: import("zod").ZodString;
247
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
248
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
249
- }, import("better-auth/*").$strip>;
250
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
251
- session: {
252
- session: Record<string, any> & {
253
- id: string;
254
- createdAt: Date;
255
- updatedAt: Date;
256
- userId: string;
257
- expiresAt: Date;
258
- token: string;
259
- ipAddress?: string | null | undefined;
260
- userAgent?: string | null | undefined;
261
- };
262
- user: Record<string, any> & {
263
- id: string;
264
- createdAt: Date;
265
- updatedAt: Date;
266
- email: string;
267
- emailVerified: boolean;
268
- name: string;
269
- image?: string | null | undefined;
270
- };
271
- };
272
- }>)[];
273
- metadata: {
274
- openapi: {
275
- description: string;
276
- responses: {
277
- "200": {
278
- description: string;
279
- content: {
280
- "application/json": {
281
- schema: {
282
- type: "object";
283
- properties: {
284
- url: {
285
- type: string;
286
- format: string;
287
- description: string;
288
- };
289
- redirect: {
290
- type: string;
291
- description: string;
292
- enum: boolean[];
293
- };
294
- };
295
- required: string[];
296
- };
297
- };
298
- };
299
- };
300
- };
301
- };
302
- };
303
- }, {
304
- url: string;
305
- redirect: boolean;
306
- }>;
307
- };
308
- options: import("better-auth/plugins/generic-oauth").GenericOAuthOptions;
309
- $ERROR_CODES: {
310
- INVALID_OAUTH_CONFIGURATION: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIGURATION">;
311
- TOKEN_URL_NOT_FOUND: import("better-auth/*").RawError<"TOKEN_URL_NOT_FOUND">;
312
- PROVIDER_CONFIG_NOT_FOUND: import("better-auth/*").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
313
- PROVIDER_ID_REQUIRED: import("better-auth/*").RawError<"PROVIDER_ID_REQUIRED">;
314
- INVALID_OAUTH_CONFIG: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIG">;
315
- SESSION_REQUIRED: import("better-auth/*").RawError<"SESSION_REQUIRED">;
316
- ISSUER_MISMATCH: import("better-auth/*").RawError<"ISSUER_MISMATCH">;
317
- ISSUER_MISSING: import("better-auth/*").RawError<"ISSUER_MISSING">;
318
- };
319
- })[]];
320
- }>>;
27
+ /**
28
+ * Get the initialized Better Auth instance (singleton).
29
+ *
30
+ * 244549: the single-flight memoisation lives in lib/single-flight.ts rather
31
+ * than inline here, and that placement is deliberate rather than tidiness.
32
+ * The bug being fixed -- a REJECTED init promise cached for the life of the
33
+ * process, so recovery was restart-only -- went unnoticed precisely because
34
+ * this module cannot be loaded in a unit test: importing it pulls in
35
+ * better-auth and the whole IDP config stack. Extracted into a zero-import
36
+ * module, the retry semantics are covered by
37
+ * test/single-flight-retry.test.cjs, which imports dist/ and therefore
38
+ * exercises the artefact that actually ships.
39
+ *
40
+ * PAY-1623 (package half, BAPert 62449): THIS FUNNEL NO LONGER BUILDS ITS OWN
41
+ * INSTANCE. It used to call `createBetterAuthInstance(config)` with no opts, so
42
+ * it carried NONE of the app-supplied plugins registered by the host app at
43
+ * startup (`configureBetterAuth()` -> `state.configuredOpts`). `samlAcs()` and
44
+ * `passkeySignIn()` arrive only that way, so `getAuthInstance()` returned an
45
+ * instance on which `auth.api.samlAcs` was `undefined` — the exact package twin
46
+ * of the portal ACS crash `t.api.samlAcs is not a function` (Jon's walk,
47
+ * 2026-09-21; Portal PR 517 fixed the portal by importing the CONFIGURED
48
+ * instance, `getBetterAuthInstance`, which is why this file must now resolve the
49
+ * same one). It also silently created a SECOND, divergent instance beside the
50
+ * one every /api/auth/* route uses — two instances, two secrets, one process.
51
+ *
52
+ * It now DELEGATES to that configured funnel, which is the single source of
53
+ * truth for the process's Better Auth instance:
54
+ * - it carries `state.configuredOpts` (better-auth.ts:930), so the app
55
+ * plugins are present;
56
+ * - its init is already single-flighted with the retry semantics described in
57
+ * lib/single-flight.ts (see getBetterAuthInstance, card 244549/338215), so
58
+ * this function keeps the same no-latch recovery contract;
59
+ * - there is now ONE instance, so the double-init / divergent-secret hazard
60
+ * this path used to create is gone by construction.
61
+ */
62
+ export declare function getAuthInstance(): Promise<any>;
321
63
  /**
322
64
  * Get the current session from a request.
323
65
  *
@@ -47,11 +47,8 @@ exports.getFreshIdpToken = getFreshIdpToken;
47
47
  exports.requireSession = requireSession;
48
48
  require("server-only");
49
49
  const better_auth_1 = require("../auth/better-auth");
50
- const idp_client_config_1 = require("../lib/idp-client-config");
51
50
  const session_store_1 = require("../lib/session-store");
52
51
  const verified_session_token_1 = require("./verified-session-token");
53
- const single_flight_1 = require("../lib/single-flight");
54
- let authInstance = null;
55
52
  function buildSessionDataFromAuthSession(session) {
56
53
  const user = session?.user;
57
54
  if (!user?.id && !user?.email) {
@@ -116,30 +113,30 @@ function attachSessionData(session, sessionData, sessionToken) {
116
113
  * test/single-flight-retry.test.cjs, which imports dist/ and therefore
117
114
  * exercises the artefact that actually ships.
118
115
  *
119
- * Semantics, all asserted by that suite: concurrent callers share ONE
120
- * underlying init; a rejection reaches every caller awaiting that attempt
121
- * (failures stay loud -- getIDPClientConfig is fail-loud by design and callers
122
- * depend on it); the NEXT call after a failure starts a fresh attempt; and a
123
- * slow failure never evicts a newer in-flight attempt.
116
+ * PAY-1623 (package half, BAPert 62449): THIS FUNNEL NO LONGER BUILDS ITS OWN
117
+ * INSTANCE. It used to call `createBetterAuthInstance(config)` with no opts, so
118
+ * it carried NONE of the app-supplied plugins registered by the host app at
119
+ * startup (`configureBetterAuth()` -> `state.configuredOpts`). `samlAcs()` and
120
+ * `passkeySignIn()` arrive only that way, so `getAuthInstance()` returned an
121
+ * instance on which `auth.api.samlAcs` was `undefined` — the exact package twin
122
+ * of the portal ACS crash `t.api.samlAcs is not a function` (Jon's walk,
123
+ * 2026-09-21; Portal PR 517 fixed the portal by importing the CONFIGURED
124
+ * instance, `getBetterAuthInstance`, which is why this file must now resolve the
125
+ * same one). It also silently created a SECOND, divergent instance beside the
126
+ * one every /api/auth/* route uses — two instances, two secrets, one process.
127
+ *
128
+ * It now DELEGATES to that configured funnel, which is the single source of
129
+ * truth for the process's Better Auth instance:
130
+ * - it carries `state.configuredOpts` (better-auth.ts:930), so the app
131
+ * plugins are present;
132
+ * - its init is already single-flighted with the retry semantics described in
133
+ * lib/single-flight.ts (see getBetterAuthInstance, card 244549/338215), so
134
+ * this function keeps the same no-latch recovery contract;
135
+ * - there is now ONE instance, so the double-init / divergent-secret hazard
136
+ * this path used to create is gone by construction.
124
137
  */
125
- const authInit = (0, single_flight_1.createSingleFlight)(async () => {
126
- const config = await (0, idp_client_config_1.getIDPClientConfig)(true);
127
- authInstance = (0, better_auth_1.createBetterAuthInstance)(config);
128
- // 185121 line 1, path 2: this module-scope instance had NO creation log
129
- // line — first-called during a config transition window it pinned a
130
- // divergent secret INVISIBLY. Name the path and the secret fingerprint
131
- // (the createBetterAuthInstance line already logged the same hash;
132
- // this line is what attributes it to THIS init path).
133
- // Guard matches createBetterAuthInstance's: getIDPClientConfig currently
134
- // throws FATAL on a missing authSecret (QAPert 27328 verified), but that
135
- // guarantee is one refactor away — log ABSENT rather than ever throw here.
136
- console.log(`[AUTH] getAuthInstance: created BA instance via server/auth.ts — authSecret ${config.authSecret ? `sha256:${(0, better_auth_1.secretFingerprint)(config.authSecret)}` : 'ABSENT'}`);
137
- return authInstance;
138
- });
139
138
  async function getAuthInstance() {
140
- if (authInstance)
141
- return authInstance;
142
- return authInit.get();
139
+ return (0, better_auth_1.getBetterAuthInstance)();
143
140
  }
144
141
  /**
145
142
  * Get the current session from a request.
@@ -49,7 +49,13 @@ export interface VibeRetryConfig {
49
49
  retryOnRateLimit?: boolean;
50
50
  }
51
51
  export interface VibeClientConfig {
52
- /** Base URL for the Vibe API (defaults to VIBE_API_URL env var) */
52
+ /**
53
+ * Base URL for the Vibe API (defaults to VIBE_API_URL env var).
54
+ *
55
+ * PAY-1767: proxy mode does NOT need this. Every request is sent to the IDP proxy as an endpoint
56
+ * PATH (`/v1/collections/...`), which is what the proxy validates and signs - it never builds a
57
+ * Vibe URL from this value. It is retained only for compatibility with consumers that still read it.
58
+ */
53
59
  baseUrl?: string;
54
60
  /** Vibe Client ID (defaults to VIBE_CLIENT_ID env var) */
55
61
  clientId?: string;
@@ -64,6 +70,15 @@ export interface VibeClientConfig {
64
70
  * When set, requests are routed through the IDP proxy instead of directly to Vibe.
65
71
  */
66
72
  appSigningKey?: string;
73
+ /**
74
+ * PAY-1767: which COLLECTION the table delegates address (defaults to VIBE_COLLECTION, then 'vibe_app').
75
+ *
76
+ * The Vibe API route is `/v1/collections/{collection}/tables/{table}`. This client used to hardcode
77
+ * `/api/v1/vibe_app/{table}` - a collection baked into the path with no route behind it. Pin the
78
+ * collection explicitly at the call site when your tables do not live in `vibe_app` (the UMI
79
+ * VIBE_COLLECTION-double-duty lesson, POST-INSTALL.md item 16).
80
+ */
81
+ collection?: string;
67
82
  /** Custom fetch function (for testing or custom auth) */
68
83
  fetch?: typeof fetch;
69
84
  /** Retry configuration for transient errors */
@@ -92,7 +107,13 @@ export interface FindManyOptions<T> {
92
107
  take?: number;
93
108
  skip?: number;
94
109
  }
95
- /** Options for findUnique */
110
+ /**
111
+ * Options for findUnique.
112
+ *
113
+ * PAY-1767 (NightHawk 64534 MUST 2): `where.id` is the DOCUMENT id - the storage-layer primary key the
114
+ * route constrains (`{id:int}`), NOT a row's own `id` column. The unwrapped row exposes it as
115
+ * `_vibe_doc_id`; use THAT value for a subsequent update/delete.
116
+ */
96
117
  export interface FindUniqueOptions {
97
118
  where: {
98
119
  id: number;
@@ -102,14 +123,16 @@ export interface FindUniqueOptions {
102
123
  export interface CreateOptions<T> {
103
124
  data: Omit<T, 'id' | 'created_at' | 'updated_at'>;
104
125
  }
105
- /** Options for update */
126
+ /**
127
+ * Options for update. `where.id` is the DOCUMENT id (see FindUniqueOptions), not a row's own id column.
128
+ */
106
129
  export interface UpdateOptions<T> {
107
130
  where: {
108
131
  id: number;
109
132
  };
110
133
  data: Partial<Omit<T, 'id' | 'created_at' | 'updated_at'>>;
111
134
  }
112
- /** Options for delete */
135
+ /** Options for delete. `where.id` is the DOCUMENT id (see FindUniqueOptions). */
113
136
  export interface DeleteOptions {
114
137
  where: {
115
138
  id: number;
@@ -122,8 +145,36 @@ export interface CountOptions<T> {
122
145
  /** Result of findMany with metadata */
123
146
  export interface FindManyResult<T> {
124
147
  data: T[];
148
+ /**
149
+ * PAY-1767 (NightHawk 64552 item B): true when `data` is a PAGE of a larger population. Read from Core's
150
+ * `pagination.totalCount`; a caller must not treat `data.length` as the table's size.
151
+ */
152
+ hasMore?: boolean;
125
153
  meta: VibeMeta;
126
154
  }
155
+ /**
156
+ * PAY-1767: the most records `createMany` will create in one call. It is per-record and NOT atomic, so a
157
+ * ceiling bounds the blast radius of a partial failure. 50 is the house's only batch ceiling (Vibe's own
158
+ * DecryptController BATCH_TOO_LARGE = 50).
159
+ */
160
+ export declare const MAX_BATCH_CREATE_ROWS = 50;
161
+ /**
162
+ * PAY-1767: the default page size findMany asks the query route for. Core clamps pageSize/limit to 100
163
+ * (DocumentsController.Query), so this is the most one call returns.
164
+ */
165
+ export declare const MAX_FIND_MANY_PAGE_SIZE = 100;
166
+ /**
167
+ * PAY-1767: map a Prisma-style `where` to Core's VibeFilterNode OBJECT.
168
+ *
169
+ * { status: 'active' } -> { 'status': { eq: 'active' } }
170
+ * { score: { gt: 5 } } -> { 'score': { gt: 5 } }
171
+ * { status: 'a', tier: { in: [] } } -> { AND: [ {status:{eq:'a'}}, {tier:{in:[]}} ] }
172
+ *
173
+ * A field name is used as-is: a top-level document column (document_id, created_at...) or a JSONB path
174
+ * (Core treats an unknown name as data->>'name'; a nested path like 'data.x' is honoured explicitly).
175
+ * An unknown OPERATOR throws - dropping an operator would widen the result silently.
176
+ */
177
+ export declare function buildVibeFilter(where: Record<string, unknown>, table: string): Record<string, unknown>;
127
178
  /**
128
179
  * Delegate class for table operations.
129
180
  * Provides Prisma-style methods for a specific table.
@@ -133,9 +184,33 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
133
184
  private client;
134
185
  constructor(tableName: T, client: VibeClient);
135
186
  /**
136
- * Find multiple records with optional filtering and pagination.
187
+ * PAY-1767: the Vibe API route for this table's document collection.
188
+ * `/v1/collections/{collection}/tables/{table}` (Core DocumentsController.cs:15) - the same shape
189
+ * generic.ts and sessions.ts already use. The OLD `/api/v1/vibe_app/{table}` had no route behind it.
190
+ */
191
+ private tableEndpoint;
192
+ /**
193
+ * Find multiple records, WITH filtering, sorting and paging honoured.
194
+ *
195
+ * PAY-1767 (NightHawk 64534 MUST 1, QAPert 64530, BAPert 64535). The GET list route
196
+ * (`GET /v1/collections/{c}/tables/{t}`, Core DocumentsController.List :44-48) binds ONLY `page` and
197
+ * `pageSize` - it reads no filter, sort or offset, so sending where/orderBy/take/skip there DROPPED them
198
+ * silently. Through the proxy that is worse than loud: a proxied MVP service account is ADMIN mode
199
+ * (HasValidContext), so the ownership filter is bypassed and an unfiltered page is 20 ARBITRARY rows of
200
+ * the tenant - caller code doing `rows[0]` gets the WRONG record with no error.
201
+ *
202
+ * So this now goes to `POST .../tables/{t}/query`, which binds QueryRequest (Filter / OrderBy / Page /
203
+ * PageSize / Limit). The FILTER is a VibeFilterNode OBJECT - `{ "<field>": { "<op>": value } }` - NOT the
204
+ * filter ARRAY that sessions.ts sends: QueryRequest.Filter is typed VibeFilterNode, which has no array
205
+ * form (measured at Core origin/master; CliInitProjectService.cs:197 builds the same object shape). An
206
+ * unmappable option THROWS rather than being dropped.
137
207
  */
138
208
  findMany(options?: FindManyOptions<VibeTableType<T>>): Promise<FindManyResult<VibeTableType<T>>>;
209
+ /**
210
+ * PAY-1767: map the Prisma-style findMany options onto Core's QueryRequest body. THROWS on anything the
211
+ * query endpoint cannot express, so nothing is silently dropped.
212
+ */
213
+ private buildQueryBody;
139
214
  /**
140
215
  * Find a single record by ID.
141
216
  * Throws VibeNotFoundError if not found.
@@ -147,8 +222,12 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
147
222
  */
148
223
  findUniqueOrNull(options: FindUniqueOptions): Promise<VibeTableType<T> | null>;
149
224
  /**
150
- * Find the first record matching the filter.
225
+ * Find the first record.
151
226
  * Returns null if none found.
227
+ *
228
+ * PAY-1767: takes the first row of the (caller-owned, created_at-ordered) list. Any where/orderBy in
229
+ * `options` is passed through to findMany, which REFUSES them on this route rather than dropping them,
230
+ * so this cannot pretend to filter.
152
231
  */
153
232
  findFirst(options?: FindManyOptions<VibeTableType<T>>): Promise<VibeTableType<T> | null>;
154
233
  /**
@@ -163,19 +242,45 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
163
242
  /**
164
243
  * Delete a record by ID (soft delete).
165
244
  * Throws VibeNotFoundError if not found.
245
+ *
246
+ * PAY-1767 (NightHawk 64578 item 2): Core answers DELETE with **204 No Content** (DocumentsController
247
+ * Delete returns NoContent()), so a SUCCESSFUL delete carries NO document - and there is none to unwrap.
248
+ * This must therefore NOT go through requireRow (that is the create/update rule: "a success WITH no
249
+ * document throws"). A 2xx with no body is the success here; the request layer already resolves it to an
250
+ * empty success. Returns null when the server sent no document.
166
251
  */
167
- delete(options: DeleteOptions): Promise<VibeTableType<T>>;
252
+ delete(options: DeleteOptions): Promise<VibeTableType<T> | null>;
253
+ /** Unwrap a single-document response, or THROW - never return an empty object typed as a row. */
254
+ private requireRow;
168
255
  /**
169
256
  * Count records matching the filter.
257
+ *
258
+ * PAY-1767 (NightHawk 64534): goes to POST /query with limit:0, which is Core's COUNT-ONLY mode
259
+ * (DocumentsController.Query returns an empty Data array plus Pagination.TotalCount). So this is the
260
+ * true population of the filter, not the first page's size.
170
261
  */
171
262
  count(options?: CountOptions<VibeTableType<T>>): Promise<number>;
172
263
  /**
173
- * Create multiple records in a batch.
264
+ * Create multiple records.
265
+ *
266
+ * PAY-1767: the old path `/api/v1/vibe_app/batch` has no route (there is no documents batch endpoint in
267
+ * Core's Vibe Public API), so this CANNOT be atomic here. Until a Core batch route exists, this creates
268
+ * each record against the REAL table endpoint in turn.
269
+ *
270
+ * BOUNDED ON PURPOSE (BAPert 64527). Non-atomic is tolerable only while the blast radius is small, so a
271
+ * request above MAX_BATCH_CREATE_ROWS is REFUSED before anything is sent. 50 matches the house's only
272
+ * batch ceiling (Vibe's DecryptController BATCH_TOO_LARGE).
273
+ *
274
+ * PARTIAL FAILURE IS REPORTED, NOT SWALLOWED (NightHawk 64534 condition (a)). A failure at item k throws
275
+ * a VibeBatchCreateError that CARRIES the count and the document ids already created - a cap cannot fix
276
+ * partial failure, only reporting it can. The created rows are unwrapped (MUST 2) so their own fields are
277
+ * present, not the wrapper's.
174
278
  */
175
279
  createMany(options: {
176
280
  data: CreateOptions<VibeTableType<T>>['data'][];
177
281
  }): Promise<{
178
282
  count: number;
283
+ ids: Array<number | string>;
179
284
  }>;
180
285
  }
181
286
  /**
@@ -205,9 +310,19 @@ export declare class VibeClient {
205
310
  * Message format: {timestamp}|{method}|{endpoint}
206
311
  */
207
312
  private generateProxySignature;
313
+ /**
314
+ * PAY-1767: the Vibe API endpoint PATH for a table in this client's collection.
315
+ * `/v1/collections/{collection}/tables/{table}[/{id}]` - what the IDP proxy validates, signs and forwards.
316
+ * Returned as a PATH, not a URL: proxy mode needs no VIBE_API_URL (the proxy owns the base).
317
+ */
318
+ tableEndpoint(table: string, id?: number | string): string;
208
319
  /**
209
320
  * Make a request through the IDP Vibe proxy.
210
321
  * The proxy handles credential injection securely.
322
+ *
323
+ * PAY-1767: `endpoint` is the RAW path (+query) to forward. It is what gets signed and sent, so this
324
+ * takes a path directly rather than parsing one out of a URL - proxy mode is complete without a Vibe
325
+ * base URL, which is exactly what an endpoint-path call needs.
211
326
  */
212
327
  private requestViaProxy;
213
328
  /**
@@ -220,27 +335,28 @@ export declare class VibeClient {
220
335
  * Routing:
221
336
  * - All requests route through the IDP proxy (requires VIBE_HMAC_KEY + IDP_URL)
222
337
  *
338
+ * PAY-1767: `urlOrPath` is EITHER a full URL or a bare Vibe path ('/v1/collections/...'). Proxy mode
339
+ * signs and forwards the PATH either way, so a caller with only a path (the table delegates) needs no
340
+ * Vibe base URL - VIBE_API_URL is not required to make a proxied call.
341
+ *
223
342
  * Retry behavior:
224
343
  * - 5xx errors: Retry up to maxRetries times with exponential backoff
225
344
  * - 429 errors: Retry once after Retry-After delay (if retryOnRateLimit enabled)
226
345
  * - 401/403 errors: No retry (auth errors)
227
346
  * - Network errors: Retry with backoff
228
347
  */
229
- request<T>(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: unknown, authToken?: string): Promise<VibeResponse<T>>;
348
+ request<T>(urlOrPath: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: unknown, authToken?: string): Promise<VibeResponse<T>>;
230
349
  /**
231
350
  * Parse error response body, handling non-JSON responses gracefully.
232
351
  */
233
352
  private parseErrorResponse;
234
353
  /**
235
354
  * Fetch the schema for type generation.
355
+ *
356
+ * PAY-1767: the old path `/api/v1/vibe_app/schema` has no route. The real per-collection schema route is
357
+ * `/v1/mvp/schemas/{collection}` (Core MvpSchemaController.cs:65). Returns the collection schema DTO.
236
358
  */
237
- getSchema(): Promise<Record<string, {
238
- columns: Array<{
239
- name: string;
240
- type: string;
241
- nullable: boolean;
242
- }>;
243
- }>>;
359
+ getSchema(): Promise<Record<string, unknown>>;
244
360
  }
245
361
  /**
246
362
  * Default Vibe client instance.