@kernhq/module-hr 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 (75) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +71 -0
  3. package/dist/contract/capabilities.d.ts +45 -0
  4. package/dist/contract/capabilities.d.ts.map +1 -0
  5. package/dist/contract/capabilities.js +114 -0
  6. package/dist/contract/capabilities.js.map +1 -0
  7. package/dist/contract/events.d.ts +74 -0
  8. package/dist/contract/events.d.ts.map +1 -0
  9. package/dist/contract/events.js +63 -0
  10. package/dist/contract/events.js.map +1 -0
  11. package/dist/contract/index.d.ts +15 -0
  12. package/dist/contract/index.d.ts.map +1 -0
  13. package/dist/contract/index.js +15 -0
  14. package/dist/contract/index.js.map +1 -0
  15. package/dist/contract/models.d.ts +457 -0
  16. package/dist/contract/models.d.ts.map +1 -0
  17. package/dist/contract/models.js +381 -0
  18. package/dist/contract/models.js.map +1 -0
  19. package/dist/contract/permissions.d.ts +174 -0
  20. package/dist/contract/permissions.d.ts.map +1 -0
  21. package/dist/contract/permissions.js +209 -0
  22. package/dist/contract/permissions.js.map +1 -0
  23. package/dist/contract/router.d.ts +2720 -0
  24. package/dist/contract/router.d.ts.map +1 -0
  25. package/dist/contract/router.js +520 -0
  26. package/dist/contract/router.js.map +1 -0
  27. package/dist/contract/settings.d.ts +20 -0
  28. package/dist/contract/settings.d.ts.map +1 -0
  29. package/dist/contract/settings.js +34 -0
  30. package/dist/contract/settings.js.map +1 -0
  31. package/dist/policy/calendar.d.ts +64 -0
  32. package/dist/policy/calendar.d.ts.map +1 -0
  33. package/dist/policy/calendar.js +113 -0
  34. package/dist/policy/calendar.js.map +1 -0
  35. package/dist/server/index.d.ts +8 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +141 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/packs/index.d.ts +50 -0
  40. package/dist/server/packs/index.d.ts.map +1 -0
  41. package/dist/server/packs/index.js +121 -0
  42. package/dist/server/packs/index.js.map +1 -0
  43. package/dist/server/router.d.ts +3095 -0
  44. package/dist/server/router.d.ts.map +1 -0
  45. package/dist/server/router.js +1537 -0
  46. package/dist/server/router.js.map +1 -0
  47. package/dist/server/schema.d.ts +2828 -0
  48. package/dist/server/schema.d.ts.map +1 -0
  49. package/dist/server/schema.js +313 -0
  50. package/dist/server/schema.js.map +1 -0
  51. package/dist/server/services/db.d.ts +22 -0
  52. package/dist/server/services/db.d.ts.map +1 -0
  53. package/dist/server/services/db.js +23 -0
  54. package/dist/server/services/db.js.map +1 -0
  55. package/dist/server/services/people.d.ts +110 -0
  56. package/dist/server/services/people.d.ts.map +1 -0
  57. package/dist/server/services/people.js +182 -0
  58. package/dist/server/services/people.js.map +1 -0
  59. package/dist/server/services/resolve.d.ts +66 -0
  60. package/dist/server/services/resolve.d.ts.map +1 -0
  61. package/dist/server/services/resolve.js +145 -0
  62. package/dist/server/services/resolve.js.map +1 -0
  63. package/migrations/0000_init.sql +223 -0
  64. package/migrations/0001_rls.sql +141 -0
  65. package/migrations/meta/0000_snapshot.json +1715 -0
  66. package/migrations/meta/_journal.json +20 -0
  67. package/package.json +59 -0
  68. package/src/client/index.ts +62 -0
  69. package/src/contract/capabilities.ts +117 -0
  70. package/src/contract/events.ts +84 -0
  71. package/src/contract/index.ts +14 -0
  72. package/src/contract/models.ts +437 -0
  73. package/src/contract/permissions.ts +217 -0
  74. package/src/contract/router.ts +611 -0
  75. package/src/contract/settings.ts +35 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Module template
