@haverstack/core 0.7.0 → 0.9.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 (65) hide show
  1. package/README.md +30 -14
  2. package/dist/access.d.ts +20 -7
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +37 -16
  5. package/dist/access.js.map +1 -1
  6. package/dist/adapter-entry.d.ts +12 -0
  7. package/dist/adapter-entry.d.ts.map +1 -0
  8. package/dist/adapter-entry.js +11 -0
  9. package/dist/adapter-entry.js.map +1 -0
  10. package/dist/attachment-download.d.ts +75 -0
  11. package/dist/attachment-download.d.ts.map +1 -0
  12. package/dist/attachment-download.js +128 -0
  13. package/dist/attachment-download.js.map +1 -0
  14. package/dist/auth.d.ts +88 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +145 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/combine.d.ts.map +1 -1
  19. package/dist/combine.js +20 -3
  20. package/dist/combine.js.map +1 -1
  21. package/dist/did-entry.d.ts +12 -0
  22. package/dist/did-entry.d.ts.map +1 -0
  23. package/dist/did-entry.js +16 -0
  24. package/dist/did-entry.js.map +1 -0
  25. package/dist/did.d.ts +51 -0
  26. package/dist/did.d.ts.map +1 -0
  27. package/dist/did.js +153 -0
  28. package/dist/did.js.map +1 -0
  29. package/dist/id.d.ts +18 -6
  30. package/dist/id.d.ts.map +1 -1
  31. package/dist/id.js +39 -10
  32. package/dist/id.js.map +1 -1
  33. package/dist/index.d.ts +15 -10
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +14 -9
  36. package/dist/index.js.map +1 -1
  37. package/dist/merge.d.ts +11 -0
  38. package/dist/merge.d.ts.map +1 -0
  39. package/dist/merge.js +38 -0
  40. package/dist/merge.js.map +1 -0
  41. package/dist/schema.d.ts +31 -9
  42. package/dist/schema.d.ts.map +1 -1
  43. package/dist/schema.js +134 -11
  44. package/dist/schema.js.map +1 -1
  45. package/dist/stack.d.ts +748 -152
  46. package/dist/stack.d.ts.map +1 -1
  47. package/dist/stack.js +1805 -328
  48. package/dist/stack.js.map +1 -1
  49. package/dist/testing.d.ts +84 -23
  50. package/dist/testing.d.ts.map +1 -1
  51. package/dist/testing.js +260 -19
  52. package/dist/testing.js.map +1 -1
  53. package/dist/types.d.ts +281 -31
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +14 -0
  56. package/dist/types.js.map +1 -1
  57. package/dist/validate.d.ts +26 -0
  58. package/dist/validate.d.ts.map +1 -1
  59. package/dist/validate.js +51 -1
  60. package/dist/validate.js.map +1 -1
  61. package/dist/wire-entry.d.ts +17 -0
  62. package/dist/wire-entry.d.ts.map +1 -0
  63. package/dist/wire-entry.js +24 -0
  64. package/dist/wire-entry.js.map +1 -0
  65. package/package.json +13 -1
package/README.md CHANGED
@@ -14,25 +14,36 @@ npm install @haverstack/core
14
14
 
15
15
  You'll also need a storage adapter:
16
16
 
