@0xobelisk/sui-common 1.2.0-pre.99 → 2.0.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 (39) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +670 -1
  3. package/dist/index.d.ts +61 -27
  4. package/dist/index.js +1078 -461
  5. package/dist/index.js.map +1 -1
  6. package/package.json +15 -17
  7. package/src/codegen/debug.ts +0 -4
  8. package/src/codegen/types/index.ts +46 -10
  9. package/src/codegen/utils/config.ts +1 -1
  10. package/src/codegen/utils/format.ts +0 -6
  11. package/src/codegen/utils/formatAndWrite.ts +10 -31
  12. package/src/codegen/utils/generateLock.ts +122 -0
  13. package/src/codegen/utils/index.ts +4 -2
  14. package/src/codegen/utils/renderMove/{schemaGen.ts → codegen.ts} +40 -28
  15. package/src/codegen/utils/renderMove/common.ts +0 -65
  16. package/src/codegen/utils/renderMove/dapp.ts +2 -14
  17. package/src/codegen/utils/renderMove/generateDappKey.ts +33 -18
  18. package/src/codegen/utils/renderMove/generateError.ts +32 -15
  19. package/src/codegen/utils/renderMove/generateGenesis.ts +55 -22
  20. package/src/codegen/utils/renderMove/generateInitTest.ts +26 -14
  21. package/src/codegen/utils/renderMove/generateObjects.ts +377 -0
  22. package/src/codegen/utils/renderMove/generatePermits.ts +151 -0
  23. package/src/codegen/utils/renderMove/generateResources.ts +894 -242
  24. package/src/codegen/utils/renderMove/generateScenes.ts +467 -0
  25. package/src/codegen/utils/renderMove/generateScript.ts +18 -13
  26. package/src/codegen/utils/renderMove/generateSystem.ts +0 -2
  27. package/src/codegen/utils/renderMove/generateUserStorageInit.ts +37 -0
  28. package/src/codegen/utils/validateConfig.ts +237 -0
  29. package/src/index.ts +0 -1
  30. package/src/modules.d.ts +0 -10
  31. package/src/codegen/modules.d.ts +0 -1
  32. package/src/codegen/utils/posixPath.ts +0 -8
  33. package/src/codegen/utils/renderMove/generateComponents.ts +0 -802
  34. package/src/codegen/utils/renderMove/generateDefaultSchema.ts +0 -216
  35. package/src/codegen/utils/renderMove/generateEvent.ts +0 -99
  36. package/src/codegen/utils/renderMove/generateSchema.ts +0 -287
  37. package/src/codegen/utils/renderMove/generateSchemaHub.ts +0 -60
  38. package/src/parseData/index.ts +0 -1
  39. package/src/parseData/parser/index.ts +0 -47
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xobelisk/sui-common",
3
- "version": "1.2.0-pre.99",
3
+ "version": "2.0.0",
4
4
  "description": "Common low level logic shared between packages",
5
5
  "keywords": [
6
6
  "sui",
@@ -31,19 +31,6 @@
31
31
  ]
32
32
  }
33
33
  },
34
- "scripts": {
35
- "build": "pnpm run type-check && pnpm run build:js",
36
- "build:js": "tsup && chmod +x ./dist/index.js",
37
- "clean": "pnpm run clean:js",
38
- "clean:js": "rimraf dist",
39
- "dev": "tsup --watch",
40
- "format": "prettier --write .",
41
- "format:check": "prettier --check .",
42
- "lint": "eslint . --ext .ts",
43
- "test": "vitest",
44
- "type-check": "tsc --noEmit",
45
- "validate": "pnpm format:check && pnpm type-check"
46
- },
47
34
  "dependencies": {
48
35
  "chalk": "^5.0.1",
49
36
  "debug": "^4.3.4",
@@ -56,8 +43,6 @@
56
43
  "path": "^0.12.7",
57
44
  "prettier": "^3.1.1",
58
45
  "prettier-plugin-move-js": "^0.0.5",
59
- "prettier-plugin-rust": "^0.1.9",
60
- "prettier-plugin-solidity": "^1.1.2",
61
46
  "typescript": "^5.8.3",
62
47
  "yargs": "^17.7.1",
63
48
  "zod": "^3.22.3",
@@ -81,5 +66,18 @@
81
66
  },