2
+
3
+ Copy this to start a Kern module. It is a working module: a `Note` entity with list, create and
4
+ delete, its own Postgres schema, row-level security, permissions, events, and a test that refuses to
5
+ let the contract and the router drift apart.
6
+
7
+ The fastest way to copy it correctly is not to copy it by hand:
8
+
9
+ ```bash
10
+ cd repos/modules
11
+ pnpm new-module crm # generates this package *and* its half in the app
12
+ ```
13
+
14
+ Read the rest of this file if you are doing it by hand, or if you want to know what the generator
15
+ wrote.
16
+
17
+ ## What is in here
18
+
19
+ | File | What it is |
20
+ |---|---|
21
+ | `src/contract.ts` | Zod models, the oRPC contract, events, permission keys, capabilities. Imported by both halves, so no Node code. |
22
+ | `src/server/schema.ts` | Drizzle tables in `mod_<id>`. |
23
+ | `src/server/_impl.ts` | The router. Separate from `index.ts` so the test can walk it without a kernel. |
24
+ | `src/server/index.ts` | `defineServerModule` — schema, migrations, router, subscriptions. |
25
+ | `src/client/index.ts` | The typed API client and module logic. **Ships as source.** |
26
+ | `src/module.test.ts` | Contract-to-router parity and the authorisation guard. Keep it. |
27
+ | `migrations/0000_init.sql` | Generated by `pnpm db:generate`. |
28
+ | `migrations/0001_rls.sql` | Hand-written. Never generated. |
29
+
30
+ ## Copying it by hand
31
+
32
+ Each of these has been got wrong before.
33
+
34
+ 1. **`package.json`** — set `name` to `@kernhq/module-<id>`, and **delete `"private": true`**. A
35
+ private package is skipped silently by changesets: the commit lands, CI is green, and nothing
36
+ publishes.
37
+ 2. **`files`** must cover every directory `./client` reaches — `src/client` *and* `src/contract`.
38
+ The client ships as source, so a re-export the tarball omits breaks the consumer and nothing
39
+ local notices, because the workspace resolves the file the package does not ship.
40
+ `pnpm check:pack` catches it.
41
+ 3. **The id agrees in four places**: `MODULE_ID`, `moduleSchema('<id>')`, `schemaFilter` in
42
+ `drizzle.config.ts`, and every permission and event prefix.
43
+ 4. **Version comes from the package**, never a literal: `packageVersion(import.meta.url)`. A literal
44
+ is not bumped by a release — chat once shipped as 0.2.0 while telling every admin it was 0.1.0,
45
+ and that literal is what `workspace_modules.installed_version` recorded.
46
+ 5. **Write the RLS migration.** `pnpm db:generate` will not. Copy `0001_rls.sql` and change the
47
+ table names; `rlsPolicySql` from `@kernhq/kernel` emits the same text.
48
+ 6. **Host it.** A module nothing imports is invisible: its tests pass, it publishes, and every call
49
+ 404s. Add it to `featureModules` in the `core` repo's `src/service.ts`, or to whichever service
50
+ should hold it.
51
+ 7. **Register the client** in the app's `src/lib/modules/registry.ts`.
52
+
53
+ ## What a module can contribute
54
+
55
+ The server half declares tables, migrations, a router, `procedures` other modules call through
56
+ `kernel.call()`, `jobs`, `subscriptions`, search indexers and lifecycle hooks.
57
+
58
+ The client half — the manifest in the app — declares `nav`, `commands`, `settingsPages`, `widgets`
59
+ for the dashboard, and `sidebar` for the column beside the rail. Read the `kern-widget` skill before
60
+ writing a widget, and `kern-module` for the whole sequence.
61
+
62
+ ## Before you call it done
63
+
64
+ ```bash
65
+ pnpm typecheck && pnpm lint && pnpm test && pnpm build
66
+ pnpm check:pack # the tarball contains what ./client imports
67
+ pnpm check:versions # the manifest version matches package.json
68
+ ```
69
+
70
+ Then use it through the interface, signed in, with the module enabled for a workspace. A module that
71
+ has never served a request is not finished, whatever the type-checker says.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * How much HR this workspace has.
3
+ *
4
+ * HR is the module capabilities were built for. One company wants a staff directory and nothing
5
+ * else; a second wants leave, balances and approvals; a third runs shift rosters and clocks people
6
+ * in at a factory gate. Those are three products under one name, and the alternatives to this
7
+ * registry are a code fork per customer or a navigation rail full of features nobody uses.
8
+ *
9
+ * **A capability is declared here only once something is behind it.** A switch that changes nothing
10
+ * is worse than a missing switch: it teaches an administrator that the switchboard does not mean
11
+ * anything. So this list grows with the module rather than describing where the module is going —
12
+ * `leave`, `attendance`, `overtime`, `rosters`, `periods` and `payroll_export` arrive with the
13
+ * phases that implement them.
14
+ *
15
+ * Two rules that decide whether something belongs here at all:
16
+ *
17
+ * - **Not a permission.** "May Ayşe approve leave" is a permission — true for her, false for someone
18
+ * else, in the same workspace. "Does this company do leave" is a capability: one answer for
19
+ * everyone, the owner included.
20
+ * - **Reversible without a migration.** Switching one off writes a boolean into module settings;
21
+ * the rows stay exactly where they are and switching it back on restores them. Anything that would
22
+ * need data thrown away is not a capability, however much it looks like one.
23
+ */
24
+ export declare const hrCapabilities: readonly {
25
+ id: string;
26
+ label: string;
27
+ dependsOn: string[];
28
+ defaultEnabled: boolean;
29
+ level: 3 | 1 | 2;
30
+ required: boolean;
31
+ description?: string | undefined;
32
+ }[];
33
+ export type HrCapabilityId = (typeof hrCapabilities)[number]['id'];
34
+ /**
35
+ * Which procedures sit behind which capability.
36
+ *
37
+ * Declared as data because a missing `requiresCapability` is invisible: the procedure compiles,
38
+ * every other test passes, and the only symptom is a workspace calling a feature it switched off.
39
+ * `module.test.ts` reads this and fails when a procedure named here is not carrying the middleware.
40
+ *
41
+ * A procedure absent from this map belongs to the module as a whole and is reachable whenever HR is
42
+ * on — which for `core` is always, because it is `required`.
43
+ */
44
+ export declare const hrCapabilityProcedures: Record<string, readonly string[]>;
45
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,cAAc;;;;;;;;GAiDzB,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;AAElE;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CA4BpE,CAAA"}
@@ -0,0 +1,114 @@
1
+ import { defineCapabilities } from '@kernhq/contracts';
2
+ /**
3
+ * How much HR this workspace has.
4
+ *
5
+ * HR is the module capabilities were built for. One company wants a staff directory and nothing
6
+ * else; a second wants leave, balances and approvals; a third runs shift rosters and clocks people
7
+ * in at a factory gate. Those are three products under one name, and the alternatives to this
8
+ * registry are a code fork per customer or a navigation rail full of features nobody uses.
9
+ *
10
+ * **A capability is declared here only once something is behind it.** A switch that changes nothing
11
+ * is worse than a missing switch: it teaches an administrator that the switchboard does not mean
12
+ * anything. So this list grows with the module rather than describing where the module is going —
13
+ * `leave`, `attendance`, `overtime`, `rosters`, `periods` and `payroll_export` arrive with the
14
+ * phases that implement them.
15
+ *
16
+ * Two rules that decide whether something belongs here at all:
17
+ *
18
+ * - **Not a permission.** "May Ayşe approve leave" is a permission — true for her, false for someone
19
+ * else, in the same workspace. "Does this company do leave" is a capability: one answer for
20
+ * everyone, the owner included.
21
+ * - **Reversible without a migration.** Switching one off writes a boolean into module settings;
22
+ * the rows stay exactly where they are and switching it back on restores them. Anything that would
23
+ * need data thrown away is not a capability, however much it looks like one.
24
+ */
25
+ export const hrCapabilities = defineCapabilities([
26
+ {
27
+ id: 'core',
28
+ label: 'People',
29
+ description: 'The staff directory, employment records and reporting lines',
30
+ required: true,
31
+ level: 1,
32
+ },
33
+ {
34
+ id: 'offices',
35
+ label: 'Offices',
36
+ description: 'More than one place of work, each with its own country, timezone and holidays',
37
+ dependsOn: ['core'],
38
+ // Off by default, and invisible when off — but the *concept* is never absent. A workspace always
39
+ // has exactly one office, built from its country when HR is enabled, and everybody is assigned
40
+ // to it. Switching this on reveals the list and the assignment control; it does not migrate
41
+ // anything, because the shape was there from the first day. A workspace that only ever has one
42
+ // office never meets the word.
43
+ defaultEnabled: false,
44
+ level: 2,
45
+ },
46
+ {
47
+ id: 'legal_entities',
48
+ label: 'Legal entities',
49
+ description: 'Several employing companies, for a group operating across borders',
50
+ // Depends on offices rather than core: a single-site company has one employer by definition, and
51
+ // the question only becomes real once there is more than one place of work.
52
+ dependsOn: ['offices'],
53
+ defaultEnabled: false,
54
+ level: 3,
55
+ },
56
+ {
57
+ id: 'calendars',
58
+ label: 'Holiday calendars',
59
+ description: 'Public holidays, company closures and the working week',
60
+ dependsOn: ['core'],
61
+ // On by default. Every company has holidays, and a directory that does not know when people are
62
+ // off is answering a question nobody asked.
63
+ defaultEnabled: true,
64
+ level: 1,
65
+ },
66
+ {
67
+ id: 'documents',
68
+ label: 'Employee documents',
69
+ description: 'Contracts, identity documents and certificates against a person',
70
+ dependsOn: ['core'],
71
+ defaultEnabled: false,
72
+ level: 2,
73
+ },
74
+ ]);
75
+ /**
76
+ * Which procedures sit behind which capability.
77
+ *
78
+ * Declared as data because a missing `requiresCapability` is invisible: the procedure compiles,
79
+ * every other test passes, and the only symptom is a workspace calling a feature it switched off.
80
+ * `module.test.ts` reads this and fails when a procedure named here is not carrying the middleware.
81
+ *
82
+ * A procedure absent from this map belongs to the module as a whole and is reachable whenever HR is
83
+ * on — which for `core` is always, because it is `required`.
84
+ */
85
+ export const hrCapabilityProcedures = {
86
+ offices: [
87
+ 'offices.list',
88
+ 'offices.get',
89
+ 'offices.create',
90
+ 'offices.update',
91
+ 'offices.archive',
92
+ 'offices.setDefault',
93
+ 'offices.assign',
94
+ 'offices.unassign',
95
+ 'offices.people',
96
+ ],
97
+ legal_entities: ['entities.list', 'entities.get', 'entities.create', 'entities.update', 'entities.archive'],
98
+ calendars: [
99
+ 'calendars.list',
100
+ 'calendars.get',
101
+ 'calendars.create',
102
+ 'calendars.update',
103
+ 'calendars.archive',
104
+ 'calendars.days.list',
105
+ 'calendars.days.add',
106
+ 'calendars.days.update',
107
+ 'calendars.days.remove',
108
+ 'calendars.pack.preview',
109
+ 'calendars.pack.apply',
110
+ 'calendars.workingDays',
111
+ ],
112
+ documents: ['documents.list', 'documents.attach', 'documents.remove'],
113
+ };
114
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC;IAC/C;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,6DAA6D;QAC1E,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,+EAA+E;QAC5F,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,iGAAiG;QACjG,+FAA+F;QAC/F,4FAA4F;QAC5F,+FAA+F;QAC/F,+BAA+B;QAC/B,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,mEAAmE;QAChF,iGAAiG;QACjG,4EAA4E;QAC5E,SAAS,EAAE,CAAC,SAAS,CAAC;QACtB,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,wDAAwD;QACrE,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,gGAAgG;QAChG,4CAA4C;QAC5C,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,CAAC;KACT;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,iEAAiE;QAC9E,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,CAAC;KACT;CACF,CAAC,CAAA;AAIF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsC;IACvE,OAAO,EAAE;QACP,cAAc;QACd,aAAa;QACb,gBAAgB;QAChB,gBAAgB;QAChB,iBAAiB;QACjB,oBAAoB;QACpB,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;KACjB;IACD,cAAc,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC;IAC3G,SAAS,EAAE;QACT,gBAAgB;QAChB,eAAe;QACf,kBAAkB;QAClB,kBAAkB;QAClB,mBAAmB;QACnB,qBAAqB;QACrB,oBAAoB;QACpB,uBAAuB;QACvB,uBAAuB;QACvB,wBAAwB;QACxB,sBAAsB;QACtB,uBAAuB;KACxB;IACD,SAAS,EAAE,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,CAAC;CACtE,CAAA"}
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * `hr.<entity>.<action>`. Anything that emits one declares it here.
4
+ *
5
+ * These are what the rest of the product reacts to. Chat wants to know when somebody joins so it can
6
+ * add them to a channel; a future calendar wants office holidays; payroll wants an employment
7
+ * change. The payloads carry ids rather than rows on purpose — a subscriber that needs the record
8
+ * asks for it with its own principal, so an event cannot become a way to read data past a permission
9
+ * check.
10
+ */
11
+ export declare const hrEvents: {
12
+ personCreated: import("@kernhq/contracts").EventDef<"hr.person.created", z.ZodObject<{
13
+ personId: z.ZodUUID;
14
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
15
+ userId: z.ZodNullable<z.ZodUUID>;
16
+ }, z.core.$strip>>;
17
+ personUpdated: import("@kernhq/contracts").EventDef<"hr.person.updated", z.ZodObject<{
18
+ personId: z.ZodUUID;
19
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
20
+ fields: z.ZodArray<z.ZodString>;
21
+ }, z.core.$strip>>;
22
+ /**
23
+ * Status moved — onboarding to active, active to terminated.
24
+ *
25
+ * Separate from `personUpdated` because the things that care about a lifecycle change (revoking
26
+ * access, closing a leave balance, ending a payroll line) do not want to filter every profile
27
+ * edit to find it.
28
+ */
29
+ personStatusChanged: import("@kernhq/contracts").EventDef<"hr.person.status_changed", z.ZodObject<{
30
+ personId: z.ZodUUID;
31
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
32
+ from: z.ZodString;
33
+ to: z.ZodString;
34
+ on: z.ZodISODate;
35
+ }, z.core.$strip>>;
36
+ employmentChanged: import("@kernhq/contracts").EventDef<"hr.employment.changed", z.ZodObject<{
37
+ personId: z.ZodUUID;
38
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
39
+ employmentId: z.ZodUUID;
40
+ effectiveFrom: z.ZodISODate;
41
+ }, z.core.$strip>>;
42
+ /**
43
+ * Somebody's office changed, or their primary did.
44
+ *
45
+ * Worth its own event because the primary office decides holidays, timezone and policy: anything
46
+ * holding a derived answer for this person has to recompute, and this is how it finds out.
47
+ */
48
+ officeAssignmentChanged: import("@kernhq/contracts").EventDef<"hr.office_assignment.changed", z.ZodObject<{
49
+ personId: z.ZodUUID;
50
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
51
+ officeId: z.ZodUUID;
52
+ isPrimary: z.ZodBoolean;
53
+ effectiveFrom: z.ZodISODate;
54
+ }, z.core.$strip>>;
55
+ officeCreated: import("@kernhq/contracts").EventDef<"hr.office.created", z.ZodObject<{
56
+ officeId: z.ZodUUID;
57
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
58
+ country: z.ZodString;
59
+ }, z.core.$strip>>;
60
+ /**
61
+ * A calendar's days changed — a holiday added, a pack applied.
62
+ *
63
+ * Everything derived from a calendar (working days, leave day counts, later the attendance day
64
+ * sheet) is stale from here. The payload names the date range touched so a consumer can recompute
65
+ * that window rather than everything.
66
+ */
67
+ calendarChanged: import("@kernhq/contracts").EventDef<"hr.calendar.changed", z.ZodObject<{
68
+ calendarId: z.ZodUUID;
69
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
70
+ from: z.ZodNullable<z.ZodISODate>;
71
+ to: z.ZodNullable<z.ZodISODate>;
72
+ }, z.core.$strip>>;
73
+ };
74
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;IASnB;;;;;;OAMG;;;;;;;;;;;;;;IAoBH;;;;;OAKG;;;;;;;;;;;;;IAeH;;;;;;OAMG;;;;;;;CAUJ,CAAA"}
@@ -0,0 +1,63 @@
1
+ import { defineEvent, WorkspaceId } from '@kernhq/contracts';
2
+ import { z } from 'zod';
3
+ /**
4
+ * `hr.<entity>.<action>`. Anything that emits one declares it here.
5
+ *
6
+ * These are what the rest of the product reacts to. Chat wants to know when somebody joins so it can
7
+ * add them to a channel; a future calendar wants office holidays; payroll wants an employment
8
+ * change. The payloads carry ids rather than rows on purpose — a subscriber that needs the record
9
+ * asks for it with its own principal, so an event cannot become a way to read data past a permission
10
+ * check.
11
+ */
12
+ export const hrEvents = {
13
+ personCreated: defineEvent('hr.person.created', z.object({ personId: z.uuid(), workspaceId: WorkspaceId, userId: z.uuid().nullable() })),
14
+ personUpdated: defineEvent('hr.person.updated', z.object({ personId: z.uuid(), workspaceId: WorkspaceId, fields: z.array(z.string()) })),
15
+ /**
16
+ * Status moved — onboarding to active, active to terminated.
17
+ *
18
+ * Separate from `personUpdated` because the things that care about a lifecycle change (revoking
19
+ * access, closing a leave balance, ending a payroll line) do not want to filter every profile
20
+ * edit to find it.
21
+ */
22
+ personStatusChanged: defineEvent('hr.person.status_changed', z.object({
23
+ personId: z.uuid(),
24
+ workspaceId: WorkspaceId,
25
+ from: z.string(),
26
+ to: z.string(),
27
+ on: z.iso.date(),
28
+ })),
29
+ employmentChanged: defineEvent('hr.employment.changed', z.object({
30
+ personId: z.uuid(),
31
+ workspaceId: WorkspaceId,
32
+ employmentId: z.uuid(),
33
+ effectiveFrom: z.iso.date(),
34
+ })),
35
+ /**
36
+ * Somebody's office changed, or their primary did.
37
+ *
38
+ * Worth its own event because the primary office decides holidays, timezone and policy: anything
39
+ * holding a derived answer for this person has to recompute, and this is how it finds out.
40
+ */
41
+ officeAssignmentChanged: defineEvent('hr.office_assignment.changed', z.object({
42
+ personId: z.uuid(),
43
+ workspaceId: WorkspaceId,
44
+ officeId: z.uuid(),
45
+ isPrimary: z.boolean(),
46
+ effectiveFrom: z.iso.date(),
47
+ })),
48
+ officeCreated: defineEvent('hr.office.created', z.object({ officeId: z.uuid(), workspaceId: WorkspaceId, country: z.string() })),
49
+ /**
50
+ * A calendar's days changed — a holiday added, a pack applied.
51
+ *
52
+ * Everything derived from a calendar (working days, leave day counts, later the attendance day
53
+ * sheet) is stale from here. The payload names the date range touched so a consumer can recompute
54
+ * that window rather than everything.
55
+ */
56
+ calendarChanged: defineEvent('hr.calendar.changed', z.object({
57
+ calendarId: z.uuid(),
58
+ workspaceId: WorkspaceId,
59
+ from: z.iso.date().nullable(),
60
+ to: z.iso.date().nullable(),
61
+ })),
62
+ };
63
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CACxF;IACD,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CACxF;IACD;;;;;;OAMG;IACH,mBAAmB,EAAE,WAAW,CAC9B,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;QACd,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KACjB,CAAC,CACH;IACD,iBAAiB,EAAE,WAAW,CAC5B,uBAAuB,EACvB,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE;QACtB,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KAC5B,CAAC,CACH;IACD;;;;;OAKG;IACH,uBAAuB,EAAE,WAAW,CAClC,8BAA8B,EAC9B,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,WAAW,EAAE,WAAW;QACxB,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;QAClB,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;QACtB,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;KAC5B,CAAC,CACH;IACD,aAAa,EAAE,WAAW,CACxB,mBAAmB,EACnB,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAChF;IACD;;;;;;OAMG;IACH,eAAe,EAAE,WAAW,CAC1B,qBAAqB,EACrB,CAAC,CAAC,MAAM,CAAC;QACP,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE;QACpB,WAAW,EAAE,WAAW;QACxB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;QAC7B,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;KAC5B,CAAC,CACH;CACF,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What HR offers, as data.
3
+ *
4
+ * Imported by **both** halves — the server implements it, the client calls it — so nothing here may
5
+ * touch Node. The contract is the only thing that crosses that line, which is why a procedure that
6
+ * exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that,
7
+ * and also that every procedure listed in `hrCapabilityProcedures` carries its capability guard.
8
+ */
9
+ export * from './capabilities.js';
10
+ export * from './events.js';
11
+ export * from './models.js';
12
+ export * from './permissions.js';
13
+ export * from './router.js';
14
+ export * from './settings.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What HR offers, as data.
3
+ *
4
+ * Imported by **both** halves — the server implements it, the client calls it — so nothing here may
5
+ * touch Node. The contract is the only thing that crosses that line, which is why a procedure that
6
+ * exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that,
7
+ * and also that every procedure listed in `hrCapabilityProcedures` carries its capability guard.
8
+ */
9
+ export * from './capabilities.js';
10
+ export * from './events.js';
11
+ export * from './models.js';
12
+ export * from './permissions.js';
13
+ export * from './router.js';
14
+ export * from './settings.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}