17
- - [`@haverstack/adapter-sqlite`](https://www.npmjs.com/package/@haverstack/adapter-sqlite) — local SQLite storage via sql.js
17
+ - [`@haverstack/adapter-local`](https://www.npmjs.com/package/@haverstack/adapter-local) — local storage (native SQLite + disk), single-app/embedded or server use
18
18
 
19
19
  ## Quick start
20
20
 
21
21
  ```ts
22
22
  import { Stack } from '@haverstack/core';
23
- import { SQLiteAdapter } from '@haverstack/adapter-sqlite';
24
-
25
- // First run — initialize a new stack
26
- const adapter = await SQLiteAdapter.initialize({
27
- path: './my-stack.db',
28
- entityId: 'my-entity-id',
23
+ import { generateDidKeypair, exportDidPrivateKeyJwk } from '@haverstack/core/did';
24
+ import { LocalAdapter } from '@haverstack/adapter-local';
25
+ import { writeFile } from 'node:fs/promises';
26
+
27
+ const dbPath = './my-stack.db';
28
+ const keyPath = './my-stack.key.json'; // see "Key custody" below for where this really belongs
29
+
30
+ // First run: neither file exists yet, so this generates an identity
31
+ // keypair and persists the private key before initializing. Every run
32
+ // after that: the db exists, so this just opens it — the entityId
33
+ // function below is never called, so no throwaway keypair is minted.
34
+ const adapter = await LocalAdapter.openOrInitialize({
35
+ path: dbPath,
29
36
  timezone: 'America/New_York',
37
+ entityId: async () => {
38
+ const { did, privateKey } = await generateDidKeypair();
39
+ await writeFile(keyPath, JSON.stringify(await exportDidPrivateKeyJwk(privateKey)));
40
+ return did;
41
+ },
30
42
  });
31
43
 
32
- // Subsequent runs — open the existing stack
33
- // const adapter = await SQLiteAdapter.open({ path: './my-stack.db' });
34
-
35
- const stack = await Stack.create(adapter);
44
+ // ownerProfile creates your own _entity profile record on first run —
45
+ // safe to keep passing on every open, it's a no-op once the record exists.
46
+ const stack = await Stack.create(adapter, { ownerProfile: { name: 'Jane Smith' } });
36
47
 
37
48
  // Define a type
38
49
  await stack.defineType('com.example.myapp/note@1', 'Note', {
@@ -59,7 +70,6 @@ const notes = await stack.query({
59
70
  });
60
71
 
61
72
  // Tear down when done
62
- await stack.flush();
63
73
  await stack.close();
64
74
  ```
65
75
 
@@ -72,7 +82,13 @@ The fundamental unit of data. Every record has:
72
82
  - A **Crockford base-32 ID** — time-sortable, human-readable, URL-safe
73
83
  - A **type** — defined by the app that created it
74
84
  - **Content** — a JSON object validated against the type's schema
75
- - Optional: `parentId`, `entityId`, `appId`, `permissions`, `associations`
85
+ - Optional: `parentId`, `entityId`, `appId`, `principalId`, `permissions`, `associations`
86
+
87
+ ### Identity
88
+
89
+ `entityId` is a DID string (e.g. `did:key:z6Mk...`) — a keypair, not a name issued by any provider. `generateDidKeypair()` mints the mandatory floor method, `did:key`. `_entity` records are local profile cards _about_ a DID (`{ did, name, handle? }`), not the identity itself — the petname pattern. See [Identity](https://github.com/haverstack/core/blob/main/docs/spec/identity.md) in the spec.
90
+
91
+ **Key custody.** Nothing in `@haverstack/core` or any adapter stores the `privateKey` — only the public `did` travels with stack data. Persisting it (JWK via `exportDidPrivateKeyJwk()`/`importDidPrivateKeyJwk()`, or the `CryptoKey` itself in a browser's IndexedDB) is on you. Losing it doesn't break anything local — but you can never again authenticate as that identity to a server, since `did:key` identity _is_ the key. See [Key custody](https://github.com/haverstack/core#key-custody) in the main README for per-platform recipes.
76
92
 
77
93
  ### Types
78
94
 
@@ -90,7 +106,7 @@ Tags, attachments, and relationships are unified under a single model:
90
106
 
91
107
  ```ts
92
108
  { kind: 'tag', label: 'favourite' }
93
- { kind: 'attachment', label: 'avatar', fileId: '...', mimeType: 'image/png' }
109
+ { kind: 'attachment', label: 'avatar', fileId: '...' }
94
110
  { kind: 'relationship', label: 'reply-to', recordId: '...' }
95
111
  ```
96
112
 
package/dist/access.d.ts CHANGED
@@ -6,20 +6,33 @@
6
6
  * storage backend. Used by ScopedStack (see stack.ts) to enforce access
7
7
  * control; exported standalone for callers that want the raw predicate.
8
8
  */
9
- import type { RecordId, StackRecord } from './types.js';
9
+ import type { Association, EntityId, RecordId, StackRecord } from './types.js';
10
10
  export type AccessMode = 'read' | 'write';
11
+ /** An entity's standing within a `_group` Record's roster. `admin` implies `member` for ACL purposes. */
12
+ export type GroupRole = 'member' | 'admin';
11
13
  /**
12
14
  * Resolves a Record by ID. Used to walk a `_group` Record's associations for
13
15
  * membership checks without requiring a full StackAdapter.
14
16
  */
15
17
  export type RecordResolver = (id: RecordId) => Promise<StackRecord | null>;
16
18
  /**
17
- * Check whether an entity has read or write access to a Record.
19
+ * Check whether an entity has read or write access to a Record: absent
20
+ * permissions = owner only; public = anyone reads; entity = direct match;
21
+ * group = walk the _group Record's roster associations. See
22
+ * docs/spec/access-control.md § Record-level permissions.
18
23
  *
19
- * - No permissions (absent/empty): owner only.
20
- * - public: anyone can read; write still requires an explicit grant.
21
- * - entity: direct entityId match.
22
- * - group: walk the _group Record's relationship associations for membership.
24
+ * The entity here is the **subject** — record-level permissions are written
25
+ * about who data is for, never about the software that carried the request.
26
+ * A delegated app's own authority is a separate question, asked of the
27
+ * principal. See docs/spec/access-control.md § Delegation: principal and
28
+ * subject.
23
29
  */
24
- export declare function checkAccess(record: StackRecord, requesterEntityId: string | null, ownerEntityId: string | null, mode: AccessMode, resolveRecord: RecordResolver): Promise<boolean>;
30
+ export declare function checkAccess(record: StackRecord, subjectEntityId: EntityId | null, ownerEntityId: EntityId | null, mode: AccessMode, resolveRecord: RecordResolver): Promise<boolean>;
31
+ /**
32
+ * Determine an entity's role within a `_group` Record's roster from its
33
+ * relationship associations. `admin` short-circuits — it's strictly more
34
+ * privileged than `member`, so a matching admin association wins regardless
35
+ * of association order.
36
+ */
37
+ export declare function groupRoleFromAssociations(associations: Association[] | undefined, entityId: EntityId): GroupRole | null;
25
38
  //# sourceMappingURL=access.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,cAAc,GAC5B,OAAO,CAAC,OAAO,CAAC,CA2BlB"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,yGAAyG;AACzG,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,QAAQ,GAAG,IAAI,EAChC,aAAa,EAAE,QAAQ,GAAG,IAAI,EAC9B,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,cAAc,GAC5B,OAAO,CAAC,OAAO,CAAC,CA4BlB;AAYD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,QAAQ,EAAE,QAAQ,GACjB,SAAS,GAAG,IAAI,CASlB"}
package/dist/access.js CHANGED
@@ -7,16 +7,20 @@
7
7
  * control; exported standalone for callers that want the raw predicate.
8
8
  */
9
9
  /**
10
- * Check whether an entity has read or write access to a Record.
10
+ * Check whether an entity has read or write access to a Record: absent
11
+ * permissions = owner only; public = anyone reads; entity = direct match;
12
+ * group = walk the _group Record's roster associations. See
13
+ * docs/spec/access-control.md § Record-level permissions.
11
14
  *
12
- * - No permissions (absent/empty): owner only.
13
- * - public: anyone can read; write still requires an explicit grant.
14
- * - entity: direct entityId match.
15
- * - group: walk the _group Record's relationship associations for membership.
15
+ * The entity here is the **subject** — record-level permissions are written
16
+ * about who data is for, never about the software that carried the request.
17
+ * A delegated app's own authority is a separate question, asked of the
18
+ * principal. See docs/spec/access-control.md § Delegation: principal and
19
+ * subject.
16
20
  */
17
- export async function checkAccess(record, requesterEntityId, ownerEntityId, mode, resolveRecord) {
21
+ export async function checkAccess(record, subjectEntityId, ownerEntityId, mode, resolveRecord) {
18
22
  // Owner always has full access.
19
- if (requesterEntityId && requesterEntityId === ownerEntityId)
23
+ if (subjectEntityId && subjectEntityId === ownerEntityId)
20
24
  return true;
21
25
  const perms = record.permissions;
22
26
  // No permissions = private.
@@ -25,15 +29,16 @@ export async function checkAccess(record, requesterEntityId, ownerEntityId, mode
25
29
  for (const p of perms) {
26
30
  if (p.access === 'public' && mode === 'read')
27
31
  return true;
28
- if (p.access === 'entity' && p.entityId === requesterEntityId) {
32
+ if (p.access === 'entity' && p.entityId === subjectEntityId) {
29
33
  if (mode === 'read' && p.read)
30
34
  return true;
31
35
  if (mode === 'write' && p.write)
32
36
  return true;
33
37
  }
34
- if (p.access === 'group' && requesterEntityId) {
35
- const member = await isGroupMember(p.groupId, requesterEntityId, resolveRecord);
36
- if (member) {
38
+ if (p.access === 'group' && subjectEntityId) {
39
+ const role = await resolveGroupRole(p.groupId, subjectEntityId, resolveRecord);
40
+ const satisfiesRole = p.role === 'admin' ? role === 'admin' : role !== null;
41
+ if (satisfiesRole) {
37
42
  if (mode === 'read' && p.read)
38
43
  return true;
39
44
  if (mode === 'write' && p.write)
@@ -43,12 +48,28 @@ export async function checkAccess(record, requesterEntityId, ownerEntityId, mode
43
48
  }
44
49
  return false;
45
50
  }
46
- async function isGroupMember(groupRecordId, entityId, resolveRecord) {
51
+ async function resolveGroupRole(groupRecordId, entityId, resolveRecord) {
47
52
  const group = await resolveRecord(groupRecordId);
48
53
  if (!group)
49
- return false;
50
- return (group.associations ?? []).some((a) => a.kind === 'relationship' &&
51
- (a.label === 'member' || a.label === 'admin') &&
52
- a.recordId === entityId);
54
+ return null;
55
+ return groupRoleFromAssociations(group.associations, entityId);
56
+ }
57
+ /**
58
+ * Determine an entity's role within a `_group` Record's roster from its
59
+ * relationship associations. `admin` short-circuits — it's strictly more
60
+ * privileged than `member`, so a matching admin association wins regardless
61
+ * of association order.
62
+ */
63
+ export function groupRoleFromAssociations(associations, entityId) {
64
+ let role = null;
65
+ for (const a of associations ?? []) {
66
+ if (a.kind === 'relationship' && a.recordId === entityId) {
67
+ if (a.label === 'admin')
68
+ return 'admin';
69
+ if (a.label === 'member')
70
+ role = 'member';
71
+ }
72
+ }
73
+ return role;
53
74
  }
54
75
  //# sourceMappingURL=access.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAmB,EACnB,iBAAgC,EAChC,aAA4B,EAC5B,IAAgB,EAChB,aAA6B;IAE7B,gCAAgC;IAChC,IAAI,iBAAiB,IAAI,iBAAiB,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IAE1E,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC;IAEjC,4BAA4B;IAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE/C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAE1D,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,iBAAiB,EAAE,CAAC;YAC9D,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC3C,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,IAAI,iBAAiB,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;YAChF,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAC3C,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,aAAuB,EACvB,QAAgB,EAChB,aAA6B;IAE7B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,CACpC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,KAAK,cAAc;QACzB,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC;QAC7C,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAC1B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAmB,EACnB,eAAgC,EAChC,aAA8B,EAC9B,IAAgB,EAChB,aAA6B;IAE7B,gCAAgC;IAChC,IAAI,eAAe,IAAI,eAAe,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IAEtE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC;IAEjC,4BAA4B;IAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE/C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAE1D,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;YAC5D,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC3C,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,IAAI,eAAe,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,OAAO,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;YAC/E,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;YAC5E,IAAI,aAAa,EAAE,CAAC;gBAClB,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAC3C,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,aAAuB,EACvB,QAAkB,EAClB,aAA6B;IAE7B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,yBAAyB,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,YAAuC,EACvC,QAAkB;IAElB,IAAI,IAAI,GAAqB,IAAI,CAAC;IAClC,KAAK,MAAM,CAAC,IAAI,YAAY,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACzD,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC;YACxC,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAAE,IAAI,GAAG,QAAQ,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @haverstack/core/adapter
3
+ * -------------------------------------------------------
4
+ * The interfaces a storage adapter implements — record storage, blob
5
+ * storage, and the capabilities an adapter declares. Audience: adapter
6
+ * implementers (see the in-repo adapters), not app or plugin code, which
7
+ * reads `stack.features` instead of these directly.
8
+ */
9
+ export type { StackRecordAdapter, StackBlobAdapter, AdapterCapabilities, BlobFileInfo, ExpectedVersionOptions, } from './types.js';
10
+ export { combineAdapters } from './combine.js';
11
+ export { assertQueryCapabilities } from './stack.js';
12
+ //# sourceMappingURL=adapter-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-entry.d.ts","sourceRoot":"","sources":["../src/adapter-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @haverstack/core/adapter
3
+ * -------------------------------------------------------
4
+ * The interfaces a storage adapter implements — record storage, blob
5
+ * storage, and the capabilities an adapter declares. Audience: adapter
6
+ * implementers (see the in-repo adapters), not app or plugin code, which
7
+ * reads `stack.features` instead of these directly.
8
+ */
9
+ export { combineAdapters } from './combine.js';
10
+ export { assertQueryCapabilities } from './stack.js';
11
+ //# sourceMappingURL=adapter-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-entry.js","sourceRoot":"","sources":["../src/adapter-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Stack — Attachment Download Content-Type Resolution
3
+ * -------------------------------------------------------
4
+ * Shared, pure implementation of the `GET /attachments/:fileId` dangerous-
5
+ * type policy (docs/spec/wire-format.md § Download). Core has no HTTP
6
+ * server of its own — this module exists so `haverstack/server` and any
7
+ * other server implementation share one safe-list and one forcing
8
+ * computation, rather than each re-deriving it and risking drift on a
9
+ * security-relevant policy (the same "written once, not a per-server
10
+ * convention" principle applied elsewhere to adapter invariants).
11
+ *
12
+ * The policy is result-not-source: `resolveAttachmentDownloadContentType()`
13
+ * computes a single candidate type from whichever source wins (explicit
14
+ * param, then filename extension, then stored metadata, then
15
+ * application/octet-stream) and applies the safe-list to *that value* —
16
+ * never to the source that produced it, so no source can route a
17
+ * dangerous type past the check.
18
+ */
19
+ /** application/octet-stream: the forced replacement for any unsafe type. */
20
+ export declare const FORCED_CONTENT_TYPE = "application/octet-stream";
21
+ export declare const NOSNIFF_HEADER_NAME = "X-Content-Type-Options";
22
+ export declare const NOSNIFF_HEADER_VALUE = "nosniff";
23
+ /**
24
+ * Whether a MIME type may be served as-is (Content-Type set to it, no
25
+ * forced download). Checked against the normalized base type — any
26
+ * parameters or casing on the input don't affect the result.
27
+ */
28
+ export declare function isSafeAttachmentContentType(mimeType: string): boolean;
29
+ /** Infers a candidate MIME type from a filename's extension. Returns undefined for no extension or an unrecognized one — the caller falls through to the next source. */
30
+ export declare function inferContentTypeFromFilename(filename: string): string | undefined;
31
+ /**
32
+ * Which `_attachment@1` record establishes a fileId's mimeType — the
33
+ * "first-recorded" rule of docs/spec/attachments.md, as a total order:
34
+ * earliest `createdAt`, ties broken by the lower `id`.
35
+ *
36
+ * The tiebreak is what makes it a rule rather than a coincidence of scan
37
+ * order. Rejecting a conflicting mimeType at write time is check-then-act
38
+ * with no storage-level uniqueness behind it, so two racing first uploads
39
+ * of the same bytes can both land on a concurrent server. Determinism
40
+ * doesn't depend on that never happening — it depends on every reader
41
+ * ordering the records the same way, which is why core's write-time
42
+ * conflict check and a server's serving choice both come through here.
43
+ *
44
+ * Generic over the record shape so a server can pass its own row type.
45
+ * Records for other fileIds must be filtered out by the caller.
46
+ */
47
+ export declare function firstRecordedAttachment<T extends {
48
+ id: string;
49
+ createdAt: Date;
50
+ }>(records: readonly T[]): T | undefined;
51
+ export type AttachmentDownloadContentType = {
52
+ /** The Content-Type header value to serve. */
53
+ contentType: string;
54
+ /**
55
+ * Whether the dangerous-type policy overrode the candidate. When true,
56
+ * the server MUST also force `Content-Disposition: attachment` — Content-
57
+ * Type forcing alone doesn't stop a browser from sniffing the body back
58
+ * into the original type without both nosniff (always required, see
59
+ * NOSNIFF_HEADER_NAME/VALUE) and a non-inline disposition.
60
+ */
61
+ forced: boolean;
62
+ };
63
+ /**
64
+ * Resolve the Content-Type served for `GET /attachments/:fileId`
65
+ * (docs/spec/wire-format.md § Download): `?contentType` wins, else
66
+ * extension inference from `?filename`, else the stored mimeType, else
67
+ * `application/octet-stream` — then the safe-list is applied to whichever
68
+ * candidate that produced.
69
+ */
70
+ export declare function resolveAttachmentDownloadContentType(input: {
71
+ contentTypeParam?: string;
72
+ filenameParam?: string;
73
+ storedMimeType?: string;
74
+ }): AttachmentDownloadContentType;
75
+ //# sourceMappingURL=attachment-download.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-download.d.ts","sourceRoot":"","sources":["../src/attachment-download.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAcH,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAE9D,eAAO,MAAM,mBAAmB,2BAA2B,CAAC;AAC5D,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAO9C;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKrE;AAmCD,yKAAyK;AACzK,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,EAC/E,OAAO,EAAE,SAAS,CAAC,EAAE,GACpB,CAAC,GAAG,SAAS,CAOf;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CAAC,KAAK,EAAE;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,6BAA6B,CAUhC"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Stack — Attachment Download Content-Type Resolution
3
+ * -------------------------------------------------------
4
+ * Shared, pure implementation of the `GET /attachments/:fileId` dangerous-
5
+ * type policy (docs/spec/wire-format.md § Download). Core has no HTTP
6
+ * server of its own — this module exists so `haverstack/server` and any
7
+ * other server implementation share one safe-list and one forcing
8
+ * computation, rather than each re-deriving it and risking drift on a
9
+ * security-relevant policy (the same "written once, not a per-server
10
+ * convention" principle applied elsewhere to adapter invariants).
11
+ *
12
+ * The policy is result-not-source: `resolveAttachmentDownloadContentType()`
13
+ * computes a single candidate type from whichever source wins (explicit
14
+ * param, then filename extension, then stored metadata, then
15
+ * application/octet-stream) and applies the safe-list to *that value* —
16
+ * never to the source that produced it, so no source can route a
17
+ * dangerous type past the check.
18
+ */
19
+ /** MIME types that pass through unforced, regardless of source. */
20
+ const SAFE_EXACT_TYPES = new Set(['application/pdf', 'text/plain', 'application/octet-stream']);
21
+ /** MIME type prefixes that pass through unforced, except for UNSAFE_OVERRIDES below. */
22
+ const SAFE_PREFIXES = ['image/', 'video/', 'audio/'];
23
+ /**
24
+ * Matches a SAFE_PREFIXES entry but is script-capable and must still be
25
+ * forced — image/svg+xml is the reason a plain prefix check isn't enough.
26
+ */
27
+ const UNSAFE_OVERRIDES = new Set(['image/svg+xml']);
28
+ /** application/octet-stream: the forced replacement for any unsafe type. */
29
+ export const FORCED_CONTENT_TYPE = 'application/octet-stream';
30
+ export const NOSNIFF_HEADER_NAME = 'X-Content-Type-Options';
31
+ export const NOSNIFF_HEADER_VALUE = 'nosniff';
32
+ /** Strips MIME parameters ("text/html; charset=utf-8" -> "text/html") and lowercases, so parameter tricks and casing can't bypass the safe-list check. */
33
+ function normalizeMimeType(mimeType) {
34
+ return mimeType.split(';')[0].trim().toLowerCase();
35
+ }
36
+ /**
37
+ * Whether a MIME type may be served as-is (Content-Type set to it, no
38
+ * forced download). Checked against the normalized base type — any
39
+ * parameters or casing on the input don't affect the result.
40
+ */
41
+ export function isSafeAttachmentContentType(mimeType) {
42
+ const normalized = normalizeMimeType(mimeType);
43
+ if (UNSAFE_OVERRIDES.has(normalized))
44
+ return false;
45
+ if (SAFE_EXACT_TYPES.has(normalized))
46
+ return true;
47
+ return SAFE_PREFIXES.some((prefix) => normalized.startsWith(prefix));
48
+ }
49
+ /**
50
+ * Extension -> candidate MIME type, used only when resolving from a
51
+ * `?filename` param with no explicit `?contentType` (spec: extension
52
+ * inference). Deliberately includes dangerous types (html, svg, js, xml)
53
+ * — forcing applies to the computed candidate, not to how it was computed,
54
+ * so listing them here is what lets the safe-list catch them.
55
+ */
56
+ const EXTENSION_MIME_TYPES = {
57
+ png: 'image/png',
58
+ jpg: 'image/jpeg',
59
+ jpeg: 'image/jpeg',
60
+ gif: 'image/gif',
61
+ webp: 'image/webp',
62
+ bmp: 'image/bmp',
63
+ avif: 'image/avif',
64
+ mp4: 'video/mp4',
65
+ webm: 'video/webm',
66
+ mov: 'video/quicktime',
67
+ mp3: 'audio/mpeg',
68
+ wav: 'audio/wav',
69
+ ogg: 'audio/ogg',
70
+ pdf: 'application/pdf',
71
+ txt: 'text/plain',
72
+ html: 'text/html',
73
+ htm: 'text/html',
74
+ xhtml: 'application/xhtml+xml',
75
+ svg: 'image/svg+xml',
76
+ js: 'text/javascript',
77
+ mjs: 'text/javascript',
78
+ xml: 'application/xml',
79
+ xsl: 'text/xsl',
80
+ };
81
+ /** Infers a candidate MIME type from a filename's extension. Returns undefined for no extension or an unrecognized one — the caller falls through to the next source. */
82
+ export function inferContentTypeFromFilename(filename) {
83
+ const match = /\.([a-z0-9]+)$/i.exec(filename);
84
+ return match ? EXTENSION_MIME_TYPES[match[1].toLowerCase()] : undefined;
85
+ }
86
+ /**
87
+ * Which `_attachment@1` record establishes a fileId's mimeType — the
88
+ * "first-recorded" rule of docs/spec/attachments.md, as a total order:
89
+ * earliest `createdAt`, ties broken by the lower `id`.
90
+ *
91
+ * The tiebreak is what makes it a rule rather than a coincidence of scan
92
+ * order. Rejecting a conflicting mimeType at write time is check-then-act
93
+ * with no storage-level uniqueness behind it, so two racing first uploads
94
+ * of the same bytes can both land on a concurrent server. Determinism
95
+ * doesn't depend on that never happening — it depends on every reader
96
+ * ordering the records the same way, which is why core's write-time
97
+ * conflict check and a server's serving choice both come through here.
98
+ *
99
+ * Generic over the record shape so a server can pass its own row type.
100
+ * Records for other fileIds must be filtered out by the caller.
101
+ */
102
+ export function firstRecordedAttachment(records) {
103
+ return records.reduce((first, record) => {
104
+ if (!first)
105
+ return record;
106
+ const delta = record.createdAt.getTime() - first.createdAt.getTime();
107
+ if (delta !== 0)
108
+ return delta < 0 ? record : first;
109
+ return record.id < first.id ? record : first;
110
+ }, undefined);
111
+ }
112
+ /**
113
+ * Resolve the Content-Type served for `GET /attachments/:fileId`
114
+ * (docs/spec/wire-format.md § Download): `?contentType` wins, else
115
+ * extension inference from `?filename`, else the stored mimeType, else
116
+ * `application/octet-stream` — then the safe-list is applied to whichever
117
+ * candidate that produced.
118
+ */
119
+ export function resolveAttachmentDownloadContentType(input) {
120
+ const candidate = input.contentTypeParam ||
121
+ (input.filenameParam && inferContentTypeFromFilename(input.filenameParam)) ||
122
+ input.storedMimeType ||
123
+ FORCED_CONTENT_TYPE;
124
+ return isSafeAttachmentContentType(candidate)
125
+ ? { contentType: candidate, forced: false }
126
+ : { contentType: FORCED_CONTENT_TYPE, forced: true };
127
+ }
128
+ //# sourceMappingURL=attachment-download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-download.js","sourceRoot":"","sources":["../src/attachment-download.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Stack — Authentication handshake primitives
3
+ * -------------------------------------------------------
4
+ * The challenge–response handshake proves a client holds the key behind a
5
+ * DID, so a server can issue it a bearer token without an out-of-band
6
+ * secret handoff. See docs/spec/wire-format.md § Authentication.
7
+ *
8
+ * What lives here is the half both sides must agree on byte for byte: the
9
+ * payload that gets signed. A client builds it to sign; a server builds it
10
+ * to verify. Two implementations of "the same string" would diverge on the
11
+ * first ambiguity, and the failure mode is a signature that verifies
12
+ * against a payload nobody meant — so there is one constructor, exported,
13
+ * the same way the attachment content-type policy is shared rather than
14
+ * re-derived.
15
+ *
16
+ * Core verifies signatures; it does not run a server. Nonce storage,
17
+ * single-use enforcement and token issuance are the server's.
18
+ */
19
+ import { type DidKeypair } from './did.js';
20
+ /**
21
+ * Malformed input to a handshake primitive — a bad origin, DID, or nonce,
22
+ * caught locally before anything is signed or verified. Outside the
23
+ * StackError hierarchy, like InvalidDidError: no Stack operation has begun,
24
+ * so there is no wire code or status to carry (docs/spec/wire-format.md
25
+ * § The taxonomy root).
26
+ */
27
+ export declare class InvalidAuthChallengeError extends Error {
28
+ constructor(message?: string);
29
+ }
30
+ /**
31
+ * Domain-separation tag. Version it rather than the payload's shape: a
32
+ * future signing primitive picks its own tag, so no signature made for one
33
+ * purpose can ever verify as another.
34
+ */
35
+ export declare const AUTH_PAYLOAD_LABEL = "haverstack-auth-v1";
36
+ export type AuthChallenge = {
37
+ /** The server's own public origin, e.g. "https://stack.example.com". */
38
+ origin: string;
39
+ /** The DID being proven. */
40
+ did: string;
41
+ /** The nonce the server issued for this DID. */
42
+ nonce: string;
43
+ };
44
+ /**
45
+ * Reduce a URL to the origin form the payload uses: scheme, host and a
46
+ * non-default port. A client derives it from the URL it dialed; a server
47
+ * MUST derive it from its own configured public origin rather than from a
48
+ * request header, which a client controls.
49
+ */
50
+ export declare const authOriginFromUrl: (url: string) => string;
51
+ /**
52
+ * Build the exact bytes a client signs and a server verifies.
53
+ *
54
+ * The origin is in the payload because the nonce alone doesn't scope a
55
+ * signature to a server: one that a client connects to can fetch a
56
+ * challenge from the client's real stack, pass that nonce along, and
57
+ * redeem the returned signature as a token. Signing the origin the client
58
+ * believes it is talking to makes such a signature verify nowhere else.
59
+ */
60
+ export declare const buildAuthChallengePayload: (challenge: AuthChallenge) => Uint8Array;
61
+ /** Sign a challenge with the private key behind its DID. */
62
+ export declare const signAuthChallenge: (privateKey: CryptoKey, challenge: AuthChallenge) => Promise<Uint8Array>;
63
+ /**
64
+ * Verify a challenge signature. Answers only "this signature was made by
65
+ * the key behind this DID, for this origin and nonce" — whether the nonce
66
+ * was ever issued, is unexpired, and has not already been spent is the
67
+ * server's to track, and all three matter.
68
+ */
69
+ export declare const verifyAuthChallenge: (challenge: AuthChallenge, signature: Uint8Array) => Promise<boolean>;
70
+ /**
71
+ * What a client hands an adapter to authenticate: a DID and a signing
72
+ * callback, never a private key. Key custody stays with the app (see
73
+ * docs/spec/identity.md), so a caller is free to back this with a hardware
74
+ * key, an OS keychain, or a prompt.
75
+ */
76
+ export type DidCredential = {
77
+ did: string;
78
+ sign: (payload: Uint8Array) => Promise<Uint8Array>;
79
+ };
80
+ /**
81
+ * Build a credential from a keypair, for callers holding key material in
82
+ * process and wanting no ceremony. Anything else implements DidCredential
83
+ * directly.
84
+ */
85
+ export declare const didCredentialFromKeypair: (keypair: DidKeypair) => DidCredential;
86
+ export declare const base64urlEncode: (bytes: Uint8Array) => string;
87
+ export declare const base64urlDecode: (encoded: string) => Uint8Array;
88
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAA+C,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAMxF;;;;;;GAMG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,SAAK;CAIzB;AAMD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AASvD,MAAM,MAAM,aAAa,GAAG;IAC1B,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,MAa/C,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,WAAW,aAAa,KAAG,UAapE,CAAC;AAMF,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,EACrB,WAAW,aAAa,KACvB,OAAO,CAAC,UAAU,CAAkE,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,WAAW,aAAa,EACxB,WAAW,UAAU,KACpB,OAAO,CAAC,OAAO,CASjB,CAAC;AAMF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,UAAU,KAAG,aAG7D,CAAC;AAMH,eAAO,MAAM,eAAe,GAAI,OAAO,UAAU,KAAG,MAInD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,UAYjD,CAAC"}