@venturekit/data 0.0.31 → 0.0.33

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 (47) hide show
  1. package/dist/files/index.d.ts +9 -0
  2. package/dist/files/index.d.ts.map +1 -0
  3. package/dist/files/index.js +8 -0
  4. package/dist/files/index.js.map +1 -0
  5. package/dist/files/postgres.d.ts +150 -0
  6. package/dist/files/postgres.d.ts.map +1 -0
  7. package/dist/files/postgres.js +194 -0
  8. package/dist/files/postgres.js.map +1 -0
  9. package/dist/idempotency/index.d.ts +9 -0
  10. package/dist/idempotency/index.d.ts.map +1 -0
  11. package/dist/idempotency/index.js +8 -0
  12. package/dist/idempotency/index.js.map +1 -0
  13. package/dist/idempotency/postgres.d.ts +107 -0
  14. package/dist/idempotency/postgres.d.ts.map +1 -0
  15. package/dist/idempotency/postgres.js +145 -0
  16. package/dist/idempotency/postgres.js.map +1 -0
  17. package/dist/internal/identifier.d.ts +16 -0
  18. package/dist/internal/identifier.d.ts.map +1 -0
  19. package/dist/internal/identifier.js +23 -0
  20. package/dist/internal/identifier.js.map +1 -0
  21. package/dist/jobs/index.d.ts +9 -0
  22. package/dist/jobs/index.d.ts.map +1 -0
  23. package/dist/jobs/index.js +8 -0
  24. package/dist/jobs/index.js.map +1 -0
  25. package/dist/jobs/postgres.d.ts +197 -0
  26. package/dist/jobs/postgres.d.ts.map +1 -0
  27. package/dist/jobs/postgres.js +270 -0
  28. package/dist/jobs/postgres.js.map +1 -0
  29. package/dist/outbox/index.d.ts +9 -0
  30. package/dist/outbox/index.d.ts.map +1 -0
  31. package/dist/outbox/index.js +8 -0
  32. package/dist/outbox/index.js.map +1 -0
  33. package/dist/outbox/postgres.d.ts +124 -0
  34. package/dist/outbox/postgres.d.ts.map +1 -0
  35. package/dist/outbox/postgres.js +177 -0
  36. package/dist/outbox/postgres.js.map +1 -0
  37. package/dist/query/index.d.ts.map +1 -1
  38. package/dist/query/index.js.map +1 -1
  39. package/dist/query/secret.d.ts.map +1 -1
  40. package/dist/query/secret.js +1 -1
  41. package/dist/query/secret.js.map +1 -1
  42. package/package.json +23 -3
  43. package/src/sql/0000_vk_data_foundation.sql +305 -0
  44. package/src/sql/vk_data_001_idempotency.sql +48 -0
  45. package/src/sql/vk_data_002_outbox.sql +99 -0
  46. package/src/sql/vk_data_003_jobs.sql +114 -0
  47. package/src/sql/vk_data_004_file_object.sql +112 -0
