@managemint-solutions/sdk 0.38.0 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +27 -27
  2. package/dist/audit/dto.js +1 -1
  3. package/dist/audit/index.d.ts +12 -12
  4. package/dist/audit/index.js +9 -9
  5. package/dist/auth-client/index.d.ts +2 -2
  6. package/dist/auth-client/index.js +2 -2
  7. package/dist/auth.d.ts +3 -3
  8. package/dist/auth.js +3 -3
  9. package/dist/billing/dto.js +1 -1
  10. package/dist/billing/index.d.ts +4 -4
  11. package/dist/billing/index.js +5 -5
  12. package/dist/billing/invoices/dto.js +1 -1
  13. package/dist/billing/invoices/index.d.ts +6 -6
  14. package/dist/billing/invoices/index.js +7 -7
  15. package/dist/client.d.ts +3 -3
  16. package/dist/client.js +1 -1
  17. package/dist/clients/index.d.ts +1 -1
  18. package/dist/clients/index.js +1 -1
  19. package/dist/configs/dto.js +3 -3
  20. package/dist/configs/index.d.ts +3 -3
  21. package/dist/configs/index.js +3 -3
  22. package/dist/errors.d.ts +2 -2
  23. package/dist/errors.js +2 -2
  24. package/dist/feature-requests/dto.js +1 -1
  25. package/dist/feature-requests/index.d.ts +3 -3
  26. package/dist/feature-requests/index.js +2 -2
  27. package/dist/index.js +2 -2
  28. package/dist/nest/index.d.ts +5 -5
  29. package/dist/nest/index.js +4 -4
  30. package/dist/notifications/index.d.ts +8 -8
  31. package/dist/notifications/index.js +8 -8
  32. package/dist/organization-invites/index.d.ts +3 -3
  33. package/dist/organization-invites/index.js +2 -2
  34. package/dist/organization-payment-methods/index.d.ts +3 -3
  35. package/dist/organization-payment-methods/index.js +3 -3
  36. package/dist/organizations/index.d.ts +7 -7
  37. package/dist/organizations/index.js +7 -7
  38. package/dist/permissions/index.d.ts +6 -6
  39. package/dist/permissions/index.js +6 -6
  40. package/dist/pricebooks/index.d.ts +2 -2
  41. package/dist/pricebooks/index.js +3 -3
  42. package/dist/projects/dto.js +2 -2
  43. package/dist/projects/index.d.ts +1 -1
  44. package/dist/projects/index.js +2 -2
  45. package/dist/service-client/index.d.ts +5 -5
  46. package/dist/service-client/index.js +5 -5
  47. package/dist/support/dto.js +1 -1
  48. package/dist/supporting-files/dto.js +1 -1
  49. package/dist/supporting-files/index.js +5 -5
  50. package/dist/supporting-files/paths.d.ts +3 -3
  51. package/dist/supporting-files/paths.js +4 -4
  52. package/dist/tasks/dto.js +4 -4
  53. package/dist/tasks/index.d.ts +1 -1
  54. package/dist/tasks/index.js +2 -2
  55. package/dist/transforms.d.ts +6 -6
  56. package/dist/transforms.js +8 -8
  57. package/dist/users/dto.d.ts +3 -3
  58. package/dist/users/dto.js +4 -4
  59. package/dist/users/index.d.ts +2 -2
  60. package/dist/users/index.js +2 -2
  61. package/dist/users/paths.d.ts +2 -2
  62. package/dist/users/paths.js +2 -2
  63. package/dist/webhook-events/index.d.ts +3 -3
  64. package/dist/webhook-events/index.js +3 -3
  65. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @managemint-solutions/sdk @supabase/supabase-js
10
10
  ```
11
11
 
12
12
  `@supabase/supabase-js` (^2.103.0), `class-validator` (^0.15) and `class-transformer` (^0.5)
13
- are peer dependencies — the request DTOs (`AddStatusDto`, `UpdateStatusDto`, `StatusIdDto`,
13
+ are peer dependencies - the request DTOs (`AddStatusDto`, `UpdateStatusDto`, `StatusIdDto`,
14
14
  `GetStatusesDto`, `AddAdditionalDetailsDto`, `UpdateAdditionalDetailsDto`,
15
15
  `AdditionalDetailsIdDto`, `GetAdditionalDetailsDto`, `AddNoteDto`, `NoteIdDto`,
16
16
  `GetNotesDto`, `SupportingFileIdDto`, `GetSupportingFilesDto`, `GetClientsDto`, `ClientIdDto`,
@@ -39,7 +39,7 @@ const supabase = createSupabaseClient({
39
39
 
40
40
  // Inputs and outputs are the shared types from @managemint-solutions/entities.
41
41
  const statuses: StatusEntity[] = await supabase.statuses.list(StatusEntityTypes.PROJECT);
42
- // `is_complete` marks the finished state and is optional — it defaults to false on create and
42
+ // `is_complete` marks the finished state and is optional - it defaults to false on create and
43
43
  // is left unchanged on update when omitted. `is_fail` and `is_triage` are read back on every
44
44
  // status but are not settable through the SDK yet.
45
45
  const created = await supabase.statuses.create({
@@ -100,7 +100,7 @@ import { SupportingFilesEntityTypes } from '@managemint-solutions/entities/suppo
100
100
  // Supporting files hang off another entity, and cover Supabase Storage as well as the table.
101
101
  const files = await supabase.supportingFiles.list(SupportingFilesEntityTypes.PROJECT, projectId);
102
102
  await supabase.supportingFiles.upload(SupportingFilesEntityTypes.PROJECT, projectId, {
103
- files: uploaded, // web `File`s — uploaded to the `supporting-files` bucket, then rowed up
103
+ files: uploaded, // web `File`s - uploaded to the `supporting-files` bucket, then rowed up
104
104
  });
105
105
  const { url, ttl } = await supabase.supportingFiles.getPresignedUrl(files[0].mms_id); // 5 min
106
106
  await supabase.supportingFiles.remove(files[0].mms_id); // deletes the row and the object
@@ -128,7 +128,7 @@ await supabase.clients.getById(client.mms_id);
128
128
  await supabase.clients.update(client.mms_id, { ...client, name: 'Acme Ltd' });
129
129
  await supabase.clients.archive(client.mms_id); // archived + archived_at/by
130
130
  await supabase.clients.unarchive(client.mms_id); // archived = false + unarchived_at/by
131
- await supabase.clients.remove(client.mms_id); // soft delete — deleted + deleted_at/by
131
+ await supabase.clients.remove(client.mms_id); // soft delete - deleted + deleted_at/by
132
132
  await supabase.clients.restore(client.mms_id); // deleted = false + restored_at/by
133
133
  ```
