@flytedesk/app-kit 0.1.3 → 0.3.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 (37) hide show
  1. package/dist/cli/flags-sync.d.ts +2 -0
  2. package/dist/cli/flags-sync.js +26 -0
  3. package/dist/cli/flags-sync.js.map +1 -0
  4. package/dist/cli/profile-sync.d.ts +2 -0
  5. package/dist/cli/profile-sync.js +26 -0
  6. package/dist/cli/profile-sync.js.map +1 -0
  7. package/dist/cli/sync-engine.d.ts +20 -0
  8. package/dist/cli/sync-engine.js +543 -0
  9. package/dist/cli/sync-engine.js.map +1 -0
  10. package/dist/cli/trace-sync.js +9 -359
  11. package/dist/cli/trace-sync.js.map +1 -1
  12. package/dist/flags/index.d.ts +49 -0
  13. package/dist/flags/index.js +49 -0
  14. package/dist/flags/index.js.map +1 -0
  15. package/dist/flags/plugin.d.ts +3 -0
  16. package/dist/flags/plugin.js +296 -0
  17. package/dist/flags/plugin.js.map +1 -0
  18. package/dist/flags/types.d.ts +159 -0
  19. package/dist/flags/types.js +11 -0
  20. package/dist/flags/types.js.map +1 -0
  21. package/dist/profile/index.d.ts +36 -0
  22. package/dist/profile/index.js +36 -0
  23. package/dist/profile/index.js.map +1 -0
  24. package/dist/profile/plugin.d.ts +3 -0
  25. package/dist/profile/plugin.js +111 -0
  26. package/dist/profile/plugin.js.map +1 -0
  27. package/dist/profile/types.d.ts +76 -0
  28. package/dist/profile/types.js +10 -0
  29. package/dist/profile/types.js.map +1 -0
  30. package/package.json +12 -2
  31. package/prisma/fragments/flags.meta.json +4 -0
  32. package/prisma/fragments/flags.prisma +52 -0
  33. package/prisma/fragments/profile.meta.json +4 -0
  34. package/prisma/fragments/profile.prisma +42 -0
  35. package/prisma/migrations/0002_app_kit_profile_init/migration.sql +15 -0
  36. package/prisma/migrations/0003_app_kit_flags_init/migration.sql +16 -0
  37. package/prisma/migrations/manifest.json +15 -0
