nomkit 0.0.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.
Files changed (82) hide show
  1. package/LICENSE.txt +21 -0
  2. package/dist/_virtual/_rolldown/runtime.js +27 -0
  3. package/dist/adapters/index.d.ts +15 -0
  4. package/dist/adapters/index.js +6 -0
  5. package/dist/cli/commands/push.d.ts +6 -0
  6. package/dist/cli/commands/push.js +143 -0
  7. package/dist/cli/index.d.ts +4 -0
  8. package/dist/cli/index.js +18 -0
  9. package/dist/cli/lib/collection_sync.d.ts +107 -0
  10. package/dist/cli/lib/collection_sync.js +158 -0
  11. package/dist/cli/lib/config_loader.d.ts +15 -0
  12. package/dist/cli/lib/config_loader.js +43 -0
  13. package/dist/cli/lib/hash.d.ts +22 -0
  14. package/dist/cli/lib/hash.js +63 -0
  15. package/dist/cli/lib/migrations.d.ts +6 -0
  16. package/dist/cli/lib/migrations.js +17 -0
  17. package/dist/client/index.d.ts +13 -0
  18. package/dist/client/index.js +34 -0
  19. package/dist/core/nomba_api/banks.d.ts +14 -0
  20. package/dist/core/nomba_api/banks.js +0 -0
  21. package/dist/core/nomba_api/charge-tokenized-card.d.ts +33 -0
  22. package/dist/core/nomba_api/charge-tokenized-card.js +0 -0
  23. package/dist/core/nomba_api/checkout.d.ts +44 -0
  24. package/dist/core/nomba_api/checkout.js +0 -0
  25. package/dist/core/nomba_api/get_checkout.d.ts +57 -0
  26. package/dist/core/nomba_api/get_checkout.js +0 -0
  27. package/dist/core/nomba_api/index.d.ts +313 -0
  28. package/dist/core/nomba_api/index.js +179 -0
  29. package/dist/core/nomba_api/lib/utils.d.ts +235 -0
  30. package/dist/core/nomba_api/lib/utils.js +313 -0
  31. package/dist/core/nomba_api/list-tokenized-cards.d.ts +24 -0
  32. package/dist/core/nomba_api/list-tokenized-cards.js +0 -0
  33. package/dist/core/nomba_api/token-manager/index.d.ts +51 -0
  34. package/dist/core/nomba_api/token-manager/index.js +109 -0
  35. package/dist/core/pg_db/index.d.ts +108 -0
  36. package/dist/core/pg_db/index.js +76 -0
  37. package/dist/core/pg_db/migrations/20260703085901_wealthy_blacklash/migration.sql +120 -0
  38. package/dist/core/pg_db/migrations/20260703085901_wealthy_blacklash/snapshot.json +1616 -0
  39. package/dist/core/pg_db/relations.d.ts +46 -0
  40. package/dist/core/pg_db/relations.js +83 -0
  41. package/dist/core/pg_db/schema.d.ts +1138 -0
  42. package/dist/core/pg_db/schema.js +124 -0
  43. package/dist/endpoints/customers/api.js +51 -0
  44. package/dist/endpoints/entitlements/api.js +42 -0
  45. package/dist/endpoints/routes.d.ts +15 -0
  46. package/dist/endpoints/routes.js +15 -0
  47. package/dist/endpoints/subscriptions/api.js +263 -0
  48. package/dist/endpoints/subscriptions/utils.js +105 -0
  49. package/dist/endpoints/webhooks/invoice/api.js +28 -0
  50. package/dist/endpoints/webhooks/nomba/api.js +76 -0
  51. package/dist/endpoints/webhooks/nomba/utils.js +36 -0
  52. package/dist/index.d.ts +204 -0
  53. package/dist/index.js +175 -0
  54. package/dist/lib/utils.d.ts +21 -0
  55. package/dist/lib/utils.js +41 -0
  56. package/dist/node_modules/.pnpm/@better-fetch_fetch@1.3.1/node_modules/@better-fetch/fetch/dist/index.js +475 -0
  57. package/dist/package.js +4 -0
  58. package/dist/queue/backends/pglite/backend.d.ts +43 -0
  59. package/dist/queue/backends/pglite/backend.js +33 -0
  60. package/dist/queue/backends/pglite/index.d.ts +4 -0
  61. package/dist/queue/backends/pglite/index.js +4 -0
  62. package/dist/queue/backends/pglite/migrations/schema.d.ts +4 -0
  63. package/dist/queue/backends/pglite/migrations/schema.js +37 -0
  64. package/dist/queue/backends/pglite/notification-channel.d.ts +17 -0
  65. package/dist/queue/backends/pglite/notification-channel.js +61 -0
  66. package/dist/queue/backends/pglite/repository.d.ts +38 -0
  67. package/dist/queue/backends/pglite/repository.js +299 -0
  68. package/dist/queue/backends/redis/index.d.ts +7 -0
  69. package/dist/queue/backends/redis/index.js +1 -0
  70. package/dist/queue/client/index.d.ts +12 -0
  71. package/dist/queue/client/index.js +31 -0
  72. package/dist/queue/endpoints/api.d.ts +53 -0
  73. package/dist/queue/endpoints/api.js +45 -0
  74. package/dist/queue/endpoints/routes.d.ts +32 -0
  75. package/dist/queue/endpoints/routes.js +5 -0
  76. package/dist/queue/init.d.ts +27 -0
  77. package/dist/queue/init.js +31 -0
  78. package/dist/queue/lib/billing.d.ts +25 -0
  79. package/dist/queue/lib/billing.js +87 -0
  80. package/dist/queue/lib/utils.d.ts +30 -0
  81. package/dist/queue/lib/utils.js +35 -0
  82. package/package.json +71 -0