134
134
 
@@ -145,7 +145,7 @@ const page = await supabase.projects.list({
145
145
  client_id: clientId, // a UUID, or the base64-JSON option value a select sends
146
146
  });
147
147
  // `search` matches name/specification; `created_*`, `start_date_*` and `end_date_*` bound the
148
- // date columns, and `status_id_in` filters by status. `status_id_not_in` is the exclusion form —
148
+ // date columns, and `status_id_in` filters by status. `status_id_not_in` is the exclusion form -
149
149
  // it keeps rows with no status, which `status_id_in` cannot express. `completed` is carried but
150
150
  // not acted on here: the api resolves it to status ids against the statuses catalogue first.
151
151
 
@@ -182,7 +182,7 @@ const page = await supabase.tasks.list({
182
182
  });
183
183
  // `search` matches name/description, and `client_id`, `assigned_to_id`, the date ranges and
184
184
  // `status_id_in` narrow it further. `status_id_not_in` excludes statuses while keeping rows with
185
- // no status; `completed` is carried but not acted on here — the api resolves it to status ids
185
+ // no status; `completed` is carried but not acted on here - the api resolves it to status ids
186
186
  // against the statuses catalogue first.
187
187
 
188
188
  const task = await supabase.tasks.create({
@@ -209,11 +209,11 @@ task carries its embedded `status`, `assigned_to`, `project` and `client`.
209
209
 
210
210
  The client decodes `{ mms_id, organization_id }` from the JWT itself and scopes every query
211
211
  by `organization_id`. `supabase.supabase` is the raw `@supabase/supabase-js` client
212
- (`TypedSupabaseClient`) — the escape hatch for queries not yet covered by a resource.
212
+ (`TypedSupabaseClient`) - the escape hatch for queries not yet covered by a resource.
213
213
 
214
214
  ## Audit
215
215
 
216
- Every write a resource makes is logged to `audit_logs` by the SDK itself — there are no database
216
+ Every write a resource makes is logged to `audit_logs` by the SDK itself - there are no database
217
217
  triggers and no audit code in any feature. A write records one row carrying the action
218
218
  (`INSERT` / `UPDATE` / `ARCHIVE` / `UNARCHIVE` / `DELETE` / `RESTORE`), the entity type and id, who
219
219
  did it, and a `changes` diff of the flat table row as real JSON (`{ field: { old, new } }`).
@@ -245,7 +245,7 @@ const supabase = createSupabaseClient({
245
245
  ```
246
246
 
247
247
  With the Nest decorator there is nowhere to pass config per request, so the handler is registered
248
- once at startup — after Sentry is initialised, before the app listens:
248
+ once at startup - after Sentry is initialised, before the app listens:
249
249
 
250
250
  ```ts
251
251
  import { configureSupabaseUserClient } from '@managemint-solutions/sdk/nest';
@@ -254,8 +254,8 @@ configureSupabaseUserClient({ onAuditError: (error) => Sentry.captureException(e
254
254
  ```
255
255
 
256
256
  Service-role work with no caller (webhooks, the queue worker, provisioning) goes through the
257
- service client's organization scope, which supplies the audit actor — `mms_id: null` for system
258
- work — and hands out audited tables for anything the SDK has no resource for:
257
+ service client's organization scope, which supplies the audit actor - `mms_id: null` for system
258
+ work - and hands out audited tables for anything the SDK has no resource for:
259
259
 
260
260
  ```ts
261
261
  import { createSupabaseServiceClient } from '@managemint-solutions/sdk';
@@ -274,20 +274,20 @@ await users.transition(userId, AuditTrailAction.DELETE, { deleted: true }); // r
274
274
  const deleted = await users.remove(userId); // hard delete; returns the old row
275
275
  ```
276
276
 
277
- Cross-tenant lookups — the organization behind a Paystack code, a ledger row by id, every
278
- organization with a Paystack customer — hang off `service` itself, because a webhook or cron does
277
+ Cross-tenant lookups - the organization behind a Paystack code, a ledger row by id, every
278
+ organization with a Paystack customer - hang off `service` itself, because a webhook or cron does
279
279
  not know its tenant until it looks one up.
280
280
 
281
281
  `update` / `transition` / `remove` pre-read the flat row (by `mms_id`, and by `organization_id`
282
- unless `scopeByOrganization: false` — the `organizations` table is its own tenant), so a missing
282
+ unless `scopeByOrganization: false` - the `organizations` table is its own tenant), so a missing
283
283
  row surfaces as the usual 406. The write itself still throws `SupabaseClientError` on failure;
284
284
  only the audit insert is swallowed.
285
285
 
286
- Only what goes through the SDK/API is logged — a raw PostgREST write or a dashboard edit is not.
286
+ Only what goes through the SDK/API is logged - a raw PostgREST write or a dashboard edit is not.
287
287
 
288
288
  ## Auth
289
289
 
290
- `SupabaseClient` needs the caller's JWT, which the auth flows are there to produce — so sign-in,
290
+ `SupabaseClient` needs the caller's JWT, which the auth flows are there to produce - so sign-in,
291
291
  refresh and the password-reset exchanges live on a separate `SupabaseAuthClient`, built from the
292
292
  publishable key alone.
293
293
 
@@ -296,7 +296,7 @@ import { createSupabaseAuthClient } from '@managemint-solutions/sdk';
296
296
 
297
297
  const auth = createSupabaseAuthClient({
298
298
  url: process.env.SUPABASE_URL!,
299
- key: process.env.SUPABASE_PUBLISHABLE_KEY!, // no caller token — these callers are logged out
299
+ key: process.env.SUPABASE_PUBLISHABLE_KEY!, // no caller token - these callers are logged out
300
300
  });
301
301
 
302
302
  const { access_token, refresh_token } = await auth.signIn({ email, password });
@@ -314,7 +314,7 @@ await auth.resetPassword({ password, access_token, refresh_token });
314
314
  client carrying the fresh access token; a failed stamp never fails the login. `signOut` takes the
315
315
  caller's access token because this client holds no session of its own: GoTrue's logout endpoint
316
316
  authenticates with the token itself, and the `local` scope ends that device's session only. Build
317
- one of these per request — supabase-js keeps the session `signIn`, `verifyOtp` and `setSession`
317
+ one of these per request - supabase-js keeps the session `signIn`, `verifyOtp` and `setSession`
318
318
  produce in the client's memory, so a shared instance would leak sessions between callers.
319
319
  `auth.supabase` is the raw `@supabase/supabase-js` client.
320
320
 
@@ -361,7 +361,7 @@ and reads rows only.
361
361
  ## Pricebooks
362
362
 
363
363
  The price catalog (`pricebooks`, `pricebook_modules`, `billing_modules`) is readable by `anon`
364
- by design — an unauthenticated signup prices against it — so `pricebooks` hangs off the same
364
+ by design - an unauthenticated signup prices against it - so `pricebooks` hangs off the same
365
365
  bearer-less `SupabaseAuthClient` as the auth flows, takes no auth context and scopes by nothing.
366
366
 
367
367
  ```ts
@@ -381,7 +381,7 @@ row joined with its `billing_modules` catalog entry). Exactly one pricebook is a
381
381
 
382
382
  ## Configs
383
383
 
384
- `public.configs` holds global named JSON configs — one `value` object per row, with no
384
+ `public.configs` holds global named JSON configs - one `value` object per row, with no
385
385
  organization on it. Every signed-in caller may read, and only the service role may write, so
386
386
  `configs` hangs off the caller's client and the service client alike and a write on the former
387
387
  fails at the database (Scott edits the rows in Supabase Studio for now). A feature-flag set is
@@ -405,9 +405,9 @@ await service.configs.remove('maintenance'); // hard delete, service role only
405
405
  The first slice of the HR module: `leave_types`, `leave_entitlements` and `leave_requests`, one
406
406
  org-scoped audited resource each on the caller's client. Every date is a calendar date
407
407
  (`YYYY-MM-DD`, the columns are `date`), and the DTOs refuse anything else; body fields are
408
- required keys, with `null` for an empty one. Balances are never stored — an entitlement's
408
+ required keys, with `null` for an empty one. Balances are never stored - an entitlement's
409
409
  `days_used` / `days_pending` / `days_remaining` are computed on read from the approved and pending
410
- requests of that user and type whose start date falls in its period — and a request's `days` is
410
+ requests of that user and type whose start date falls in its period - and a request's `days` is
411
411
  a snapshot taken when it was filed, so a holiday added later does not rewrite approved history.
412
412
 
413
413
  The calendar arithmetic is plain exported functions, shared with the api:
@@ -452,7 +452,7 @@ accepts `leave_requests` and `leave_entitlements` alongside the CRM entity types
452
452
 
453
453
  The public-holiday calendar, `public_holidays`, on the caller's client as `supabase.publicHolidays`.
454
454
  Global like configs: there is no organization on a row, every signed-in caller reads the same
455
- list, and only the service role may write — Scott maintains it in Supabase Studio, so the SDK is
455
+ list, and only the service role may write - Scott maintains it in Supabase Studio, so the SDK is
456
456
  read-only here. Several holidays may share a date; only the ones with `religion` null count as a
457
457
  day off for everyone.
458
458
 
@@ -467,7 +467,7 @@ Every error the SDK throws is a `SupabaseClientError` carrying the HTTP `status`
467
467
  with and the client-facing body, `toResponse()` → `{ error, message }`. Postgrest/Postgres
468
468
  errors are mapped by code: `PGRST*` codes to their documented statuses, an RLS or grant denial
469
469
  (`42501`) to **403**, a unique or foreign-key violation (`23505`, `23503`) to **409**, a not-null,
470
- check or malformed-literal failure (`23502`, `23514`, `22P02`) to **400**, anything else 500 —
470
+ check or malformed-literal failure (`23502`, `23514`, `22P02`) to **400**, anything else 500 -
471
471
  the message is always Postgrest's own. Storage errors carry the storage error's own HTTP status
472
472
  (500 when it has none); Supabase Auth errors likewise, as `Authentication Error`; a rejected or
473
473
  missing JWT is `401 Authentication Error`. Consumers return these as-is instead of mapping
@@ -505,7 +505,7 @@ once a day however busy it is. Every other call is one round trip. Values are JS
505
505
  by the client; `get<T>` / `getField<T>` are the caller's promise about what was written, not a
506
506
  check.
507
507
 
508
- The client takes its URL and token as config — the SDK reads no environment variables — and
508
+ The client takes its URL and token as config - the SDK reads no environment variables - and
509
509
  retries once with a three-second timeout per request. It **throws** on a failed call: what a
510
510
  failure means is the caller's decision, and both callers today fail open (a miss on the portal,
511
511
  a Sentry report on the api). The key prefix and TTL for the profile hash live in
@@ -533,7 +533,7 @@ malformed token becomes an `UnauthorizedException`. The DTOs work with Nest's gl
533
533
  `ValidationPipe` as-is (`whitelist`, `forbidNonWhitelisted`, `transform`).
534
534
 
535
535
  `toHttpException(error)` converts a `SupabaseClientError` into an `HttpException` with the
536
- same status and body — call it from your global exception filter so services can let SDK
536
+ same status and body - call it from your global exception filter so services can let SDK
537
537
  errors propagate without any try/catch.
538
538
 
539
539
  ## Development
@@ -541,7 +541,7 @@ errors propagate without any try/catch.
541
541
  ```bash
542
542
  npm install
543
543
  npm test # jest + ts-jest; specs live in src/<resource>/tests/*.spec.ts
544
- npm run build # tsc -p tsconfig.build.json (excludes specs), CommonJS output in dist/ — the type gate
544
+ npm run build # tsc -p tsconfig.build.json (excludes specs), CommonJS output in dist/ - the type gate
545
545
  ```
546
546
 
547
547
  The SDK carries no generated database types: the schema of record is the api's
package/dist/audit/dto.js CHANGED
@@ -67,7 +67,7 @@ __decorate([
67
67
  (0, class_validator_1.IsOptional)(),
68
68
  (0, validators_1.IsNullable)()
69
69
  // Still v4: `audit_logs.created_by` is a `users.mms_id`, which is the GoTrue auth uid the
70
- // signup trigger copies out of `auth.users.id` — the table's uuidv7 default never fires.
70
+ // signup trigger copies out of `auth.users.id` - the table's uuidv7 default never fires.
71
71
  ,
72
72
  (0, class_validator_1.IsUUID)('4', { message: 'Created by must be a valid UUID v4' }),
73
73
  __metadata("design:type", Object)
@@ -6,17 +6,17 @@ import type { GetAuditTrailDto } from './dto';
6
6
  export * from './dto';
7
7
  /**
8
8
  * Who a write is attributed to. `AuthContext` is assignable to it; the api's admin-client
9
- * modules build one by hand (system jobs pass `mms_id: null` — `created_by` is nullable).
9
+ * modules build one by hand (system jobs pass `mms_id: null` - `created_by` is nullable).
10
10
  */
11
11
  export type AuditActor = {
12
12
  mms_id: string | null;
13
13
  organization_id: string;
14
14
  };
15
- /** A flat database row — always `select('*')`, never an entity shape with embedded joins. */
15
+ /** A flat database row - always `select('*')`, never an entity shape with embedded joins. */
16
16
  export type AuditRow = Record<string, unknown>;
17
17
  /**
18
18
  * Drops the internal `id bigint` from a row on its way out of the SDK. Every table has one
19
- * alongside its public `mms_id uuid`, and only the database is meant to see it — a caller that
19
+ * alongside its public `mms_id uuid`, and only the database is meant to see it - a caller that
20
20
  * never learns the sequence cannot come to depend on it, or leak how many rows there are.
21
21
  */
22
22
  export declare const withoutInternalId: <T extends Record<string, unknown>>(row: T) => Omit<T, "id">;
@@ -42,12 +42,12 @@ export type AuditErrorHandler = (error: unknown, entries: AuditEntry[]) => void;
42
42
  export declare const AUDIT_EXCLUDED_COLUMNS: ReadonlySet<string>;
43
43
  /**
44
44
  * Columns whose *value* never reaches the trail. `audit_logs` is readable by any member with
45
- * `read_logs`, so a value copied into `changes` is a value published to the whole organization —
45
+ * `read_logs`, so a value copied into `changes` is a value published to the whole organization -
46
46
  * and these are either provider credentials and identifiers (the `paystack_*` customer and
47
47
  * subscription codes, card metadata, tokens, an idempotency key that would let a replay be
48
48
  * forged) or content the base table deliberately withholds (`authenticated` has no grant on
49
49
  * `notifications.rendered_content`, so the rendered email must not arrive by the back door).
50
- * The column still shows up in the trail — that it changed is the point — with both sides
50
+ * The column still shows up in the trail - that it changed is the point - with both sides
51
51
  * replaced by `'[redacted]'`.
52
52
  */
53
53
  export declare const AUDIT_REDACTED_COLUMNS: ReadonlySet<string>;
@@ -57,18 +57,18 @@ export declare const isRedactedColumn: (name: string) => boolean;
57
57
  * The fields that differ between two flat rows, as `{ field: { old, new } }`. Values stay real
58
58
  * JSON (booleans stay booleans, jsonb stays an object) and are compared by serialized value, so
59
59
  * arrays and objects are equal when their contents are. A redacted column is compared on its
60
- * real values and only then redacted, so it is reported when — and only when — it truly moved.
60
+ * real values and only then redacted, so it is reported when - and only when - it truly moved.
61
61
  */
62
62
  export declare const diffChanges: (before: AuditRow, after: AuditRow) => AuditChanges;
63
63
  /**
64
- * A whole row as a change set — `'new'` for an insert, `'old'` for a hard delete — so the trail
64
+ * A whole row as a change set - `'new'` for an insert, `'old'` for a hard delete - so the trail
65
65
  * stays self-contained once the row is gone. Columns that hold nothing are left out; a redacted
66
66
  * column that holds something is present, as `'[redacted]'`.
67
67
  */
68
68
  export declare const snapshotChanges: (row: AuditRow, side: "new" | "old") => AuditChanges;
69
69
  /**
70
70
  * The `audit_logs` table. Reads are the audit trail endpoint; writes go through `record`, which
71
- * every audited write calls for itself — no feature ever writes an audit line by hand.
71
+ * every audited write calls for itself - no feature ever writes an audit line by hand.
72
72
  */
73
73
  export declare class AuditResource {
74
74
  private readonly supabase;
@@ -76,7 +76,7 @@ export declare class AuditResource {
76
76
  private readonly onError?;
77
77
  constructor(supabase: SupabaseJsClient, actor: AuditActor, onError?: AuditErrorHandler | undefined);
78
78
  /**
79
- * Writes the audit row(s) — one insert, however many entries. **Never throws**: a logging
79
+ * Writes the audit row(s) - one insert, however many entries. **Never throws**: a logging
80
80
  * problem must not fail the business write, so a failure goes to `onAuditError`
81
81
  * (default `console.error`) instead.
82
82
  */
@@ -97,7 +97,7 @@ type AuditedPayload = AuditRow | ((before: AuditRow) => AuditRow);
97
97
  /** The keyed update every audited write builds, before any caller-supplied guard. */
98
98
  declare const buildUpdate: (supabase: SupabaseJsClient, table: string, id: string, payload: AuditRow, organizationId: string | null) => import("@supabase/postgrest-js").PostgrestFilterBuilder<any, any, any, null, string, unknown, "PATCH", false>;
99
99
  /**
100
- * Narrows an audited update to the rows it may still touch — the compare-and-set half of a
100
+ * Narrows an audited update to the rows it may still touch - the compare-and-set half of a
101
101
  * guarded write, e.g. `(query) => query.eq('billing_status', expectedFrom)`.
102
102
  */
103
103
  export type AuditGuard = (builder: ReturnType<typeof buildUpdate>) => ReturnType<typeof buildUpdate>;
@@ -106,10 +106,10 @@ export type AuditedTable = {
106
106
  insertMany(payloads: AuditRow[]): Promise<AuditRow[]>;
107
107
  update(id: string, payload: AuditedPayload): Promise<AuditRow>;
108
108
  /**
109
- * An update that only lands while `guard` still holds — the filter *is* the concurrency
109
+ * An update that only lands while `guard` still holds - the filter *is* the concurrency
110
110
  * control, not just a key. Resolves to `null` when the guard matched nothing (someone else
111
111
  * moved the row first): nothing was written, so nothing is audited. Use it wherever losing
112
- * the race must be a no-op rather than an overwrite — billing status moves, the
112
+ * the race must be a no-op rather than an overwrite - billing status moves, the
113
113
  * send-exactly-once gates, first-occurrence-only delivery rollups.
114
114
  */
115
115
  updateWhere(id: string, guard: AuditGuard, payload: AuditedPayload): Promise<AuditRow | null>;
@@ -21,7 +21,7 @@ const transforms_1 = require("../transforms");
21
21
  __exportStar(require("./dto"), exports);
22
22
  /**
23
23
  * Drops the internal `id bigint` from a row on its way out of the SDK. Every table has one
24
- * alongside its public `mms_id uuid`, and only the database is meant to see it — a caller that
24
+ * alongside its public `mms_id uuid`, and only the database is meant to see it - a caller that
25
25
  * never learns the sequence cannot come to depend on it, or leak how many rows there are.
26
26
  */
27
27
  const withoutInternalId = (row) => {
@@ -56,12 +56,12 @@ exports.AUDIT_EXCLUDED_COLUMNS = new Set([
56
56
  ]);
57
57
  /**
58
58
  * Columns whose *value* never reaches the trail. `audit_logs` is readable by any member with
59
- * `read_logs`, so a value copied into `changes` is a value published to the whole organization —
59
+ * `read_logs`, so a value copied into `changes` is a value published to the whole organization -
60
60
  * and these are either provider credentials and identifiers (the `paystack_*` customer and
61
61
  * subscription codes, card metadata, tokens, an idempotency key that would let a replay be
62
62
  * forged) or content the base table deliberately withholds (`authenticated` has no grant on
63
63
  * `notifications.rendered_content`, so the rendered email must not arrive by the back door).
64
- * The column still shows up in the trail — that it changed is the point — with both sides
64
+ * The column still shows up in the trail - that it changed is the point - with both sides
65
65
  * replaced by `'[redacted]'`.
66
66
  */
67
67
  exports.AUDIT_REDACTED_COLUMNS = new Set([
@@ -87,7 +87,7 @@ const normalize = (value) => value ?? null;
87
87
  * The fields that differ between two flat rows, as `{ field: { old, new } }`. Values stay real
88
88
  * JSON (booleans stay booleans, jsonb stays an object) and are compared by serialized value, so
89
89
  * arrays and objects are equal when their contents are. A redacted column is compared on its
90
- * real values and only then redacted, so it is reported when — and only when — it truly moved.
90
+ * real values and only then redacted, so it is reported when - and only when - it truly moved.
91
91
  */
92
92
  const diffChanges = (before, after) => {
93
93
  const changes = {};
@@ -106,7 +106,7 @@ const diffChanges = (before, after) => {
106
106
  };
107
107
  exports.diffChanges = diffChanges;
108
108
  /**
109
- * A whole row as a change set — `'new'` for an insert, `'old'` for a hard delete — so the trail
109
+ * A whole row as a change set - `'new'` for an insert, `'old'` for a hard delete - so the trail
110
110
  * stays self-contained once the row is gone. Columns that hold nothing are left out; a redacted
111
111
  * column that holds something is present, as `'[redacted]'`.
112
112
  */
@@ -134,7 +134,7 @@ const AUDIT_TRAIL_SELECT = `
134
134
  `;
135
135
  /**
136
136
  * The `audit_logs` table. Reads are the audit trail endpoint; writes go through `record`, which
137
- * every audited write calls for itself — no feature ever writes an audit line by hand.
137
+ * every audited write calls for itself - no feature ever writes an audit line by hand.
138
138
  */
139
139
  class AuditResource {
140
140
  supabase;
@@ -146,7 +146,7 @@ class AuditResource {
146
146
  this.onError = onError;
147
147
  }
148
148
  /**
149
- * Writes the audit row(s) — one insert, however many entries. **Never throws**: a logging
149
+ * Writes the audit row(s) - one insert, however many entries. **Never throws**: a logging
150
150
  * problem must not fail the business write, so a failure goes to `onAuditError`
151
151
  * (default `console.error`) instead.
152
152
  */
@@ -257,7 +257,7 @@ const auditedTable = (supabase, actor, audit, options) => {
257
257
  throw (0, errors_1.mapPostgrestError)(error);
258
258
  const after = data;
259
259
  await recordWrite(id, action, before, after);
260
- // `before` stays whole — it is internal to the diff — but what the caller gets does not
260
+ // `before` stays whole - it is internal to the diff - but what the caller gets does not
261
261
  // carry the internal `id`.
262
262
  return (0, exports.withoutInternalId)(after);
263
263
  };
@@ -269,7 +269,7 @@ const auditedTable = (supabase, actor, audit, options) => {
269
269
  .maybeSingle();
270
270
  if (error)
271
271
  throw (0, errors_1.mapPostgrestError)(error);
272
- // The guard lost the race — the row is untouched, so there is nothing to audit.
272
+ // The guard lost the race - the row is untouched, so there is nothing to audit.
273
273
  if (!data)
274
274
  return null;
275
275
  const after = data;
@@ -27,13 +27,13 @@ export declare class SupabaseAuthClient {
27
27
  * other sessions alone.
28
28
  */
29
29
  signOut(accessToken: string): Promise<void>;
30
- /** `redirectTo` is passed in — the SDK reads no environment variables. */
30
+ /** `redirectTo` is passed in - the SDK reads no environment variables. */
31
31
  sendPasswordResetEmail(input: SendPasswordResetEmailDto, redirectTo: string): Promise<void>;
32
32
  exchangeRecoveryToken(input: ExchangeTokenHashDto): Promise<ExchangeTokenResponse>;
33
33
  exchangeConfirmToken(input: ExchangeTokenHashDto): Promise<ExchangeTokenResponse>;
34
34
  resetPassword(input: ResetPasswordDto): Promise<void>;
35
35
  /**
36
- * Stamps `users.last_logged_in` as the user who just signed in — the users UPDATE policy
36
+ * Stamps `users.last_logged_in` as the user who just signed in - the users UPDATE policy
37
37
  * allows `auth.uid() = mms_id`, so the write goes through a throwaway client carrying the
38
38
  * fresh access token rather than this session-less one. Parity with the api: the result is
39
39
  * ignored, a failed stamp must not fail the login.
@@ -74,7 +74,7 @@ class SupabaseAuthClient {
74
74
  if (error)
75
75
  throw (0, errors_1.mapAuthError)(error);
76
76
  }
77
- /** `redirectTo` is passed in — the SDK reads no environment variables. */
77
+ /** `redirectTo` is passed in - the SDK reads no environment variables. */
78
78
  async sendPasswordResetEmail(input, redirectTo) {
79
79
  const { error } = await this.supabase.auth.resetPasswordForEmail(input.email, { redirectTo });
80
80
  if (error)
@@ -116,7 +116,7 @@ class SupabaseAuthClient {
116
116
  throw (0, errors_1.mapAuthError)(updateError);
117
117
  }
118
118
  /**
119
- * Stamps `users.last_logged_in` as the user who just signed in — the users UPDATE policy
119
+ * Stamps `users.last_logged_in` as the user who just signed in - the users UPDATE policy
120
120
  * allows `auth.uid() = mms_id`, so the write goes through a throwaway client carrying the
121
121
  * fresh access token rather than this session-less one. Parity with the api: the result is
122
122
  * ignored, a failed stamp must not fail the login.
package/dist/auth.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type SupabaseClient as SupabaseJsClient } from '@supabase/supabase-js';
2
2
  /**
3
- * A raw supabase-js client, built the one way the SDK ever needs one: server-side and stateless —
4
- * nothing is stored, refreshed or read out of a URL — carrying the caller's bearer token when
3
+ * A raw supabase-js client, built the one way the SDK ever needs one: server-side and stateless -
4
+ * nothing is stored, refreshed or read out of a URL - carrying the caller's bearer token when
5
5
  * there is one, so RLS sees them.
6
6
  */
7
7
  export declare const createRawClient: (url: string, key: string, accessToken?: string) => SupabaseJsClient;
@@ -42,7 +42,7 @@ export type VerifyAccessTokenConfig = {
42
42
  /**
43
43
  * Establishes who the caller is, from a token whose signature is actually checked.
44
44
  *
45
- * Identity comes from the verified JWT and tenancy comes from `public.users` — never from
45
+ * Identity comes from the verified JWT and tenancy comes from `public.users` - never from
46
46
  * `user_metadata`, which the end user can rewrite through GoTrue. The profile read runs under the
47
47
  * caller's own RLS (a user may read their own row), so the `organization_id` returned here is the
48
48
  * same one `current_user_organization_id()` gives every policy.
package/dist/auth.js CHANGED
@@ -4,8 +4,8 @@ exports.verifyAccessToken = exports.createRawClient = void 0;
4
4
  const supabase_js_1 = require("@supabase/supabase-js");
5
5
  const errors_1 = require("./errors");
6
6
  /**
7
- * A raw supabase-js client, built the one way the SDK ever needs one: server-side and stateless —
8
- * nothing is stored, refreshed or read out of a URL — carrying the caller's bearer token when
7
+ * A raw supabase-js client, built the one way the SDK ever needs one: server-side and stateless -
8
+ * nothing is stored, refreshed or read out of a URL - carrying the caller's bearer token when
9
9
  * there is one, so RLS sees them.
10
10
  */
11
11
  const createRawClient = (url, key, accessToken) => (0, supabase_js_1.createClient)(url, key, {
@@ -21,7 +21,7 @@ const authError = (message) => new errors_1.SupabaseClientError({ status: 401, e
21
21
  /**
22
22
  * Establishes who the caller is, from a token whose signature is actually checked.
23
23
  *
24
- * Identity comes from the verified JWT and tenancy comes from `public.users` — never from
24
+ * Identity comes from the verified JWT and tenancy comes from `public.users` - never from
25
25
  * `user_metadata`, which the end user can rewrite through GoTrue. The profile read runs under the
26
26
  * caller's own RLS (a user may read their own row), so the `organization_id` returned here is the
27
27
  * same one `current_user_organization_id()` gives every policy.
@@ -85,7 +85,7 @@ __decorate([
85
85
  __metadata("design:type", String)
86
86
  ], PaymentIdDto.prototype, "paymentId", void 0);
87
87
  // `status_in` and `type_in` arrive as CSV strings and are transformed to enum values, so
88
- // they are omitted from the implemented contract — the same exception GetInvoicesQueryDto makes.
88
+ // they are omitted from the implemented contract - the same exception GetInvoicesQueryDto makes.
89
89
  class GetPaymentsQueryDto {
90
90
  page = 1;
91
91
  page_size = 25;
@@ -8,7 +8,7 @@ export * from './dto';
8
8
  /**
9
9
  * The readable half of the ledger. `billing_transactions` and `billing_events` are org-scoped
10
10
  * for READ, and the catalog is readable by `anon`, so all of this runs perfectly well on the
11
- * caller's own client — and should, so that RLS is what scopes it rather than a filter the api
11
+ * caller's own client - and should, so that RLS is what scopes it rather than a filter the api
12
12
  * remembered to add.
13
13
  */
14
14
  export declare class BillingReadsResource {
@@ -24,15 +24,15 @@ export declare class BillingReadsResource {
24
24
  * status. RLS scopes the rows; the eq is belt-and-braces.
25
25
  */
26
26
  listPayments(query: GetPaymentsQueryDto): Promise<MmsList<PaymentSummary>>;
27
- /** The full payment record, or null — RLS makes another tenant's id look missing. */
27
+ /** The full payment record, or null - RLS makes another tenant's id look missing. */
28
28
  paymentById(paymentId: string): Promise<PaymentEntity | null>;
29
29
  /** Whether a catalog module is still on sale. The catalog is public, not tenant-scoped. */
30
30
  isModuleActive(moduleId: string): Promise<boolean>;
31
31
  }
32
32
  /**
33
33
  * The ledger writes, on the service client only. Every write policy on `billing_transactions`
34
- * and `billing_events` is `false` for `authenticated` — the ledger is written by the billing
35
- * service and by webhooks, never by a caller — so these are deliberately not reachable from
34
+ * and `billing_events` is `false` for `authenticated` - the ledger is written by the billing
35
+ * service and by webhooks, never by a caller - so these are deliberately not reachable from
36
36
  * `SupabaseClient`.
37
37
  */
38
38
  export declare class BillingResource extends BillingReadsResource {
@@ -25,7 +25,7 @@ const BILLING_TRANSACTIONS_TABLE = {
25
25
  table: 'billing_transactions',
26
26
  entityType: enum_1.AuditTrailEntityType.BILLING_TRANSACTIONS,
27
27
  };
28
- /** The wire shape of a payment list row — never the internal bigint id, never the checkout secrets. */
28
+ /** The wire shape of a payment list row - never the internal bigint id, never the checkout secrets. */
29
29
  const PAYMENT_SUMMARY_SELECT = 'mms_id, created_at, transaction_type, transaction_status, amount_minor, currency, paid_at, failure_reason';
30
30
  /**
31
31
  * PaymentEntity, column for column, plus the renewal's invoice through the unique
@@ -36,7 +36,7 @@ const PAYMENT_ENTITY_SELECT = `${PAYMENT_SUMMARY_SELECT}, updated_at, active_sea
36
36
  /**
37
37
  * The readable half of the ledger. `billing_transactions` and `billing_events` are org-scoped
38
38
  * for READ, and the catalog is readable by `anon`, so all of this runs perfectly well on the
39
- * caller's own client — and should, so that RLS is what scopes it rather than a filter the api
39
+ * caller's own client - and should, so that RLS is what scopes it rather than a filter the api
40
40
  * remembered to add.
41
41
  */
42
42
  class BillingReadsResource {
@@ -97,7 +97,7 @@ class BillingReadsResource {
97
97
  throw (0, errors_1.mapPostgrestError)(error);
98
98
  return (0, transforms_1.toMmsList)(data, page, pageSize, count);
99
99
  }
100
- /** The full payment record, or null — RLS makes another tenant's id look missing. */
100
+ /** The full payment record, or null - RLS makes another tenant's id look missing. */
101
101
  async paymentById(paymentId) {
102
102
  const { data, error } = await this.supabase
103
103
  .from('billing_transactions')
@@ -130,8 +130,8 @@ class BillingReadsResource {
130
130
  exports.BillingReadsResource = BillingReadsResource;
131
131
  /**
132
132
  * The ledger writes, on the service client only. Every write policy on `billing_transactions`
133
- * and `billing_events` is `false` for `authenticated` — the ledger is written by the billing
134
- * service and by webhooks, never by a caller — so these are deliberately not reachable from
133
+ * and `billing_events` is `false` for `authenticated` - the ledger is written by the billing
134
+ * service and by webhooks, never by a caller - so these are deliberately not reachable from
135
135
  * `SupabaseClient`.
136
136
  */
137
137
  class BillingResource extends BillingReadsResource {
@@ -24,7 +24,7 @@ __decorate([
24
24
  __metadata("design:type", String)
25
25
  ], InvoiceIdDto.prototype, "invoiceId", void 0);
26
26
  // `status_in` arrives as a CSV string and is transformed to enum values, so it is omitted
27
- // from the implemented contract — the same exception GetUsersQueryDto makes for active_in.
27
+ // from the implemented contract - the same exception GetUsersQueryDto makes for active_in.
28
28
  class GetInvoicesQueryDto {
29
29
  page = 1;
30
30
  page_size = 25;
@@ -6,7 +6,7 @@ import type { GetInvoicesQueryDto } from './dto';
6
6
  export * from './dto';
7
7
  /**
8
8
  * The readable half. `invoices` is SELECT-only for `authenticated` with an org-predicate
9
- * policy — the same posture as the ledger it mirrors — so reads run on the caller's own
9
+ * policy - the same posture as the ledger it mirrors - so reads run on the caller's own
10
10
  * client and RLS is what scopes them.
11
11
  */
12
12
  export declare class InvoicesReadsResource {
@@ -15,14 +15,14 @@ export declare class InvoicesReadsResource {
15
15
  constructor(supabase: TypedSupabaseClient, actor: AuditActor);
16
16
  /** The organization's invoices, newest first. RLS scopes the rows; the eq is belt-and-braces. */
17
17
  list(query: GetInvoicesQueryDto): Promise<MmsList<InvoiceSummary>>;
18
- /** The full invoice document data, or null — RLS makes another tenant's id look missing. */
18
+ /** The full invoice document data, or null - RLS makes another tenant's id look missing. */
19
19
  byId(invoiceId: string): Promise<InvoiceEntity | null>;
20
20
  }
21
21
  /**
22
- * The document writes, on the service client only — every write policy on `invoices` is
22
+ * The document writes, on the service client only - every write policy on `invoices` is
23
23
  * `false` for `authenticated`. Rows are written by the api's billing service beside a
24
- * renewal's ledger write — a renewal is inserted already paid, so an invoice never moves
25
- * status after it is issued — never by a caller.
24
+ * renewal's ledger write - a renewal is inserted already paid, so an invoice never moves
25
+ * status after it is issued - never by a caller.
26
26
  */
27
27
  export declare class InvoicesResource extends InvoicesReadsResource {
28
28
  private readonly invoices;
@@ -32,7 +32,7 @@ export declare class InvoicesResource extends InvoicesReadsResource {
32
32
  /**
33
33
  * The organization's highest invoice number, 0 when it has none. The api assigns
34
34
  * `max + 1` and lets the `(organization_id, invoice_number)` unique index arbitrate a
35
- * concurrent insert — the retry lives there, not here.
35
+ * concurrent insert - the retry lives there, not here.
36
36
  */
37
37
  maxNumber(): Promise<number>;
38
38
  }
@@ -24,13 +24,13 @@ const INVOICES_TABLE = {
24
24
  table: 'invoices',
25
25
  entityType: enum_1.AuditTrailEntityType.INVOICE,
26
26
  };
27
- /** The wire shape of an invoice list row — never the internal bigint id. */
27
+ /** The wire shape of an invoice list row - never the internal bigint id. */
28
28
  const INVOICE_SUMMARY_SELECT = 'mms_id, invoice_number, transaction_type, status, issued_at, paid_at, amount_minor, currency';
29
29
  /** InvoiceEntity, column for column: the summary plus the frozen document data. */
30
30
  const INVOICE_ENTITY_SELECT = `${INVOICE_SUMMARY_SELECT}, billing_transaction_id, paystack_reference, period_start, period_end, line_items, billed_to`;
31
31
  /**
32
32
  * The readable half. `invoices` is SELECT-only for `authenticated` with an org-predicate
33
- * policy — the same posture as the ledger it mirrors — so reads run on the caller's own
33
+ * policy - the same posture as the ledger it mirrors - so reads run on the caller's own
34
34
  * client and RLS is what scopes them.
35
35
  */
36
36
  class InvoicesReadsResource {
@@ -61,7 +61,7 @@ class InvoicesReadsResource {
61
61
  throw (0, errors_1.mapPostgrestError)(error);
62
62
  return (0, transforms_1.toMmsList)(data, page, pageSize, count);
63
63
  }
64
- /** The full invoice document data, or null — RLS makes another tenant's id look missing. */
64
+ /** The full invoice document data, or null - RLS makes another tenant's id look missing. */
65
65
  async byId(invoiceId) {
66
66
  const { data, error } = await this.supabase
67
67
  .from('invoices')
@@ -76,10 +76,10 @@ class InvoicesReadsResource {
76
76
  }
77
77
  exports.InvoicesReadsResource = InvoicesReadsResource;
78
78
  /**
79
- * The document writes, on the service client only — every write policy on `invoices` is
79
+ * The document writes, on the service client only - every write policy on `invoices` is
80
80
  * `false` for `authenticated`. Rows are written by the api's billing service beside a
81
- * renewal's ledger write — a renewal is inserted already paid, so an invoice never moves
82
- * status after it is issued — never by a caller.
81
+ * renewal's ledger write - a renewal is inserted already paid, so an invoice never moves
82
+ * status after it is issued - never by a caller.
83
83
  */
84
84
  class InvoicesResource extends InvoicesReadsResource {
85
85
  invoices;
@@ -94,7 +94,7 @@ class InvoicesResource extends InvoicesReadsResource {
94
94
  /**
95
95
  * The organization's highest invoice number, 0 when it has none. The api assigns
96
96
  * `max + 1` and lets the `(organization_id, invoice_number)` unique index arbitrate a
97
- * concurrent insert — the retry lives there, not here.
97
+ * concurrent insert - the retry lives there, not here.
98
98
  */
99
99
  async maxNumber() {
100
100
  const { data, error } = await this.supabase