@@ -0,0 +1,296 @@
1
+ import fp from "fastify-plugin";
2
+ const DEFAULT_ROUTE_PREFIX = "/flags";
3
+ const DEFAULT_PAGE_SIZE = 25;
4
+ const MAX_PAGE_SIZE = 200;
5
+ function assertRequiredOptions(options) {
6
+ if (!options.store) {
7
+ throw new Error('@flytedesk/app-kit/flags: missing required option "store"');
8
+ }
9
+ if (typeof options.store.list !== "function" ||
10
+ typeof options.store.findByKey !== "function" ||
11
+ typeof options.store.upsert !== "function") {
12
+ throw new Error("@flytedesk/app-kit/flags: options.store must implement list(), findByKey(key), and upsert(key, patch)");
13
+ }
14
+ if (typeof options.describeFlag !== "function") {
15
+ throw new Error('@flytedesk/app-kit/flags: missing required option "describeFlag"');
16
+ }
17
+ }
18
+ function toFlagResponse(row, meta) {
19
+ return {
20
+ key: row.key,
21
+ project: meta.project,
22
+ label: meta.label,
23
+ description: meta.description,
24
+ enabled: row.enabled,
25
+ rollout: row.rollout,
26
+ roles: row.roles,
27
+ users: row.users,
28
+ owner: meta.owner,
29
+ updated: row.updatedAt.toISOString(),
30
+ };
31
+ }
32
+ /**
33
+ * Not exported: this plugin's own minimal 401 check on `request.authUser`, kept
34
+ * local rather than importing ../auth/guards.js's requireAuth — identical contract
35
+ * and identical implementation to src/profile/plugin.ts's own requireAuthUser (see
36
+ * that file's doc comment for the full rationale: no hard dependency on the auth
37
+ * module's internals beyond the `authUser` decoration contract, so any consumer
38
+ * populating request.authUser some other way still works).
39
+ */
40
+ function requireAuthUser(request, reply) {
41
+ if (!request.authUser) {
42
+ reply
43
+ .code(401)
44
+ .send({ error: "Authentication required", code: "unauthenticated" });
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ function coercePositiveInt(value, fallback, max) {
50
+ const n = typeof value === "string"
51
+ ? Number(value)
52
+ : typeof value === "number"
53
+ ? value
54
+ : NaN;
55
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1)
56
+ return fallback;
57
+ return max != null ? Math.min(n, max) : n;
58
+ }
59
+ function parseListQuery(query) {
60
+ return {
61
+ page: coercePositiveInt(query.page, 1),
62
+ pageSize: coercePositiveInt(query.pageSize, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE),
63
+ sort: typeof query.sort === "string" ? query.sort : undefined,
64
+ dir: query.dir === "asc" ? "asc" : "desc",
65
+ q: typeof query.q === "string" ? query.q : "",
66
+ };
67
+ }
68
+ /** Repeated querystring keys (?project=a&project=b) arrive as arrays already —
69
+ * Fastify's querystring parser gives us those natively, same as the reference's
70
+ * own asArray relies on. */
71
+ function asArray(value) {
72
+ if (value == null)
73
+ return [];
74
+ if (Array.isArray(value))
75
+ return value.filter((v) => typeof v === "string");
76
+ return typeof value === "string" ? [value] : [];
77
+ }
78
+ function buildPager(page, pageSize, total) {
79
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
80
+ const clampedPage = Math.min(Math.max(1, page), totalPages);
81
+ return {
82
+ page: clampedPage,
83
+ pageSize,
84
+ total,
85
+ totalPages,
86
+ from: total === 0 ? 0 : (clampedPage - 1) * pageSize + 1,
87
+ to: Math.min(total, clampedPage * pageSize),
88
+ hasPrev: clampedPage > 1,
89
+ hasNext: clampedPage < totalPages,
90
+ };
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // PATCH body validation — hand-written for the same no-new-dependency reason as
94
+ // the list query parsing above. Same fields/constraints as the reference's zod
95
+ // schema: enabled (boolean), rollout (integer 0-100), roles (string[]), users
96
+ // (object of string -> boolean), all optional.
97
+ // ---------------------------------------------------------------------------
98
+ function parsePatchBody(body) {
99
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
100
+ return { error: "Request body must be a JSON object" };
101
+ }
102
+ const input = body;
103
+ const patch = {};
104
+ if ("enabled" in input) {
105
+ if (typeof input.enabled !== "boolean") {
106
+ return { error: '"enabled" must be a boolean' };
107
+ }
108
+ patch.enabled = input.enabled;
109
+ }
110
+ if ("rollout" in input) {
111
+ const rollout = input.rollout;
112
+ if (typeof rollout !== "number" ||
113
+ !Number.isInteger(rollout) ||
114
+ rollout < 0 ||
115
+ rollout > 100) {
116
+ return { error: '"rollout" must be an integer between 0 and 100' };
117
+ }
118
+ patch.rollout = rollout;
119
+ }
120
+ if ("roles" in input) {
121
+ const roles = input.roles;
122
+ if (!Array.isArray(roles) || roles.some((r) => typeof r !== "string")) {
123
+ return { error: '"roles" must be an array of strings' };
124
+ }
125
+ patch.roles = roles;
126
+ }
127
+ if ("users" in input) {
128
+ const users = input.users;
129
+ if (typeof users !== "object" ||
130
+ users === null ||
131
+ Array.isArray(users) ||
132
+ Object.values(users).some((v) => typeof v !== "boolean")) {
133
+ return {
134
+ error: '"users" must be an object mapping user ids to booleans',
135
+ };
136
+ }
137
+ patch.users = users;
138
+ }
139
+ return patch;
140
+ }
141
+ const appKitFlagsPlugin = async (fastify, options) => {
142
+ assertRequiredOptions(options);
143
+ // Same registration-time contract as src/profile/plugin.ts — see that file's
144
+ // matching check for the full rationale. Register @flytedesk/app-kit/auth (or
145
+ // your own equivalent) BEFORE this plugin.
146
+ if (!fastify.hasRequestDecorator("authUser")) {
147
+ throw new Error("@flytedesk/app-kit/flags: request.authUser is not decorated on this Fastify instance yet. " +
148
+ "This plugin resolves the caller from request.authUser and must be registered AFTER " +
149
+ "whatever populates it — @flytedesk/app-kit/auth's flytedeskAuth plugin, or the " +
150
+ "consuming app's own equivalent decorator.");
151
+ }
152
+ const routePrefix = options.routePrefix ?? DEFAULT_ROUTE_PREFIX;
153
+ const store = options.store;
154
+ const describeFlag = options.describeFlag;
155
+ /**
156
+ * GET {routePrefix} — search/filter/sort/paginate every flag that has metadata
157
+ * (ported from the reference GET /flags: same q/project/state filters, same
158
+ * project-then-arbitrary-column sort, same facet counts over the filtered set
159
+ * before pagination is applied). A flag with no `describeFlag` metadata is
160
+ * excluded entirely, matching the reference's `.filter((x) => x.meta)`.
161
+ *
162
+ * No permission check baked in here — media-planner's own `flags.manage` is its
163
+ * own RBAC category, entirely the consumer's concern via their own `preHandler`
164
+ * on this route (same boundary /profile draws around authorization).
165
+ */
166
+ fastify.get(routePrefix, async (request, reply) => {
167
+ if (!requireAuthUser(request, reply))
168
+ return;
169
+ const query = request.query;
170
+ const parsedQuery = parseListQuery(query);
171
+ const projectFilter = asArray(query.project);
172
+ const stateFilter = asArray(query.state);
173
+ const rows = await store.list();
174
+ let joined = rows
175
+ .map((row) => ({ row, meta: describeFlag(row.key) }))
176
+ .filter((x) => x.meta !== undefined);
177
+ if (parsedQuery.q) {
178
+ const term = parsedQuery.q.toLowerCase();
179
+ joined = joined.filter((x) => x.row.key.toLowerCase().includes(term) ||
180
+ x.meta.label.toLowerCase().includes(term) ||
181
+ x.meta.description.toLowerCase().includes(term) ||
182
+ x.meta.owner.toLowerCase().includes(term));
183
+ }
184
+ if (projectFilter.length) {
185
+ joined = joined.filter((x) => projectFilter.includes(x.meta.project));
186
+ }
187
+ if (stateFilter.length) {
188
+ joined = joined.filter((x) => stateFilter.includes(x.row.enabled ? "on" : "off"));
189
+ }
190
+ const projectFacets = {};
191
+ const stateFacets = {};
192
+ for (const x of joined) {
193
+ projectFacets[x.meta.project] = (projectFacets[x.meta.project] ?? 0) + 1;
194
+ const stateKey = x.row.enabled ? "on" : "off";
195
+ stateFacets[stateKey] = (stateFacets[stateKey] ?? 0) + 1;
196
+ }
197
+ const sortKey = parsedQuery.sort ?? "project";
198
+ joined.sort((a, b) => {
199
+ const av = sortKey === "project"
200
+ ? a.meta.project
201
+ : a.row[sortKey];
202
+ const bv = sortKey === "project"
203
+ ? b.meta.project
204
+ : b.row[sortKey];
205
+ const cmp = String(av).localeCompare(String(bv));
206
+ return parsedQuery.dir === "asc" ? cmp : -cmp;
207
+ });
208
+ const total = joined.length;
209
+ const page = Math.min(Math.max(1, parsedQuery.page), Math.max(1, Math.ceil(total / parsedQuery.pageSize)));
210
+ const paged = joined.slice((page - 1) * parsedQuery.pageSize, page * parsedQuery.pageSize);
211
+ const body = {
212
+ rows: paged.map((x) => toFlagResponse(x.row, x.meta)),
213
+ pager: buildPager(page, parsedQuery.pageSize, total),
214
+ sort: { key: sortKey, dir: parsedQuery.dir },
215
+ filter: {
216
+ applied: { project: projectFilter, state: stateFilter },
217
+ q: parsedQuery.q,
218
+ facets: { project: projectFacets, state: stateFacets },
219
+ },
220
+ };
221
+ return body;
222
+ });
223
+ /**
224
+ * GET {routePrefix}/:key — single flag lookup. Not present in the reference
225
+ * flags.ts, but a natural, trivial addition given FlagStore already exposes
226
+ * findByKey — completing the CRUD surface the store interface implies, not scope
227
+ * creep. 404s when the key has no store row OR no describeFlag metadata, so this
228
+ * agrees with the list endpoint about what counts as "a flag" rather than
229
+ * exposing a key here that the list silently hides.
230
+ */
231
+ fastify.get(`${routePrefix}/:key`, async (request, reply) => {
232
+ if (!requireAuthUser(request, reply))
233
+ return;
234
+ const meta = describeFlag(request.params.key);
235
+ if (!meta) {
236
+ return reply.code(404).send({ error: "Not found", code: "not_found" });
237
+ }
238
+ const row = await store.findByKey(request.params.key);
239
+ if (!row) {
240
+ return reply.code(404).send({ error: "Not found", code: "not_found" });
241
+ }
242
+ return toFlagResponse(row, meta);
243
+ });
244
+ /**
245
+ * PATCH {routePrefix}/:key — partial update of enabled/rollout/roles/users
246
+ * (ported from the reference PATCH /flags/:flagKey: same validation, same
247
+ * only-write-what-changed semantics). Requires the same describeFlag metadata as
248
+ * GET/:key (a deliberate generalization beyond the reference, which never
249
+ * consulted FLAG_ROADMAP for PATCH at all — see FlagMeta's doc comment on why: a
250
+ * uniform "no metadata means it isn't a flag through this API" contract across
251
+ * every route is more predictable for a package with multiple consumers than
252
+ * replicating that one route's inconsistency).
253
+ *
254
+ * Always calls `options.onChange` after a successful update, even when the patch
255
+ * changed nothing — mirrors the reference's own unconditional `recordAudit` call
256
+ * (whose `detail` falls back to "No change" rather than being skipped).
257
+ */
258
+ fastify.patch(`${routePrefix}/:key`, async (request, reply) => {
259
+ if (!requireAuthUser(request, reply))
260
+ return;
261
+ const parsed = parsePatchBody(request.body);
262
+ if ("error" in parsed) {
263
+ return reply
264
+ .code(400)
265
+ .send({ error: parsed.error, code: "invalid_body" });
266
+ }
267
+ const meta = describeFlag(request.params.key);
268
+ const existing = meta ? await store.findByKey(request.params.key) : null;
269
+ if (!meta || !existing) {
270
+ return reply.code(404).send({ error: "Not found", code: "not_found" });
271
+ }
272
+ const changes = {};
273
+ if (parsed.enabled !== undefined && parsed.enabled !== existing.enabled) {
274
+ changes.enabled = parsed.enabled;
275
+ }
276
+ if (parsed.rollout !== undefined && parsed.rollout !== existing.rollout) {
277
+ changes.rollout = parsed.rollout;
278
+ }
279
+ if (parsed.roles !== undefined) {
280
+ changes.roles = parsed.roles;
281
+ }
282
+ if (parsed.users !== undefined) {
283
+ changes.users = parsed.users;
284
+ }
285
+ const updated = await store.upsert(existing.key, changes);
286
+ await options.onChange?.(existing.key, changes, {
287
+ id: request.authUser.id,
288
+ name: request.authUser.name,
289
+ });
290
+ return toFlagResponse(updated, meta);
291
+ });
292
+ };
293
+ export const appKitFlags = fp(appKitFlagsPlugin, {
294
+ name: "@flytedesk/app-kit/flags",
295
+ });
296
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/flags/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Hand-written row/option types for @flytedesk/app-kit/flags.
3
+ *
4
+ * Field names here mirror prisma/fragments/flags.prisma's camelCase model fields
5
+ * (key/project/enabled/rollout/roles/users/updatedAt) — a consumer's generated
6
+ * PrismaClient, once they've run `flags-sync init` and their own `prisma generate`,
7
+ * satisfies FlagStore directly via `prisma.appKitFeatureFlag.findMany` /
8
+ * `.findUnique` / `.upsert`.
9
+ */
10
+ export interface FlagRow {
11
+ key: string;
12
+ /** The row's own project column (mirrors media-planner's FeatureFlag.project
13
+ * exactly). NOT what list/GET responses display as `project`, or what the
14
+ * `project` list filter matches against — see FlagMeta.project below and
15
+ * plugin.ts's doc comment on why. */
16
+ project: string;
17
+ enabled: boolean;
18
+ rollout: number;
19
+ /** Opaque role ids from whatever IdP/RBAC system the consumer uses — this
20
+ * package has no opinion on what they mean, same as media-planner's own
21
+ * FeatureFlag.roles. */
22
+ roles: string[];
23
+ /** Per-user override bag, keyed by whatever user id the consumer uses, each
24
+ * value a hard on/off override that supersedes `enabled`/`rollout`/`roles` for
25
+ * that one user. Evaluating overrides against a specific caller is the
26
+ * consumer's own concern (same boundary media-planner draws) — this plugin only
27
+ * ever reads/writes the bag as a whole, never evaluates it. */
28
+ users: Record<string, boolean>;
29
+ updatedAt: Date;
30
+ }
31
+ /** The fields a PATCH may write — all optional, only the ones present in the
32
+ * request body (and that actually differ from the current row) are written. */
33
+ export type FlagFields = Pick<FlagRow, "enabled" | "rollout" | "roles" | "users">;
34
+ export type FlagPatch = Partial<FlagFields>;
35
+ /**
36
+ * Flag metadata a consumer resolves from its own source of truth (media-planner's
37
+ * own reference implementation used a hardcoded `FLAG_ROADMAP` array) — the one
38
+ * genuinely app-specific piece of the reference `flags.ts` this module generalizes
39
+ * away via `AppKitFlagsOptions.describeFlag`. `project` here (NOT `FlagRow.project`)
40
+ * is what every route actually filters/sorts/displays as "the flag's project" —
41
+ * ported faithfully from the reference, which reads `meta.project` everywhere and
42
+ * never reads the DB row's own `project` column.
43
+ */
44
+ export interface FlagMeta {
45
+ label: string;
46
+ description: string;
47
+ owner: string;
48
+ project: string;
49
+ }
50
+ /**
51
+ * Minimal persistence seam this plugin needs — deliberately NOT a dependency on
52
+ * `@prisma/client`, mirroring every other app-kit module's *Store interface (see
53
+ * src/profile/types.ts's ProfileStore, src/trace/types.ts's TracePrismaClient). A
54
+ * consumer's generated PrismaClient trivially satisfies this shape:
55
+ *
56
+ * const store: FlagStore = {
57
+ * list: () => prisma.appKitFeatureFlag.findMany(),
58
+ * findByKey: (key) => prisma.appKitFeatureFlag.findUnique({ where: { key } }),
59
+ * upsert: (key, patch) =>
60
+ * prisma.appKitFeatureFlag.upsert({
61
+ * where: { key },
62
+ * create: { key, project: "unknown", ...patch },
63
+ * update: patch,
64
+ * }),
65
+ * };
66
+ *
67
+ * A hand-rolled adapter can back it with anything else (raw SQL, a different ORM,
68
+ * an in-memory store for tests — see src/flags/plugin.test.ts).
69
+ */
70
+ export interface FlagStore {
71
+ list(): Promise<FlagRow[]>;
72
+ /** Must return `null`, not throw/404, when no row exists for `key`. */
73
+ findByKey(key: string): Promise<FlagRow | null>;
74
+ /** Upserts the row for `key`, writing only the fields present in `patch` (fields
75
+ * absent from `patch` are left unchanged on an existing row). This plugin only
76
+ * ever calls this on a key it already confirmed exists via findByKey first (see
77
+ * plugin.ts's PATCH handler) — the "upsert" naming matches ProfileStore's for
78
+ * interface-shape consistency across app-kit's *Store seams, not because this
79
+ * plugin ever creates a flag through it. */
80
+ upsert(key: string, patch: FlagPatch): Promise<FlagRow>;
81
+ }
82
+ export interface AppKitFlagsOptions {
83
+ /** The persistence seam — see FlagStore above. Required. */
84
+ store: FlagStore;
85
+ /**
86
+ * Resolves a flag key to its display metadata, or `undefined` if the key isn't
87
+ * known to the consumer's own catalog (e.g. media-planner's FLAG_ROADMAP). A flag
88
+ * with no metadata is treated as if it doesn't exist through this API at all —
89
+ * excluded from GET {routePrefix} (matching the reference's `.filter((x) =>
90
+ * x.meta)`), and 404s from GET/PATCH {routePrefix}/:key — so every route agrees
91
+ * on the same set of "real" flags rather than list hiding a key that :key would
92
+ * still happily serve. Required.
93
+ */
94
+ describeFlag: (key: string) => FlagMeta | undefined;
95
+ /**
96
+ * Fired after every successful PATCH, whether or not the patch actually changed
97
+ * anything (mirrors the reference's own unconditional `recordAudit` call, whose
98
+ * `detail` falls back to "No change" rather than skipping the call) — so a
99
+ * consumer replicating its own audit trail doesn't have to separately guess
100
+ * whether "nothing changed" is worth recording. `changes` contains only the
101
+ * fields that differed from the row's previous value, matching what was actually
102
+ * written. Not baked into a specific audit/trace mechanism (no dependency on
103
+ * @flytedesk/app-kit/trace or any other) — the consumer wires this to whatever
104
+ * they already use, same boundary `/profile` never needed to draw because it had
105
+ * no audit trail to begin with.
106
+ */
107
+ onChange?: (key: string, changes: FlagPatch, actor: {
108
+ id: string;
109
+ name: string;
110
+ }) => void | Promise<void>;
111
+ /** Base path the GET/PATCH routes are mounted under. Default "/flags". */
112
+ routePrefix?: string;
113
+ }
114
+ /** What GET `{routePrefix}` returns for one flag, GET `{routePrefix}/:key`, and
115
+ * PATCH `{routePrefix}/:key` all share. */
116
+ export interface FlagResponseBody {
117
+ key: string;
118
+ project: string;
119
+ label: string;
120
+ description: string;
121
+ enabled: boolean;
122
+ rollout: number;
123
+ roles: string[];
124
+ users: Record<string, boolean>;
125
+ owner: string;
126
+ /** ISO 8601. */
127
+ updated: string;
128
+ }
129
+ export interface FlagPager {
130
+ page: number;
131
+ pageSize: number;
132
+ total: number;
133
+ totalPages: number;
134
+ from: number;
135
+ to: number;
136
+ hasPrev: boolean;
137
+ hasNext: boolean;
138
+ }
139
+ /** What GET `{routePrefix}` returns — search/filter/sort/pagination/facets, same
140
+ * shape as the reference `GET /flags`. */
141
+ export interface FlagListResponseBody {
142
+ rows: FlagResponseBody[];
143
+ pager: FlagPager;
144
+ sort: {
145
+ key: string;
146
+ dir: "asc" | "desc";
147
+ };
148
+ filter: {
149
+ applied: {
150
+ project: string[];
151
+ state: string[];
152
+ };
153
+ q: string;
154
+ facets: {
155
+ project: Record<string, number>;
156
+ state: Record<string, number>;
157
+ };
158
+ };
159
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Hand-written row/option types for @flytedesk/app-kit/flags.
3
+ *
4
+ * Field names here mirror prisma/fragments/flags.prisma's camelCase model fields
5
+ * (key/project/enabled/rollout/roles/users/updatedAt) — a consumer's generated
6
+ * PrismaClient, once they've run `flags-sync init` and their own `prisma generate`,
7
+ * satisfies FlagStore directly via `prisma.appKitFeatureFlag.findMany` /
8
+ * `.findUnique` / `.upsert`.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/flags/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Generic self-service "edit my own profile fields" module (title/phone/timezone/
3
+ * bio) — DEC-39, DEC-52. Ships a Prisma schema fragment + a raw SQL migration + a
4
+ * `profile-sync` CLI (all generalized from @flytedesk/app-kit/trace's DEC-39
5
+ * machinery — see prisma/fragments/profile.prisma and src/cli/profile-sync.ts) plus
6
+ * a real Fastify plugin with GET/PATCH routes, so flytedesk-id (and other flytedesk
7
+ * apps after it) don't each hand-roll their own GET/PATCH /me route + migration for
8
+ * this.
9
+ *
10
+ * Usage:
11
+ *
12
+ * import Fastify from "fastify";
13
+ * import { flytedeskAuth } from "@flytedesk/app-kit/auth";
14
+ * import { appKitProfile } from "@flytedesk/app-kit/profile";
15
+ *
16
+ * const app = Fastify();
17
+ * await app.register(flytedeskAuth, { ...authOptions });
18
+ * // Must come after flytedeskAuth (or whatever else decorates request.authUser)
19
+ * // — see plugin.ts's registration-time check.
20
+ * await app.register(appKitProfile, {
21
+ * store: {
22
+ * findByUserId: (userId) => prisma.appKitUserProfile.findUnique({ where: { userId } }),
23
+ * upsert: (userId, patch) =>
24
+ * prisma.appKitUserProfile.upsert({
25
+ * where: { userId },
26
+ * create: { userId, ...patch },
27
+ * update: patch,
28
+ * }),
29
+ * },
30
+ * });
31
+ *
32
+ * // GET /profile -> { userId, title, phone, timezone, bio, updatedAt }
33
+ * // PATCH /profile { title?, phone?, timezone?, bio? } -> same shape
34
+ */
35
+ export { appKitProfile } from "./plugin.js";
36
+ export type { AppKitProfileOptions, ProfileFields, ProfilePatch, ProfileResponseBody, ProfileRow, ProfileStore, } from "./types.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Generic self-service "edit my own profile fields" module (title/phone/timezone/
3
+ * bio) — DEC-39, DEC-52. Ships a Prisma schema fragment + a raw SQL migration + a
4
+ * `profile-sync` CLI (all generalized from @flytedesk/app-kit/trace's DEC-39
5
+ * machinery — see prisma/fragments/profile.prisma and src/cli/profile-sync.ts) plus
6
+ * a real Fastify plugin with GET/PATCH routes, so flytedesk-id (and other flytedesk
7
+ * apps after it) don't each hand-roll their own GET/PATCH /me route + migration for
8
+ * this.
9
+ *
10
+ * Usage:
11
+ *
12
+ * import Fastify from "fastify";
13
+ * import { flytedeskAuth } from "@flytedesk/app-kit/auth";
14
+ * import { appKitProfile } from "@flytedesk/app-kit/profile";
15
+ *
16
+ * const app = Fastify();
17
+ * await app.register(flytedeskAuth, { ...authOptions });
18
+ * // Must come after flytedeskAuth (or whatever else decorates request.authUser)
19
+ * // — see plugin.ts's registration-time check.
20
+ * await app.register(appKitProfile, {
21
+ * store: {
22
+ * findByUserId: (userId) => prisma.appKitUserProfile.findUnique({ where: { userId } }),
23
+ * upsert: (userId, patch) =>
24
+ * prisma.appKitUserProfile.upsert({
25
+ * where: { userId },
26
+ * create: { userId, ...patch },
27
+ * update: patch,
28
+ * }),
29
+ * },
30
+ * });
31
+ *
32
+ * // GET /profile -> { userId, title, phone, timezone, bio, updatedAt }
33
+ * // PATCH /profile { title?, phone?, timezone?, bio? } -> same shape
34
+ */
35
+ export { appKitProfile } from "./plugin.js";
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/profile/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import type { AppKitProfileOptions } from "./types.js";
3
+ export declare const appKitProfile: FastifyPluginAsync<AppKitProfileOptions>;