@mostajs/subscriptions-plan 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.
Files changed (61) hide show
  1. package/dist/api/plans.route.d.ts +17 -0
  2. package/dist/api/plans.route.d.ts.map +1 -0
  3. package/dist/api/plans.route.js +66 -0
  4. package/dist/api/plans.route.js.map +1 -0
  5. package/dist/api/subscriptions.route.d.ts +10 -0
  6. package/dist/api/subscriptions.route.d.ts.map +1 -0
  7. package/dist/api/subscriptions.route.js +65 -0
  8. package/dist/api/subscriptions.route.js.map +1 -0
  9. package/dist/api/usage.route.d.ts +10 -0
  10. package/dist/api/usage.route.d.ts.map +1 -0
  11. package/dist/api/usage.route.js +47 -0
  12. package/dist/api/usage.route.js.map +1 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +11 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/lib/module-info.d.ts +10 -0
  18. package/dist/lib/module-info.d.ts.map +1 -0
  19. package/dist/lib/module-info.js +17 -0
  20. package/dist/lib/module-info.js.map +1 -0
  21. package/dist/lib/plan-factory.d.ts +9 -0
  22. package/dist/lib/plan-factory.d.ts.map +1 -0
  23. package/dist/lib/plan-factory.js +38 -0
  24. package/dist/lib/plan-factory.js.map +1 -0
  25. package/dist/lib/quota-check.d.ts +43 -0
  26. package/dist/lib/quota-check.d.ts.map +1 -0
  27. package/dist/lib/quota-check.js +171 -0
  28. package/dist/lib/quota-check.js.map +1 -0
  29. package/dist/lib/stripe-billing.d.ts +51 -0
  30. package/dist/lib/stripe-billing.d.ts.map +1 -0
  31. package/dist/lib/stripe-billing.js +150 -0
  32. package/dist/lib/stripe-billing.js.map +1 -0
  33. package/dist/register.d.ts +10 -0
  34. package/dist/register.d.ts.map +1 -0
  35. package/dist/register.js +12 -0
  36. package/dist/register.js.map +1 -0
  37. package/dist/schemas/invoice.schema.d.ts +3 -0
  38. package/dist/schemas/invoice.schema.d.ts.map +1 -0
  39. package/dist/schemas/invoice.schema.js +27 -0
  40. package/dist/schemas/invoice.schema.js.map +1 -0
  41. package/dist/schemas/plan.schema.d.ts +3 -0
  42. package/dist/schemas/plan.schema.d.ts.map +1 -0
  43. package/dist/schemas/plan.schema.js +25 -0
  44. package/dist/schemas/plan.schema.js.map +1 -0
  45. package/dist/schemas/subscription.schema.d.ts +3 -0
  46. package/dist/schemas/subscription.schema.d.ts.map +1 -0
  47. package/dist/schemas/subscription.schema.js +24 -0
  48. package/dist/schemas/subscription.schema.js.map +1 -0
  49. package/dist/schemas/usage-log.schema.d.ts +3 -0
  50. package/dist/schemas/usage-log.schema.d.ts.map +1 -0
  51. package/dist/schemas/usage-log.schema.js +24 -0
  52. package/dist/schemas/usage-log.schema.js.map +1 -0
  53. package/dist/server.d.ts +9 -0
  54. package/dist/server.d.ts.map +1 -0
  55. package/dist/server.js +17 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/types/index.d.ts +83 -0
  58. package/dist/types/index.d.ts.map +1 -0
  59. package/dist/types/index.js +4 -0
  60. package/dist/types/index.js.map +1 -0
  61. package/package.json +46 -0