@@ -0,0 +1,112 @@
1
+ -- @venturekit/data — uploaded-object metadata.
2
+ --
3
+ -- Table created by this migration:
4
+ -- vk_file_object — the row that makes an S3 object findable and deletable
5
+ --
6
+ -- Backs `@venturekit/data/files`.
7
+ --
8
+ -- # Why a table when @venturekit/storage already talks to S3
9
+ --
10
+ -- That package is complete on the object: put, get, head, presign, copy, list,
11
+ -- image optimisation. What a bucket cannot tell you is anything a product needs
12
+ -- to know about an upload — which tenant owns it, what it is for, who put it
13
+ -- there, whether the same bytes are already stored, and when it may be deleted.
14
+ -- `ListObjectsV2` plus a key-naming convention is the usual substitute, and it
15
+ -- fails the first time somebody needs "every document for this tenant, newest
16
+ -- first" or a retention rule that differs per purpose.
17
+ --
18
+ -- # The deletion order is the point
19
+ --
20
+ -- Rows are never hard-deleted on the request path. `archived_at` is set, and a
21
+ -- retention sweep removes the object and the row together — but "together" is
22
+ -- not available: there is no transaction spanning S3 and Postgres, so one of the
23
+ -- two failure modes has to be chosen deliberately.
24
+ --
25
+ -- * row first, then object → an orphaned object nobody can account for. Only
26
+ -- discoverable by diffing the entire bucket against this table.
27
+ -- * object first, then row → a row naming a key that 404s. Discoverable by
28
+ -- walking rows, which is cheap, and invisible to users anyway because the
29
+ -- row is already archived.
30
+ --
31
+ -- So the sweep deletes objects first and rows second, and a crash in between
32
+ -- leaves the recoverable state rather than the silent one. `sweepArchivedFiles`
33
+ -- is re-runnable for exactly this reason.
34
+ --
35
+ -- Additive for existing projects: one new table, no existing file edited, and
36
+ -- nothing writes to it until a project calls `recordFileObject()`.
37
+
38
+ CREATE TABLE IF NOT EXISTS vk_file_object (
39
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
40
+
41
+ -- NULL for platform-wide artefacts belonging to no tenant. No foreign key:
42
+ -- the table a tenant lives in is the consumer's, and `vk_tenants` only exists
43
+ -- with @venturekit-pro/tenancy installed.
44
+ tenant_id uuid,
45
+
46
+ -- What the file is FOR, which is what decides its retention and who may read
47
+ -- it. Free text rather than an enum because the vocabulary is the product's —
48
+ -- 'student_photo' carries obligations 'report_artefact' does not, and only the
49
+ -- consumer knows which of theirs is which.
50
+ purpose text NOT NULL,
51
+
52
+ -- Stored, not derived: the bucket changes per stage and per region, and a row
53
+ -- written in one stage must stay resolvable after a restore into another.
54
+ bucket text NOT NULL,
55
+ object_key text NOT NULL,
56
+
57
+ content_type text NOT NULL,
58
+ size_bytes bigint NOT NULL CHECK (size_bytes >= 0),
59
+
60
+ -- SHA-256 hex. Lets a re-upload of identical bytes be recognised instead of
61
+ -- duplicated, and lets a restore be verified.
62
+ checksum char(64),
63
+
64
+ -- The name the user's file had. Never the storage key: a key built from user
65
+ -- input is a path-traversal question and a collision waiting to happen.
66
+ original_name text,
67
+ uploaded_by uuid,
68
+
69
+ created_at timestamptz NOT NULL DEFAULT now(),
70
+ updated_at timestamptz NOT NULL DEFAULT now(),
71
+
72
+ -- Soft delete. Set on the request path; the retention sweep is what actually
73
+ -- removes the object and then this row.
74
+ archived_at timestamptz,
75
+
76
+ -- One row per object. A second row for the same key would mean two records
77
+ -- claiming one set of bytes, and archiving either would delete the object out
78
+ -- from under the other.
79
+ CONSTRAINT vk_file_object_bucket_key_uniq UNIQUE (bucket, object_key)
80
+ );
81
+
82
+ -- "Every live file for this tenant, of this purpose, newest first" — the query
83
+ -- the table exists for. Partial, so archived rows do not bloat it.
84
+ CREATE INDEX IF NOT EXISTS vk_file_object_tenant_purpose_idx
85
+ ON vk_file_object (tenant_id, purpose, created_at DESC)
86
+ WHERE archived_at IS NULL;
87
+
88
+ -- De-duplication on re-upload: same bytes, same tenant, already stored.
89
+ CREATE INDEX IF NOT EXISTS vk_file_object_checksum_idx
90
+ ON vk_file_object (tenant_id, checksum)
91
+ WHERE archived_at IS NULL AND checksum IS NOT NULL;
92
+
93
+ -- The retention sweep's claim.
94
+ CREATE INDEX IF NOT EXISTS vk_file_object_archived_idx
95
+ ON vk_file_object (archived_at)
96
+ WHERE archived_at IS NOT NULL;
97
+
98
+ -- # On row-level security
99
+ --
100
+ -- No policy is installed here, because `vk_install_tenant_guards()` needs the
101
+ -- name of the consumer's application role and this migration cannot know it. A
102
+ -- project that wants RLS on this table should call the installer itself:
103
+ --
104
+ -- SELECT vk_install_tenant_guards('public', 'vk_file_object', 'app_user');
105
+ --
106
+ -- Until then, reads that serve a request must carry the scope predicate, which
107
+ -- `listFileObjects()` and `findFileObjectByChecksum()` do by default.
108
+
109
+ COMMENT ON TABLE vk_file_object IS
110
+ 'Metadata for an object in S3. The bytes live in the bucket; this row is what makes them findable, attributable and deletable. See @venturekit/data/files.';
111
+ COMMENT ON COLUMN vk_file_object.archived_at IS
112
+ 'Soft delete. The retention sweep deletes the OBJECT first and then this row, so a crash leaves a recoverable dangling row rather than an unaccountable orphaned object.';