@86d-store/memberships 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 (110) hide show
  1. package/README.md +193 -0
  2. package/dist/admin/components/_shared.d.ts +13 -0
  3. package/dist/admin/components/_shared.d.ts.map +1 -0
  4. package/dist/admin/components/_shared.js +17 -0
  5. package/dist/admin/components/membership-admin.d.ts +2 -0
  6. package/dist/admin/components/membership-admin.d.ts.map +1 -0
  7. package/dist/admin/components/membership-admin.js +47 -0
  8. package/dist/admin/components/membership-plans.d.ts +2 -0
  9. package/dist/admin/components/membership-plans.d.ts.map +1 -0
  10. package/dist/admin/components/membership-plans.js +153 -0
  11. package/dist/admin/endpoints/add-benefit.d.ts +28 -0
  12. package/dist/admin/endpoints/add-benefit.d.ts.map +1 -0
  13. package/dist/admin/endpoints/add-benefit.js +37 -0
  14. package/dist/admin/endpoints/cancel-membership.d.ts +16 -0
  15. package/dist/admin/endpoints/cancel-membership.d.ts.map +1 -0
  16. package/dist/admin/endpoints/cancel-membership.js +19 -0
  17. package/dist/admin/endpoints/create-plan.d.ts +29 -0
  18. package/dist/admin/endpoints/create-plan.d.ts.map +1 -0
  19. package/dist/admin/endpoints/create-plan.js +48 -0
  20. package/dist/admin/endpoints/delete-plan.d.ts +16 -0
  21. package/dist/admin/endpoints/delete-plan.d.ts.map +1 -0
  22. package/dist/admin/endpoints/delete-plan.js +14 -0
  23. package/dist/admin/endpoints/gate-product.d.ts +19 -0
  24. package/dist/admin/endpoints/gate-product.d.ts.map +1 -0
  25. package/dist/admin/endpoints/gate-product.js +25 -0
  26. package/dist/admin/endpoints/get-membership.d.ts +18 -0
  27. package/dist/admin/endpoints/get-membership.d.ts.map +1 -0
  28. package/dist/admin/endpoints/get-membership.js +15 -0
  29. package/dist/admin/endpoints/get-stats.d.ts +6 -0
  30. package/dist/admin/endpoints/get-stats.d.ts.map +1 -0
  31. package/dist/admin/endpoints/get-stats.js +7 -0
  32. package/dist/admin/endpoints/list-memberships.d.ts +20 -0
  33. package/dist/admin/endpoints/list-memberships.d.ts.map +1 -0
  34. package/dist/admin/endpoints/list-memberships.js +32 -0
  35. package/dist/admin/endpoints/list-plans.d.ts +16 -0
  36. package/dist/admin/endpoints/list-plans.d.ts.map +1 -0
  37. package/dist/admin/endpoints/list-plans.js +29 -0
  38. package/dist/admin/endpoints/pause-membership.d.ts +16 -0
  39. package/dist/admin/endpoints/pause-membership.d.ts.map +1 -0
  40. package/dist/admin/endpoints/pause-membership.js +19 -0
  41. package/dist/admin/endpoints/remove-benefit.d.ts +16 -0
  42. package/dist/admin/endpoints/remove-benefit.d.ts.map +1 -0
  43. package/dist/admin/endpoints/remove-benefit.js +14 -0
  44. package/dist/admin/endpoints/resume-membership.d.ts +16 -0
  45. package/dist/admin/endpoints/resume-membership.d.ts.map +1 -0
  46. package/dist/admin/endpoints/resume-membership.js +19 -0
  47. package/dist/admin/endpoints/routes.d.ts +237 -0
  48. package/dist/admin/endpoints/routes.d.ts.map +1 -0
  49. package/dist/admin/endpoints/routes.js +30 -0
  50. package/dist/admin/endpoints/ungate-product.d.ts +13 -0
  51. package/dist/admin/endpoints/ungate-product.d.ts.map +1 -0
  52. package/dist/admin/endpoints/ungate-product.js +22 -0
  53. package/dist/admin/endpoints/update-plan.d.ts +32 -0
  54. package/dist/admin/endpoints/update-plan.d.ts.map +1 -0
  55. package/dist/admin/endpoints/update-plan.js +62 -0
  56. package/dist/index.d.ts +10 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +75 -0
  59. package/dist/schema.d.ts +102 -0
  60. package/dist/schema.d.ts.map +1 -0
  61. package/dist/schema.js +63 -0
  62. package/dist/service-impl.d.ts +4 -0
  63. package/dist/service-impl.d.ts.map +1 -0
  64. package/dist/service-impl.js +414 -0
  65. package/dist/service.d.ts +145 -0
  66. package/dist/service.d.ts.map +1 -0
  67. package/dist/service.js +1 -0
  68. package/dist/store/components/_hooks.d.ts +9 -0
  69. package/dist/store/components/_hooks.d.ts.map +1 -0
  70. package/dist/store/components/_hooks.js +13 -0
  71. package/dist/store/components/_utils.d.ts +11 -0
  72. package/dist/store/components/_utils.d.ts.map +1 -0
  73. package/dist/store/components/_utils.js +98 -0
  74. package/dist/store/components/mdx.d.ts +10 -0
  75. package/dist/store/components/mdx.d.ts.map +1 -0
  76. package/dist/store/components/mdx.js +9 -0
  77. package/dist/store/components/my-membership.d.ts +2 -0
  78. package/dist/store/components/my-membership.d.ts.map +1 -0
  79. package/dist/store/components/my-membership.js +42 -0
  80. package/dist/store/components/my-membership.mdx +13 -0
  81. package/dist/store/components/plan-detail.d.ts +5 -0
  82. package/dist/store/components/plan-detail.d.ts.map +1 -0
  83. package/dist/store/components/plan-detail.js +50 -0
  84. package/dist/store/components/plan-detail.mdx +15 -0
  85. package/dist/store/components/plan-listing.d.ts +2 -0
  86. package/dist/store/components/plan-listing.d.ts.map +1 -0
  87. package/dist/store/components/plan-listing.js +21 -0
  88. package/dist/store/components/plan-listing.mdx +13 -0
  89. package/dist/store/endpoints/cancel.d.ts +16 -0
  90. package/dist/store/endpoints/cancel.d.ts.map +1 -0
  91. package/dist/store/endpoints/cancel.js +25 -0
  92. package/dist/store/endpoints/check-access.d.ts +10 -0
  93. package/dist/store/endpoints/check-access.d.ts.map +1 -0
  94. package/dist/store/endpoints/check-access.js +20 -0
  95. package/dist/store/endpoints/get-membership.d.ts +19 -0
  96. package/dist/store/endpoints/get-membership.d.ts.map +1 -0
  97. package/dist/store/endpoints/get-membership.js +18 -0
  98. package/dist/store/endpoints/get-plan.d.ts +18 -0
  99. package/dist/store/endpoints/get-plan.d.ts.map +1 -0
  100. package/dist/store/endpoints/get-plan.js +17 -0
  101. package/dist/store/endpoints/list-plans.d.ts +11 -0
  102. package/dist/store/endpoints/list-plans.d.ts.map +1 -0
  103. package/dist/store/endpoints/list-plans.js +18 -0
  104. package/dist/store/endpoints/routes.d.ts +82 -0
  105. package/dist/store/endpoints/routes.d.ts.map +1 -0
  106. package/dist/store/endpoints/routes.js +14 -0
  107. package/dist/store/endpoints/subscribe.d.ts +16 -0
  108. package/dist/store/endpoints/subscribe.d.ts.map +1 -0
  109. package/dist/store/endpoints/subscribe.js +30 -0
  110. package/package.json +93 -0
