Skip to content

Commit

Permalink
Merge pull request #53 from snaplet/v1.1.0-relese
Browse files Browse the repository at this point in the history
chore: v1.1.0 release
  • Loading branch information
avallete authored Dec 12, 2023
2 parents 44dca33 + 98a2afc commit b05f19b
Show file tree
Hide file tree
Showing 10 changed files with 858 additions and 11 deletions.
95 changes: 94 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,21 @@ Takes in an [input](#input) and returns a string value resembling a [phone numbe

```js
copycat.phoneNumber('foo')
// => '+18312203332869'
// => '+208438699696662'
```
```js
copycat.phoneNumber('foo', { prefixes: ['+3319900', '+3363998'], min: 1000, max: 9999 })
// => '+33639987662'
```

**note** The strings _resemble_ phone numbers, but will not always be valid. For example, the country dialing code may not exist, or for a particular country, the number of digits may be incorrect. Please let us know if you need valid
phone numbers, and feel free to contribute :)

#### `options`
- **`min=10000000000`:** Constrain generated values to be greater than or equal to `min` allow to control the minimum number of digits in the phone number
- **`max=999999999999999`:** Constrain generated values to be less than or equal to `max` allow to control the maximum number of digits in the phone number
- **`prefixes`:** An array of strings that should be used as prefixes for the generated phone numbers. Allowing to control the country dialing code.

### `copycat.username(input)`

