@managemint-solutions/sdk 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/README.md +88 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +30 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +23 -0
- package/dist/client.js.map +1 -0
- package/dist/database.types.d.ts +2570 -0
- package/dist/database.types.js +12 -0
- package/dist/database.types.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +132 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/nest/index.d.ts +9 -0
- package/dist/nest/index.js +47 -0
- package/dist/nest/index.js.map +1 -0
- package/dist/statuses/dto.d.ts +20 -0
- package/dist/statuses/dto.js +75 -0
- package/dist/statuses/dto.js.map +1 -0
- package/dist/statuses/index.d.ts +15 -0
- package/dist/statuses/index.js +86 -0
- package/dist/statuses/index.js.map +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ManageMint Solutions SDK
|
|
2
|
+
|
|
3
|
+
Typed Supabase data-access SDK for ManageMint Solutions. Query logic and the generated
|
|
4
|
+
database types live here; consumers (api, portal) get typed responses with no casts.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @managemint-solutions/sdk @supabase/supabase-js
|
|
10
|
+
```
|
|
11
|
+
|
|
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`,
|
|
14
|
+
`GetStatusesDto`) ship with their class-validator decorators so consumers validate against
|
|
15
|
+
the same rules the SDK's input types describe. `@nestjs/common` (^11) is an optional peer
|
|
16
|
+
dependency, needed only for the `@managemint-solutions/sdk/nest` subpath.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createSupabaseClient, isPostgrestError } from '@managemint-solutions/sdk';
|
|
22
|
+
import type { StatusEntity } from '@managemint-solutions/entities/status';
|
|
23
|
+
import { StatusEntityTypes } from '@managemint-solutions/entities/status/enum';
|
|
24
|
+
|
|
25
|
+
const supabase = createSupabaseClient({
|
|
26
|
+
url: process.env.SUPABASE_URL!,
|
|
27
|
+
key: process.env.SUPABASE_PUBLISHABLE_KEY!, // queries run under the caller's RLS
|
|
28
|
+
accessToken: userJwt,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Inputs and outputs are the shared types from @managemint-solutions/entities.
|
|
32
|
+
const statuses: StatusEntity[] = await supabase.statuses.list(StatusEntityTypes.PROJECT);
|
|
33
|
+
const created = await supabase.statuses.create({
|
|
34
|
+
label: 'In progress',
|
|
35
|
+
colour: '#2ecc71',
|
|
36
|
+
entity: StatusEntityTypes.PROJECT,
|
|
37
|
+
});
|
|
38
|
+
await supabase.statuses.update(created.mms_id, { label: 'Doing', colour: '#2ecc71' });
|
|
39
|
+
await supabase.statuses.remove(created.mms_id);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The client decodes `{ mms_id, organization_id }` from the JWT itself and scopes every query
|
|
43
|
+
by `organization_id`. `supabase.supabase` is the raw typed `@supabase/supabase-js` client
|
|
44
|
+
(`TypedSupabaseClient`) — the escape hatch for queries not yet covered by a resource.
|
|
45
|
+
|
|
46
|
+
## Errors
|
|
47
|
+
|
|
48
|
+
Every error the SDK throws is a `SupabaseClientError` carrying the HTTP `status` to respond
|
|
49
|
+
with and the client-facing body, `toResponse()` → `{ error, message }`. Postgrest/Postgres
|
|
50
|
+
errors are translated by `mapPostgrestError` (Postgrest code → status, RLS permission and
|
|
51
|
+
module denials → readable messages); JWT problems are `401 Authentication Error`. Consumers
|
|
52
|
+
return these as-is instead of mapping database errors themselves.
|
|
53
|
+
|
|
54
|
+
## NestJS
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { SupabaseUserClient } from '@managemint-solutions/sdk/nest';
|
|
58
|
+
import { GetStatusesDto, SupabaseClient } from '@managemint-solutions/sdk';
|
|
59
|
+
|
|
60
|
+
@Controller('statuses')
|
|
61
|
+
export class StatusesController {
|
|
62
|
+
@Get(':entity')
|
|
63
|
+
getStatuses(@SupabaseUserClient() supabase: SupabaseClient, @Param() params: GetStatusesDto) {
|
|
64
|
+
return supabase.statuses.list(params.entity);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The decorator builds a per-request client from the request's `Bearer` token plus the
|
|
70
|
+
`SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` environment variables. A missing or
|
|
71
|
+
malformed token becomes an `UnauthorizedException`. The DTOs work with Nest's global
|
|
72
|
+
`ValidationPipe` as-is (`whitelist`, `forbidNonWhitelisted`, `transform`).
|
|
73
|
+
|
|
74
|
+
`toHttpException(error)` converts a `SupabaseClientError` into an `HttpException` with the
|
|
75
|
+
same status and body — call it from your global exception filter so services can let SDK
|
|
76
|
+
errors propagate without any try/catch.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
npm test # jest + ts-jest; specs live in src/<resource>/tests/*.spec.ts
|
|
83
|
+
npm run build # tsc -p tsconfig.build.json (excludes specs), CommonJS output in dist/ — the type gate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`src/database.types.ts` is generated by `supabase gen types typescript` and is committed
|
|
87
|
+
here; regenerating it produces a reviewable diff and requires a version bump.
|
|
88
|
+
Publishing happens on push to `main` via `.github/workflows/release.yml`.
|
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeAuthContext = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const authError = (message) => new errors_1.SupabaseClientError({ status: 401, error: 'Authentication Error', message });
|
|
6
|
+
const decodeBase64Url = (segment) => typeof Buffer !== 'undefined'
|
|
7
|
+
? Buffer.from(segment, 'base64url').toString('utf8')
|
|
8
|
+
: atob(segment.replace(/-/g, '+').replace(/_/g, '/')); // browser (portal later)
|
|
9
|
+
const decodeAuthContext = (accessToken) => {
|
|
10
|
+
const parts = accessToken.split('.');
|
|
11
|
+
if (parts.length !== 3)
|
|
12
|
+
throw authError('Malformed JWT');
|
|
13
|
+
let payload;
|
|
14
|
+
try {
|
|
15
|
+
payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw authError('Invalid JWT payload');
|
|
19
|
+
}
|
|
20
|
+
const mms_id = payload.sub;
|
|
21
|
+
const organization_id = payload.user_metadata?.organization_id;
|
|
22
|
+
if (typeof mms_id !== 'string' ||
|
|
23
|
+
typeof organization_id !== 'string' ||
|
|
24
|
+
organization_id.trim() === '') {
|
|
25
|
+
throw authError('JWT is missing mms_id/organization_id claims');
|
|
26
|
+
}
|
|
27
|
+
return { mms_id, organization_id };
|
|
28
|
+
};
|
|
29
|
+
exports.decodeAuthContext = decodeAuthContext;
|
|
30
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;AAAA,qCAA+C;AAI/C,MAAM,SAAS,GAAG,CAAC,OAAe,EAAuB,EAAE,CACzD,IAAI,4BAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,CAAC;AAEnF,MAAM,eAAe,GAAG,CAAC,OAAe,EAAU,EAAE,CAClD,OAAO,MAAM,KAAK,WAAW;IAC3B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACpD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,yBAAyB;AAE7E,MAAM,iBAAiB,GAAG,CAAC,WAAmB,EAAe,EAAE;IACpE,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,SAAS,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,OAAyE,CAAC;IAC9E,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAC3B,MAAM,eAAe,GAAG,OAAO,CAAC,aAAa,EAAE,eAAe,CAAC;IAC/D,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,OAAO,eAAe,KAAK,QAAQ;QACnC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,EAC7B,CAAC;QACD,MAAM,SAAS,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;AACrC,CAAC,CAAC;AAtBW,QAAA,iBAAiB,qBAsB5B"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SupabaseClient as SupabaseJsClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { Database } from './database.types';
|
|
3
|
+
import { type AuthContext } from './auth';
|
|
4
|
+
import { StatusesResource } from './statuses';
|
|
5
|
+
export type TypedSupabaseClient = SupabaseJsClient<Database>;
|
|
6
|
+
export type SupabaseClientConfig = {
|
|
7
|
+
url: string;
|
|
8
|
+
key: string;
|
|
9
|
+
accessToken: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class SupabaseClient {
|
|
12
|
+
readonly supabase: TypedSupabaseClient;
|
|
13
|
+
readonly auth: AuthContext;
|
|
14
|
+
readonly statuses: StatusesResource;
|
|
15
|
+
constructor(config: SupabaseClientConfig);
|
|
16
|
+
}
|
|
17
|
+
export declare const createSupabaseClient: (config: SupabaseClientConfig) => SupabaseClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSupabaseClient = exports.SupabaseClient = void 0;
|
|
4
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
5
|
+
const auth_1 = require("./auth");
|
|
6
|
+
const statuses_1 = require("./statuses");
|
|
7
|
+
class SupabaseClient {
|
|
8
|
+
supabase; // escape hatch for not-yet-migrated queries
|
|
9
|
+
auth;
|
|
10
|
+
statuses;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.auth = (0, auth_1.decodeAuthContext)(config.accessToken);
|
|
13
|
+
this.supabase = (0, supabase_js_1.createClient)(config.url, config.key, {
|
|
14
|
+
global: { headers: { Authorization: `Bearer ${config.accessToken}` } },
|
|
15
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
16
|
+
});
|
|
17
|
+
this.statuses = new statuses_1.StatusesResource(this.supabase, this.auth);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.SupabaseClient = SupabaseClient;
|
|
21
|
+
const createSupabaseClient = (config) => new SupabaseClient(config);
|
|
22
|
+
exports.createSupabaseClient = createSupabaseClient;
|
|
23
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,uDAA8F;AAE9F,iCAA6D;AAC7D,yCAA8C;AAU9C,MAAa,cAAc;IAChB,QAAQ,CAAsB,CAAC,4CAA4C;IAC3E,IAAI,CAAc;IAClB,QAAQ,CAAmB;IAEpC,YAAY,MAA4B;QACtC,IAAI,CAAC,IAAI,GAAG,IAAA,wBAAiB,EAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,IAAA,0BAAY,EAAW,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE;YAC7D,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE;YACtE,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;SACzD,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,GAAG,IAAI,2BAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC;CACF;AAbD,wCAaC;AAEM,MAAM,oBAAoB,GAAG,CAAC,MAA4B,EAAkB,EAAE,CACnF,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;AADhB,QAAA,oBAAoB,wBACJ"}
|