82
67
  "publishConfig": {
83
68
  "access": "public"
69
+ },
70
+ "scripts": {
71
+ "build": "pnpm run type-check && pnpm run build:js",
72
+ "build:js": "tsup && chmod +x ./dist/index.js",
73
+ "clean": "pnpm run clean:js",
74
+ "clean:js": "rimraf dist",
75
+ "dev": "tsup --watch",
76
+ "format": "prettier --write .",
77
+ "format:check": "prettier --check .",
78
+ "lint": "eslint . --ext .ts",
79
+ "test": "vitest",
80
+ "type-check": "tsc --noEmit",
81
+ "validate": "pnpm format:check && pnpm type-check"
84
82
  }
85
- }
83
+ }
@@ -1,10 +1,6 @@
1
1
  import { debug as parentDebug } from '../debug';
2
2
 
3
3
  export const debug = parentDebug.extend('codegen');
4
- export const error = parentDebug.extend('codegen');
5
4
 
6
5
  // Pipe debug output to stdout instead of stderr
7
6
  debug.log = console.debug.bind(console);
8
-
9
- // Pipe error output to stderr
10
- error.log = console.error.bind(console);
@@ -19,33 +19,69 @@ export type MoveType =
19
19
  | 'vector<u256>'
20
20
  | string;
21
21
 
22
- // Define the type of Schema
23
22
  export type Component = {
24
23
  offchain?: boolean;
24
+ global?: boolean;
25
25
  fields: Record<string, MoveType>;
26
26
  keys?: string[];
27
+ // Storage extension annotations
28
+ reactive?: boolean; // generate _reactive cross-user write variants
29
+ fungible?: boolean; // generate add/sub instead of set
30
+ transferable?: boolean; // generate cross-layer transfer functions
31
+ listable?: boolean; // generate list/buy/cancel_listing/expire_listing
27
32
  };
28
33
 
29
- // Empty object type, used to represent components with only id key
30
- export type EmptyComponent = Record<string, never>;
34
+ /** Config for a DApp-owned named shared object (e.g. guild, boss). */
35
+ export type ObjectConfig = {
36
+ fields: Record<string, MoveType>;
37
+ /** Resources (from the `resources` section) this object accepts for transfers. */
38
+ accepts?: string[];
39
+ /** Other objects/scenes whose data can be transferred into this object. */
40
+ acceptsFrom?: string[];
41
+ /** If true, only the DApp admin can call create_<key>. */
42
+ adminOnly?: boolean;
43
+ };
44
+
45
+ /** Config for a standalone ScenePermit (participant management only). */
46
+ export type PermitConfig = {
47
+ // Reserved for future permit-level options.
48
+ };
49
+
50
+ export type SceneAuthorization = { kind: 'permit'; permit: string } | { kind: 'system' };
51
+
52
+ /** Config for a SceneStorage object (pure data storage, symmetric to ObjectConfig). */
53
+ export type SceneConfig = {
54
+ fields: Record<string, MoveType>;
55
+ /** Resources this scene accepts for transfers. */
56
+ accepts?: string[];
57
+ /** Other objects/scenes whose data can be transferred into this scene. */
58
+ acceptsFrom?: string[];
59
+ /** Explicit write authorization model for this SceneStorage. */
60
+ authorization: SceneAuthorization;
61
+ };
62
+
63
+ export type ErrorDefinition = {
64
+ message: string;
65
+ };
66
+
67
+ export type ErrorEntry = string | ErrorDefinition;
31
68
 
32
69
  export type DubheConfig = {
33
70
  name: string;
34
71
  description: string;
35
72
  enums?: Record<string, string[]>;
36
- components: Record<string, Component | MoveType | EmptyComponent>;
37
- resources: Record<string, Component | MoveType>;
38
- errors?: Record<string, string>;
73
+ resources?: Record<string, Component | MoveType>;
74
+ objects?: Record<string, ObjectConfig>;
75
+ permits?: Record<string, PermitConfig>;
76
+ scenes?: Record<string, SceneConfig>;
77
+ errors?: Record<string, ErrorEntry>;
39
78
  };
40
79
 
41
80
  export type DubheMetadata = {
42
- components: Record<string, Component | MoveType | EmptyComponent>;
43
81
  resources: Record<string, Component | MoveType>;
44
82
  enums: Record<string, string[]>;
45
83
  };
46
84
 
47
85
  export type BaseType = any;
48
- export type ErrorData = any;
86
+ export type ErrorData = Record<string, ErrorEntry>;
49
87
  export type EventData = any;
