@odla-ai/chapter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @odla-ai/chapter
2
+
3
+ A foundation for **membership sites**. One `defineChapter({...})` config
4
+ stands up a full member site — join/apply, Stripe membership, Google booking, a
5
+ member area, an admin console, and a CRM — or an **admin-only hub**, on odla-db +
6
+ Clerk + [@odla-ai/crm](https://odla.ai/docs/packages/crm) + calendar + email, with
7
+ a themeable web-component chrome engine.
8
+
9
+ ```sh
10
+ npm i @odla-ai/chapter
11
+ ```
12
+
13
+ > **Agentic experiment.** Built and maintained by AI agents from bounded runbooks
14
+ > with human review. Review the documented guarantees before relying on it.
15
+
16
+ ## The shape
17
+
18
+ - **One config, two profiles.** `defineChapter()` validates at import and returns
19
+ a resolved engine. `mode: "chapter"` is the full public member site; `mode:
20
+ "hub"` is admin-only and CRM-focused (a directory/registry over the same CRM).
21
+ Same worker shell, auth, CRM, chrome, and provisioning — the mode gates the
22
+ member/join/payment route surface.
23
+ - **The worker is the package.** `chapterWorker({ chapter })` is the whole
24
+ Cloudflare `ExportedHandler` (auth verify, CRM mount, Stripe, calendar, email,
25
+ webhooks, admin), wrapped in observability. A site's `src/worker.ts` is ~3
26
+ lines. Everything brand-specific (prices, policy copy, email templates,
27
+ scheduling) is read at runtime from a single odla-db `groups` row, never
28
+ hardcoded.
29
+ - **Provisioning is declarative.** `createChapterIntegration(chapter, opts)` is a
30
+ CLI-consumable descriptor that composes the crm namespaces + the chapter
31
+ namespaces (`applications`, `groups`, `meetings`, `emailLog`, `admins`) + a
32
+ guarded group-row seed. Drop it in `odla.config.mjs` `integrations: [...]`.
33
+ - **Chrome, not content.** `./ui` ships the Preact islands (admin/member/join)
34
+ and a themeable `<site-header>`/`<site-footer>` engine over `@odla-ai/ui`; each
35
+ site authors its own marketing page bodies from its brandbook.
36
+
37
+ ## Quick start
38
+
39
+ ```ts
40
+ // src/chapter.config.mjs
41
+ import { defineChapter } from "@odla-ai/chapter";
42
+
43
+ export const chapter = defineChapter({
44
+ id: "example-chapter",
45
+ name: "Example Chapter",
46
+ url: "example.com",
47
+ mode: "chapter",
48
+ brand: { palette: { /* --ui-* overrides */ }, wordmark: "Example Chapter" },
49
+ prices: { standardCents: 100000, foundingDiscountCents: 10000 },
50
+ emails: { notificationEmail: "hello@example.com" },
51
+ });
52
+ ```
53
+
54
+ ```ts
55
+ // src/worker.ts
56
+ import { chapterWorker } from "@odla-ai/chapter/worker";
57
+ import { chapter } from "./chapter.config.mjs";
58
+ export default chapterWorker({ chapter });
59
+ ```
60
+
61
+ ```ts
62
+ // odla.config.mjs
63
+ import { createChapterIntegration } from "@odla-ai/chapter";
64
+ import { chapter } from "./src/chapter.config.mjs";
65
+ export default {
66
+ app: { id: chapter.id, name: chapter.name },
67
+ services: chapter.services,
68
+ integrations: [createChapterIntegration(chapter)],
69
+ };
70
+ ```
71
+
72
+ MIT © odla
package/dist/index.cjs ADDED
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ buildGroupSeed: () => buildGroupSeed,
24
+ chapterDb: () => chapterDb,
25
+ createChapterIntegration: () => createChapterIntegration,
26
+ defaultCrm: () => defaultCrm,
27
+ defineChapter: () => defineChapter
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/config.ts
32
+ var import_crm = require("@odla-ai/crm");
33
+
34
+ // src/schema.ts
35
+ function attr(type, flags = {}) {
36
+ return {
37
+ type,
38
+ unique: flags.unique ?? false,
39
+ indexed: flags.indexed ?? false,
40
+ optional: flags.optional ?? false
41
+ };
42
+ }
43
+ var id = () => attr("string", { unique: true, indexed: true });
44
+ var admins = {
45
+ attrs: {
46
+ id: id(),
47
+ email: attr("string", { unique: true, indexed: true }),
48
+ name: attr("string", { optional: true }),
49
+ note: attr("string", { optional: true })
50
+ }
51
+ };
52
+ var applications = {
53
+ attrs: {
54
+ id: id(),
55
+ firstName: attr("string"),
56
+ lastName: attr("string"),
57
+ email: attr("string", { indexed: true }),
58
+ referral: attr("string"),
59
+ referralName: attr("string", { optional: true }),
60
+ whoYouAre: attr("string"),
61
+ focus: attr("json"),
62
+ linkedin: attr("string", { optional: true }),
63
+ message: attr("string"),
64
+ status: attr("string", { indexed: true }),
65
+ createdAt: attr("number", { indexed: true }),
66
+ meetingAt: attr("number", { indexed: true, optional: true }),
67
+ meetingLink: attr("string", { optional: true }),
68
+ clerkUserId: attr("string", { indexed: true, optional: true }),
69
+ phone: attr("string", { optional: true }),
70
+ state: attr("string", { optional: true }),
71
+ groupId: attr("string", { indexed: true, optional: true }),
72
+ stripeCustomerId: attr("string", { indexed: true, optional: true }),
73
+ stripeSubscriptionId: attr("string", { indexed: true, optional: true }),
74
+ renewalAt: attr("number", { optional: true }),
75
+ disclaimerAckAt: attr("number", { optional: true }),
76
+ refundPolicyAckAt: attr("number", { optional: true }),
77
+ prepEmailSentAt: attr("number", { optional: true }),
78
+ canceled: attr("boolean", { optional: true })
79
+ }
80
+ };
81
+ var groups = {
82
+ attrs: {
83
+ id: id(),
84
+ name: attr("string"),
85
+ standardPriceCents: attr("number"),
86
+ foundingDiscountCents: attr("number"),
87
+ stripePriceId: attr("string", { optional: true }),
88
+ stripePublishableKey: attr("string", { optional: true }),
89
+ notificationEmail: attr("string"),
90
+ replyTo: attr("string"),
91
+ debugEmail: attr("string", { optional: true }),
92
+ calendarLink: attr("string", { optional: true }),
93
+ disclaimerText: attr("string"),
94
+ refundPolicyText: attr("string"),
95
+ trustCopy: attr("string"),
96
+ commitmentText: attr("string", { optional: true }),
97
+ normsText: attr("string", { optional: true }),
98
+ emailTemplates: attr("json"),
99
+ schedulingJson: attr("json", { optional: true }),
100
+ createdAt: attr("number", { indexed: true })
101
+ }
102
+ };
103
+ var meetings = {
104
+ attrs: {
105
+ id: id(),
106
+ applicationId: attr("string", { indexed: true }),
107
+ groupId: attr("string", { indexed: true }),
108
+ startAt: attr("number", { indexed: true }),
109
+ endAt: attr("number"),
110
+ timezone: attr("string"),
111
+ status: attr("string", { indexed: true }),
112
+ googleEventId: attr("string", { indexed: true, optional: true }),
113
+ meetUrl: attr("string", { optional: true }),
114
+ htmlLink: attr("string", { optional: true }),
115
+ drift: attr("string", { indexed: true, optional: true }),
116
+ driftGoogleStartAt: attr("number", { optional: true }),
117
+ driftDetectedAt: attr("number", { optional: true }),
118
+ adoptedFromGoogleAt: attr("number", { optional: true }),
119
+ createdAt: attr("number", { indexed: true })
120
+ }
121
+ };
122
+ var emailLog = {
123
+ attrs: {
124
+ id: id(),
125
+ groupId: attr("string", { indexed: true }),
126
+ applicationId: attr("string", { indexed: true, optional: true }),
127
+ to: attr("string", { indexed: true }),
128
+ template: attr("string", { indexed: true }),
129
+ subject: attr("string"),
130
+ body: attr("string", { optional: true }),
131
+ transport: attr("string"),
132
+ messageId: attr("string", { optional: true }),
133
+ redirected: attr("boolean", { optional: true }),
134
+ dedupeKey: attr("string", { indexed: true, optional: true }),
135
+ error: attr("string", { optional: true }),
136
+ sentAt: attr("number", { indexed: true })
137
+ }
138
+ };
139
+ function chapterDb(mode) {
140
+ const entities = mode === "hub" ? { admins } : { admins, applications, groups, meetings, emailLog };
141
+ const schema = { entities, links: {} };
142
+ const rules = {};
143
+ for (const ns of Object.keys(entities)) {
144
+ rules[ns] = { view: "false", create: "false", update: "false", delete: "false" };
145
+ }
146
+ return { schema, rules };
147
+ }
148
+
149
+ // src/defaults.ts
150
+ var TEMPLATES = {
151
+ personal: {
152
+ class: "transactional",
153
+ vars: ["firstName", "subject", "body"],
154
+ defaults: { subject: "{{subject}}", text: "{{body}}" }
155
+ },
156
+ announcement: {
157
+ class: "marketing",
158
+ vars: ["firstName", "subject", "body", "unsubscribeUrl"],
159
+ defaults: { subject: "{{subject}}", text: "{{body}}\n\n{{unsubscribeUrl}}" }
160
+ }
161
+ };
162
+ function personType() {
163
+ return {
164
+ label: "Person",
165
+ labelPlural: "People",
166
+ nameField: "name",
167
+ emailField: "email",
168
+ fields: {
169
+ name: { type: "string", label: "Name", required: true },
170
+ email: { type: "email", label: "Email" },
171
+ firstName: { type: "string", label: "First name" },
172
+ lastName: { type: "string", label: "Last name" },
173
+ phone: { type: "string", label: "Phone" },
174
+ state: { type: "string", label: "State", slot: "s1" },
175
+ whoYouAre: { type: "string", label: "Who they are", slot: "s2" },
176
+ referral: { type: "string", label: "Referral source", slot: "s3" },
177
+ linkedin: { type: "string", label: "LinkedIn" },
178
+ focus: { type: "json", label: "Focus areas" },
179
+ message: { type: "string", label: "Intro message" }
180
+ },
181
+ pipeline: {
182
+ stages: [
183
+ { id: "submitted", label: "Submitted" },
184
+ { id: "paid_pending_vetting", label: "Paid, pending vetting" },
185
+ { id: "call_scheduled", label: "Call scheduled" },
186
+ { id: "interviewed", label: "Interviewed" },
187
+ { id: "approved", label: "Approved" },
188
+ { id: "declined", label: "Declined" },
189
+ { id: "refunded", label: "Refunded" }
190
+ ]
191
+ },
192
+ facets: { identity: true, email: true, rank: "manual" }
193
+ };
194
+ }
195
+ function companyType() {
196
+ return {
197
+ label: "Business",
198
+ labelPlural: "Businesses",
199
+ nameField: "name",
200
+ fields: {
201
+ name: { type: "string", label: "Name", required: true },
202
+ domain: { type: "string", label: "Domain / website", slot: "s1" },
203
+ industry: { type: "string", label: "Industry", slot: "s2" },
204
+ location: { type: "string", label: "Location", slot: "s3" },
205
+ chapter: { type: "string", label: "Chapter", slot: "s4" },
206
+ linkedin: { type: "string", label: "LinkedIn" },
207
+ notes: { type: "string", label: "Notes" }
208
+ },
209
+ facets: { rank: "manual" }
210
+ };
211
+ }
212
+ function defaultCrm(mode) {
213
+ if (mode === "hub") {
214
+ return {
215
+ types: { person: personType(), company: companyType() },
216
+ relations: { works_at: { from: "person", to: "company", label: "works at", reverseLabel: "team" } },
217
+ templates: TEMPLATES
218
+ };
219
+ }
220
+ return {
221
+ types: { person: personType() },
222
+ templates: TEMPLATES
223
+ };
224
+ }
225
+
226
+ // src/group.ts
227
+ var DEFAULT_SCHEDULING = {
228
+ slotMinutes: 45,
229
+ days: [1, 2, 3, 4, 5],
230
+ startHour: 9,
231
+ endHour: 17,
232
+ timezone: "America/Los_Angeles",
233
+ minNoticeHours: 24,
234
+ windowDays: 14
235
+ };
236
+ function defaultEmailTemplates(name) {
237
+ const sign = `
238
+
239
+ Warmly,
240
+ ${name}`;
241
+ return {
242
+ adminNotification: {
243
+ subject: `New application \u2014 {{firstName}} {{lastName}}`,
244
+ text: `A new application came in for ${name}.
245
+
246
+ Name: {{firstName}} {{lastName}}
247
+ Email: {{email}}`
248
+ },
249
+ paymentConfirmation: {
250
+ subject: `Welcome to ${name}`,
251
+ text: `Hi {{firstName}},
252
+
253
+ Your membership payment is confirmed. We'll be in touch to schedule your intro call.${sign}`
254
+ },
255
+ prepEmail: {
256
+ subject: `Your ${name} intro call`,
257
+ text: `Hi {{firstName}},
258
+
259
+ Looking forward to our call at {{meetingTime}}. {{meetingLink}}${sign}`
260
+ },
261
+ onboardingInvite: {
262
+ subject: `You're in \u2014 ${name}`,
263
+ text: `Hi {{firstName}},
264
+
265
+ Welcome to ${name}. Your member area is here: {{membersUrl}}${sign}`
266
+ }
267
+ };
268
+ }
269
+ function buildGroupSeed(config) {
270
+ const prices = config.prices;
271
+ const emails = config.emails;
272
+ const policy = config.policy ?? {};
273
+ const scheduling = {
274
+ ...DEFAULT_SCHEDULING,
275
+ ...config.scheduling ?? {},
276
+ summaryTemplate: config.scheduling?.summaryTemplate ?? `${config.name}: introduction call with {{firstName}} {{lastName}}`
277
+ };
278
+ const row = {
279
+ id: config.id,
280
+ name: config.name,
281
+ standardPriceCents: prices?.standardCents ?? 0,
282
+ foundingDiscountCents: prices?.foundingDiscountCents ?? 0,
283
+ notificationEmail: emails?.notificationEmail ?? "",
284
+ replyTo: emails?.replyTo ?? emails?.notificationEmail ?? "",
285
+ disclaimerText: policy.disclaimerText ?? "",
286
+ refundPolicyText: policy.refundPolicyText ?? "",
287
+ trustCopy: policy.trustCopy ?? "",
288
+ emailTemplates: emails?.templates ?? defaultEmailTemplates(config.name),
289
+ schedulingJson: scheduling
290
+ };
291
+ if (emails?.debugEmail) row.debugEmail = emails.debugEmail;
292
+ if (policy.commitmentText) row.commitmentText = policy.commitmentText;
293
+ if (policy.normsText) row.normsText = policy.normsText;
294
+ return row;
295
+ }
296
+
297
+ // src/config.ts
298
+ var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
299
+ function isResolvedCrm(x) {
300
+ return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
301
+ }
302
+ function defineChapter(config) {
303
+ if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
304
+ const { id: id2, name } = config;
305
+ if (typeof id2 !== "string" || !SLUG.test(id2)) {
306
+ throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id2)}`);
307
+ }
308
+ if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
309
+ const mode = config.mode ?? "chapter";
310
+ if (mode !== "chapter" && mode !== "hub") throw new Error(`defineChapter.mode: must be "chapter" or "hub" \u2014 got ${JSON.stringify(config.mode)}`);
311
+ const crm = isResolvedCrm(config.crm) ? config.crm : (0, import_crm.defineCrm)(config.crm ?? defaultCrm(mode));
312
+ if (mode === "chapter") {
313
+ if (!config.emails || typeof config.emails.notificationEmail !== "string" || config.emails.notificationEmail === "") {
314
+ throw new Error("defineChapter.emails.notificationEmail: required in chapter mode");
315
+ }
316
+ if (!config.prices || typeof config.prices.standardCents !== "number") {
317
+ throw new Error("defineChapter.prices.standardCents: required in chapter mode");
318
+ }
319
+ }
320
+ const { schema, rules } = chapterDb(mode);
321
+ const services = config.services ?? ["db", "calendar", "o11y"];
322
+ const chapter = {
323
+ config,
324
+ id: id2,
325
+ name,
326
+ mode,
327
+ crm,
328
+ schema,
329
+ rules,
330
+ services,
331
+ groupSeed: () => mode === "chapter" ? buildGroupSeed(config) : null
332
+ };
333
+ if (config.url !== void 0) chapter.url = config.url;
334
+ return chapter;
335
+ }
336
+
337
+ // src/descriptor.ts
338
+ var import_crm2 = require("@odla-ai/crm");
339
+ function createChapterIntegration(chapter, options = {}) {
340
+ const basePath = options.basePath ?? "/api/crm";
341
+ const now = options.now ?? Date.now();
342
+ const emails = chapter.config.emails;
343
+ const crmDesc = (0, import_crm2.createCrmIntegration)(chapter.crm, {
344
+ basePath,
345
+ now,
346
+ ...emails?.notificationEmail ? { notificationEmail: emails.notificationEmail } : {},
347
+ ...emails?.replyTo ? { replyTo: emails.replyTo } : {},
348
+ ...emails?.debugEmail ? { debugEmail: emails.debugEmail } : {}
349
+ });
350
+ const seeds = [...crmDesc.seeds ?? []];
351
+ const group = chapter.groupSeed();
352
+ if (group) {
353
+ seeds.push({ id: "group", ns: "groups", key: { attr: "id", value: chapter.id }, attrs: { ...group, createdAt: now } });
354
+ }
355
+ return {
356
+ id: "chapter",
357
+ title: `Chapter \u2014 ${chapter.name}`,
358
+ npm: "@odla-ai/chapter",
359
+ schema: {
360
+ entities: { ...crmDesc.schema.entities, ...chapter.schema.entities },
361
+ links: { ...crmDesc.schema.links, ...chapter.schema.links }
362
+ },
363
+ rules: { ...crmDesc.rules, ...chapter.rules },
364
+ seeds,
365
+ probes: [...crmDesc.probes ?? []]
366
+ };
367
+ }
368
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/schema.ts","../src/defaults.ts","../src/group.ts","../src/descriptor.ts"],"sourcesContent":["// @odla-ai/chapter — core (platform-neutral). The UI kit lives at ./ui.\n// chapterWorker (the full Cloudflare handler) lands with the worker port.\nexport { defineChapter } from \"./config\";\nexport { createChapterIntegration } from \"./descriptor\";\nexport { chapterDb } from \"./schema\";\nexport { defaultCrm } from \"./defaults\";\nexport { buildGroupSeed } from \"./group\";\n\nexport type { ChapterIntegrationDescriptor, ChapterIntegrationOptions } from \"./descriptor\";\nexport type {\n Chapter,\n ChapterConfig,\n ChapterMode,\n ChapterBrand,\n ChapterPrices,\n ChapterPolicy,\n ChapterEmails,\n ChapterScheduling,\n EmailTemplate,\n DbSchema,\n DbRules,\n Attr,\n AttrType,\n Entity,\n Rule,\n} from \"./types\";\n","// defineChapter — validate-at-import config, reusing @odla-ai/crm's defineCrm.\n// A bad config throws here (startup), never at request time.\nimport { defineCrm } from \"@odla-ai/crm\";\nimport type { CrmConfig, Crm } from \"@odla-ai/crm\";\nimport type { Chapter, ChapterConfig, ChapterMode } from \"./types\";\nimport { chapterDb } from \"./schema\";\nimport { defaultCrm } from \"./defaults\";\nimport { buildGroupSeed } from \"./group\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\n\nfunction isResolvedCrm(x: CrmConfig | Crm | undefined): x is Crm {\n return (\n !!x &&\n typeof x === \"object\" &&\n \"prepare\" in x &&\n typeof (x as { prepare?: unknown }).prepare === \"function\"\n );\n}\n\n/**\n * Validate a chapter/hub config and resolve its engine — the CRM, the chapter's\n * odla-db schema/rules, and the seed groups row. Throws at import on a bad\n * config (bad slug, wrong mode, missing chapter-mode prices/emails), never at\n * request time.\n */\nexport function defineChapter(config: ChapterConfig): Chapter {\n if (!config || typeof config !== \"object\") throw new Error(\"defineChapter: a config object is required\");\n const { id, name } = config;\n if (typeof id !== \"string\" || !SLUG.test(id)) {\n throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) — got ${JSON.stringify(id)}`);\n }\n if (typeof name !== \"string\" || name.trim() === \"\") throw new Error(\"defineChapter.name: a non-empty name is required\");\n\n const mode: ChapterMode = config.mode ?? \"chapter\";\n if (mode !== \"chapter\" && mode !== \"hub\") throw new Error(`defineChapter.mode: must be \"chapter\" or \"hub\" — got ${JSON.stringify(config.mode)}`);\n\n const crm: Crm = isResolvedCrm(config.crm) ? config.crm : defineCrm(config.crm ?? defaultCrm(mode));\n\n if (mode === \"chapter\") {\n if (!config.emails || typeof config.emails.notificationEmail !== \"string\" || config.emails.notificationEmail === \"\") {\n throw new Error(\"defineChapter.emails.notificationEmail: required in chapter mode\");\n }\n if (!config.prices || typeof config.prices.standardCents !== \"number\") {\n throw new Error(\"defineChapter.prices.standardCents: required in chapter mode\");\n }\n }\n\n const { schema, rules } = chapterDb(mode);\n const services = config.services ?? [\"db\", \"calendar\", \"o11y\"];\n\n const chapter: Chapter = {\n config,\n id,\n name,\n mode,\n crm,\n schema,\n rules,\n services,\n groupSeed: () => (mode === \"chapter\" ? buildGroupSeed(config) : null),\n };\n if (config.url !== undefined) chapter.url = config.url;\n return chapter;\n}\n","// The chapter's own odla-db namespaces: applications, groups, meetings, and\n// emailLog, plus a single default-deny `admins` allowlist. The `crm_*`\n// namespaces are contributed separately by @odla-ai/crm's integration and\n// merged by the CLI.\nimport type { Attr, AttrType, DbRules, DbSchema, Entity, ChapterMode } from \"./types\";\n\nfunction attr(type: AttrType, flags: { unique?: boolean; indexed?: boolean; optional?: boolean } = {}): Attr {\n return {\n type,\n unique: flags.unique ?? false,\n indexed: flags.indexed ?? false,\n optional: flags.optional ?? false,\n };\n}\nconst id = (): Attr => attr(\"string\", { unique: true, indexed: true });\n\n// The allowlist that gates admin access in BOTH modes. Studio-write-only:\n// deny-all, and no worker route ever writes it. Creation time is odla-db's\n// built-in $createdAt. One row per admin, keyed by lowercased email.\nconst admins: Entity = {\n attrs: {\n id: id(),\n email: attr(\"string\", { unique: true, indexed: true }),\n name: attr(\"string\", { optional: true }),\n note: attr(\"string\", { optional: true }),\n },\n};\n\n// One row per membership application (chapter mode). Field names mirror the\n// join form. status pipeline drives the provisional -> member promotion.\nconst applications: Entity = {\n attrs: {\n id: id(),\n firstName: attr(\"string\"),\n lastName: attr(\"string\"),\n email: attr(\"string\", { indexed: true }),\n referral: attr(\"string\"),\n referralName: attr(\"string\", { optional: true }),\n whoYouAre: attr(\"string\"),\n focus: attr(\"json\"),\n linkedin: attr(\"string\", { optional: true }),\n message: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n createdAt: attr(\"number\", { indexed: true }),\n meetingAt: attr(\"number\", { indexed: true, optional: true }),\n meetingLink: attr(\"string\", { optional: true }),\n clerkUserId: attr(\"string\", { indexed: true, optional: true }),\n phone: attr(\"string\", { optional: true }),\n state: attr(\"string\", { optional: true }),\n groupId: attr(\"string\", { indexed: true, optional: true }),\n stripeCustomerId: attr(\"string\", { indexed: true, optional: true }),\n stripeSubscriptionId: attr(\"string\", { indexed: true, optional: true }),\n renewalAt: attr(\"number\", { optional: true }),\n disclaimerAckAt: attr(\"number\", { optional: true }),\n refundPolicyAckAt: attr(\"number\", { optional: true }),\n prepEmailSentAt: attr(\"number\", { optional: true }),\n canceled: attr(\"boolean\", { optional: true }),\n },\n};\n\n// Per-group settings — prices, policy copy, email templates, scheduling — never\n// in code. Seeded once from the defineChapter config (see group.ts).\nconst groups: Entity = {\n attrs: {\n id: id(),\n name: attr(\"string\"),\n standardPriceCents: attr(\"number\"),\n foundingDiscountCents: attr(\"number\"),\n stripePriceId: attr(\"string\", { optional: true }),\n stripePublishableKey: attr(\"string\", { optional: true }),\n notificationEmail: attr(\"string\"),\n replyTo: attr(\"string\"),\n debugEmail: attr(\"string\", { optional: true }),\n calendarLink: attr(\"string\", { optional: true }),\n disclaimerText: attr(\"string\"),\n refundPolicyText: attr(\"string\"),\n trustCopy: attr(\"string\"),\n commitmentText: attr(\"string\", { optional: true }),\n normsText: attr(\"string\", { optional: true }),\n emailTemplates: attr(\"json\"),\n schedulingJson: attr(\"json\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Intro-call meetings: the source of truth for scheduling; Google Calendar is a\n// projection. Drift fields record when Google disagrees with us.\nconst meetings: Entity = {\n attrs: {\n id: id(),\n applicationId: attr(\"string\", { indexed: true }),\n groupId: attr(\"string\", { indexed: true }),\n startAt: attr(\"number\", { indexed: true }),\n endAt: attr(\"number\"),\n timezone: attr(\"string\"),\n status: attr(\"string\", { indexed: true }),\n googleEventId: attr(\"string\", { indexed: true, optional: true }),\n meetUrl: attr(\"string\", { optional: true }),\n htmlLink: attr(\"string\", { optional: true }),\n drift: attr(\"string\", { indexed: true, optional: true }),\n driftGoogleStartAt: attr(\"number\", { optional: true }),\n driftDetectedAt: attr(\"number\", { optional: true }),\n adoptedFromGoogleAt: attr(\"number\", { optional: true }),\n createdAt: attr(\"number\", { indexed: true }),\n },\n};\n\n// Audit of every transactional send.\nconst emailLog: Entity = {\n attrs: {\n id: id(),\n groupId: attr(\"string\", { indexed: true }),\n applicationId: attr(\"string\", { indexed: true, optional: true }),\n to: attr(\"string\", { indexed: true }),\n template: attr(\"string\", { indexed: true }),\n subject: attr(\"string\"),\n body: attr(\"string\", { optional: true }),\n transport: attr(\"string\"),\n messageId: attr(\"string\", { optional: true }),\n redirected: attr(\"boolean\", { optional: true }),\n dedupeKey: attr(\"string\", { indexed: true, optional: true }),\n error: attr(\"string\", { optional: true }),\n sentAt: attr(\"number\", { indexed: true }),\n },\n};\n\n/** The chapter's own schema + deny-all rules for a mode. `hub` needs only the\n * `admins` allowlist (its records live in `crm_*`); `chapter` adds the\n * operational membership tables. */\nexport function chapterDb(mode: ChapterMode): { schema: DbSchema; rules: DbRules } {\n const entities: Record<string, Entity> =\n mode === \"hub\" ? { admins } : { admins, applications, groups, meetings, emailLog };\n const schema: DbSchema = { entities, links: {} };\n const rules: DbRules = {};\n for (const ns of Object.keys(entities)) {\n rules[ns] = { view: \"false\", create: \"false\", update: \"false\", delete: \"false\" };\n }\n return { schema, rules };\n}\n","// Per-mode default CRM configs. Consumers pass their own via defineChapter's\n// `crm`; these are sensible starting points. `chapter` is a person lead pipeline\n// (fed from `applications`); `hub` adds businesses (people + companies).\nimport type { CrmConfig } from \"@odla-ai/crm\";\nimport type { ChapterMode } from \"./types\";\n\nconst TEMPLATES: CrmConfig[\"templates\"] = {\n personal: {\n class: \"transactional\",\n vars: [\"firstName\", \"subject\", \"body\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\" },\n },\n announcement: {\n class: \"marketing\",\n vars: [\"firstName\", \"subject\", \"body\", \"unsubscribeUrl\"],\n defaults: { subject: \"{{subject}}\", text: \"{{body}}\\n\\n{{unsubscribeUrl}}\" },\n },\n};\n\nfunction personType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Person\",\n labelPlural: \"People\",\n nameField: \"name\",\n emailField: \"email\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n email: { type: \"email\", label: \"Email\" },\n firstName: { type: \"string\", label: \"First name\" },\n lastName: { type: \"string\", label: \"Last name\" },\n phone: { type: \"string\", label: \"Phone\" },\n state: { type: \"string\", label: \"State\", slot: \"s1\" },\n whoYouAre: { type: \"string\", label: \"Who they are\", slot: \"s2\" },\n referral: { type: \"string\", label: \"Referral source\", slot: \"s3\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n focus: { type: \"json\", label: \"Focus areas\" },\n message: { type: \"string\", label: \"Intro message\" },\n },\n pipeline: {\n stages: [\n { id: \"submitted\", label: \"Submitted\" },\n { id: \"paid_pending_vetting\", label: \"Paid, pending vetting\" },\n { id: \"call_scheduled\", label: \"Call scheduled\" },\n { id: \"interviewed\", label: \"Interviewed\" },\n { id: \"approved\", label: \"Approved\" },\n { id: \"declined\", label: \"Declined\" },\n { id: \"refunded\", label: \"Refunded\" },\n ],\n },\n facets: { identity: true, email: true, rank: \"manual\" },\n };\n}\n\nfunction companyType(): NonNullable<CrmConfig[\"types\"]>[string] {\n return {\n label: \"Business\",\n labelPlural: \"Businesses\",\n nameField: \"name\",\n fields: {\n name: { type: \"string\", label: \"Name\", required: true },\n domain: { type: \"string\", label: \"Domain / website\", slot: \"s1\" },\n industry: { type: \"string\", label: \"Industry\", slot: \"s2\" },\n location: { type: \"string\", label: \"Location\", slot: \"s3\" },\n chapter: { type: \"string\", label: \"Chapter\", slot: \"s4\" },\n linkedin: { type: \"string\", label: \"LinkedIn\" },\n notes: { type: \"string\", label: \"Notes\" },\n },\n facets: { rank: \"manual\" },\n };\n}\n\n/** The per-mode default CRM config: `chapter` = a person lead pipeline;\n * `hub` = people + businesses with a works_at relation. */\nexport function defaultCrm(mode: ChapterMode): CrmConfig {\n if (mode === \"hub\") {\n return {\n types: { person: personType(), company: companyType() },\n relations: { works_at: { from: \"person\", to: \"company\", label: \"works at\", reverseLabel: \"team\" } },\n templates: TEMPLATES,\n };\n }\n return {\n types: { person: personType() },\n templates: TEMPLATES,\n };\n}\n","// Build the seed `groups` row from a defineChapter config. This is the whole\n// per-chapter payload the worker reads at runtime (prices/policy/emails/\n// scheduling), so nothing brand-specific is hardcoded in the worker. createdAt\n// is stamped by the integration/seed layer, not here.\nimport type { ChapterConfig, ChapterScheduling } from \"./types\";\n\nconst DEFAULT_SCHEDULING: Required<Omit<ChapterScheduling, \"summaryTemplate\">> = {\n slotMinutes: 45,\n days: [1, 2, 3, 4, 5],\n startHour: 9,\n endHour: 17,\n timezone: \"America/Los_Angeles\",\n minNoticeHours: 24,\n windowDays: 14,\n};\n\nfunction defaultEmailTemplates(name: string): Record<string, { subject: string; text: string }> {\n const sign = `\\n\\nWarmly,\\n${name}`;\n return {\n adminNotification: {\n subject: `New application — {{firstName}} {{lastName}}`,\n text: `A new application came in for ${name}.\\n\\nName: {{firstName}} {{lastName}}\\nEmail: {{email}}`,\n },\n paymentConfirmation: {\n subject: `Welcome to ${name}`,\n text: `Hi {{firstName}},\\n\\nYour membership payment is confirmed. We'll be in touch to schedule your intro call.${sign}`,\n },\n prepEmail: {\n subject: `Your ${name} intro call`,\n text: `Hi {{firstName}},\\n\\nLooking forward to our call at {{meetingTime}}. {{meetingLink}}${sign}`,\n },\n onboardingInvite: {\n subject: `You're in — ${name}`,\n text: `Hi {{firstName}},\\n\\nWelcome to ${name}. Your member area is here: {{membersUrl}}${sign}`,\n },\n };\n}\n\n/** The `groups` row (attrs) for `chapter` mode. Missing config falls back to\n * empty copy / defaults, so a minimal config still provisions cleanly. */\nexport function buildGroupSeed(config: ChapterConfig): Record<string, unknown> {\n const prices = config.prices;\n const emails = config.emails;\n const policy = config.policy ?? {};\n const scheduling = {\n ...DEFAULT_SCHEDULING,\n ...(config.scheduling ?? {}),\n summaryTemplate:\n config.scheduling?.summaryTemplate ?? `${config.name}: introduction call with {{firstName}} {{lastName}}`,\n };\n const row: Record<string, unknown> = {\n id: config.id,\n name: config.name,\n standardPriceCents: prices?.standardCents ?? 0,\n foundingDiscountCents: prices?.foundingDiscountCents ?? 0,\n notificationEmail: emails?.notificationEmail ?? \"\",\n replyTo: emails?.replyTo ?? emails?.notificationEmail ?? \"\",\n disclaimerText: policy.disclaimerText ?? \"\",\n refundPolicyText: policy.refundPolicyText ?? \"\",\n trustCopy: policy.trustCopy ?? \"\",\n emailTemplates: emails?.templates ?? defaultEmailTemplates(config.name),\n schedulingJson: scheduling,\n };\n if (emails?.debugEmail) row.debugEmail = emails.debugEmail;\n if (policy.commitmentText) row.commitmentText = policy.commitmentText;\n if (policy.normsText) row.normsText = policy.normsText;\n return row;\n}\n","// The CLI-consumable provisioning descriptor. It composes @odla-ai/crm's\n// integration (crm_* namespaces + crm_config seed + route probe) with the\n// chapter's own namespaces and a guarded `groups`-row seed, so a site's\n// odla.config.mjs lists ONE integration. The CLI reads it structurally\n// (matching OdlaIntegration) and merges schema/rules by namespace.\nimport { createCrmIntegration } from \"@odla-ai/crm\";\nimport type { Chapter } from \"./types\";\n\n/** Options for {@link createChapterIntegration}. */\nexport interface ChapterIntegrationOptions {\n /** CRM route mount point. Default \"/api/crm\". */\n basePath?: string;\n /** Seed timestamp override for reproducible builds/tests. */\n now?: number;\n}\n\ninterface IntegrationSeed {\n id: string;\n ns: string;\n key: { attr: string; value: string };\n attrs: Record<string, unknown>;\n}\n\n/** Structural match for the CLI's `OdlaIntegration`. */\nexport interface ChapterIntegrationDescriptor {\n id: string;\n title: string;\n npm: string;\n schema: { entities: Record<string, unknown>; links: Record<string, unknown> };\n rules: Record<string, unknown>;\n seeds: IntegrationSeed[];\n probes: Array<{ path: string; expectedStatus: number }>;\n}\n\n/**\n * Build the one CLI-consumable integration for a chapter/hub: the crm_*\n * namespaces + crm_config seed + route probe, merged with the chapter's own\n * namespaces and a guarded `groups`-row seed. Drop it in odla.config.mjs's\n * `integrations` array.\n */\nexport function createChapterIntegration(\n chapter: Chapter,\n options: ChapterIntegrationOptions = {},\n): ChapterIntegrationDescriptor {\n const basePath = options.basePath ?? \"/api/crm\";\n const now = options.now ?? Date.now();\n const emails = chapter.config.emails;\n\n const crmDesc = createCrmIntegration(chapter.crm, {\n basePath,\n now,\n ...(emails?.notificationEmail ? { notificationEmail: emails.notificationEmail } : {}),\n ...(emails?.replyTo ? { replyTo: emails.replyTo } : {}),\n ...(emails?.debugEmail ? { debugEmail: emails.debugEmail } : {}),\n });\n\n const seeds: IntegrationSeed[] = [...(crmDesc.seeds ?? [])];\n const group = chapter.groupSeed();\n if (group) {\n seeds.push({ id: \"group\", ns: \"groups\", key: { attr: \"id\", value: chapter.id }, attrs: { ...group, createdAt: now } });\n }\n\n return {\n id: \"chapter\",\n title: `Chapter — ${chapter.name}`,\n npm: \"@odla-ai/chapter\",\n schema: {\n entities: { ...crmDesc.schema.entities, ...chapter.schema.entities },\n links: { ...crmDesc.schema.links, ...chapter.schema.links },\n },\n rules: { ...crmDesc.rules, ...chapter.rules },\n seeds,\n probes: [...(crmDesc.probes ?? [])],\n };\n}\n"],"mappings":"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","names":["id","import_crm"]}