@@ -0,0 +1,17 @@
1
+ import type { IDialect } from '@mostajs/orm';
2
+ type PermissionChecker = (permission: string) => Promise<{
3
+ error: Response | null;
4
+ session: unknown;
5
+ }>;
6
+ /**
7
+ * Create CRUD handlers for Plans.
8
+ * GET is public (pricing page). POST/PUT/DELETE require admin permission.
9
+ */
10
+ export declare function createPlanHandlers(dialect: IDialect, checkPermission?: PermissionChecker): {
11
+ GET: (req: Request) => Promise<Response>;
12
+ POST: (req: Request) => Promise<Response>;
13
+ PUT: (req: Request, id: string) => Promise<Response>;
14
+ DELETE: (req: Request, id: string) => Promise<Response>;
15
+ };
16
+ export {};
17
+ //# sourceMappingURL=plans.route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plans.route.d.ts","sourceRoot":"","sources":["../../src/api/plans.route.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAI5C,KAAK,iBAAiB,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IACvD,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAC,CAAA;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,iBAAiB;eAI/D,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;gBAU1B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;eA4B5B,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC;kBAepC,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC;EAcnE"}
@@ -0,0 +1,66 @@
1
+ // @mostajs/subscriptions-plan — Plans API route handlers
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { getPlanRepo } from '../lib/plan-factory.js';
4
+ /**
5
+ * Create CRUD handlers for Plans.
6
+ * GET is public (pricing page). POST/PUT/DELETE require admin permission.
7
+ */
8
+ export function createPlanHandlers(dialect, checkPermission) {
9
+ const repo = getPlanRepo(dialect);
10
+ /** GET /plans — list active plans (public) */
11
+ async function GET(req) {
12
+ const url = new URL(req.url);
13
+ const all = url.searchParams.get('all') === 'true';
14
+ const filter = all ? {} : { active: true };
15
+ const plans = await repo.findAll(filter, { sort: { sortOrder: 1 } });
16
+ return Response.json(plans);
17
+ }
18
+ /** POST /plans — create plan (admin) */
19
+ async function POST(req) {
20
+ if (checkPermission) {
21
+ const { error } = await checkPermission('cloud.admin.plans');
22
+ if (error)
23
+ return error;
24
+ }
25
+ const data = await req.json();
26
+ if (!data.name || !data.slug || data.price === undefined || !data.limits) {
27
+ return Response.json({ error: 'name, slug, price and limits are required' }, { status: 400 });
28
+ }
29
+ // Check slug uniqueness
30
+ const existing = await repo.findAll({ slug: data.slug });
31
+ if (existing.length > 0) {
32
+ return Response.json({ error: `Plan with slug "${data.slug}" already exists` }, { status: 409 });
33
+ }
34
+ const plan = await repo.create(data);
35
+ return Response.json(plan, { status: 201 });
36
+ }
37
+ /** PUT /plans/:id — update plan (admin) */
38
+ async function PUT(req, id) {
39
+ if (checkPermission) {
40
+ const { error } = await checkPermission('cloud.admin.plans');
41
+ if (error)
42
+ return error;
43
+ }
44
+ const data = await req.json();
45
+ const plan = await repo.update(id, data);
46
+ if (!plan) {
47
+ return Response.json({ error: 'Plan not found' }, { status: 404 });
48
+ }
49
+ return Response.json(plan);
50
+ }
51
+ /** DELETE /plans/:id — deactivate plan (admin, soft) */
52
+ async function DELETE(req, id) {
53
+ if (checkPermission) {
54
+ const { error } = await checkPermission('cloud.admin.plans');
55
+ if (error)
56
+ return error;
57
+ }
58
+ const plan = await repo.update(id, { active: false });
59
+ if (!plan) {
60
+ return Response.json({ error: 'Plan not found' }, { status: 404 });
61
+ }
62
+ return Response.json({ ok: true, deactivated: id });
63
+ }
64
+ return { GET, POST, PUT, DELETE };
65
+ }
66
+ //# sourceMappingURL=plans.route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plans.route.js","sourceRoot":"","sources":["../../src/api/plans.route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { IDialect } from '@mostajs/orm';
2
+ /**
3
+ * Create handlers for subscription management.
4
+ */
5
+ export declare function createSubscriptionHandlers(dialect: IDialect): {
6
+ GET: (req: Request) => Promise<Response>;
7
+ POST: (req: Request) => Promise<Response>;
8
+ PUT: (req: Request, id: string) => Promise<Response>;
9
+ };
10
+ //# sourceMappingURL=subscriptions.route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.route.d.ts","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAI5C;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,QAAQ;eAKlC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;gBAoB1B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;eAqC5B,OAAO,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC;EAUhE"}
@@ -0,0 +1,65 @@
1
+ // @mostajs/subscriptions-plan — Subscriptions API route handlers
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { getSubscriptionRepo, getPlanRepo } from '../lib/plan-factory.js';
4
+ /**
5
+ * Create handlers for subscription management.
6
+ */
7
+ export function createSubscriptionHandlers(dialect) {
8
+ const subRepo = getSubscriptionRepo(dialect);
9
+ const planRepo = getPlanRepo(dialect);
10
+ /** GET /subscription?accountId=... — get active subscription */
11
+ async function GET(req) {
12
+ const url = new URL(req.url);
13
+ const accountId = url.searchParams.get('accountId');
14
+ if (!accountId) {
15
+ return Response.json({ error: 'accountId required' }, { status: 400 });
16
+ }
17
+ const subs = await subRepo.findAll({ account: accountId, status: 'active' });
18
+ if (subs.length === 0) {
19
+ return Response.json(null);
20
+ }
21
+ // Enrich with plan details
22
+ const sub = subs[0];
23
+ const plan = await planRepo.findById(sub.planId);
24
+ return Response.json({ ...sub, plan });
25
+ }
26
+ /** POST /subscription — create subscription (e.g., after Stripe webhook) */
27
+ async function POST(req) {
28
+ const data = await req.json();
29
+ if (!data.accountId || !data.planId) {
30
+ return Response.json({ error: 'accountId and planId are required' }, { status: 400 });
31
+ }
32
+ // Verify plan exists
33
+ const plan = await planRepo.findById(data.planId);
34
+ if (!plan) {
35
+ return Response.json({ error: 'Plan not found' }, { status: 404 });
36
+ }
37
+ // Cancel existing active subscriptions
38
+ const existing = await subRepo.findAll({ account: data.accountId, status: 'active' });
39
+ for (const old of existing) {
40
+ await subRepo.update(old.id, { status: 'canceled' });
41
+ }
42
+ // Create new subscription
43
+ const sub = await subRepo.create({
44
+ account: data.accountId,
45
+ plan: data.planId,
46
+ status: data.status ?? 'active',
47
+ stripeSubId: data.stripeSubId,
48
+ currentPeriodStart: data.currentPeriodStart ?? new Date().toISOString(),
49
+ currentPeriodEnd: data.currentPeriodEnd,
50
+ trialEnd: data.trialEnd,
51
+ });
52
+ return Response.json(sub, { status: 201 });
53
+ }
54
+ /** PUT /subscription/:id — update subscription status */
55
+ async function PUT(req, id) {
56
+ const data = await req.json();
57
+ const sub = await subRepo.update(id, data);
58
+ if (!sub) {
59
+ return Response.json({ error: 'Subscription not found' }, { status: 404 });
60
+ }
61
+ return Response.json(sub);
62
+ }
63
+ return { GET, POST, PUT };
64
+ }
65
+ //# sourceMappingURL=subscriptions.route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.route.js","sourceRoot":"","sources":["../../src/api/subscriptions.route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { IDialect } from '@mostajs/orm';
2
+ /**
3
+ * Create handlers for usage tracking.
4
+ */
5
+ export declare function createUsageHandlers(dialect: IDialect): {
6
+ GET: (req: Request) => Promise<Response>;
7
+ GETToday: (req: Request) => Promise<Response>;
8
+ POST: (req: Request) => Promise<Response>;
9
+ };
10
+ //# sourceMappingURL=usage.route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.route.d.ts","sourceRoot":"","sources":["../../src/api/usage.route.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAI5C;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,QAAQ;eAG3B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;oBAatB,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;gBAmB/B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAkBrD"}
@@ -0,0 +1,47 @@
1
+ // @mostajs/subscriptions-plan — Usage API route handlers
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { getUsageLogRepo } from '../lib/plan-factory.js';
4
+ import { getUsageSummary, incrementUsage } from '../lib/quota-check.js';
5
+ /**
6
+ * Create handlers for usage tracking.
7
+ */
8
+ export function createUsageHandlers(dialect) {
9
+ /** GET /usage?accountId=...&from=... — usage summary */
10
+ async function GET(req) {
11
+ const url = new URL(req.url);
12
+ const accountId = url.searchParams.get('accountId');
13
+ if (!accountId) {
14
+ return Response.json({ error: 'accountId required' }, { status: 400 });
15
+ }
16
+ const from = url.searchParams.get('from') ?? new Date().toISOString().slice(0, 10);
17
+ const summary = await getUsageSummary(dialect, accountId, from);
18
+ return Response.json(summary);
19
+ }
20
+ /** GET /usage/today?accountId=... — today's usage */
21
+ async function GETToday(req) {
22
+ const url = new URL(req.url);
23
+ const accountId = url.searchParams.get('accountId');
24
+ if (!accountId) {
25
+ return Response.json({ error: 'accountId required' }, { status: 400 });
26
+ }
27
+ const today = new Date().toISOString().slice(0, 10);
28
+ const repo = getUsageLogRepo(dialect);
29
+ const logs = await repo.findAll({ account: accountId, date: today });
30
+ if (logs.length === 0) {
31
+ return Response.json({ date: today, requests: 0, reads: 0, writes: 0, errors: 0, bandwidth: 0 });
32
+ }
33
+ return Response.json(logs[0]);
34
+ }
35
+ /** POST /usage/increment — fire-and-forget usage increment */
36
+ async function POST(req) {
37
+ const body = await req.json();
38
+ if (!body.accountId || !body.type) {
39
+ return Response.json({ error: 'accountId and type required' }, { status: 400 });
40
+ }
41
+ // Fire-and-forget
42
+ incrementUsage(dialect, body.accountId, body.projectId, body.type).catch(() => { });
43
+ return Response.json({ ok: true });
44
+ }
45
+ return { GET, GETToday, POST };
46
+ }
47
+ //# sourceMappingURL=usage.route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.route.js","sourceRoot":"","sources":["../../src/api/usage.route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type { PlanDTO, PlanLimits, SubscriptionDTO, SubscriptionStatus, InvoiceDTO, InvoiceStatus, UsageLogDTO, BillingConfig, QuotaCheckResult, } from './types/index.js';
2
+ export { PlanSchema } from './schemas/plan.schema.js';
3
+ export { SubscriptionSchema } from './schemas/subscription.schema.js';
4
+ export { InvoiceSchema } from './schemas/invoice.schema.js';
5
+ export { UsageLogSchema } from './schemas/usage-log.schema.js';
6
+ export { moduleInfo } from './lib/module-info.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,OAAO,EACP,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // @mostajs/subscriptions-plan — Client-safe exports
2
+ // Subscription plans, billing, invoices and usage tracking
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ // Schemas
5
+ export { PlanSchema } from './schemas/plan.schema.js';
6
+ export { SubscriptionSchema } from './schemas/subscription.schema.js';
7
+ export { InvoiceSchema } from './schemas/invoice.schema.js';
8
+ export { UsageLogSchema } from './schemas/usage-log.schema.js';
9
+ // Module info
10
+ export { moduleInfo } from './lib/module-info.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,2DAA2D;AAC3D,wCAAwC;AAexC,UAAU;AACV,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { EntitySchema } from '@mostajs/orm';
2
+ export declare function getSchemas(): EntitySchema[];
3
+ export declare const moduleInfo: {
4
+ name: string;
5
+ label: string;
6
+ description: string;
7
+ version: string;
8
+ schemas: string[];
9
+ };
10
+ //# sourceMappingURL=module-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-info.d.ts","sourceRoot":"","sources":["../../src/lib/module-info.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAMhD,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAE3C;AAED,eAAO,MAAM,UAAU;;;;;;CAMtB,CAAA"}
@@ -0,0 +1,17 @@
1
+ // @mostajs/subscriptions-plan — Module info
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { PlanSchema } from '../schemas/plan.schema.js';
4
+ import { SubscriptionSchema } from '../schemas/subscription.schema.js';
5
+ import { InvoiceSchema } from '../schemas/invoice.schema.js';
6
+ import { UsageLogSchema } from '../schemas/usage-log.schema.js';
7
+ export function getSchemas() {
8
+ return [PlanSchema, SubscriptionSchema, InvoiceSchema, UsageLogSchema];
9
+ }
10
+ export const moduleInfo = {
11
+ name: 'subscriptions-plan',
12
+ label: 'Subscriptions & Plans',
13
+ description: 'Subscription plans, billing, invoices and usage tracking',
14
+ version: '0.1.0',
15
+ schemas: ['Plan', 'Subscription', 'Invoice', 'UsageLog'],
16
+ };
17
+ //# sourceMappingURL=module-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-info.js","sourceRoot":"","sources":["../../src/lib/module-info.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,wCAAwC;AAGxC,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAE/D,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,UAAU,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,CAAC,CAAA;AACxE,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,oBAAoB;IAC1B,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EAAE,0DAA0D;IACvE,OAAO,EAAE,OAAO;IAChB,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,CAAC;CACzD,CAAA"}
@@ -0,0 +1,9 @@
1
+ import { BaseRepository } from '@mostajs/orm';
2
+ import type { IDialect } from '@mostajs/orm';
3
+ import type { PlanDTO, SubscriptionDTO, InvoiceDTO, UsageLogDTO } from '../types/index.js';
4
+ export declare function getPlanRepo(dialect: IDialect): BaseRepository<PlanDTO>;
5
+ export declare function getSubscriptionRepo(dialect: IDialect): BaseRepository<SubscriptionDTO>;
6
+ export declare function getInvoiceRepo(dialect: IDialect): BaseRepository<InvoiceDTO>;
7
+ export declare function getUsageLogRepo(dialect: IDialect): BaseRepository<UsageLogDTO>;
8
+ export declare function resetRepos(): void;
9
+ //# sourceMappingURL=plan-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-factory.d.ts","sourceRoot":"","sources":["../../src/lib/plan-factory.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO1F,wBAAgB,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAGtE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc,CAAC,eAAe,CAAC,CAGtF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAG5E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc,CAAC,WAAW,CAAC,CAG9E;AAED,wBAAgB,UAAU,IAAI,IAAI,CAKjC"}
@@ -0,0 +1,38 @@
1
+ // @mostajs/subscriptions-plan — Repository factories
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { BaseRepository } from '@mostajs/orm';
4
+ import { PlanSchema } from '../schemas/plan.schema.js';
5
+ import { SubscriptionSchema } from '../schemas/subscription.schema.js';
6
+ import { InvoiceSchema } from '../schemas/invoice.schema.js';
7
+ import { UsageLogSchema } from '../schemas/usage-log.schema.js';
8
+ let planRepo = null;
9
+ let subscriptionRepo = null;
10
+ let invoiceRepo = null;
11
+ let usageLogRepo = null;
12
+ export function getPlanRepo(dialect) {
13
+ if (!planRepo)
14
+ planRepo = new BaseRepository(PlanSchema, dialect);
15
+ return planRepo;
16
+ }
17
+ export function getSubscriptionRepo(dialect) {
18
+ if (!subscriptionRepo)
19
+ subscriptionRepo = new BaseRepository(SubscriptionSchema, dialect);
20
+ return subscriptionRepo;
21
+ }
22
+ export function getInvoiceRepo(dialect) {
23
+ if (!invoiceRepo)
24
+ invoiceRepo = new BaseRepository(InvoiceSchema, dialect);
25
+ return invoiceRepo;
26
+ }
27
+ export function getUsageLogRepo(dialect) {
28
+ if (!usageLogRepo)
29
+ usageLogRepo = new BaseRepository(UsageLogSchema, dialect);
30
+ return usageLogRepo;
31
+ }
32
+ export function resetRepos() {
33
+ planRepo = null;
34
+ subscriptionRepo = null;
35
+ invoiceRepo = null;
36
+ usageLogRepo = null;
37
+ }
38
+ //# sourceMappingURL=plan-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-factory.js","sourceRoot":"","sources":["../../src/lib/plan-factory.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,wCAAwC;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAG/D,IAAI,QAAQ,GAAmC,IAAI,CAAA;AACnD,IAAI,gBAAgB,GAA2C,IAAI,CAAA;AACnE,IAAI,WAAW,GAAsC,IAAI,CAAA;AACzD,IAAI,YAAY,GAAuC,IAAI,CAAA;AAE3D,MAAM,UAAU,WAAW,CAAC,OAAiB;IAC3C,IAAI,CAAC,QAAQ;QAAE,QAAQ,GAAG,IAAI,cAAc,CAAU,UAAU,EAAE,OAAO,CAAC,CAAA;IAC1E,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAiB;IACnD,IAAI,CAAC,gBAAgB;QAAE,gBAAgB,GAAG,IAAI,cAAc,CAAkB,kBAAkB,EAAE,OAAO,CAAC,CAAA;IAC1G,OAAO,gBAAgB,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAiB;IAC9C,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,IAAI,cAAc,CAAa,aAAa,EAAE,OAAO,CAAC,CAAA;IACtF,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAiB;IAC/C,IAAI,CAAC,YAAY;QAAE,YAAY,GAAG,IAAI,cAAc,CAAc,cAAc,EAAE,OAAO,CAAC,CAAA;IAC1F,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,QAAQ,GAAG,IAAI,CAAA;IACf,gBAAgB,GAAG,IAAI,CAAA;IACvB,WAAW,GAAG,IAAI,CAAA;IAClB,YAAY,GAAG,IAAI,CAAA;AACrB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { IDialect } from '@mostajs/orm';
2
+ import type { PlanLimits, QuotaCheckResult } from '../types/index.js';
3
+ /**
4
+ * Check if an account is within quota for a given resource.
5
+ */
6
+ export declare function checkQuota(dialect: IDialect, accountId: string, resource: 'requests' | 'projects' | 'apiKeys', currentCount?: number): Promise<QuotaCheckResult>;
7
+ /**
8
+ * Check if a dialect is allowed by the plan.
9
+ */
10
+ export declare function isDialectAllowed(limits: PlanLimits, dialect: string): boolean;
11
+ /**
12
+ * Check if a transport is allowed by the plan.
13
+ */
14
+ export declare function isTransportAllowed(limits: PlanLimits, transport: string): boolean;
15
+ /**
16
+ * Get usage summary for an account (current period).
17
+ */
18
+ export declare function getUsageSummary(dialect: IDialect, accountId: string, periodStart: string): Promise<{
19
+ totalRequests: number;
20
+ totalReads: number;
21
+ totalWrites: number;
22
+ totalErrors: number;
23
+ days: number;
24
+ }>;
25
+ /**
26
+ * Increment usage for today (fire-and-forget pattern).
27
+ */
28
+ export declare function incrementUsage(dialect: IDialect, accountId: string, projectId: string | undefined, type: 'read' | 'write' | 'error'): Promise<void>;
29
+ /**
30
+ * Default plan seeds (Free, Medium, Premium).
31
+ */
32
+ export declare const DEFAULT_PLANS: {
33
+ name: string;
34
+ slug: string;
35
+ price: number;
36
+ currency: string;
37
+ interval: string;
38
+ limits: PlanLimits;
39
+ features: string[];
40
+ active: boolean;
41
+ sortOrder: number;
42
+ }[];
43
+ //# sourceMappingURL=quota-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-check.d.ts","sourceRoot":"","sources":["../../src/lib/quota-check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAA;AAGlF;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,EAC7C,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAoE3B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAG7E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGjF;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAchH;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAC/B,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;YAOjB,UAAU;;;;GAwBlB,CAAA"}
@@ -0,0 +1,171 @@
1
+ // @mostajs/subscriptions-plan — Quota enforcement
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ import { getUsageLogRepo, getPlanRepo, getSubscriptionRepo } from './plan-factory.js';
4
+ /**
5
+ * Check if an account is within quota for a given resource.
6
+ */
7
+ export async function checkQuota(dialect, accountId, resource, currentCount) {
8
+ // Load subscription → plan → limits
9
+ const subRepo = getSubscriptionRepo(dialect);
10
+ const subs = await subRepo.findAll({ account: accountId, status: 'active' });
11
+ if (subs.length === 0) {
12
+ return { allowed: false, reason: 'No active subscription', current: 0, limit: 0, resource };
13
+ }
14
+ const sub = subs[0];
15
+ const planRepo = getPlanRepo(dialect);
16
+ // M2O relation field: sub.plan contains the FK id (not sub.planId)
17
+ const planId = sub.plan ?? sub.planId;
18
+ const plan = await planRepo.findById(planId);
19
+ if (!plan) {
20
+ return { allowed: false, reason: 'Plan not found', current: 0, limit: 0, resource };
21
+ }
22
+ const rawLimits = plan.limits;
23
+ const limits = typeof rawLimits === 'string' ? JSON.parse(rawLimits) : rawLimits;
24
+ switch (resource) {
25
+ case 'requests': {
26
+ const limit = limits.requestsPerDay;
27
+ if (limit === -1)
28
+ return { allowed: true, current: currentCount ?? 0, limit: -1, resource };
29
+ const today = new Date().toISOString().slice(0, 10);
30
+ const usageRepo = getUsageLogRepo(dialect);
31
+ const usageLogs = await usageRepo.findAll({ account: accountId, date: today });
32
+ const current = usageLogs.reduce((sum, u) => sum + u.requests, 0);
33
+ return {
34
+ allowed: current < limit,
35
+ current,
36
+ limit,
37
+ resource,
38
+ reason: current >= limit ? 'Daily request limit reached' : undefined,
39
+ };
40
+ }
41
+ case 'projects': {
42
+ const limit = limits.maxProjects;
43
+ if (limit === -1)
44
+ return { allowed: true, current: currentCount ?? 0, limit: -1, resource };
45
+ const current = currentCount ?? 0;
46
+ return {
47
+ allowed: current < limit,
48
+ current,
49
+ limit,
50
+ resource,
51
+ reason: current >= limit ? 'Project limit reached' : undefined,
52
+ };
53
+ }
54
+ case 'apiKeys': {
55
+ const limit = limits.maxApiKeys;
56
+ if (limit === -1)
57
+ return { allowed: true, current: currentCount ?? 0, limit: -1, resource };
58
+ const current = currentCount ?? 0;
59
+ return {
60
+ allowed: current < limit,
61
+ current,
62
+ limit,
63
+ resource,
64
+ reason: current >= limit ? 'API key limit reached' : undefined,
65
+ };
66
+ }
67
+ default:
68
+ return { allowed: false, reason: `Unknown resource: ${resource}`, current: 0, limit: 0, resource };
69
+ }
70
+ }
71
+ /**
72
+ * Check if a dialect is allowed by the plan.
73
+ */
74
+ export function isDialectAllowed(limits, dialect) {
75
+ if (limits.dialects === '*')
76
+ return true;
77
+ return limits.dialects.includes(dialect.toLowerCase());
78
+ }
79
+ /**
80
+ * Check if a transport is allowed by the plan.
81
+ */
82
+ export function isTransportAllowed(limits, transport) {
83
+ if (limits.transports === '*')
84
+ return true;
85
+ return limits.transports.includes(transport.toLowerCase());
86
+ }
87
+ /**
88
+ * Get usage summary for an account (current period).
89
+ */
90
+ export async function getUsageSummary(dialect, accountId, periodStart) {
91
+ const usageRepo = getUsageLogRepo(dialect);
92
+ const logs = await usageRepo.findAll({
93
+ account: accountId,
94
+ date: { $gte: periodStart },
95
+ });
96
+ return {
97
+ totalRequests: logs.reduce((s, l) => s + l.requests, 0),
98
+ totalReads: logs.reduce((s, l) => s + l.reads, 0),
99
+ totalWrites: logs.reduce((s, l) => s + l.writes, 0),
100
+ totalErrors: logs.reduce((s, l) => s + l.errors, 0),
101
+ days: logs.length,
102
+ };
103
+ }
104
+ /**
105
+ * Increment usage for today (fire-and-forget pattern).
106
+ */
107
+ export async function incrementUsage(dialect, accountId, projectId, type) {
108
+ const usageRepo = getUsageLogRepo(dialect);
109
+ const today = new Date().toISOString().slice(0, 10);
110
+ // Find or create today's log
111
+ const existing = await usageRepo.findAll({ account: accountId, date: today });
112
+ if (existing.length > 0) {
113
+ const log = existing[0];
114
+ const update = { requests: log.requests + 1 };
115
+ if (type === 'read')
116
+ update.reads = log.reads + 1;
117
+ if (type === 'write')
118
+ update.writes = log.writes + 1;
119
+ if (type === 'error')
120
+ update.errors = log.errors + 1;
121
+ await usageRepo.update(log.id, update);
122
+ }
123
+ else {
124
+ await usageRepo.create({
125
+ account: accountId,
126
+ project: projectId,
127
+ date: today,
128
+ requests: 1,
129
+ reads: type === 'read' ? 1 : 0,
130
+ writes: type === 'write' ? 1 : 0,
131
+ errors: type === 'error' ? 1 : 0,
132
+ bandwidth: 0,
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * Default plan seeds (Free, Medium, Premium).
138
+ */
139
+ export const DEFAULT_PLANS = [
140
+ {
141
+ name: 'Free', slug: 'free', price: 0, currency: 'usd', interval: 'month',
142
+ limits: {
143
+ maxProjects: 1, maxApiKeys: 1, requestsPerDay: 1000,
144
+ maxPoolSize: 5, dialects: ['sqlite', 'postgres'],
145
+ transports: ['rest', 'mcp'], replication: false,
146
+ },
147
+ features: ['1 projet', '1 000 req/jour', 'REST + MCP', 'Community support'],
148
+ active: true, sortOrder: 0,
149
+ },
150
+ {
151
+ name: 'Medium', slug: 'medium', price: 2900, currency: 'usd', interval: 'month',
152
+ limits: {
153
+ maxProjects: 5, maxApiKeys: 5, requestsPerDay: 50000,
154
+ maxPoolSize: 50, dialects: ['sqlite', 'postgres', 'mysql', 'mongodb', 'mariadb'],
155
+ transports: ['rest', 'mcp', 'graphql', 'trpc', 'odata'], replication: false,
156
+ },
157
+ features: ['5 projets', '50 000 req/jour', '5 transports', 'Email support'],
158
+ active: true, sortOrder: 1,
159
+ },
160
+ {
161
+ name: 'Premium', slug: 'premium', price: 9900, currency: 'usd', interval: 'month',
162
+ limits: {
163
+ maxProjects: -1, maxApiKeys: -1, requestsPerDay: -1,
164
+ maxPoolSize: 200, dialects: '*',
165
+ transports: '*', replication: true,
166
+ },
167
+ features: ['Projets illimites', 'Requetes illimitees', '13 dialectes', '11 transports', 'CQRS/CDC', 'Support prioritaire'],
168
+ active: true, sortOrder: 2,
169
+ },
170
+ ];
171
+ //# sourceMappingURL=quota-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-check.js","sourceRoot":"","sources":["../../src/lib/quota-check.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,wCAAwC;AAIxC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAErF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAiB,EACjB,SAAiB,EACjB,QAA6C,EAC7C,YAAqB;IAErB,oCAAoC;IACpC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAC5C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC5E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAA;IAC7F,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACnB,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;IACrC,mEAAmE;IACnE,MAAM,MAAM,GAAI,GAAW,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,CAAA;IAC9C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAA;IACrF,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAA;IAC7B,MAAM,MAAM,GAAe,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAuB,CAAA;IAE1G,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAA;YACnC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAA;YAE3F,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YACnD,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YAC9E,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,CAAc,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;YAEtF,OAAO;gBACL,OAAO,EAAE,OAAO,GAAG,KAAK;gBACxB,OAAO;gBACP,KAAK;gBACL,QAAQ;gBACR,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,SAAS;aACrE,CAAA;QACH,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAA;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAA;YAC3F,MAAM,OAAO,GAAG,YAAY,IAAI,CAAC,CAAA;YACjC,OAAO;gBACL,OAAO,EAAE,OAAO,GAAG,KAAK;gBACxB,OAAO;gBACP,KAAK;gBACL,QAAQ;gBACR,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS;aAC/D,CAAA;QACH,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAA;YAC/B,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAA;YAC3F,MAAM,OAAO,GAAG,YAAY,IAAI,CAAC,CAAA;YACjC,OAAO;gBACL,OAAO,EAAE,OAAO,GAAG,KAAK;gBACxB,OAAO;gBACP,KAAK;gBACL,QAAQ;gBACR,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS;aAC/D,CAAA;QACH,CAAC;QAED;YACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAA;IACtG,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB,EAAE,OAAe;IAClE,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB,EAAE,SAAiB;IACtE,IAAI,MAAM,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAC1C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAA;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAiB,EACjB,SAAiB,EACjB,WAAmB;IAEnB,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC;QACnC,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;KAC5B,CAAC,CAAA;IAEF,OAAO;QACL,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,CAAc,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5E,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,CAAc,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACtE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,CAAc,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,CAAc,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,IAAI,EAAE,IAAI,CAAC,MAAM;KAClB,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAiB,EACjB,SAAiB,EACjB,SAA6B,EAC7B,IAAgC;IAEhC,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEnD,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAE7E,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,MAAM,GAA4B,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAA;QACtE,IAAI,IAAI,KAAK,MAAM;YAAE,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAA;QACjD,IAAI,IAAI,KAAK,OAAO;YAAE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA;QACpD,IAAI,IAAI,KAAK,OAAO;YAAE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA;QACpD,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAa,CAAC,CAAA;IAC/C,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,SAAS,EAAE,CAAC;SACK,CAAC,CAAA;IACtB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B;QACE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO;QACxE,MAAM,EAAE;YACN,WAAW,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,cAAc,EAAE,IAAI;YACnD,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;YAChD,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,KAAK;SAClC;QACf,QAAQ,EAAE,CAAC,UAAU,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,CAAC;QAC3E,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;KAC3B;IACD;QACE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO;QAC/E,MAAM,EAAE;YACN,WAAW,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK;YACpD,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC;YAChF,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,KAAK;SAC9D;QACf,QAAQ,EAAE,CAAC,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,CAAC;QAC3E,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;KAC3B;IACD;QACE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO;QACjF,MAAM,EAAE;YACN,WAAW,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;YACnD,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAY;YACxC,UAAU,EAAE,GAAY,EAAE,WAAW,EAAE,IAAI;SAC9B;QACf,QAAQ,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,qBAAqB,CAAC;QAC1H,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;KAC3B;CACF,CAAA"}