@open-mercato/onboarding 0.7.0 → 0.7.1-develop.7103.1.41ff100d93

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.
@@ -1,2 +1,2 @@
1
- [build:onboarding] found 34 entry points
1
+ [build:onboarding] found 35 entry points
2
2
  [build:onboarding] built successfully
@@ -8,7 +8,7 @@ var __decorateClass = (decorators, target, key, kind) => {
8
8
  if (kind && result) __defProp(target, key, result);
9
9
  return result;
10
10
  };
11
- import { Entity, PrimaryKey, Property, Unique } from "@mikro-orm/decorators/legacy";
11
+ import { Entity, Index, PrimaryKey, Property, Unique } from "@mikro-orm/decorators/legacy";
12
12
  let OnboardingRequest = class {
13
13
  constructor() {
14
14
  this.status = "pending";
@@ -94,7 +94,7 @@ __decorateClass([
94
94
  ], OnboardingRequest.prototype, "deletedAt", 2);
95
95
  OnboardingRequest = __decorateClass([
96
96
  Entity({ tableName: "onboarding_requests" }),
97
- Unique({ properties: ["email"] }),
97
+ Index({ name: "onboarding_requests_email_idx", properties: ["email"] }),
98
98
  Unique({ properties: ["emailHash"] }),
99
99
  Unique({ properties: ["tokenHash"] })
100
100
  ], OnboardingRequest);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/onboarding/data/entities.ts"],
4
- "sourcesContent": ["import { Entity, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\ntype OnboardingStatus = 'pending' | 'processing' | 'completed' | 'expired'\n\n@Entity({ tableName: 'onboarding_requests' })\n@Unique({ properties: ['email'] })\n@Unique({ properties: ['emailHash'] })\n@Unique({ properties: ['tokenHash'] })\nexport class OnboardingRequest {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n emailHash?: string | null\n\n @Property({ name: 'token_hash', type: 'text' })\n tokenHash!: string\n\n @Property({ type: 'text', default: 'pending' })\n status: OnboardingStatus = 'pending'\n\n @Property({ name: 'first_name', type: 'text' })\n firstName!: string\n\n @Property({ name: 'last_name', type: 'text' })\n lastName!: string\n\n @Property({ name: 'organization_name', type: 'text' })\n organizationName!: string\n\n @Property({ type: 'text', nullable: true })\n locale?: string | null\n\n @Property({ name: 'terms_accepted', type: 'boolean', default: false })\n termsAccepted: boolean = false\n\n @Property({ name: 'marketing_consent', type: 'boolean', default: false, nullable: true })\n marketingConsent?: boolean | null = false\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'processing_started_at', type: Date, nullable: true })\n processingStartedAt?: Date | null\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'completed_at', type: Date, nullable: true })\n completedAt?: Date | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'user_id', type: 'uuid', nullable: true })\n userId?: string | null\n\n @Property({ name: 'last_email_sent_at', type: Date, nullable: true })\n lastEmailSentAt?: Date | null\n\n @Property({ name: 'preparation_started_at', type: Date, nullable: true })\n preparationStartedAt?: Date | null\n\n @Property({ name: 'preparation_completed_at', type: Date, nullable: true })\n preparationCompletedAt?: Date | null\n\n @Property({ name: 'ready_email_sent_at', type: Date, nullable: true })\n readyEmailSentAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\nexport type { OnboardingStatus }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\n\ntype OnboardingStatus = 'pending' | 'processing' | 'completed' | 'expired'\n\n@Entity({ tableName: 'onboarding_requests' })\n@Index({ name: 'onboarding_requests_email_idx', properties: ['email'] })\n@Unique({ properties: ['emailHash'] })\n@Unique({ properties: ['tokenHash'] })\nexport class OnboardingRequest {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n email!: string\n\n @Property({ name: 'email_hash', type: 'text', nullable: true })\n emailHash?: string | null\n\n @Property({ name: 'token_hash', type: 'text' })\n tokenHash!: string\n\n @Property({ type: 'text', default: 'pending' })\n status: OnboardingStatus = 'pending'\n\n @Property({ name: 'first_name', type: 'text' })\n firstName!: string\n\n @Property({ name: 'last_name', type: 'text' })\n lastName!: string\n\n @Property({ name: 'organization_name', type: 'text' })\n organizationName!: string\n\n @Property({ type: 'text', nullable: true })\n locale?: string | null\n\n @Property({ name: 'terms_accepted', type: 'boolean', default: false })\n termsAccepted: boolean = false\n\n @Property({ name: 'marketing_consent', type: 'boolean', default: false, nullable: true })\n marketingConsent?: boolean | null = false\n\n @Property({ name: 'password_hash', type: 'text', nullable: true })\n passwordHash?: string | null\n\n @Property({ name: 'processing_started_at', type: Date, nullable: true })\n processingStartedAt?: Date | null\n\n @Property({ name: 'expires_at', type: Date })\n expiresAt!: Date\n\n @Property({ name: 'completed_at', type: Date, nullable: true })\n completedAt?: Date | null\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId?: string | null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'user_id', type: 'uuid', nullable: true })\n userId?: string | null\n\n @Property({ name: 'last_email_sent_at', type: Date, nullable: true })\n lastEmailSentAt?: Date | null\n\n @Property({ name: 'preparation_started_at', type: Date, nullable: true })\n preparationStartedAt?: Date | null\n\n @Property({ name: 'preparation_completed_at', type: Date, nullable: true })\n preparationCompletedAt?: Date | null\n\n @Property({ name: 'ready_email_sent_at', type: Date, nullable: true })\n readyEmailSentAt?: Date | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })\n updatedAt?: Date\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\nexport type { OnboardingStatus }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,15 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260817081500_onboarding extends Migration {
3
+ up() {
4
+ this.addSql(`alter table "onboarding_requests" drop constraint if exists "onboarding_requests_email_unique";`);
5
+ this.addSql(`create index if not exists "onboarding_requests_email_idx" on "onboarding_requests" ("email");`);
6
+ }
7
+ down() {
8
+ this.addSql(`drop index if exists "onboarding_requests_email_idx";`);
9
+ this.addSql(`alter table "onboarding_requests" add constraint "onboarding_requests_email_unique" unique ("email");`);
10
+ }
11
+ }
12
+ export {
13
+ Migration20260817081500_onboarding
14
+ };
15
+ //# sourceMappingURL=Migration20260817081500_onboarding.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/onboarding/migrations/Migration20260817081500_onboarding.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260817081500_onboarding extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"onboarding_requests\" drop constraint if exists \"onboarding_requests_email_unique\";`);\n this.addSql(`create index if not exists \"onboarding_requests_email_idx\" on \"onboarding_requests\" (\"email\");`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop index if exists \"onboarding_requests_email_idx\";`);\n this.addSql(`alter table \"onboarding_requests\" add constraint \"onboarding_requests_email_unique\" unique (\"email\");`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,2CAA2C,UAAU;AAAA,EAEvD,KAA2B;AAClC,SAAK,OAAO,iGAAiG;AAC7G,SAAK,OAAO,gGAAgG;AAAA,EAC9G;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,uDAAuD;AACnE,SAAK,OAAO,uGAAuG;AAAA,EACrH;AAEF;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/onboarding",
3
- "version": "0.7.0",
3
+ "version": "0.7.1-develop.7103.1.41ff100d93",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -70,10 +70,10 @@
70
70
  }
71
71
  },