Takes in an [input](#input) and returns a string value resembling a username.
Expand All @@ -368,6 +377,90 @@ copycat.username('foo')
#### `options`
- **`limit`:** Constrain generated values to be less than or equal to `limit` number of chars

### `copycat.unique(input, method, store, options)`

The `unique` function is tailored to maintain uniqueness of values after they have undergone a specific transformation.
This method is especially useful when the transformed values need to be unique, regardless of whether the input values are identical.
It will do so by trying to generate a new value multiples times (up to attempts time) until it finds a unique one.

**note** This method will try its best to generate unique values, but be aware of these limitations:
1. The uniqueness is not guaranteed, but the probability of generating a duplicate is lower as the number of attempts increases.
2. On the contrary of the other methods, the `unique` method is not stateless. It will store the generated values in the `store` object to ensure uniqueness.
Meaning that the deterministic property over input is not guaranteed anymore. Now the determinism is based over a combination of:
- the `input` value
- the state of the `store` object
- the number of `attempts`
3. The `unique` method as it alter the global copycat hashKey between attemps before restoring the original one, it is not thread safe.
4. If duplicates exists in the passed `input` accross calls, the method might hide those duplicates by generating a unique value for each of them.
If you want to ensure duplicate value for duplicate input you should use the `uniqueByInput` method.

#### `parameters`

- **`input`** (_Input_): The seed input for the generation method.
- **`method`** (_Function_): A deterministic function that takes `input` and returns a value of type `T`.
- **`store`** (_Store<T>_): A store object to track generated values and ensure uniqueness. It must have `has(value: T): boolean` and `add(value: T): void` methods.
- **`options`** (_UniqueOptions_): An optional configuration object for additional control.

#### `options`

- **`attempts`** (_number_): The maximum number of attempts to generate a unique value. Defaults to 10.
- **`attemptsReached`** (_Function_): An optional callback function that is called when the maximum number of attempts is reached.

```js
// Define a method to generate a value
const generateValue = (seed) => {
return copycat.int(seed, { max: 3 });
};
// Create a store to track unique values
const store = new Set();
// Use the unique method to generate a unique number
copycat.unique('exampleSeed', generateValue, store);
// => 3
copycat.unique('exampleSeed1', generateValue, store);
// => 1
copycat.unique('exampleSeed', generateValue, store);
// => 0
```

### `copycat.uniqueByInput(input, method, inputStore, resultStore, options)`

The `uniqueByInput` function is designed to generate unique values while preserving duplicates for identical inputs.
It is particularly useful in scenarios where input consistency needs to be maintained alongside the uniqueness of the transformed values.
- **Preserving Input Duplication**: If the same input is provided multiple times, `uniqueByInput` ensures that the transformed value is consistently the same for each occurrence of that input.
- **Uniqueness Preservation**: For new and unique inputs, `uniqueByInput` employs the `unique` method to generate distinct values, avoiding duplicates in the `resultStore`.

#### `parameters`

- **`input`** (_Input_): The seed input for the generation method.
- **`method`** (_Function_): A deterministic function that takes `input` and returns a value of type `T`.
- **`inputStore`** (_Store_): A store object to track the inputs and ensure consistent output for duplicate inputs.
- **`resultStore`** (_Store_): A store object to track the generated values and ensure their uniqueness.
- **`options`** (_UniqueOptions_): An optional configuration object for additional control.

#### `options`

- **`attempts`** (_number_): The maximum number of attempts to generate a unique value after transformation. Defaults to 10.
- **`attemptsReached`** (_Function_): An optional callback function that is invoked when the maximum number of attempts is reached.

```js
// Define a method to generate a value
const method = (seed) => {
return copycat.int(seed, { max: 3 });
};

// Create stores to track unique values and inputs
const resultStore = new Set();
const inputStore = new Set();

// Generate a unique number or retrieve the existing one for duplicate input
copycat.uniqueByInput('exampleSeed', method, inputStore, resultStore);
// => 3
copycat.uniqueByInput('exampleSeed1', method, inputStore, resultStore);
// => 1
copycat.uniqueByInput('exampleSeed', method, inputStore, resultStore);
// => 3
```

### `copycat.password(input)`

Takes in an [`input`](#input) value and returns a string value resembling a password.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@snaplet/copycat",
"version": "0.18.1",
"version": "1.1.0",
"description": "",
"main": "dist/index.js",
"files": [
Expand Down
30 changes: 30 additions & 0 deletions scripts/collisionsBasic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { v4: uuid } = require('uuid')
const {copycat, fictional} = require('../dist/index')

const TRANSFORMATIONS = {
...fictional,
...copycat,
}

const METHOD = process.env.METHOD ? process.env.METHOD : 'phoneNumber'
const MAX_N = +(process.env.MAX_N ?? 999999)

function main() {
let firstColision = null
let colisions = 0
const colide = new Set()
for (let i = 0; i < MAX_N; i++) {
const result = TRANSFORMATIONS[METHOD](uuid())
if (colide.has(result)) {
colisions++
if (firstColision == null) {
firstColision = i
}
} else {
colide.add(result)
}
}
console.log(`firstColision: ${firstColision} colided ${colisions} times`)
}

main()
4 changes: 3 additions & 1 deletion src/copycat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ export * from './ipv4'
export * from './mac'
export * from './userAgent'
export * from './scramble'
export * from './hash'
export * from './oneOfString'
export { generateHashKey, setHashKey } from './hash'
export { unique } from './unique'
export { uniqueByInput } from './uniqueByInput'
18 changes: 16 additions & 2 deletions src/hash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { hash } from 'fictional'
import { HashKey, hash } from 'fictional'

// We need to be able to get and set the hash key used by fictional from outside of fictional.
// for the copycat.unique method to work.
const hashKey = {
value: hash.generateKey('chinochinochino!') as string | HashKey,
}
export function getHashKey() {
return hashKey.value
}

function setKey(key: string | HashKey) {
hashKey.value = key
return hash.setKey(key)
}

// We'll use this function to generate a hash key using fictional requirement
// for a 16 character string from arbitrary long string input.
Expand All @@ -19,7 +33,7 @@ function derive16CharacterString(input: string) {
return output.padEnd(16, '0')
}

export const setHashKey = hash.setKey
export const setHashKey = setKey

export const generateHashKey = (input: string) => {
return input.length === 16
Expand Down
73 changes: 67 additions & 6 deletions src/phoneNumber.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
import { join, char, times } from 'fictional'
import { int, oneOf } from 'fictional'
import { Input } from './types'

export const phoneNumber = join('', [
'+',
times([2, 3], char.digit),
times([10, 12], char.digit),
])
type PhoneNumberOptions = {
/**
* An array of prefixes to use when generating a phone number.
* Can be used to generate a fictional phone number instead of a random one.
* Using fictional phone numbers might make the generation slower. And might increase likelihood of collisions.
* @example
* ```ts
* phoneNumber(seed, {
* // Generate a French phone number within fictional phone number delimited range (cf: https://en.wikipedia.org/wiki/Fictitious_telephone_number)
* prefixes: ['+3319900', '+3326191', '+3335301'],
* // A french phone number is 11 digits long (including the prefix) so there is no need to generate a number longer than 4 digits
* min: 1000, max: 9999
* })
* ```
* @example
*
* ```ts
* phoneNumber(seed, {
* // Generate a New Jersey fictional phone number
* prefixes: ['+201555'],
* min: 1000, max: 9999
* })
* ```
* @default undefined
*/
prefixes?: Array<string>
/**
* The minimum number to generate.
* @default 10000000000
*/
min?: number
/**
* The maximum number to generate.
* @default 999999999999999
*/
max?: number
}

export const phoneNumber = (
input: Input,
options: PhoneNumberOptions = { min: 10000000000, max: 999999999999999 }
) => {
// Use provided min and max, or default values if not provided
const min = options.min ?? 10000000000
const max = options.max ?? 999999999999999

if (options.prefixes) {
const prefix =
options.prefixes.length > 1
? // If multiple prefixes are provided, pick one deterministically
oneOf(input, options.prefixes)
: options.prefixes[0]
const prefixLength = prefix.length

// Adjust min and max based on prefix length to keep a valid number of digits in the phone number
const adjustedMin = Math.max(min, 10 ** (10 - prefixLength))
const adjustedMax = Math.min(max, 10 ** (15 - prefixLength) - 1)
return `${prefix}${int(input, {
min: adjustedMin,
max: adjustedMax,
})}`
}

return `+${int(input, { min, max })}`
}
Loading

0 comments on commit b05f19b

Please sign in to comment.