@@ -0,0 +1,76 @@
1
+ import { __exportAll } from "../../../_virtual/_rolldown/runtime.js";
2
+ import { subscriptions } from "../../../core/pg_db/schema.js";
3
+ import { defineNomkitMethod } from "../../../lib/utils.js";
4
+ import { getPaymentInterval, isValidSignature } from "./utils.js";
5
+ import z from "zod";
6
+ import { eq } from "drizzle-orm";
7
+ //#region endpoints/webhooks/nomba/api.ts
8
+ var api_exports = /* @__PURE__ */ __exportAll({ nombaWebhook: () => nombaWebhook });
9
+ const nombaWebhook = defineNomkitMethod({
10
+ input: z.object({ event_type: z.enum(["payment_success", "payment_failed"]) }),
11
+ route: {
12
+ path: "/webhooks/nomba",
13
+ clientOnly: true,
14
+ requireHeaders: true
15
+ }
16
+ }, async (ctx) => {
17
+ const db = ctx.nomkit.database;
18
+ const ev = ctx.input;
19
+ const meta = ev.data?.order?.orderMetaData;
20
+ if (!meta) return ctx.json({ msg: "Invalid event metadata" }, { status: 404 });
21
+ if (!isValidSignature({
22
+ headers: ctx.headers,
23
+ ev,
24
+ nomkit: ctx.nomkit
25
+ })) return ctx.json({ msg: "Invalid webhook payload bad signature" }, { status: 404 });
26
+ const subscriptionId = meta["nomkit.subscription.id"];
27
+ const previousSubscriptionId = meta["nomkit.subscription.previous_subscription_id"];
28
+ const kind = meta["nomkit.subscription.kind"];
29
+ const subscription = await db.query.subscriptions.findFirst({ where: { id: subscriptionId } });
30
+ if (!subscription) {
31
+ console.log("Webhook for unknown subscription");
32
+ return ctx.json({ msg: "Unknown webhook subscription" }, { status: 500 });
33
+ }
34
+ if (subscription.customerId !== meta["nomkit.subscription.customer_id"]) {
35
+ console.log("Webhook metadata customer mismatch");
36
+ return ctx.json({ msg: "Webhook metadata customer mismatch" }, { status: 404 });
37
+ }
38
+ if (subscription.status === "active") {
39
+ console.log("Already processed");
40
+ return ctx.json({ msg: "Already processed" });
41
+ }
42
+ if (ev.event_type === "payment_failed") {
43
+ console.log("Payment failed, subscription canceled");
44
+ await db.update(subscriptions).set({
45
+ status: "cancelled",
46
+ endedAt: /* @__PURE__ */ new Date()
47
+ }).where(eq(subscriptions.id, subscription.id));
48
+ return ctx.json({ msg: "Payment failed, subscription canceled" });
49
+ }
50
+ const plan = await db.query.products.findFirst({ where: { internalId: subscription.productInternalId } });
51
+ if (!plan) {
52
+ console.log("Subscription references missing product");
53
+ return ctx.json({ msg: "Subscription references missing product" });
54
+ }
55
+ const paidAt = ev.data.transaction.time;
56
+ const { periodStart, periodEnd } = getPaymentInterval({
57
+ paidAt,
58
+ interval: plan.priceInterval
59
+ });
60
+ await db.update(subscriptions).set({
61
+ status: "active",
62
+ currentPeriodStart: new Date(periodStart.epochMilliseconds),
63
+ currentPeriodEnd: new Date(periodEnd.epochMilliseconds)
64
+ }).where(eq(subscriptions.id, subscription.id));
65
+ if ((kind === "upgrade" || kind === "downgrade") && previousSubscriptionId) {
66
+ const previousSubscription = await db.query.subscriptions.findFirst({ where: { id: previousSubscriptionId } });
67
+ if (!previousSubscription) console.log("Previous subscription not found, skipping cancel");
68
+ else if (previousSubscription.status !== "cancelled") await db.update(subscriptions).set({
69
+ status: "cancelled",
70
+ endedAt: /* @__PURE__ */ new Date()
71
+ }).where(eq(subscriptions.id, previousSubscription.id));
72
+ }
73
+ return ctx.json({ msg: "Webhook processed" });
74
+ });
75
+ //#endregion
76
+ export { api_exports, nombaWebhook };
@@ -0,0 +1,36 @@
1
+ import { hashPayload } from "../../../core/nomba_api/index.js";
2
+ import ms from "ms";
3
+ import { Temporal } from "temporal-polyfill";
4
+ //#region endpoints/webhooks/nomba/utils.ts
5
+ async function isValidSignature(args) {
6
+ const headers = args.headers;
7
+ const signature = headers.get("nomba-sig-value") ?? headers.get("nomba-signature");
8
+ const timestamp = headers.get("nomba-timestamp");
9
+ if (!signature) {
10
+ console.log("There is no nomba-sig-value header");
11
+ return null;
12
+ }
13
+ if (!timestamp) {
14
+ console.log("There is no nomba-timestamp header");
15
+ return null;
16
+ }
17
+ const payload = args.ev;
18
+ const hash = await hashPayload({
19
+ payload,
20
+ secretKey: args.nomkit.nomba.getWebhookSecret(),
21
+ timestamp
22
+ });
23
+ return signature.toLowerCase() !== hash.toLowerCase();
24
+ }
25
+ function getPaymentInterval(args) {
26
+ const periodStart = Temporal.Instant.from(args.paidAt);
27
+ const milliseconds = ms(args.interval);
28
+ if (typeof milliseconds !== "number" || Number.isNaN(milliseconds)) throw new Error("You have an invalid interval");
29
+ if (milliseconds < 0) throw new Error("timestamp cannot include negative intervals");
30
+ return {
31
+ periodStart,
32
+ periodEnd: periodStart.add(Temporal.Duration.from({ milliseconds }))
33
+ };
34
+ }
35
+ //#endregion
36
+ export { getPaymentInterval, isValidSignature };
@@ -0,0 +1,204 @@
1
+ import { NombaAPI } from "./core/nomba_api/index.js";
2
+ import { DatabaseConfig } from "./adapters/index.js";
3
+ import { Customer } from "./core/pg_db/schema.js";
4
+ import { routes } from "./queue/endpoints/routes.js";
5
+ import { InferRouteAPI } from "./queue/client/index.js";
6
+ import { StringValue } from "ms";
7
+
8
+ //#region index.d.ts
9
+ declare const DAYLY_INTERVAL: unique symbol;
10
+ declare const MONTHLY_INTERVAL: unique symbol;
11
+ declare const ANNUALLY_INTERVAL: unique symbol;
12
+ declare const Interval: {
13
+ /** Defaults to `"24 hours"` */readonly DAYLY: typeof DAYLY_INTERVAL; /** Approx to `"28 days"` every cycle */
14
+ readonly MONTHLY: typeof MONTHLY_INTERVAL; /** Period of `"1 year"` every cycle */
15
+ readonly ANNUALLY: typeof ANNUALLY_INTERVAL;
16
+ readonly custom: <T extends StringValue>(every: T) => {
17
+ interval: "custom";
18
+ every: T;
19
+ };
20
+ };
21
+ type BuiltinInterval = Exclude<(typeof Interval)[keyof typeof Interval], typeof Interval.custom>;
22
+ type CustomInterval = ReturnType<typeof Interval.custom>;
23
+ type Interval = BuiltinInterval | CustomInterval;
24
+ type Retries<T extends string = string> = {
25
+ interval: "custom";
26
+ after: T;
27
+ };
28
+ declare const Retry: {
29
+ readonly after: <const T extends StringValue>(after: T) => Retries<T>;
30
+ };
31
+ type Prettify<T> = { -readonly [K in keyof T]: T[K] } & {};
32
+ interface FeatureOpts<T extends string = string> {
33
+ id: T;
34
+ label: string;
35
+ type: "boolean" | "metered";
36
+ }
37
+ type MeteredOpts = {
38
+ limit: number;
39
+ reset: "week" | "month" | "year";
40
+ };
41
+ type BooleanFeature<L> = () => Prettify<L>;
42
+ type MeteredFeature<L> = <const MFeature extends MeteredOpts>(args: MFeature) => Prettify<L & MFeature>;
43
+ type BooleanFeatureOpts<T extends string = string> = FeatureOpts<T> & {
44
+ type: "boolean";
45
+ };
46
+ type MeteredFeatureOpts<T extends string = string> = FeatureOpts<T> & {
47
+ type: "metered";
48
+ };
49
+ declare function feature<const TFeature extends BooleanFeatureOpts>(args: TFeature): BooleanFeature<TFeature>;
50
+ declare function feature<const TFeature extends MeteredFeatureOpts>(args: TFeature): MeteredFeature<TFeature>;
51
+ type Feature = BooleanFeatureOpts | MeteredFeatureOpts;
52
+ interface PlanOptions<TIncludes extends readonly Feature[] = [], T extends string = string> {
53
+ id: T;
54
+ label: string;
55
+ includes?: TIncludes;
56
+ price?: {
57
+ /**
58
+ * Amount in naira
59
+ */
60
+ amount: number;
61
+ interval: Interval;
62
+ };
63
+ }
64
+ type Plan<TIncludes extends readonly Feature[] = [], T extends string = string> = Omit<PlanOptions<TIncludes, T>, "price"> & {
65
+ price?: {
66
+ amount: number;
67
+ interval: string;
68
+ };
69
+ };
70
+ declare function plan<TIncludes extends readonly Feature[] = [], const T extends string = string>(options: PlanOptions<TIncludes, T>): Plan<TIncludes, T>;
71
+ type SubscribeOptions<TPlanId extends string> = {
72
+ customerId: string;
73
+ planID: TPlanId;
74
+ successURL: string;
75
+ cancelURL?: string;
76
+ };
77
+ type SubscribeResult = {
78
+ checkoutURL: string | null;
79
+ };
80
+ type CheckOptions<TFeatureId extends string> = {
81
+ customerId: string;
82
+ featureId: TFeatureId;
83
+ };
84
+ type CheckResult<T> = T extends {
85
+ type: "metered";
86
+ } ? {
87
+ allowed: boolean;
88
+ balance: {
89
+ limit: number;
90
+ remaining: number;
91
+ resetAt: string;
92
+ unlimited: boolean;
93
+ };
94
+ } : T extends {
95
+ type: "boolean";
96
+ } ? {
97
+ allowed: boolean;
98
+ } : never;
99
+ type ConsumeOptions<TFeatureId> = {
100
+ customerId: string;
101
+ featureId: TFeatureId;
102
+ amount: number;
103
+ };
104
+ type ConsumeResult<TFeatureId> = {
105
+ featureId: TFeatureId;
106
+ balance: number;
107
+ limit: number;
108
+ unlimited?: boolean;
109
+ };
110
+ type GetCustomer = {
111
+ customerId?: string;
112
+ externalId?: string;
113
+ };
114
+ type GetCustomerResult = Customer | null;
115
+ type UpsertCustomer = {
116
+ name?: string;
117
+ externalId?: string;
118
+ email: string;
119
+ metadata: Record<string, string> | null;
120
+ };
121
+ type ListCustomers = {
122
+ cursor?: string;
123
+ limit: number;
124
+ };
125
+ type ListCustomersResult = Customer[];
126
+ type DeleteCustomer = {
127
+ customerId: string;
128
+ };
129
+ type InferBooleanFeatures<T> = T extends {
130
+ id: infer X;
131
+ type: "boolean";
132
+ } ? X : never;
133
+ type InferMeteredFeatures<T> = T extends {
134
+ id: infer X;
135
+ type: "metered";
136
+ } ? X : never;
137
+ interface NombaProvider {
138
+ accountId: string;
139
+ clientId: string;
140
+ clientSecret: string;
141
+ webhookSecret: string;
142
+ }
143
+ type InferRetryPolicy<T extends string> = `after:${T}`;
144
+ type EventHandler<TArgs = unknown> = (args: TArgs) => void | Promise<void>;
145
+ type RetryEvents<T extends string> = Partial<Record<T, EventHandler<{
146
+ trigger: string;
147
+ customer: Customer;
148
+ }>>>;
149
+ type DefaultEvents<R extends string> = Partial<{
150
+ "customer.updated": EventHandler;
151
+ "after:*": EventHandler<{
152
+ trigger: R;
153
+ customer: Customer;
154
+ isStale: boolean;
155
+ }>;
156
+ }>;
157
+ type Event<T extends string = string> = DefaultEvents<T> & RetryEvents<InferRetryPolicy<T>>;
158
+ interface CreateNomKit<TRetries extends readonly Retries[], TProducts extends Plan<any>[] = Plan<any>[]> {
159
+ database: DatabaseConfig;
160
+ products: TProducts;
161
+ enableProrating?: boolean;
162
+ paymentProviders: {
163
+ nomba: NombaProvider;
164
+ };
165
+ queueConfig: {
166
+ baseURL: string;
167
+ };
168
+ retryPolicy: TRetries;
169
+ on?: Event<TRetries[number]["after"]>;
170
+ }
171
+ type NomKitOptions = CreateNomKit<Retries[], Plan[]>;
172
+ interface NomkitContext {
173
+ database: DatabaseConfig;
174
+ nomba: NombaAPI;
175
+ queueClient: InferRouteAPI<typeof routes>;
176
+ on?: Event<string>;
177
+ }
178
+ declare function createNomkit<const TRetries extends Retries[], const TProducts extends Plan<any>[]>(args: CreateNomKit<TRetries, TProducts>): {
179
+ options: CreateNomKit<TRetries, TProducts>;
180
+ api: {
181
+ subscribe(data: SubscribeOptions<TProducts[number]["id"]>): Promise<SubscribeResult | null>;
182
+ /**
183
+ * Entitlements represent what a customer can currently do based on
184
+ * their active plan.
185
+ */
186
+ entitlements: {
187
+ check<T extends NonNullable<TProducts[number]["includes"]>[number]["id"]>(data: CheckOptions<T>): Promise<CheckResult<Extract<NonNullable<TProducts[number]["includes"]>[number], {
188
+ id: T;
189
+ }>>>;
190
+ consume<T extends InferMeteredFeatures<NonNullable<TProducts[number]["includes"]>[number]>>(data: ConsumeOptions<T>): Promise<ConsumeResult<T>>;
191
+ };
192
+ customers: {
193
+ get(args?: GetCustomer): Promise<GetCustomerResult>;
194
+ upsert(args: UpsertCustomer): Promise<boolean>;
195
+ list(args: ListCustomers): Promise<ListCustomersResult>;
196
+ delete(args: DeleteCustomer): Promise<boolean>;
197
+ };
198
+ };
199
+ handler(args?: {
200
+ basePath?: string;
201
+ }): (request: Request) => Promise<Response>;
202
+ };
203
+ //#endregion
204
+ export { BooleanFeatureOpts, CheckOptions, CheckResult, ConsumeOptions, ConsumeResult, CreateNomKit, DeleteCustomer, Feature, FeatureOpts, GetCustomer, GetCustomerResult, InferBooleanFeatures, InferMeteredFeatures, Interval, ListCustomers, ListCustomersResult, MeteredFeatureOpts, MeteredOpts, NomKitOptions, NombaProvider, NomkitContext, Plan, Retries, Retry, SubscribeOptions, SubscribeResult, UpsertCustomer, createNomkit, feature, plan };
package/dist/index.js ADDED
@@ -0,0 +1,175 @@
1
+ import { NombaAPI } from "./core/nomba_api/index.js";
2
+ import { createRouterMiddleware } from "./lib/utils.js";
3
+ import { routes } from "./endpoints/routes.js";
4
+ import { createQueueClient } from "./queue/client/index.js";
5
+ import { createRouter } from "better-call";
6
+ import ms from "ms";
7
+ //#region index.ts
8
+ const DAYLY_INTERVAL = Symbol("24 hours");
9
+ const MONTHLY_INTERVAL = Symbol("28 days");
10
+ const ANNUALLY_INTERVAL = Symbol("1 year");
11
+ const Interval = {
12
+ /** Defaults to `"24 hours"` */
13
+ DAYLY: DAYLY_INTERVAL,
14
+ /** Approx to `"28 days"` every cycle */
15
+ MONTHLY: MONTHLY_INTERVAL,
16
+ /** Period of `"1 year"` every cycle */
17
+ ANNUALLY: ANNUALLY_INTERVAL,
18
+ custom(every) {
19
+ return {
20
+ interval: "custom",
21
+ every
22
+ };
23
+ }
24
+ };
25
+ const Retry = { after(after) {
26
+ return {
27
+ interval: "custom",
28
+ after
29
+ };
30
+ } };
31
+ function feature(args) {
32
+ if (args.type === "boolean") return (() => args);
33
+ return ((input) => ({
34
+ ...args,
35
+ ...input
36
+ }));
37
+ }
38
+ function plan(options) {
39
+ enforceUniqueFeatureIds(options.includes);
40
+ const planPrice = enforceAmountInNaira(options.price);
41
+ let finalInterval = "";
42
+ const interval = planPrice?.interval;
43
+ if (interval === DAYLY_INTERVAL) finalInterval = DAYLY_INTERVAL.description;
44
+ if (interval === MONTHLY_INTERVAL) finalInterval = MONTHLY_INTERVAL.description;
45
+ if (interval === ANNUALLY_INTERVAL) finalInterval = ANNUALLY_INTERVAL.description;
46
+ if (typeof interval === "object" && interval?.interval === "custom") {
47
+ const milliseconds = ms(interval.every);
48
+ if (typeof milliseconds !== "number" || Number.isNaN(milliseconds)) throw new Error("You have an invalid interval");
49
+ if (milliseconds < 0) throw new Error("timestamp cannot include negative intervals");
50
+ finalInterval = interval.every;
51
+ }
52
+ const results = {
53
+ id: options.id,
54
+ label: options.label,
55
+ includes: options.includes
56
+ };
57
+ if (planPrice !== null) results.price = {
58
+ amount: Number(planPrice.amount),
59
+ interval: finalInterval
60
+ };
61
+ return results;
62
+ }
63
+ function createNomkit(args) {
64
+ if (!args) throw new Error("Missing required createNomkit arguments");
65
+ enforceUniqueProductIds(args.products);
66
+ const isSandbox = (process.env.IS_CREATE_NOMKIT_SANDBOX ?? "")?.length > 0;
67
+ const nomba = new NombaAPI({
68
+ accountId: args.paymentProviders.nomba.accountId,
69
+ clientId: args.paymentProviders.nomba.clientId,
70
+ clientSecret: args.paymentProviders.nomba.clientSecret,
71
+ webhookSecret: args.paymentProviders.nomba.webhookSecret,
72
+ environment: isSandbox ? "sandbox" : "production"
73
+ });
74
+ nomba.init();
75
+ const queueClient = createQueueClient({ baseURL: args.queueConfig.baseURL });
76
+ const routerContext = {
77
+ database: args.database,
78
+ nomba,
79
+ queueClient,
80
+ on: args.on
81
+ };
82
+ const router = createRouter(routes, {
83
+ routerContext,
84
+ routerMiddleware: [...createRouterMiddleware(async () => {})]
85
+ });
86
+ return {
87
+ options: args,
88
+ api: {
89
+ async subscribe(data) {
90
+ return router.endpoints.subscribe({
91
+ body: data,
92
+ context: routerContext
93
+ });
94
+ },
95
+ /**
96
+ * Entitlements represent what a customer can currently do based on
97
+ * their active plan.
98
+ */
99
+ entitlements: {
100
+ async check(data) {
101
+ return router.endpoints.checkEntitlements({
102
+ body: data,
103
+ context: routerContext
104
+ });
105
+ },
106
+ async consume(data) {
107
+ return router.endpoints.consumeEntitlements({
108
+ body: data,
109
+ context: routerContext
110
+ });
111
+ }
112
+ },
113
+ customers: {
114
+ async get(args) {
115
+ return router.endpoints.getCustomer({
116
+ body: args ?? {},
117
+ context: routerContext
118
+ });
119
+ },
120
+ async upsert(args) {
121
+ return router.endpoints.upsertCustomer({
122
+ body: args,
123
+ context: routerContext
124
+ });
125
+ },
126
+ async list(args) {
127
+ return router.endpoints.listCustomers({
128
+ body: args,
129
+ context: routerContext
130
+ });
131
+ },
132
+ async delete(args) {
133
+ return router.endpoints.deleteCustomer({
134
+ body: args,
135
+ context: routerContext
136
+ });
137
+ }
138
+ }
139
+ },
140
+ handler(args) {
141
+ const { basePath = "/nomkit" } = args ?? {};
142
+ return async (request) => {
143
+ const url = new URL(request.url);
144
+ if (url.pathname.startsWith(basePath)) url.pathname = url.pathname.slice(basePath.length) || "/";
145
+ return router.handler(new Request(url, request));
146
+ };
147
+ }
148
+ };
149
+ }
150
+ function enforceUniqueProductIds(products = []) {
151
+ products.reduce((acc, curr) => {
152
+ if (acc.includes(curr.id)) throw new Error(`Product with id: "${curr.id}" already exists.`);
153
+ return [...acc, curr.id];
154
+ }, []);
155
+ }
156
+ function enforceUniqueFeatureIds(features = []) {
157
+ features.reduce((acc, curr) => {
158
+ if (acc.includes(curr.id)) throw new Error(`Feature with id: "${curr.id}" already exists.`);
159
+ return [...acc, curr.id];
160
+ }, []);
161
+ }
162
+ function enforceAmountInNaira(plan) {
163
+ if (plan == null) return null;
164
+ const { amount } = plan;
165
+ if (typeof amount !== "number") throw new TypeError("Plan amount must be a number expressed in naira.");
166
+ if (!Number.isFinite(amount)) throw new TypeError("Plan amount must be a finite number.");
167
+ if (amount <= 0) throw new RangeError("Plan amount must be greater than zero.");
168
+ if (amount < 100) throw new RangeError("Plan amount must be at least ₦100.00.");
169
+ return {
170
+ amount: amount.toFixed(2),
171
+ interval: plan.interval
172
+ };
173
+ }
174
+ //#endregion
175
+ export { Interval, Retry, createNomkit, feature, plan };
@@ -0,0 +1,21 @@
1
+ import { Endpoint, EndpointContext } from "better-call";
2
+ import { z as z$1 } from "zod";
3
+
4
+ //#region lib/utils.d.ts
5
+ type DefineMethod<TSchema extends z$1.ZodType, TPath extends string, TRequireHeaders extends boolean = boolean, TRequireRequest extends boolean = boolean> = {
6
+ input: TSchema;
7
+ route: {
8
+ path: TPath;
9
+ requireHeaders?: TRequireRequest;
10
+ requireRequest?: TRequireHeaders;
11
+ clientOnly?: boolean;
12
+ };
13
+ };
14
+ type InferEndpoint<T> = T extends Endpoint<any, any, infer Request, any, any, infer Response, infer Metadata, any> ? {
15
+ request: Request;
16
+ response: Response;
17
+ metadata: Metadata;
18
+ } : never;
19
+ type InferRequired<R> = R extends boolean ? R : false;
20
+ //#endregion
21
+ export { DefineMethod, InferEndpoint, InferRequired };
@@ -0,0 +1,41 @@
1
+ import { getDrizzle } from "../core/pg_db/index.js";
2
+ import { createEndpoint, createMiddleware } from "better-call";
3
+ import { customAlphabet } from "nanoid";
4
+ //#region lib/utils.ts
5
+ const DEFAULT_PREFIX = "nomkit_order_";
6
+ const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-", 18);
7
+ const getOrderReference = (prefix = DEFAULT_PREFIX) => {
8
+ const id = generateId();
9
+ return prefix ? `${prefix}${id}` : id;
10
+ };
11
+ createMiddleware(async () => void 0);
12
+ function defineNomkitMethod(args, handler) {
13
+ return createEndpoint(args.route.path, {
14
+ method: "POST",
15
+ body: args.input,
16
+ ...args.route.clientOnly === true && { metadata: { scope: "http" } }
17
+ }, async (ctx) => {
18
+ const { context = {}, body, ...baseCtx } = ctx;
19
+ const { database, ...nomkitCtx } = context;
20
+ const db = getDrizzle(database);
21
+ return handler({
22
+ ...baseCtx,
23
+ nomkit: {
24
+ ...nomkitCtx,
25
+ database: db
26
+ },
27
+ input: body
28
+ });
29
+ });
30
+ }
31
+ const createRouterMiddleware = (cb) => {
32
+ const path = "/**";
33
+ return [{
34
+ path,
35
+ middleware: createEndpoint(path, { method: "*" }, async (ctx) => {
36
+ await cb?.();
37
+ })
38
+ }];
39
+ };
40
+ //#endregion
41
+ export { createRouterMiddleware, defineNomkitMethod, getOrderReference };