72
72
  "peerDependencies": {
73
- "@open-mercato/shared": "0.7.0"
73
+ "@open-mercato/shared": "0.7.1-develop.7103.1.41ff100d93"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.7.0",
76
+ "@open-mercato/shared": "0.7.1-develop.7103.1.41ff100d93",
77
77
  "@types/jest": "^30.0.0",
78
78
  "jest": "^30.4.2",
79
79
  "ts-jest": "^29.4.12",
@@ -86,5 +86,6 @@
86
86
  "type": "git",
87
87
  "url": "https://github.com/open-mercato/open-mercato",
88
88
  "directory": "packages/onboarding"
89
- }
89
+ },
90
+ "stableVersion": "0.7.0"
90
91
  }
@@ -0,0 +1,92 @@
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { MetadataStorage } from '@mikro-orm/core'
4
+ import { OnboardingRequest } from '../modules/onboarding/data/entities'
5
+
6
+ type SnapshotIndex = {
7
+ keyName: string
8
+ columnNames: string[]
9
+ unique: boolean
10
+ }
11
+
12
+ type SnapshotTable = {
13
+ name: string
14
+ indexes: SnapshotIndex[]
15
+ }
16
+
17
+ type Snapshot = {
18
+ tables: SnapshotTable[]
19
+ }
20
+
21
+ function onboardingRequestUniqueProperties(): string[][] {
22
+ const metadata = Object.values(MetadataStorage.getMetadata()).find(
23
+ (candidate) => candidate.className === OnboardingRequest.name,
24
+ )
25
+ if (!metadata) throw new Error('[internal] OnboardingRequest decorator metadata was not registered')
26
+ return metadata.uniques.map((unique) =>
27
+ Array.isArray(unique.properties) ? [...unique.properties] : [String(unique.properties)],
28
+ )
29
+ }
30
+
31
+ function onboardingRequestIndexProperties(): string[][] {
32
+ const metadata = Object.values(MetadataStorage.getMetadata()).find(
33
+ (candidate) => candidate.className === OnboardingRequest.name,
34
+ )
35
+ if (!metadata) throw new Error('[internal] OnboardingRequest decorator metadata was not registered')
36
+ return metadata.indexes.map((index) =>
37
+ Array.isArray(index.properties) ? [...index.properties] : [String(index.properties)],
38
+ )
39
+ }
40
+
41
+ const migrationsDir = join(__dirname, '..', 'modules', 'onboarding', 'migrations')
42
+
43
+ function onboardingRequestSnapshotIndexes(): SnapshotIndex[] {
44
+ const snapshotPath = join(migrationsDir, '.snapshot-open-mercato.json')
45
+ const snapshot: Snapshot = JSON.parse(readFileSync(snapshotPath, 'utf8'))
46
+ const table = snapshot.tables.find((candidate) => candidate.name === 'onboarding_requests')
47
+ if (!table) throw new Error('[internal] onboarding_requests is missing from the migration snapshot')
48
+ return table.indexes
49
+ }
50
+
51
+ function onboardingMigrationSql(): string {
52
+ return readdirSync(migrationsDir)
53
+ .filter((entry) => entry.endsWith('.ts'))
54
+ .map((entry) => readFileSync(join(migrationsDir, entry), 'utf8'))
55
+ .join('\n')
56
+ }
57
+
58
+ describe('onboarding request email uniqueness', () => {
59
+ it('does not declare a unique constraint on the non-deterministically encrypted email column', () => {
60
+ expect(onboardingRequestUniqueProperties()).not.toContainEqual(['email'])
61
+ })
62
+
63
+ it('keeps emailHash as the deduplication contract', () => {
64
+ expect(onboardingRequestUniqueProperties()).toContainEqual(['emailHash'])
65
+ })
66
+
67
+ it('keeps tokenHash unique so a token can only address one request', () => {
68
+ expect(onboardingRequestUniqueProperties()).toContainEqual(['tokenHash'])
69
+ })
70
+
71
+ it('carries no unique index over email in the migration snapshot', () => {
72
+ const uniqueColumns = onboardingRequestSnapshotIndexes()
73
+ .filter((index) => index.unique)
74
+ .map((index) => index.columnNames)
75
+ expect(uniqueColumns).not.toContainEqual(['email'])
76
+ expect(uniqueColumns).toContainEqual(['email_hash'])
77
+ })
78
+
79
+ it('keeps a non-unique index over email so the deduplication lookup stays indexable', () => {
80
+ expect(onboardingRequestIndexProperties()).toContainEqual(['email'])
81
+ const nonUniqueColumns = onboardingRequestSnapshotIndexes()
82
+ .filter((index) => !index.unique)
83
+ .map((index) => index.columnNames)
84
+ expect(nonUniqueColumns).toContainEqual(['email'])
85
+ })
86
+
87
+ it('applies the same schema to a live database through the migrations', () => {
88
+ const sql = onboardingMigrationSql()
89
+ expect(sql).toContain('drop constraint if exists "onboarding_requests_email_unique"')
90
+ expect(sql).toContain('create index if not exists "onboarding_requests_email_idx" on "onboarding_requests" ("email")')
91
+ })
92
+ })
@@ -1,9 +1,9 @@
1
- import { Entity, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
1
+ import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
2
2
 
3
3
  type OnboardingStatus = 'pending' | 'processing' | 'completed' | 'expired'
4
4
 
5
5
  @Entity({ tableName: 'onboarding_requests' })
6
- @Unique({ properties: ['email'] })
6
+ @Index({ name: 'onboarding_requests_email_idx', properties: ['email'] })
7
7
  @Unique({ properties: ['emailHash'] })
8
8
  @Unique({ properties: ['tokenHash'] })
9
9
  export class OnboardingRequest {
@@ -260,9 +260,9 @@
260
260
  "unique": true
261
261
  },
262
262
  {
263
- "keyName": "onboarding_requests_email_unique",
263
+ "keyName": "onboarding_requests_email_hash_unique",
264
264
  "columnNames": [
265
- "email"
265
+ "email_hash"
266
266
  ],
267
267
  "composite": false,
268
268
  "constraint": true,
@@ -270,14 +270,14 @@
270
270
  "unique": true
271
271
  },
272
272
  {
273
- "keyName": "onboarding_requests_email_hash_unique",
273
+ "keyName": "onboarding_requests_email_idx",
274
274
  "columnNames": [
275
- "email_hash"
275
+ "email"
276
276
  ],
277
277
  "composite": false,
278
- "constraint": true,
278
+ "constraint": false,
279
279
  "primary": false,
280
- "unique": true
280
+ "unique": false
281
281
  },
282
282
  {
283
283
  "keyName": "onboarding_requests_pkey",
@@ -0,0 +1,15 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260817081500_onboarding extends Migration {
4
+
5
+ override up(): void | Promise<void> {
6
+ this.addSql(`alter table "onboarding_requests" drop constraint if exists "onboarding_requests_email_unique";`);
7
+ this.addSql(`create index if not exists "onboarding_requests_email_idx" on "onboarding_requests" ("email");`);
8
+ }
9
+
10
+ override down(): void | Promise<void> {
11
+ this.addSql(`drop index if exists "onboarding_requests_email_idx";`);
12
+ this.addSql(`alter table "onboarding_requests" add constraint "onboarding_requests_email_unique" unique ("email");`);
13
+ }
14
+
15
+ }