package/dist/schema.js ADDED
@@ -0,0 +1,63 @@
1
+ import { col } from "@86d-store/core/schema/col";
2
+ import { z } from "zod";
3
+ export const membershipsMembershipPlanShape = z.object({
4
+ id: z.string().register(col, { pk: true }),
5
+ name: z.string(),
6
+ slug: z.string().register(col, { unique: true }),
7
+ description: z.string().optional(),
8
+ price: z.number(),
9
+ billingInterval: z.string(),
10
+ trialDays: z.number(),
11
+ features: z.record(z.string(), z.unknown()).optional(),
12
+ isActive: z.boolean(),
13
+ maxMembers: z.number().optional(),
14
+ sortOrder: z.number(),
15
+ createdAt: z.coerce.date().default(() => new Date()),
16
+ updatedAt: z.coerce.date().default(() => new Date()),
17
+ });
18
+ export const membershipsMembershipShape = z.object({
19
+ id: z.string().register(col, { pk: true }),
20
+ customerId: z.string().register(col, { index: true }),
21
+ planId: z.string().register(col, { index: true }),
22
+ status: z.string(),
23
+ startDate: z.coerce.date(),
24
+ endDate: z.coerce.date().optional(),
25
+ trialEndDate: z.coerce.date().optional(),
26
+ cancelledAt: z.coerce.date().optional(),
27
+ pausedAt: z.coerce.date().optional(),
28
+ createdAt: z.coerce.date().default(() => new Date()),
29
+ updatedAt: z.coerce.date().default(() => new Date()),
30
+ });
31
+ export const membershipsMembershipBenefitShape = z.object({
32
+ id: z.string().register(col, { pk: true }),
33
+ planId: z.string().register(col, { index: true }),
34
+ type: z.string(),
35
+ value: z.string(),
36
+ description: z.string().optional(),
37
+ isActive: z.boolean(),
38
+ createdAt: z.coerce.date().default(() => new Date()),
39
+ });
40
+ export const membershipsMembershipProductShape = z.object({
41
+ id: z.string().register(col, { pk: true }),
42
+ planId: z.string().register(col, { index: true }),
43
+ productId: z.string().register(col, { index: true }),
44
+ assignedAt: z.coerce.date().default(() => new Date()),
45
+ });
46
+ /** Native Relational storage for memberships. */
47
+ export const membershipsStorage = {
48
+ kind: "relational",
49
+ tables: {
50
+ membershipPlan: {
51
+ shape: membershipsMembershipPlanShape,
52
+ },
53
+ membership: {
54
+ shape: membershipsMembershipShape,
55
+ },
56
+ membershipBenefit: {
57
+ shape: membershipsMembershipBenefitShape,
58
+ },
59
+ membershipProduct: {
60
+ shape: membershipsMembershipProductShape,
61
+ },
62
+ },
63
+ };
@@ -0,0 +1,4 @@
1
+ import type { ModuleDataService } from "@86d-store/core/types/module";
2
+ import type { MembershipController } from "./service";
3
+ export declare function createMembershipController(data: ModuleDataService): MembershipController;
4
+ //# sourceMappingURL=service-impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-impl.d.ts","sourceRoot":"","sources":["../src/service-impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAGX,oBAAoB,EAIpB,MAAM,WAAW,CAAC;AAgBnB,wBAAgB,0BAA0B,CACzC,IAAI,EAAE,iBAAiB,GACrB,oBAAoB,CAoftB"}
@@ -0,0 +1,414 @@
1
+ function buildFindOptions(opts) {
2
+ const result = {};
3
+ if (opts.where)
4
+ result.where = opts.where;
5
+ if (opts.orderBy)
6
+ result.orderBy = opts.orderBy;
7
+ if (opts.take != null)
8
+ result.take = opts.take;
9
+ if (opts.skip != null)
10
+ result.skip = opts.skip;
11
+ return result;
12
+ }
13
+ export function createMembershipController(data) {
14
+ return {
15
+ // ── Plans ──────────────────────────────────────────
16
+ async createPlan(params) {
17
+ const id = crypto.randomUUID();
18
+ const now = new Date();
19
+ const plan = {
20
+ id,
21
+ name: params.name,
22
+ slug: params.slug,
23
+ price: params.price,
24
+ billingInterval: params.billingInterval,
25
+ trialDays: params.trialDays ?? 0,
26
+ isActive: params.isActive ?? true,
27
+ sortOrder: params.sortOrder ?? 0,
28
+ createdAt: now,
29
+ updatedAt: now,
30
+ ...(params.description != null && {
31
+ description: params.description,
32
+ }),
33
+ ...(params.features != null && { features: params.features }),
34
+ ...(params.maxMembers != null && {
35
+ maxMembers: params.maxMembers,
36
+ }),
37
+ };
38
+ await data.upsert("membershipPlan", id, plan);
39
+ return plan;
40
+ },
41
+ async getPlan(id) {
42
+ const raw = await data.get("membershipPlan", id);
43
+ return raw;
44
+ },
45
+ async getPlanBySlug(slug) {
46
+ const results = (await data.findMany("membershipPlan", {
47
+ where: { slug },
48
+ }));
49
+ return results[0] ?? null;
50
+ },
51
+ async updatePlan(id, params) {
52
+ const existing = await data.get("membershipPlan", id);
53
+ if (!existing)
54
+ return null;
55
+ const current = existing;
56
+ const base = {
57
+ id: current.id,
58
+ name: params.name ?? current.name,
59
+ slug: params.slug ?? current.slug,
60
+ price: params.price ?? current.price,
61
+ billingInterval: params.billingInterval ?? current.billingInterval,
62
+ trialDays: params.trialDays ?? current.trialDays,
63
+ isActive: params.isActive ?? current.isActive,
64
+ sortOrder: params.sortOrder ?? current.sortOrder,
65
+ createdAt: current.createdAt,
66
+ updatedAt: new Date(),
67
+ };
68
+ const optionalFields = {};
69
+ const descVal = params.description === null
70
+ ? null
71
+ : (params.description ?? current.description);
72
+ if (descVal != null)
73
+ optionalFields.description = descVal;
74
+ const featVal = params.features === null ? null : (params.features ?? current.features);
75
+ if (featVal != null)
76
+ optionalFields.features = featVal;
77
+ const maxVal = params.maxMembers === null
78
+ ? null
79
+ : (params.maxMembers ?? current.maxMembers);
80
+ if (maxVal != null)
81
+ optionalFields.maxMembers = maxVal;
82
+ const updated = { ...base, ...optionalFields };
83
+ await data.upsert("membershipPlan", id, updated);
84
+ return updated;
85
+ },
86
+ async deletePlan(id) {
87
+ const existing = await data.get("membershipPlan", id);
88
+ if (!existing)
89
+ return false;
90
+ // Cascade: remove benefits
91
+ const benefits = (await data.findMany("membershipBenefit", {
92
+ where: { planId: id },
93
+ }));
94
+ for (const b of benefits) {
95
+ await data.delete("membershipBenefit", b.id);
96
+ }
97
+ // Cascade: remove gated products
98
+ const products = (await data.findMany("membershipProduct", {
99
+ where: { planId: id },
100
+ }));
101
+ for (const p of products) {
102
+ await data.delete("membershipProduct", p.id);
103
+ }
104
+ // Cascade: remove memberships
105
+ const memberships = (await data.findMany("membership", {
106
+ where: { planId: id },
107
+ }));
108
+ for (const m of memberships) {
109
+ await data.delete("membership", m.id);
110
+ }
111
+ await data.delete("membershipPlan", id);
112
+ return true;
113
+ },
114
+ async listPlans(params) {
115
+ const where = {};
116
+ if (params?.isActive !== undefined)
117
+ where.isActive = params.isActive;
118
+ return (await data.findMany("membershipPlan", buildFindOptions({
119
+ where,
120
+ orderBy: { sortOrder: "asc" },
121
+ take: params?.take,
122
+ skip: params?.skip,
123
+ })));
124
+ },
125
+ async countPlans(params) {
126
+ const where = {};
127
+ if (params?.isActive !== undefined)
128
+ where.isActive = params.isActive;
129
+ const results = (await data.findMany("membershipPlan", {
130
+ where,
131
+ }));
132
+ return results.length;
133
+ },
134
+ // ── Memberships ───────────────────────────────────
135
+ async subscribe(params) {
136
+ const plan = await data.get("membershipPlan", params.planId);
137
+ if (!plan)
138
+ throw new Error("Plan not found");
139
+ const typed = plan;
140
+ if (!typed.isActive)
141
+ throw new Error("Plan is not active");
142
+ // Check max members limit
143
+ if (typed.maxMembers != null) {
144
+ const existing = (await data.findMany("membership", {
145
+ where: { planId: params.planId, status: "active" },
146
+ }));
147
+ const trialMembers = (await data.findMany("membership", {
148
+ where: { planId: params.planId, status: "trial" },
149
+ }));
150
+ if (existing.length + trialMembers.length >= typed.maxMembers) {
151
+ throw new Error("Plan has reached maximum members");
152
+ }
153
+ }
154
+ // Cancel any existing active/trial membership for this customer
155
+ const current = (await data.findMany("membership", {
156
+ where: { customerId: params.customerId },
157
+ }));
158
+ for (const m of current) {
159
+ if (m.status === "active" || m.status === "trial") {
160
+ const cancelled = {
161
+ ...m,
162
+ status: "cancelled",
163
+ cancelledAt: new Date(),
164
+ updatedAt: new Date(),
165
+ };
166
+ await data.upsert("membership", m.id, cancelled);
167
+ }
168
+ }
169
+ const id = crypto.randomUUID();
170
+ const now = new Date();
171
+ const hasTrial = typed.trialDays > 0;
172
+ const membership = {
173
+ id,
174
+ customerId: params.customerId,
175
+ planId: params.planId,
176
+ status: hasTrial ? "trial" : "active",
177
+ startDate: now,
178
+ createdAt: now,
179
+ updatedAt: now,
180
+ };
181
+ if (hasTrial) {
182
+ const trialEnd = new Date(now);
183
+ trialEnd.setDate(trialEnd.getDate() + typed.trialDays);
184
+ membership.trialEndDate = trialEnd;
185
+ }
186
+ if (typed.billingInterval !== "lifetime") {
187
+ const endDate = new Date(now);
188
+ if (typed.billingInterval === "monthly") {
189
+ endDate.setMonth(endDate.getMonth() + 1);
190
+ }
191
+ else {
192
+ endDate.setFullYear(endDate.getFullYear() + 1);
193
+ }
194
+ membership.endDate = endDate;
195
+ }
196
+ await data.upsert("membership", id, membership);
197
+ return membership;
198
+ },
199
+ async cancelMembership(id) {
200
+ const existing = await data.get("membership", id);
201
+ if (!existing)
202
+ return null;
203
+ const current = existing;
204
+ if (current.status === "cancelled" || current.status === "expired") {
205
+ return current;
206
+ }
207
+ const updated = {
208
+ ...current,
209
+ status: "cancelled",
210
+ cancelledAt: new Date(),
211
+ updatedAt: new Date(),
212
+ };
213
+ await data.upsert("membership", id, updated);
214
+ return updated;
215
+ },
216
+ async pauseMembership(id) {
217
+ const existing = await data.get("membership", id);
218
+ if (!existing)
219
+ return null;
220
+ const current = existing;
221
+ if (current.status !== "active" && current.status !== "trial") {
222
+ return current;
223
+ }
224
+ const updated = {
225
+ ...current,
226
+ status: "paused",
227
+ pausedAt: new Date(),
228
+ updatedAt: new Date(),
229
+ };
230
+ await data.upsert("membership", id, updated);
231
+ return updated;
232
+ },
233
+ async resumeMembership(id) {
234
+ const existing = await data.get("membership", id);
235
+ if (!existing)
236
+ return null;
237
+ const current = existing;
238
+ if (current.status !== "paused")
239
+ return current;
240
+ const updated = {
241
+ ...current,
242
+ status: "active",
243
+ updatedAt: new Date(),
244
+ };
245
+ await data.upsert("membership", id, updated);
246
+ return updated;
247
+ },
248
+ async getMembership(id) {
249
+ const raw = await data.get("membership", id);
250
+ return raw;
251
+ },
252
+ async getCustomerMembership(customerId) {
253
+ const memberships = (await data.findMany("membership", {
254
+ where: { customerId },
255
+ }));
256
+ // Find the most relevant membership: active > trial > paused
257
+ const active = memberships.find((m) => m.status === "active" ||
258
+ m.status === "trial" ||
259
+ m.status === "paused");
260
+ if (!active)
261
+ return null;
262
+ const plan = await data.get("membershipPlan", active.planId);
263
+ if (!plan)
264
+ return null;
265
+ return {
266
+ ...active,
267
+ plan: plan,
268
+ };
269
+ },
270
+ async listMemberships(params) {
271
+ const where = {};
272
+ if (params?.planId !== undefined)
273
+ where.planId = params.planId;
274
+ if (params?.status !== undefined)
275
+ where.status = params.status;
276
+ return (await data.findMany("membership", buildFindOptions({
277
+ where,
278
+ orderBy: { createdAt: "desc" },
279
+ take: params?.take,
280
+ skip: params?.skip,
281
+ })));
282
+ },
283
+ async countMemberships(params) {
284
+ const where = {};
285
+ if (params?.planId !== undefined)
286
+ where.planId = params.planId;
287
+ if (params?.status !== undefined)
288
+ where.status = params.status;
289
+ const results = (await data.findMany("membership", {
290
+ where,
291
+ }));
292
+ return results.length;
293
+ },
294
+ // ── Benefits ──────────────────────────────────────
295
+ async addBenefit(params) {
296
+ const id = crypto.randomUUID();
297
+ const benefit = {
298
+ id,
299
+ planId: params.planId,
300
+ type: params.type,
301
+ value: params.value,
302
+ isActive: params.isActive ?? true,
303
+ createdAt: new Date(),
304
+ ...(params.description != null && {
305
+ description: params.description,
306
+ }),
307
+ };
308
+ await data.upsert("membershipBenefit", id, benefit);
309
+ return benefit;
310
+ },
311
+ async removeBenefit(id) {
312
+ const existing = await data.get("membershipBenefit", id);
313
+ if (!existing)
314
+ return false;
315
+ await data.delete("membershipBenefit", id);
316
+ return true;
317
+ },
318
+ async listBenefits(planId) {
319
+ return (await data.findMany("membershipBenefit", {
320
+ where: { planId },
321
+ }));
322
+ },
323
+ async getCustomerBenefits(customerId) {
324
+ const memberships = (await data.findMany("membership", {
325
+ where: { customerId },
326
+ }));
327
+ const active = memberships.find((m) => m.status === "active" || m.status === "trial");
328
+ if (!active)
329
+ return [];
330
+ const benefits = (await data.findMany("membershipBenefit", {
331
+ where: { planId: active.planId, isActive: true },
332
+ }));
333
+ return benefits;
334
+ },
335
+ // ── Product gating ────────────────────────────────
336
+ async gateProduct(params) {
337
+ // Check if already gated to this plan
338
+ const existing = (await data.findMany("membershipProduct", {
339
+ where: { planId: params.planId, productId: params.productId },
340
+ }));
341
+ if (existing.length > 0)
342
+ return existing[0];
343
+ const id = crypto.randomUUID();
344
+ const gated = {
345
+ id,
346
+ planId: params.planId,
347
+ productId: params.productId,
348
+ assignedAt: new Date(),
349
+ };
350
+ await data.upsert("membershipProduct", id, gated);
351
+ return gated;
352
+ },
353
+ async ungateProduct(params) {
354
+ const existing = (await data.findMany("membershipProduct", {
355
+ where: { planId: params.planId, productId: params.productId },
356
+ }));
357
+ if (existing.length === 0)
358
+ return false;
359
+ for (const item of existing) {
360
+ await data.delete("membershipProduct", item.id);
361
+ }
362
+ return true;
363
+ },
364
+ async listGatedProducts(params) {
365
+ return (await data.findMany("membershipProduct", buildFindOptions({
366
+ where: { planId: params.planId },
367
+ orderBy: { assignedAt: "desc" },
368
+ take: params.take,
369
+ skip: params.skip,
370
+ })));
371
+ },
372
+ async countGatedProducts(planId) {
373
+ const results = (await data.findMany("membershipProduct", {
374
+ where: { planId },
375
+ }));
376
+ return results.length;
377
+ },
378
+ async canAccessProduct(params) {
379
+ // Check if the product is gated at all
380
+ const gated = (await data.findMany("membershipProduct", {
381
+ where: { productId: params.productId },
382
+ }));
383
+ // Not gated — everyone can access
384
+ if (gated.length === 0)
385
+ return true;
386
+ // Gated — check if customer has an active membership for any qualifying plan
387
+ const memberships = (await data.findMany("membership", {
388
+ where: { customerId: params.customerId },
389
+ }));
390
+ const activeMembership = memberships.find((m) => m.status === "active" || m.status === "trial");
391
+ if (!activeMembership)
392
+ return false;
393
+ return gated.some((g) => g.planId === activeMembership.planId);
394
+ },
395
+ // ── Admin ─────────────────────────────────────────
396
+ async getStats() {
397
+ const allPlans = (await data.findMany("membershipPlan", {}));
398
+ const allMemberships = (await data.findMany("membership", {}));
399
+ const allGated = (await data.findMany("membershipProduct", {}));
400
+ const stats = {
401
+ totalPlans: allPlans.length,
402
+ activePlans: allPlans.filter((p) => p.isActive).length,
403
+ totalMembers: allMemberships.length,
404
+ activeMembers: allMemberships.filter((m) => m.status === "active")
405
+ .length,
406
+ trialMembers: allMemberships.filter((m) => m.status === "trial").length,
407
+ cancelledMembers: allMemberships.filter((m) => m.status === "cancelled")
408
+ .length,
409
+ gatedProducts: new Set(allGated.map((g) => g.productId)).size,
410
+ };
411
+ return stats;
412
+ },
413
+ };
414
+ }
@@ -0,0 +1,145 @@
1
+ import type { ModuleController } from "@86d-store/core/types/module";
2
+ export type BillingInterval = "monthly" | "yearly" | "lifetime";
3
+ export type MembershipStatus = "active" | "trial" | "expired" | "cancelled" | "paused";
4
+ export type BenefitType = "discount_percentage" | "free_shipping" | "early_access" | "exclusive_products" | "priority_support";
5
+ export type MembershipPlan = {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ description?: string;
10
+ price: number;
11
+ billingInterval: BillingInterval;
12
+ trialDays: number;
13
+ features?: string[];
14
+ isActive: boolean;
15
+ maxMembers?: number;
16
+ sortOrder: number;
17
+ createdAt: Date;
18
+ updatedAt: Date;
19
+ };
20
+ export type Membership = {
21
+ id: string;
22
+ customerId: string;
23
+ planId: string;
24
+ status: MembershipStatus;
25
+ startDate: Date;
26
+ endDate?: Date;
27
+ trialEndDate?: Date;
28
+ cancelledAt?: Date;
29
+ pausedAt?: Date;
30
+ createdAt: Date;
31
+ updatedAt: Date;
32
+ };
33
+ export type MembershipBenefit = {
34
+ id: string;
35
+ planId: string;
36
+ type: BenefitType;
37
+ value: string;
38
+ description?: string;
39
+ isActive: boolean;
40
+ createdAt: Date;
41
+ };
42
+ export type MembershipProduct = {
43
+ id: string;
44
+ planId: string;
45
+ productId: string;
46
+ assignedAt: Date;
47
+ };
48
+ export type MembershipWithPlan = Membership & {
49
+ plan: MembershipPlan;
50
+ };
51
+ export type MembershipStats = {
52
+ totalPlans: number;
53
+ activePlans: number;
54
+ totalMembers: number;
55
+ activeMembers: number;
56
+ trialMembers: number;
57
+ cancelledMembers: number;
58
+ gatedProducts: number;
59
+ };
60
+ export type MembershipController = ModuleController & {
61
+ createPlan(params: {
62
+ name: string;
63
+ slug: string;
64
+ description?: string;
65
+ price: number;
66
+ billingInterval: BillingInterval;
67
+ trialDays?: number;
68
+ features?: string[];
69
+ isActive?: boolean;
70
+ maxMembers?: number;
71
+ sortOrder?: number;
72
+ }): Promise<MembershipPlan>;
73
+ getPlan(id: string): Promise<MembershipPlan | null>;
74
+ getPlanBySlug(slug: string): Promise<MembershipPlan | null>;
75
+ updatePlan(id: string, params: {
76
+ name?: string;
77
+ slug?: string;
78
+ description?: string | null;
79
+ price?: number;
80
+ billingInterval?: BillingInterval;
81
+ trialDays?: number;
82
+ features?: string[] | null;
83
+ isActive?: boolean;
84
+ maxMembers?: number | null;
85
+ sortOrder?: number;
86
+ }): Promise<MembershipPlan | null>;
87
+ deletePlan(id: string): Promise<boolean>;
88
+ listPlans(params?: {
89
+ isActive?: boolean;
90
+ take?: number;
91
+ skip?: number;
92
+ }): Promise<MembershipPlan[]>;
93
+ countPlans(params?: {
94
+ isActive?: boolean;
95
+ }): Promise<number>;
96
+ subscribe(params: {
97
+ customerId: string;
98
+ planId: string;
99
+ }): Promise<Membership>;
100
+ cancelMembership(id: string): Promise<Membership | null>;
101
+ pauseMembership(id: string): Promise<Membership | null>;
102
+ resumeMembership(id: string): Promise<Membership | null>;
103
+ getMembership(id: string): Promise<Membership | null>;
104
+ getCustomerMembership(customerId: string): Promise<MembershipWithPlan | null>;
105
+ listMemberships(params?: {
106
+ planId?: string;
107
+ status?: MembershipStatus;
108
+ take?: number;
109
+ skip?: number;
110
+ }): Promise<Membership[]>;
111
+ countMemberships(params?: {
112
+ planId?: string;
113
+ status?: MembershipStatus;
114
+ }): Promise<number>;
115
+ addBenefit(params: {
116
+ planId: string;
117
+ type: BenefitType;
118
+ value: string;
119
+ description?: string;
120
+ isActive?: boolean;
121
+ }): Promise<MembershipBenefit>;
122
+ removeBenefit(id: string): Promise<boolean>;
123
+ listBenefits(planId: string): Promise<MembershipBenefit[]>;
124
+ getCustomerBenefits(customerId: string): Promise<MembershipBenefit[]>;
125
+ gateProduct(params: {
126
+ planId: string;
127
+ productId: string;
128
+ }): Promise<MembershipProduct>;
129
+ ungateProduct(params: {
130
+ planId: string;
131
+ productId: string;
132
+ }): Promise<boolean>;
133
+ listGatedProducts(params: {
134
+ planId: string;
135
+ take?: number;
136
+ skip?: number;
137
+ }): Promise<MembershipProduct[]>;
138
+ countGatedProducts(planId: string): Promise<number>;
139
+ canAccessProduct(params: {
140
+ customerId: string;
141
+ productId: string;
142
+ }): Promise<boolean>;
143
+ getStats(): Promise<MembershipStats>;
144
+ };
145
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export declare function useMembershipsApi(): {
2
+ listPlans: import("@86d-store/core/client/types").AnyEndpointHook;
3
+ getPlan: import("@86d-store/core/client/types").AnyEndpointHook;
4
+ getMyMembership: import("@86d-store/core/client/types").AnyEndpointHook;
5
+ subscribe: import("@86d-store/core/client/types").AnyEndpointHook;
6
+ cancel: import("@86d-store/core/client/types").AnyEndpointHook;
7
+ checkAccess: import("@86d-store/core/client/types").AnyEndpointHook;
8
+ };
9
+ //# sourceMappingURL=_hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_hooks.d.ts","sourceRoot":"","sources":["../../../src/store/components/_hooks.ts"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB;;;;;;;EAYhC"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { useModuleClient } from "@86d-store/core/client/provider";
3
+ export function useMembershipsApi() {
4
+ const client = useModuleClient();
5
+ return {
6
+ listPlans: client.module("memberships").store["/memberships/plans"],
7
+ getPlan: client.module("memberships").store["/memberships/plans/:slug"],
8
+ getMyMembership: client.module("memberships").store["/memberships/my-membership"],
9
+ subscribe: client.module("memberships").store["/memberships/subscribe"],
10
+ cancel: client.module("memberships").store["/memberships/cancel"],
11
+ checkAccess: client.module("memberships").store["/memberships/check-access"],
12
+ };
13
+ }