@odla-ai/chapter 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/index.cjs +24 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +35 -4
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +7 -0
- package/dist/worker/index.d.ts +7 -0
- package/dist/worker/index.js +35 -4
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.d.cts
CHANGED
|
@@ -154,6 +154,12 @@ interface ChapterApplication {
|
|
|
154
154
|
defaultMaxLen?: number;
|
|
155
155
|
/** Max JSON request body in bytes. Default 32768. */
|
|
156
156
|
bodyCap?: number;
|
|
157
|
+
/** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
|
|
158
|
+
* writing a row with no consent record. Default `false` for back-compat —
|
|
159
|
+
* but turn it on if the disclaimer is a compliance record: a missing ack is
|
|
160
|
+
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
161
|
+
* and surfaces on the first test submit, not intermittently in production. */
|
|
162
|
+
requireDisclaimerAck?: boolean;
|
|
157
163
|
}
|
|
158
164
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
159
165
|
interface ResolvedApplication {
|
|
@@ -162,6 +168,7 @@ interface ResolvedApplication {
|
|
|
162
168
|
maxLen: Record<string, number>;
|
|
163
169
|
defaultMaxLen: number;
|
|
164
170
|
bodyCap: number;
|
|
171
|
+
requireDisclaimerAck: boolean;
|
|
165
172
|
}
|
|
166
173
|
/** The `defineChapter()` config a site fills in. */
|
|
167
174
|
interface ChapterConfig {
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -154,6 +154,12 @@ interface ChapterApplication {
|
|
|
154
154
|
defaultMaxLen?: number;
|
|
155
155
|
/** Max JSON request body in bytes. Default 32768. */
|
|
156
156
|
bodyCap?: number;
|
|
157
|
+
/** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
|
|
158
|
+
* writing a row with no consent record. Default `false` for back-compat —
|
|
159
|
+
* but turn it on if the disclaimer is a compliance record: a missing ack is
|
|
160
|
+
* otherwise silent, permanent and unreconstructible. Failure is deterministic
|
|
161
|
+
* and surfaces on the first test submit, not intermittently in production. */
|
|
162
|
+
requireDisclaimerAck?: boolean;
|
|
157
163
|
}
|
|
158
164
|
/** The fully-resolved application config carried on the {@link Chapter}. */
|
|
159
165
|
interface ResolvedApplication {
|
|
@@ -162,6 +168,7 @@ interface ResolvedApplication {
|
|
|
162
168
|
maxLen: Record<string, number>;
|
|
163
169
|
defaultMaxLen: number;
|
|
164
170
|
bodyCap: number;
|
|
171
|
+
requireDisclaimerAck: boolean;
|
|
165
172
|
}
|
|
166
173
|
/** The `defineChapter()` config a site fills in. */
|
|
167
174
|
interface ChapterConfig {
|
package/dist/worker/index.js
CHANGED
|
@@ -157,6 +157,20 @@ async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// src/member.ts
|
|
160
|
+
function hasDisclaimerAck(fields) {
|
|
161
|
+
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
162
|
+
}
|
|
163
|
+
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
164
|
+
function applicantProfile(chapter, fields) {
|
|
165
|
+
const profile = {};
|
|
166
|
+
for (const f of [...chapter.application.required, ...chapter.application.optional]) {
|
|
167
|
+
if (IDENTITY_FIELDS.has(f)) continue;
|
|
168
|
+
const v = fields[f];
|
|
169
|
+
if (typeof v === "string" && v.trim() !== "") profile[f] = v.trim();
|
|
170
|
+
}
|
|
171
|
+
if (fields.focus !== void 0) profile.focus = fields.focus;
|
|
172
|
+
return Object.keys(profile).length > 0 ? profile : void 0;
|
|
173
|
+
}
|
|
160
174
|
async function submitApplication(db, chapter, fields, opts) {
|
|
161
175
|
const app = chapter.application;
|
|
162
176
|
for (const f of app.required) {
|
|
@@ -168,6 +182,10 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
168
182
|
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
169
183
|
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
170
184
|
}
|
|
185
|
+
const acked = hasDisclaimerAck(fields);
|
|
186
|
+
if (app.requireDisclaimerAck && !acked) {
|
|
187
|
+
return { ok: false, error: "disclaimerAck is required" };
|
|
188
|
+
}
|
|
171
189
|
const id = opts.newId();
|
|
172
190
|
const row = { id, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
173
191
|
for (const f of [...app.required, ...app.optional]) {
|
|
@@ -175,11 +193,12 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
175
193
|
}
|
|
176
194
|
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
177
195
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
196
|
+
if (acked) row.disclaimerAckAt = opts.now;
|
|
178
197
|
const { duplicate } = await db.transact(
|
|
179
198
|
[{ t: "update", ns: "applications", id, attrs: row }],
|
|
180
199
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
181
200
|
);
|
|
182
|
-
return { ok: true, id, duplicate, status: chapter.pipeline.initial };
|
|
201
|
+
return { ok: true, id, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
183
202
|
}
|
|
184
203
|
function joinConfig(group, paymentsReady) {
|
|
185
204
|
return {
|
|
@@ -488,8 +507,13 @@ async function provisionApplicant(db, chapter, applicationId, fields) {
|
|
|
488
507
|
try {
|
|
489
508
|
const secret = await getVaultSecret(db, "clerk_secret_key");
|
|
490
509
|
if (secret) {
|
|
491
|
-
|
|
492
|
-
|
|
510
|
+
const profile = applicantProfile(chapter, fields);
|
|
511
|
+
const publicMetadata = { applicationId, ...profile ? { profile } : {} };
|
|
512
|
+
if (chapter.account === "create") {
|
|
513
|
+
await createClerkUser(secret, { email, firstName: s(fields.firstName), lastName: s(fields.lastName), publicMetadata });
|
|
514
|
+
} else {
|
|
515
|
+
await createClerkInvitation(secret, { email, publicMetadata });
|
|
516
|
+
}
|
|
493
517
|
}
|
|
494
518
|
} catch {
|
|
495
519
|
}
|
|
@@ -630,7 +654,14 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
630
654
|
}
|
|
631
655
|
await provisionApplicant(db, chapter, result.id, parsed);
|
|
632
656
|
}
|
|
633
|
-
return json({
|
|
657
|
+
return json({
|
|
658
|
+
id: result.id,
|
|
659
|
+
duplicate: result.duplicate,
|
|
660
|
+
status: result.status,
|
|
661
|
+
// Echoed so a site can see (and assert in an integration test) whether its
|
|
662
|
+
// join page actually posted the ack. Absent consent is otherwise invisible.
|
|
663
|
+
disclaimerAckAt: result.disclaimerAckAt
|
|
664
|
+
});
|
|
634
665
|
}
|
|
635
666
|
return null;
|
|
636
667
|
};
|