50
- export type SchemaData = any;
51
- export type SchemaType = any;
@@ -34,7 +34,7 @@ export async function loadConfig(configPath?: string): Promise<unknown> {
34
34
  }
35
35
  }
36
36
 
37
- export async function resolveConfigPath(configPath: string | undefined, toFileURL?: boolean) {
37
+ async function resolveConfigPath(configPath: string | undefined, toFileURL?: boolean) {
38
38
  if (configPath === undefined) {
39
39
  configPath = await getUserConfigPath();
40
40
  } else {
@@ -28,9 +28,3 @@ export async function formatMove(content: string, prettierConfigPath?: string):
28
28
  return content;
29
29
  }
30
30
  }
31
-
32
- export async function formatTypescript(content: string): Promise<string> {
33
- return prettier.format(content, {
34
- parser: 'typescript'
35
- });
36
- }
@@ -1,6 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { formatTypescript } from './format';
4
3
  import { debug } from '../debug';
5
4
 
6
5
  export async function formatAndWriteMove(
@@ -9,19 +8,18 @@ export async function formatAndWriteMove(
9
8
  logPrefix?: string
10
9
  ): Promise<void> {
11
10
  const formattedOutput = output;
12
- let schemaPrefix = ` // Copyright (c) Obelisk Labs, Inc.
13
- // SPDX-License-Identifier: Apache-2.0
14
- #[allow(unused_use)]
15
-
16
- /* Autogenerated file. Do not edit manually. */
17
-
18
- `;
11
+ const fileHeader = `// Copyright (c) Obelisk Labs, Inc.
12
+ // SPDX-License-Identifier: Apache-2.0
19
13
 
20
- let initPrefix = `#[test_only]`;
14
+ /* Autogenerated file. Do not edit manually. */
21
15
 
22
- let code = schemaPrefix + formattedOutput;
16
+ `;
23
17
 
24
- let deployHookPrefix = `#[allow(lint(share_owned))]`;
18
+ const initPrefix = `#[test_only]\n`;
19
+
20
+ let code = fileHeader + formattedOutput;
21
+
22
+ const deployHookPrefix = `#[allow(lint(share_owned))]\n`;
25
23
 
26
24
  if (
27
25
  fullOutputPath.includes('.toml') ||
@@ -29,7 +27,7 @@ export async function formatAndWriteMove(
29
27
  fullOutputPath.includes('migrate')
30
28
  ) {
31
29
  code = formattedOutput;
32
- } else if (fullOutputPath.includes('init')) {
30
+ } else if (fullOutputPath.includes('init_test')) {
33
31
  code = initPrefix + formattedOutput;
34
32
  } else if (fullOutputPath.includes('genesis')) {
35
33
  code = deployHookPrefix + formattedOutput;
@@ -39,22 +37,3 @@ export async function formatAndWriteMove(
39
37
  await fs.writeFile(fullOutputPath, code);
40
38
  debug(`${logPrefix}: ${fullOutputPath}`);
41
39
  }
42
-
43
- /**
44
- * Formats typescript code using prettier and write it to a file
45
- * @param output typescript code
46
- * @param fullOutputPath full path to the output file
47
- * @param logPrefix prefix for debug logs
48
- */
49
- export async function formatAndWriteTypescript(
50
- output: string,
51
- fullOutputPath: string,
52
- logPrefix: string
53
- ): Promise<void> {
54
- const formattedOutput = await formatTypescript(output);
55
-
56
- await fs.mkdir(path.dirname(fullOutputPath), { recursive: true });
57
-
58
- await fs.writeFile(fullOutputPath, formattedOutput);
59
- debug(`${logPrefix}: ${fullOutputPath}`);
60
- }
@@ -0,0 +1,122 @@
1
+ import chalk from 'chalk';
2
+ import { DubheConfig, Component, MoveType } from '../types';
3
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
4
+ import path from 'node:path';
5
+
6
+ /** Snapshot of field definitions for a single resource/object/scene. */
7
+ type FieldSnapshot = Record<string, string>;
8
+
9
+ /** Top-level structure of a dubhe.lock.json file. */
10
+ interface DubheLock {
11
+ version: 1;
12
+ resources: Record<string, FieldSnapshot>;
13
+ objects: Record<string, FieldSnapshot>;
14
+ scenes: Record<string, FieldSnapshot>;
15
+ }
16
+
17
+ /** Extract the field map for a resource entry (handles both shorthand and full form). */
18
+ function resourceFields(entry: Component | MoveType): FieldSnapshot {
19
+ if (typeof entry === 'string') {
20
+ return { value: entry };
21
+ }
22
+ const fields: FieldSnapshot = {};
23
+ for (const [k, v] of Object.entries(entry.fields ?? {})) {
24
+ fields[k] = v as string;
25
+ }
26
+ return fields;
27
+ }
28
+
29
+ /** Extract the field map for an object or scene entry. */
30
+ function objectOrSceneFields(entry: { fields: Record<string, MoveType> }): FieldSnapshot {
31
+ const fields: FieldSnapshot = {};
32
+ for (const [k, v] of Object.entries(entry.fields)) {
33
+ fields[k] = v as string;
34
+ }
35
+ return fields;
36
+ }
37
+
38
+ /** Build a full DubheLock snapshot from a DubheConfig. */
39
+ function buildLock(config: DubheConfig): DubheLock {
40
+ const resources: Record<string, FieldSnapshot> = {};
41
+ for (const [name, entry] of Object.entries(config.resources ?? {})) {
42
+ resources[name] = resourceFields(entry);
43
+ }
44
+
45
+ const objects: Record<string, FieldSnapshot> = {};
46
+ for (const [name, entry] of Object.entries(config.objects ?? {})) {
47
+ objects[name] = objectOrSceneFields(entry);
48
+ }
49
+
50
+ const scenes: Record<string, FieldSnapshot> = {};
51
+ for (const [name, entry] of Object.entries(config.scenes ?? {})) {
52
+ scenes[name] = objectOrSceneFields(entry);
53
+ }
54
+
55
+ return { version: 1, resources, objects, scenes };
56
+ }
57
+
58
+ type SectionName = 'resources' | 'objects' | 'scenes';
59
+
60
+ /** Compare old vs new field snapshots and throw on breaking changes. */
61
+ function checkSection(
62
+ section: SectionName,
63
+ oldSection: Record<string, FieldSnapshot>,
64
+ newSection: Record<string, FieldSnapshot>
65
+ ): void {
66
+ for (const [name, newFields] of Object.entries(newSection)) {
67
+ const oldFields = oldSection[name];
68
+ if (!oldFields) continue; // New entry — allowed.
69
+
70
+ for (const [field, oldType] of Object.entries(oldFields)) {
71
+ if (!(field in newFields)) {
72
+ throw new Error(
73
+ `[dubhe] Breaking change detected in ${section}.${name}:\n` +
74
+ ` Field "${field}" was removed.\n\n` +
75
+ `Resources, objects, and scenes are stored as raw bytes on-chain.\n` +
76
+ `Removing fields corrupts existing data. Use a new name (e.g. "${name}_v2") for breaking changes.`
77
+ );
78
+ }
79
+ if (newFields[field] !== oldType) {
80
+ throw new Error(
81
+ `[dubhe] Breaking change detected in ${section}.${name}:\n` +
82
+ ` Field "${field}" type changed from "${oldType}" to "${newFields[field]}".\n\n` +
83
+ `Resources, objects, and scenes are stored as raw bytes on-chain.\n` +
84
+ `Changing field types corrupts existing data. Use a new name (e.g. "${name}_v2") for breaking changes.`
85
+ );
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Read the existing lock file (if any), check for breaking changes against the
93
+ * current config, then write an updated lock file.
94
+ *
95
+ * Call this at the start of `codegen()` before any file generation begins.
96
+ *
97
+ * @param rootDir The project root directory (same dir passed to `codegen()`).
98
+ * @param config The parsed DubheConfig.
99
+ */
100
+ export function checkAndUpdateLock(rootDir: string, config: DubheConfig): void {
101
+ const lockPath = path.join(rootDir, `${config.name}.lock.json`);
102
+ const newLock = buildLock(config);
103
+
104
+ if (existsSync(lockPath)) {
105
+ let oldLock: DubheLock;
106
+ try {
107
+ oldLock = JSON.parse(readFileSync(lockPath, 'utf-8')) as DubheLock;
108
+ } catch {
109
+ console.warn(
110
+ chalk.yellow('[dubhe]') + ` Could not parse ${chalk.bold(lockPath)}, skipping break-check.`
111
+ );
112
+ writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n', 'utf-8');
113
+ return;
114
+ }
115
+
116
+ checkSection('resources', oldLock.resources ?? {}, newLock.resources);
117
+ checkSection('objects', oldLock.objects ?? {}, newLock.objects);
118
+ checkSection('scenes', oldLock.scenes ?? {}, newLock.scenes);
119
+ }
120
+
121
+ writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n', 'utf-8');
122
+ }
@@ -1,6 +1,8 @@
1
1
  export * from './format';
2
2
  export * from './formatAndWrite';
3
- export * from './posixPath';
4
- export * from './renderMove/schemaGen';
3
+ export * from './renderMove/codegen';
5
4
  export * from './renderMove/dapp';
5
+ export * from './renderMove/generateObjects';
6
+ export * from './renderMove/generateScenes';
6
7
  export * from './config';
8
+ export * from './generateLock';
@@ -1,33 +1,43 @@
1
1
  import { DubheConfig } from '../../types';
2
2
  import { existsSync } from 'fs';
3
- // import { rmdirSync } from 'fs'; // Unused
4
3
  import { deleteFolderRecursive } from './common';
5
4
  import { generateToml } from './generateToml';
6
- // import { generateSchemaData, generateSchemaStructure } from './generateSchema';
7
5
  import { generateDeployHook, generateMigrate } from './generateScript';
8
6
  import { generateDappKey } from './generateDappKey';
9
- // import { generateSchemaEvent } from // Unused './generateEvent';
10
7
  import { generateSystemsAndTests } from './generateSystem';
11
- // import { generateSchemaHub } from // Unused './generateSchemaHub';
12
- import { generateSchemaError } from './generateError';
13
- // import { generateDefaultSchema } from // Unused './generateDefaultSchema';
8
+ import { generateError } from './generateError';
14
9
  import { generateInitTest } from './generateInitTest';
15
- import { generateComponents } from './generateComponents';
16
10
  import { generateGenesis } from './generateGenesis';
17
11
  import { generateEnums } from './generateEnums';
18
12
  import { generateResources } from './generateResources';
13
+ import { generateObjects } from './generateObjects';
14
+ import { generatePermits } from './generatePermits';
15
+ import { generateScenes } from './generateScenes';
16
+ import { generateUserStorageInit } from './generateUserStorageInit';
17
+ import { checkAndUpdateLock } from '../generateLock';
18
+ import { validateConfig } from '../validateConfig';
19
19
  import path from 'node:path';
20
20
 
21
- export async function schemaGen(
21
+ export async function codegen(
22
22
  rootDir: string,
23
23
  config: DubheConfig,
24
- network?: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
24
+ network?: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
25
+ initialMode: 0 | 1 = 1
25
26
  ) {
26
- console.log('\n🚀 Starting Schema Generation Process...');
27
+ console.log('\n🚀 Starting Code Generation Process...');
27
28
  console.log('📋 Project Configuration:');
28
29
  console.log(` └─ Name: ${config.name}`);
29
30
  console.log(` └─ Description: ${config.description || 'No description provided'}`);
30
31
  console.log(` └─ Network: ${network || 'testnet'}`);
32
+ console.log(` └─ Settlement Mode: ${initialMode === 1 ? 'USER_PAYS' : 'DAPP_SUBSIDIZES'}`);
33
+
34
+ // Validate config once here — defineConfig also validates on load, but that
35
+ // is the same process call, so we suppress duplicates by making this the
36
+ // single codegen-phase validation point.
37
+ validateConfig(config);
38
+
39
+ // Check for breaking field changes and update the lock file.
40
+ checkAndUpdateLock(rootDir, config);
31
41
 
32
42
  console.log(rootDir);
33
43
  const projectDir = path.join(rootDir, 'src', config.name);
@@ -42,7 +52,7 @@ export async function schemaGen(
42
52
 
43
53
  const genesisPath = path.join(projectDir, 'sources', 'codegen', 'genesis.move');
44
54
  if (!existsSync(genesisPath)) {
45
- await generateGenesis(config, genesisPath);
55
+ await generateGenesis(config, genesisPath, initialMode);
46
56
  }
47
57
 
48
58
  const initTestPath = path.join(projectDir, 'sources', 'codegen', 'init_test.move');
@@ -57,22 +67,20 @@ export async function schemaGen(
57
67
 
58
68
  const deployHookPath = path.join(projectDir, 'sources', 'scripts', 'deploy_hook.move');
59
69
  if (!existsSync(deployHookPath)) {
60
- await generateDeployHook(config, deployHookPath);
61
- }
62
-
63
- const componentsPath = path.join(projectDir, 'sources', 'codegen', 'components');
64
- if (!existsSync(componentsPath)) {
65
- await generateComponents(config, componentsPath);
66
- } else {
67
- await generateComponents(config, componentsPath);
70
+ await generateDeployHook(config, deployHookPath, initialMode);
68
71
  }
69
72
 
70
73
  const resourcesPath = path.join(projectDir, 'sources', 'codegen', 'resources');
71
- if (!existsSync(resourcesPath)) {
72
- await generateResources(config, resourcesPath);
73
- } else {
74
- await generateResources(config, resourcesPath);
75
- }
74
+ await generateResources(config, resourcesPath);
75
+
76
+ const objectsPath = path.join(projectDir, 'sources', 'codegen', 'objects');
77
+ await generateObjects(config, objectsPath);
78
+
79
+ const permitsPath = path.join(projectDir, 'sources', 'codegen', 'permits');
80
+ await generatePermits(config, permitsPath);
81
+
82
+ const scenesPath = path.join(projectDir, 'sources', 'codegen', 'scenes');
83
+ await generateScenes(config, scenesPath);
76
84
 
77
85
  const enumsPath = path.join(projectDir, 'sources', 'codegen', 'enums');
78
86
  if (!existsSync(enumsPath)) {
@@ -80,12 +88,16 @@ export async function schemaGen(
80
88
  }
81
89
 
82
90
  if (config.errors) {
83
- await generateSchemaError(config.name, config.errors, rootDir);
91
+ await generateError(config.name, config.errors, rootDir);
84
92
  }
85
93
 
86
- // await generateDefaultSchema(config, rootDir);
87
- // await generateInit(config, rootDir);
94
+ const userStorageInitPath = path.join(projectDir, 'sources', 'codegen', 'user_storage_init.move');
95
+ await generateUserStorageInit(config, userStorageInitPath);
96
+
88
97
  await generateSystemsAndTests(config, rootDir);
89
98
  await generateMigrate(config, rootDir);
90
- console.log('\n✅ Schema Generation Process Complete!\n');
99
+ console.log('\n✅ Code Generation Complete!\n');
91
100
  }
101
+
102
+ /** @deprecated Use `codegen` instead. */
103
+ export const schemaGen = codegen;
@@ -1,4 +1,3 @@
1
- // import { MoveType } from '../../types'; // Unused
2
1
  import fs from 'fs';
3
2
 
4
3
  export function deleteFolderRecursive(path: string) {
@@ -14,67 +13,3 @@ export function deleteFolderRecursive(path: string) {
14
13
  fs.rmdirSync(path);
15
14
  }
16
15
  }
17
-
18
- export function capitalizeFirstLetter(input: string): string {
19
- return input.charAt(0).toUpperCase() + input.slice(1);
20
- }
21
-
22
- /**
23
- *
24
- * @param values
25
- * @param prefixArgs
26
- * @return [ name, age, birth_time ]
27
- */
28
- export function getStructAttrs(values: Record<string, string> | string): string {
29
- return Object.entries(values)
30
- .map(([key, _]) => `${key}`)
31
- .join(',');
32
- }
33
-
34
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
35
- function isAddress(str: string): boolean {
36
- const regex = /^0x[a-fA-F0-9]+$/;
37
- return regex.test(str);
38
- }
39
-
40
- /**
41
- *
42
- * @param values
43
- * @return ( bool , u64 , u64)
44
- */
45
- // export function getStructTypes(values: SchemaType): string {
46
- export function getStructTypes(values: Record<string, string>): string {
47
- return `(${Object.entries(values).map(([_, type]) => `${type}`)})`;
48
- }
49
-
50
- /**
51
- *
52
- * @param values
53
- * @return Attributes and types of the struct. [ name: string, age: u64 ]
54
- */
55
- export function getStructAttrsWithType(values: Record<string, string>): string[] {
56
- return Object.entries(values).map(([key, type]) => `${key}: ${type}`);
57
- }
58
-
59
- /**
60
- * @param values
61
- * @return [ data.name, data.age ]
62
- */
63
- export function getStructAttrsQuery(values: Record<string, string>): string[] {
64
- return Object.entries(values).map(([key, _]) => `self.${key}`);
65
- }
66
-
67
- export function containsString(obj: Record<string, any>, searchString: string): boolean {
68
- for (const key in obj) {
69
- if (obj.hasOwnProperty(key)) {
70
- const value = obj[key];
71
- if (
72
- (typeof value === 'string' && value === searchString) ||
73
- (typeof value === 'string' && value.includes(searchString) && value.includes('>'))
74
- ) {
75
- return true;
76
- }
77
- }
78
- }
79
- return false;
80
- }
@@ -1,19 +1,7 @@
1
1
  import { DubheConfig } from '../../types';
2
-
3
- const checkDuplicateKeys = (config: DubheConfig): void => {
4
- const componentKeys = Object.keys(config.components || {});
5
- const resourceKeys = Object.keys(config.resources || {});
6
-
7
- const duplicates = componentKeys.filter((key) => resourceKeys.includes(key));
8
-
9
- if (duplicates.length > 0) {
10
- throw new Error(
11
- `Duplicate keys found between components and resources: ${duplicates.join(', ')}`
12
- );
13
- }
14
- };
2
+ import { validateConfigErrors } from '../validateConfig';
15
3
 
16
4
  export const defineConfig = (config: DubheConfig): DubheConfig => {
17
- checkDuplicateKeys(config);
5
+ validateConfigErrors(config);
18
6
  return config;
19
7
  };
@@ -3,30 +3,45 @@ import { formatAndWriteMove } from '../formatAndWrite';
3
3
 
4
4
  export async function generateDappKey(config: DubheConfig, path: string) {
5
5
  let code = `module ${config.name}::dapp_key {
6
- use std::type_name;
7
- use sui::address;
8
- use std::ascii::String;
6
+ use std::type_name;
7
+ use sui::address;
8
+ use std::ascii::String;
9
9
 
10
- /// Authorization token for the app.
10
+ /// DappKey — package-level authorization token for this DApp.
11
+ ///
12
+ /// SECURITY: \`new()\` is intentionally \`public(package)\`.
13
+ /// Only code compiled into this package can construct a DappKey instance.
14
+ /// All framework write functions (\`set_record\`, \`set_field\`,
15
+ /// \`take_record\`, \`create_user_storage\`, …) require \`_auth: DappKey\`
16
+ /// as proof that the call originated from inside this package — an
17
+ /// external PTB cannot fabricate that proof.
18
+ ///
19
+ /// NEVER change \`new()\` to \`public\`, and never accept a DappKey
20
+ /// value as a parameter from an external caller. Either mistake removes
21
+ /// every package-level access guard, allowing any PTB to write arbitrary
22
+ /// user data or register UserStorages without going through the DApp's
23
+ /// own entry functions.
11
24
 
12
- public struct DappKey has copy, drop {}
25
+ public struct DappKey has copy, drop {}
13
26
 
14
- public(package) fun new(): DappKey {
15
- DappKey { }
16
- }
27
+ /// Constructs an authorization token. Callable only from within this package.
28
+ /// Pass the result as \`_auth\` to any framework function that requires it.
29
+ public(package) fun new(): DappKey {
30
+ DappKey {}
31
+ }
17
32
 
18
- public fun to_string(): String {
19
- type_name::get<DappKey>().into_string()
20
- }
33
+ public fun to_string(): String {
34
+ type_name::with_defining_ids<DappKey>().into_string()
35
+ }
21
36
 
22
- public fun package_id(): address {
23
- let package_id_str = type_name::get<DappKey>().get_address();
24
- address::from_ascii_bytes(package_id_str.as_bytes())
25
- }
37
+ public fun package_id(): address {
38
+ let package_id_str = type_name::with_defining_ids<DappKey>().address_string();
39
+ address::from_ascii_bytes(package_id_str.as_bytes())
40
+ }
26
41
 
27
- public fun eq<DappKey1: copy + drop, DappKey2: copy + drop>(_: &DappKey1, _: &DappKey2): bool {
28
- type_name::get<DappKey1>() == type_name::get<DappKey2>()
29
- }
42
+ public fun eq<DappKey1: copy + drop, DappKey2: copy + drop>(_: &DappKey1, _: &DappKey2): bool {
43
+ type_name::with_defining_ids<DappKey1>() == type_name::with_defining_ids<DappKey2>()
44
+ }
30
45
  }
31
46
  `;
32
47
  await formatAndWriteMove(code, path, 'formatAndWriteMove');